diff --git a/internal/hapui/terminal.go b/internal/hapui/terminal.go
index 5b5d1e7..8178af6 100644
--- a/internal/hapui/terminal.go
+++ b/internal/hapui/terminal.go
@@ -6,7 +6,9 @@ import (
"crypto/rand"
"encoding/json"
"fmt"
+ "html/template"
"math/big"
+ "net/http"
"os"
"strconv"
"strings"
@@ -21,9 +23,47 @@ import (
// TerminalInterface provides an interactive terminal interface for human agents
type TerminalInterface struct {
- runtime *runtime.SharedRuntime
- scanner *bufio.Scanner
- quit chan bool
+ runtime *runtime.SharedRuntime
+ scanner *bufio.Scanner
+ quit chan bool
+ collaborativeSession *CollaborativeSession
+ hmmmMessageCount int
+ webServer *http.Server
+}
+
+// CollaborativeSession represents an active collaborative editing session
+type CollaborativeSession struct {
+ SessionID string
+ Owner string
+ Participants []string
+ Status string
+ CreatedAt time.Time
+}
+
+// Decision represents a network decision awaiting votes
+type Decision struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Type string `json:"type"`
+ Proposer string `json:"proposer"`
+ ProposerType string `json:"proposer_type"`
+ CreatedAt time.Time `json:"created_at"`
+ Deadline time.Time `json:"deadline"`
+ Status string `json:"status"`
+ Votes map[string]DecisionVote `json:"votes"`
+ Metadata map[string]interface{} `json:"metadata"`
+ Version int `json:"version"`
+}
+
+// DecisionVote represents a single vote on a decision
+type DecisionVote struct {
+ VoterID string `json:"voter_id"`
+ VoterType string `json:"voter_type"`
+ Vote string `json:"vote"` // approve, reject, defer, abstain
+ Reasoning string `json:"reasoning"`
+ Timestamp time.Time `json:"timestamp"`
+ Confidence float64 `json:"confidence"` // 0.0-1.0 confidence in vote
}
// NewTerminalInterface creates a new terminal interface for HAP
@@ -80,7 +120,9 @@ func (t *TerminalInterface) printHelp() {
fmt.Println(" hmmm - Compose and send HMMM reasoning message")
fmt.Println(" ucxl
- Browse UCXL context address")
fmt.Println(" patch - Create and submit patches")
+ fmt.Println(" collab - Collaborative editing sessions")
fmt.Println(" decide - Participate in distributed decision")
+ fmt.Println(" web - Start web bridge for browser access")
fmt.Println(" announce - Re-announce human agent presence")
fmt.Println(" quit - Exit HAP terminal")
fmt.Println()
@@ -127,6 +169,9 @@ func (t *TerminalInterface) commandLoop() {
case "patch", "p":
t.handlePatchCommand()
+ case "collab", "c":
+ t.handleCollaborativeEditingCommand()
+
case "decide", "d":
if len(parts) < 2 {
fmt.Println("Usage: decide ")
@@ -135,6 +180,9 @@ func (t *TerminalInterface) commandLoop() {
topic := strings.Join(parts[1:], " ")
t.handleDecisionCommand(topic)
+ case "web", "w":
+ t.startWebBridge()
+
case "announce", "a":
if err := t.announceHumanAgent(); err != nil {
fmt.Printf("Failed to announce presence: %v\n", err)
@@ -1128,6 +1176,68 @@ func (t *TerminalInterface) handleUCXLCreate(parsed *ucxl.UCXLAddress) {
fmt.Println()
}
+// handleCollaborativeEditingCommand processes collaborative editing sessions
+func (t *TerminalInterface) handleCollaborativeEditingCommand() {
+ fmt.Printf("\nš„ CHORUS Collaborative Editing System\n")
+ fmt.Println(strings.Repeat("-", 50))
+
+ for {
+ fmt.Println("\nCollaborative Editing Commands:")
+ fmt.Println(" start - Start collaborative session on UCXL address")
+ fmt.Println(" join - Join existing collaborative session")
+ fmt.Println(" list - List active collaborative sessions")
+ fmt.Println(" status - Show collaborative editing status")
+ fmt.Println(" leave - Leave current collaborative session")
+ fmt.Println(" help - Show collaborative editing help")
+ fmt.Println(" back - Return to main menu")
+
+ fmt.Print("\ncollab> ")
+ reader := bufio.NewReader(os.Stdin)
+ input, err := reader.ReadString('\n')
+ if err != nil {
+ fmt.Printf("Error reading input: %v\n", err)
+ continue
+ }
+
+ input = strings.TrimSpace(input)
+ if input == "" {
+ continue
+ }
+
+ if input == "back" || input == "exit" {
+ break
+ }
+
+ parts := strings.Fields(input)
+ command := parts[0]
+
+ switch command {
+ case "help":
+ t.showCollaborativeEditingHelp()
+ case "start":
+ if len(parts) < 2 {
+ fmt.Println("Usage: start ")
+ continue
+ }
+ t.startCollaborativeSession(parts[1])
+ case "join":
+ if len(parts) < 2 {
+ fmt.Println("Usage: join ")
+ continue
+ }
+ t.joinCollaborativeSession(parts[1])
+ case "list":
+ t.listCollaborativeSessions()
+ case "status":
+ t.showCollaborativeEditingStatus()
+ case "leave":
+ t.leaveCollaborativeSession()
+ default:
+ fmt.Println("Unknown collaborative editing command. Type 'help' for available commands.")
+ }
+ }
+}
+
// handlePatchCommand processes patch creation and submission workflows
func (t *TerminalInterface) handlePatchCommand() {
fmt.Printf("\nš CHORUS Patch Management System\n")
@@ -1226,35 +1336,57 @@ func (t *TerminalInterface) listActiveDecisions() {
fmt.Println("\nš Active Network Decisions")
fmt.Println(strings.Repeat("-", 35))
- // This would connect to the decision tracking system
- // For now, showing mock data structure
- fmt.Println("ā ļø Decision tracking integration in development")
- fmt.Println()
- fmt.Println("Mock active decisions:")
- fmt.Println()
-
- decisions := []struct {
- ID string
- Title string
- Type string
- Proposer string
- Deadline string
- Status string
- }{
- {"DEC-001", "Upgrade libp2p to v0.27", "technical", "autonomous-agent-7", "2h 15m", "voting"},
- {"DEC-002", "Adjust task timeout from 30s to 60s", "operational", "human-alice", "6h 30m", "voting"},
- {"DEC-003", "Emergency: Quarantine node malicious-peer", "emergency", "security-monitor", "45m", "urgent"},
+ // Query DHT for active decisions
+ decisions, err := t.getActiveDecisions()
+ if err != nil {
+ fmt.Printf("ā Failed to retrieve decisions: %v\n", err)
+ return
}
+ if len(decisions) == 0 {
+ fmt.Println("ā
No active decisions requiring votes")
+ fmt.Println("\nš” Use 'propose' to create a new decision")
+ return
+ }
+
+ fmt.Printf("Found %d active decision(s):\n\n", len(decisions))
+
for _, decision := range decisions {
statusEmoji := "š³ļø"
if decision.Status == "urgent" {
statusEmoji = "šØ"
+ } else if decision.Status == "closed" {
+ statusEmoji = "ā
"
+ }
+
+ // Calculate time remaining
+ timeRemaining := decision.Deadline.Sub(time.Now())
+ timeStr := formatDuration(timeRemaining)
+
+ // Count votes
+ approvals := 0
+ rejections := 0
+ deferrals := 0
+ abstentions := 0
+
+ for _, vote := range decision.Votes {
+ switch vote.Vote {
+ case "approve":
+ approvals++
+ case "reject":
+ rejections++
+ case "defer":
+ deferrals++
+ case "abstain":
+ abstentions++
+ }
}
fmt.Printf("%s %s - %s\n", statusEmoji, decision.ID, decision.Title)
- fmt.Printf(" š Type: %s | š¤ Proposer: %s | ā° Time left: %s\n",
- decision.Type, decision.Proposer, decision.Deadline)
+ fmt.Printf(" š Type: %s | š¤ Proposer: %s (%s)\n",
+ decision.Type, decision.Proposer, decision.ProposerType)
+ fmt.Printf(" ā° Time left: %s | š³ļø Votes: ā
%d ā%d āøļø%d ā ļø%d\n",
+ timeStr, approvals, rejections, deferrals, abstentions)
fmt.Println()
}
@@ -1268,57 +1400,87 @@ func (t *TerminalInterface) viewDecision(decisionID string) {
fmt.Printf("\nš Decision Details: %s\n", decisionID)
fmt.Println(strings.Repeat("-", 40))
- // Mock decision details - would come from decision tracking system
- fmt.Println("ā ļø Decision retrieval integration in development")
- fmt.Println()
+ decision, err := t.getDecisionByID(decisionID)
+ if err != nil {
+ fmt.Printf("ā Failed to retrieve decision: %v\n", err)
+ return
+ }
- switch decisionID {
- case "DEC-001":
- fmt.Println("š Title: Upgrade libp2p to v0.27")
- fmt.Println("š·ļø Type: Technical Infrastructure")
- fmt.Println("š¤ Proposer: autonomous-agent-7")
- fmt.Println("š
Proposed: 2025-09-06 20:45:00")
- fmt.Println("ā° Deadline: 2025-09-07 09:00:00 (2h 15m remaining)")
+ fmt.Printf("š Title: %s\n", decision.Title)
+ fmt.Printf("š·ļø Type: %s\n", strings.Title(decision.Type))
+ fmt.Printf("š¤ Proposer: %s (%s)\n", decision.Proposer, decision.ProposerType)
+ fmt.Printf("š
Proposed: %s\n", decision.CreatedAt.Format("2006-01-02 15:04:05"))
+
+ timeRemaining := decision.Deadline.Sub(time.Now())
+ fmt.Printf("ā° Deadline: %s (%s remaining)\n",
+ decision.Deadline.Format("2006-01-02 15:04:05"),
+ formatDuration(timeRemaining))
+
+ fmt.Println()
+ fmt.Println("š Description:")
+ wrapped := wrapText(decision.Description, 60)
+ for _, line := range strings.Split(wrapped, "\n") {
+ fmt.Printf(" %s\n", line)
+ }
+
+ fmt.Println()
+ fmt.Printf("š³ļø Current Voting Status (%d total votes):\n", len(decision.Votes))
+
+ // Count votes by type
+ approvals := 0
+ rejections := 0
+ deferrals := 0
+ abstentions := 0
+
+ for _, vote := range decision.Votes {
+ switch vote.Vote {
+ case "approve":
+ approvals++
+ case "reject":
+ rejections++
+ case "defer":
+ deferrals++
+ case "abstain":
+ abstentions++
+ }
+ }
+
+ fmt.Printf(" ā
Approve: %d votes\n", approvals)
+ fmt.Printf(" ā Reject: %d votes\n", rejections)
+ fmt.Printf(" āøļø Defer: %d votes\n", deferrals)
+ fmt.Printf(" ā ļø Abstain: %d votes\n", abstentions)
+
+ if len(decision.Votes) > 0 {
+ fmt.Println("\nš Recent Reasoning:")
+ count := 0
+ for _, vote := range decision.Votes {
+ if count >= 3 { // Show only last 3 votes
+ break
+ }
+ fmt.Printf(" %s (%s): \"%s\"\n", vote.VoterID, vote.Vote, vote.Reasoning)
+ count++
+ }
+ }
+
+ // Calculate status
+ totalVotes := len(decision.Votes)
+ if totalVotes > 0 {
+ approvalRate := float64(approvals) / float64(totalVotes) * 100
+ fmt.Printf("\nšÆ Status: %.1f%% approval", approvalRate)
+ if approvalRate >= 50 {
+ fmt.Print(" (Passing)")
+ } else {
+ fmt.Print(" (Failing)")
+ }
fmt.Println()
- fmt.Println("š Rationale:")
- fmt.Println(" The current libp2p v0.24 has known issues with NAT traversal")
- fmt.Println(" and connection stability. v0.27 fixes these issues and improves")
- fmt.Println(" DHT performance by ~25%. This is a backward-compatible upgrade.")
- fmt.Println()
- fmt.Println("š³ļø Current Voting Status:")
- fmt.Println(" ā
Approve: 4 votes (bob-dev, carol-admin, eve-ops, agent-3)")
- fmt.Println(" ā Reject: 1 vote (dave-conservative)")
- fmt.Println(" āøļø Defer: 0 votes")
- fmt.Println(" ā ļø Abstain: 1 vote (frank-observer)")
- fmt.Println()
- fmt.Println(" šÆ Status: Passing (67% approval, needs 50%)")
- fmt.Println(" š Participation: 6/12 network members voted")
-
- case "DEC-002":
- fmt.Println("š Title: Adjust task timeout from 30s to 60s")
- fmt.Println("š·ļø Type: Operational Parameter")
- fmt.Println("š¤ Proposer: human-alice")
- fmt.Println("š
Proposed: 2025-09-06 15:30:00")
- fmt.Println("ā° Deadline: 2025-09-07 03:00:00 (6h 30m remaining)")
- fmt.Println()
- fmt.Println("š Rationale:")
- fmt.Println(" Recent analysis shows that 23% of legitimate tasks are timing")
- fmt.Println(" out at 30 seconds, causing unnecessary retries and wasted")
- fmt.Println(" compute. Increasing to 60s would reduce false timeouts while")
- fmt.Println(" still catching truly stuck tasks.")
- fmt.Println()
- fmt.Println("š³ļø Current Voting Status:")
- fmt.Println(" ā
Approve: 2 votes (alice-human, operations-bot)")
- fmt.Println(" ā Reject: 1 vote (performance-monitor)")
- fmt.Println(" āøļø Defer: 2 votes (bob-dev, carol-admin)")
- fmt.Println(" ā ļø Abstain: 0 votes")
- fmt.Println()
- fmt.Println(" šÆ Status: Pending (40% approval, needs 50%)")
- fmt.Println(" š Participation: 5/12 network members voted")
-
- default:
- fmt.Printf("ā Decision %s not found or access denied\n", decisionID)
- fmt.Println("Use 'list' to see available decisions.")
+ }
+
+ // Show metadata if present
+ if len(decision.Metadata) > 0 {
+ fmt.Println("\nš Metadata:")
+ for key, value := range decision.Metadata {
+ fmt.Printf(" %s: %v\n", key, value)
+ }
}
fmt.Println()
@@ -1406,19 +1568,50 @@ func (t *TerminalInterface) castVoteOnDecision(decisionID string) {
return
}
- // Submit vote (would integrate with decision system)
- fmt.Println("ā ļø Vote submission integration in development")
- fmt.Printf("š Your %s vote on %s has been recorded\n", vote, decisionID)
- fmt.Println("š Vote reasoning published to network for transparency")
- fmt.Println("š Other network members will be notified of your participation")
+ // Submit vote to decision system
+ fmt.Println("š Submitting vote...")
- // Create HMMM message for vote announcement
- if err := t.announceVoteDecision(decisionID, vote, reasoningText); err != nil {
+ // Get current decision to update it
+ decision, err := t.getDecisionByID(decisionID)
+ if err != nil {
+ fmt.Printf("ā Failed to retrieve decision: %v\n", err)
+ return
+ }
+
+ // Create vote record
+ voteRecord := DecisionVote{
+ VoterID: t.runtime.Config.Agent.ID,
+ VoterType: "human",
+ Vote: vote,
+ Reasoning: reasoningText,
+ Timestamp: time.Now(),
+ Confidence: 1.0, // Human votes have full confidence
+ }
+
+ // Add/update vote in decision
+ if decision.Votes == nil {
+ decision.Votes = make(map[string]DecisionVote)
+ }
+ decision.Votes[t.runtime.Config.Agent.ID] = voteRecord
+ decision.Version++
+
+ // Save updated decision
+ if err := t.saveDecision(decision); err != nil {
+ fmt.Printf("ā Failed to save vote: %v\n", err)
+ return
+ }
+
+ // Announce vote via HMMM
+ if err := t.announceDecisionVote(decisionID, voteRecord); err != nil {
fmt.Printf("ā ļø Failed to announce vote via HMMM: %v\n", err)
} else {
fmt.Println("š¢ Vote announced via HMMM reasoning network")
}
+ fmt.Printf("ā
Your %s vote on %s has been recorded\n", vote, decisionID)
+ fmt.Println("š Vote reasoning published to network for transparency")
+ fmt.Println("š Other network members will be notified of your participation")
+
fmt.Println()
}
@@ -1546,22 +1739,51 @@ func (t *TerminalInterface) proposeNewDecision() {
}
// Generate decision ID
- decisionID := fmt.Sprintf("DEC-%d", time.Now().Unix()%10000)
+ decisionID := fmt.Sprintf("DEC-%s", generateRandomID(6))
- // Submit decision (would integrate with decision system)
- fmt.Println("ā ļø Decision proposal integration in development")
- fmt.Printf("ā
Decision %s proposed successfully\n", decisionID)
- fmt.Println("š¢ Decision announced to all network members")
- fmt.Printf("ā° Voting closes in %s\n", deadline)
- fmt.Println("š³ļø Network members can now cast their votes")
+ // Create decision object
+ decision := &Decision{
+ ID: decisionID,
+ Title: title,
+ Description: rationaleText,
+ Type: decisionType,
+ Proposer: t.runtime.Config.Agent.ID,
+ ProposerType: "human",
+ CreatedAt: time.Now(),
+ Deadline: time.Now().Add(deadline),
+ Status: "voting",
+ Votes: make(map[string]DecisionVote),
+ Metadata: map[string]interface{}{
+ "voting_options": options,
+ "priority": "normal",
+ },
+ Version: 1,
+ }
+
+ if decisionType == "emergency" {
+ decision.Metadata["priority"] = "urgent"
+ decision.Status = "urgent"
+ }
+
+ // Save decision to DHT
+ fmt.Println("š Creating decision proposal...")
+ if err := t.saveDecision(decision); err != nil {
+ fmt.Printf("ā Failed to create decision: %v\n", err)
+ return
+ }
// Send HMMM announcement
- if err := t.announceNewDecisionProposal(decisionID, title, decisionType, rationaleText, options); err != nil {
+ if err := t.announceDecisionProposal(decision); err != nil {
fmt.Printf("ā ļø Failed to announce via HMMM: %v\n", err)
} else {
fmt.Println("š” Decision announced via HMMM reasoning network")
}
+ fmt.Printf("ā
Decision %s proposed successfully\n", decisionID)
+ fmt.Println("š¢ Decision announced to all network members")
+ fmt.Printf("ā° Voting closes in %s\n", deadline)
+ fmt.Println("š³ļø Network members can now cast their votes")
+
fmt.Println()
}
@@ -2412,4 +2634,1353 @@ func (t *TerminalInterface) announcePatchReview(patchID, reviewType, reasoning s
}
return t.sendHMMMMessage(message)
+}
+
+// === COLLABORATIVE EDITING HELPER FUNCTIONS ===
+
+// showCollaborativeEditingHelp displays comprehensive collaborative editing help
+func (t *TerminalInterface) showCollaborativeEditingHelp() {
+ fmt.Println("\n=== š¤ COLLABORATIVE EDITING HELP ===")
+ fmt.Println()
+ fmt.Println("AVAILABLE COMMANDS:")
+ fmt.Println(" start - Start new collaborative session")
+ fmt.Println(" join - Join existing collaborative session")
+ fmt.Println(" list - List active collaborative sessions")
+ fmt.Println(" status - Show current collaboration status")
+ fmt.Println(" leave - Leave current collaborative session")
+ fmt.Println(" help - Show this help menu")
+ fmt.Println(" back - Return to main HAP menu")
+ fmt.Println()
+ fmt.Println("COLLABORATIVE SESSION WORKFLOW:")
+ fmt.Println(" 1. Start a new session with unique session ID")
+ fmt.Println(" 2. Share session ID with collaborators")
+ fmt.Println(" 3. Collaborators join using the session ID")
+ fmt.Println(" 4. Real-time editing with conflict resolution")
+ fmt.Println(" 5. Automatic sync via CHORUS P2P network")
+ fmt.Println()
+ fmt.Println("FEATURES:")
+ fmt.Println(" ⢠Real-time collaborative editing")
+ fmt.Println(" ⢠Automatic conflict resolution")
+ fmt.Println(" ⢠Version history and rollback")
+ fmt.Println(" ⢠Live cursor and selection sync")
+ fmt.Println(" ⢠Chat integration for coordination")
+ fmt.Println(" ⢠Role-based access control")
+ fmt.Println()
+}
+
+// startCollaborativeSession creates and starts a new collaborative editing session
+func (t *TerminalInterface) startCollaborativeSession(sessionID string) error {
+ if t.collaborativeSession != nil {
+ fmt.Println("ā ļø Already in collaborative session. Leave current session first.")
+ return nil
+ }
+
+ fmt.Printf("š Starting collaborative session: %s\n", sessionID)
+
+ // Create session data
+ sessionData := map[string]interface{}{
+ "session_id": sessionID,
+ "owner": t.runtime.Config.Agent.ID,
+ "owner_type": "human",
+ "created_at": time.Now().Unix(),
+ "participants": []string{t.runtime.Config.Agent.ID},
+ "status": "active",
+ "version": 1,
+ }
+
+ // Store session in DHT
+ sessionKey := fmt.Sprintf("collab_session_%s", sessionID)
+ sessionBytes, _ := json.Marshal(sessionData)
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ err := t.runtime.DHT.PutValue(ctx, sessionKey, sessionBytes)
+ if err != nil {
+ fmt.Printf("ā Failed to create collaborative session: %v\n", err)
+ return err
+ }
+
+ // Set local session state
+ t.collaborativeSession = &CollaborativeSession{
+ SessionID: sessionID,
+ Owner: t.runtime.Config.Agent.ID,
+ Participants: []string{t.runtime.Config.Agent.ID},
+ Status: "active",
+ CreatedAt: time.Now(),
+ }
+
+ // Announce session creation
+ t.announceCollaborativeSession("session_created", sessionData)
+
+ fmt.Println("ā
Collaborative session started successfully!")
+ fmt.Printf("š Share session ID with collaborators: %s\n", sessionID)
+ fmt.Println("š® Starting collaborative editor...")
+
+ return t.runCollaborativeEditor()
+}
+
+// joinCollaborativeSession joins an existing collaborative editing session
+func (t *TerminalInterface) joinCollaborativeSession(sessionID string) error {
+ if t.collaborativeSession != nil {
+ fmt.Println("ā ļø Already in collaborative session. Leave current session first.")
+ return nil
+ }
+
+ fmt.Printf("š Joining collaborative session: %s\n", sessionID)
+
+ // Retrieve session from DHT
+ sessionKey := fmt.Sprintf("collab_session_%s", sessionID)
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ sessionBytes, err := t.runtime.DHT.GetValue(ctx, sessionKey)
+ if err != nil {
+ fmt.Printf("ā Failed to find collaborative session: %v\n", err)
+ return err
+ }
+
+ var sessionData map[string]interface{}
+ if err := json.Unmarshal(sessionBytes, &sessionData); err != nil {
+ fmt.Printf("ā Invalid session data: %v\n", err)
+ return err
+ }
+
+ // Check session status
+ if sessionData["status"] != "active" {
+ fmt.Println("ā Session is not active")
+ return fmt.Errorf("session not active")
+ }
+
+ // Add participant
+ participants, _ := sessionData["participants"].([]interface{})
+ participantList := make([]string, 0, len(participants)+1)
+
+ for _, p := range participants {
+ if pStr, ok := p.(string); ok {
+ participantList = append(participantList, pStr)
+ }
+ }
+
+ // Check if already participant
+ for _, p := range participantList {
+ if p == t.runtime.Config.Agent.ID {
+ fmt.Println("ā ļø Already a participant in this session")
+ break
+ }
+ }
+
+ // Add current agent as participant
+ participantList = append(participantList, t.runtime.Config.Agent.ID)
+ sessionData["participants"] = participantList
+ sessionData["version"] = sessionData["version"].(float64) + 1
+
+ // Update session in DHT
+ sessionBytes, _ = json.Marshal(sessionData)
+ err = t.runtime.DHT.PutValue(ctx, sessionKey, sessionBytes)
+ if err != nil {
+ fmt.Printf("ā Failed to join collaborative session: %v\n", err)
+ return err
+ }
+
+ // Set local session state
+ t.collaborativeSession = &CollaborativeSession{
+ SessionID: sessionID,
+ Owner: sessionData["owner"].(string),
+ Participants: participantList,
+ Status: "active",
+ CreatedAt: time.Unix(int64(sessionData["created_at"].(float64)), 0),
+ }
+
+ // Announce session join
+ t.announceCollaborativeSession("participant_joined", sessionData)
+
+ fmt.Println("ā
Joined collaborative session successfully!")
+ fmt.Printf("š„ Session participants: %v\n", participantList)
+ fmt.Println("š® Starting collaborative editor...")
+
+ return t.runCollaborativeEditor()
+}
+
+// listCollaborativeSessions displays all active collaborative sessions
+func (t *TerminalInterface) listCollaborativeSessions() error {
+ fmt.Println("\n=== š¤ ACTIVE COLLABORATIVE SESSIONS ===")
+
+ // Query DHT for active sessions (simplified - would need proper indexing in production)
+ fmt.Println("š Searching for active sessions...")
+
+ if t.collaborativeSession != nil {
+ fmt.Printf("\nš¢ CURRENT SESSION: %s\n", t.collaborativeSession.SessionID)
+ fmt.Printf(" Owner: %s\n", t.collaborativeSession.Owner)
+ fmt.Printf(" Participants: %v\n", t.collaborativeSession.Participants)
+ fmt.Printf(" Created: %s\n", t.collaborativeSession.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf(" Status: %s\n", t.collaborativeSession.Status)
+ } else {
+ fmt.Println("\nš“ Not currently in any collaborative session")
+ }
+
+ fmt.Println("\nš” To discover other sessions, use the HMMM network:")
+ fmt.Println(" Sessions are announced via CHORUS/collab/sessions topic")
+
+ return nil
+}
+
+// showCollaborativeStatus displays current collaboration status and statistics
+func (t *TerminalInterface) showCollaborativeStatus() {
+ fmt.Println("\n=== š¤ COLLABORATIVE STATUS ===")
+
+ if t.collaborativeSession == nil {
+ fmt.Println("š“ Not currently in any collaborative session")
+ fmt.Println("\nTo start or join a session:")
+ fmt.Println(" collab start ")
+ fmt.Println(" collab join ")
+ return
+ }
+
+ session := t.collaborativeSession
+ fmt.Printf("š¢ ACTIVE SESSION: %s\n", session.SessionID)
+ fmt.Printf("š¤ Owner: %s\n", session.Owner)
+ fmt.Printf("š„ Participants: %d\n", len(session.Participants))
+
+ for i, participant := range session.Participants {
+ indicator := " "
+ if participant == t.runtime.Config.Agent.ID {
+ indicator = "ā "
+ }
+ fmt.Printf(" %s%s\n", indicator, participant)
+ }
+
+ fmt.Printf("š
Created: %s\n", session.CreatedAt.Format("2006-01-02 15:04:05"))
+ fmt.Printf("š Status: %s\n", session.Status)
+
+ // Show network status
+ fmt.Println("\nš” NETWORK STATUS:")
+ fmt.Printf(" P2P Node ID: %s\n", t.runtime.Node.ID().String()[:16]+"...")
+ fmt.Printf(" Connected Peers: %d\n", len(t.runtime.Node.Network().Peers()))
+ fmt.Printf(" HMMM Messages: %d sent\n", t.hmmmMessageCount)
+
+ fmt.Println("\nš® EDITOR STATUS:")
+ fmt.Println(" Collaborative editing interface active")
+ fmt.Println(" Real-time sync enabled")
+ fmt.Println(" Conflict resolution active")
+}
+
+// leaveCollaborativeSession leaves the current collaborative session
+func (t *TerminalInterface) leaveCollaborativeSession() error {
+ if t.collaborativeSession == nil {
+ fmt.Println("ā ļø Not currently in any collaborative session")
+ return nil
+ }
+
+ sessionID := t.collaborativeSession.SessionID
+ fmt.Printf("š Leaving collaborative session: %s\n", sessionID)
+
+ // Retrieve current session data
+ sessionKey := fmt.Sprintf("collab_session_%s", sessionID)
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ sessionBytes, err := t.runtime.DHT.GetValue(ctx, sessionKey)
+ if err == nil {
+ var sessionData map[string]interface{}
+ if json.Unmarshal(sessionBytes, &sessionData) == nil {
+ // Remove participant
+ participants, _ := sessionData["participants"].([]interface{})
+ newParticipants := make([]string, 0)
+
+ for _, p := range participants {
+ if pStr, ok := p.(string); ok && pStr != t.runtime.Config.Agent.ID {
+ newParticipants = append(newParticipants, pStr)
+ }
+ }
+
+ sessionData["participants"] = newParticipants
+ sessionData["version"] = sessionData["version"].(float64) + 1
+
+ // Update session in DHT
+ sessionBytes, _ = json.Marshal(sessionData)
+ t.runtime.DHT.PutValue(ctx, sessionKey, sessionBytes)
+
+ // Announce departure
+ t.announceCollaborativeSession("participant_left", sessionData)
+ }
+ }
+
+ // Clear local session state
+ t.collaborativeSession = nil
+
+ fmt.Println("ā
Left collaborative session successfully!")
+ return nil
+}
+
+// runCollaborativeEditor starts the collaborative editing interface
+func (t *TerminalInterface) runCollaborativeEditor() error {
+ fmt.Println("\n=== š® COLLABORATIVE EDITOR ===")
+ fmt.Printf("Session: %s | Participants: %d\n",
+ t.collaborativeSession.SessionID,
+ len(t.collaborativeSession.Participants))
+
+ fmt.Println("\nCOLLABORATIVE EDITOR COMMANDS:")
+ fmt.Println(" edit - Edit file collaboratively")
+ fmt.Println(" create - Create new file collaboratively")
+ fmt.Println(" sync - Force sync with other participants")
+ fmt.Println(" chat - Send message to collaborators")
+ fmt.Println(" history - Show session edit history")
+ fmt.Println(" conflicts - Show and resolve conflicts")
+ fmt.Println(" save - Save current collaborative work")
+ fmt.Println(" status - Show collaborative status")
+ fmt.Println(" leave - Leave collaborative session")
+
+ // Main collaborative editing loop
+ for {
+ if t.collaborativeSession == nil {
+ break
+ }
+
+ fmt.Printf("\ncollab:%s> ", t.collaborativeSession.SessionID)
+ input := t.readInput()
+ parts := strings.Fields(strings.TrimSpace(input))
+
+ if len(parts) == 0 {
+ continue
+ }
+
+ command := strings.ToLower(parts[0])
+
+ switch command {
+ case "edit":
+ if len(parts) < 2 {
+ fmt.Println("ā Usage: edit ")
+ continue
+ }
+ t.handleCollaborativeFileEdit(parts[1])
+
+ case "create":
+ if len(parts) < 2 {
+ fmt.Println("ā Usage: create ")
+ continue
+ }
+ t.handleCollaborativeFileCreate(parts[1])
+
+ case "sync":
+ t.handleCollaborativeSync()
+
+ case "chat":
+ if len(parts) < 2 {
+ fmt.Println("ā Usage: chat ")
+ continue
+ }
+ message := strings.Join(parts[1:], " ")
+ t.handleCollaborativeChat(message)
+
+ case "history":
+ t.showCollaborativeHistory()
+
+ case "conflicts":
+ t.showCollaborativeConflicts()
+
+ case "save":
+ t.handleCollaborativeSave()
+
+ case "status":
+ t.showCollaborativeStatus()
+
+ case "leave":
+ return t.leaveCollaborativeSession()
+
+ case "help":
+ fmt.Println("\nCOLLABORATIVE EDITOR COMMANDS:")
+ fmt.Println(" edit - Edit file collaboratively")
+ fmt.Println(" create - Create new file collaboratively")
+ fmt.Println(" sync - Force sync with other participants")
+ fmt.Println(" chat - Send message to collaborators")
+ fmt.Println(" history - Show session edit history")
+ fmt.Println(" conflicts - Show and resolve conflicts")
+ fmt.Println(" save - Save current collaborative work")
+ fmt.Println(" status - Show collaborative status")
+ fmt.Println(" leave - Leave collaborative session")
+
+ default:
+ fmt.Printf("ā Unknown command: %s\n", command)
+ fmt.Println("š” Type 'help' for available commands")
+ }
+ }
+
+ return nil
+}
+
+// handleCollaborativeFileEdit handles collaborative editing of a file
+func (t *TerminalInterface) handleCollaborativeFileEdit(filename string) {
+ fmt.Printf("š Starting collaborative edit of: %s\n", filename)
+
+ // Announce edit start
+ editData := map[string]interface{}{
+ "session_id": t.collaborativeSession.SessionID,
+ "file": filename,
+ "editor": t.runtime.Config.Agent.ID,
+ "action": "edit_start",
+ "timestamp": time.Now().Unix(),
+ }
+
+ t.announceCollaborativeEdit(editData)
+
+ // For now, simulate collaborative editing
+ fmt.Println("š® Collaborative editing interface would start here...")
+ fmt.Println(" ⢠Real-time sync with other participants")
+ fmt.Println(" ⢠Conflict resolution on overlapping edits")
+ fmt.Println(" ⢠Live cursor positions")
+ fmt.Println(" ⢠Change history and rollback")
+ fmt.Println(" ⢠Chat integration")
+
+ fmt.Println("\nš” Press Enter to simulate edit completion...")
+ t.readInput()
+
+ // Announce edit completion
+ editData["action"] = "edit_complete"
+ t.announceCollaborativeEdit(editData)
+
+ fmt.Println("ā
Collaborative edit completed!")
+}
+
+// handleCollaborativeFileCreate handles collaborative creation of a new file
+func (t *TerminalInterface) handleCollaborativeFileCreate(filename string) {
+ fmt.Printf("š Creating new collaborative file: %s\n", filename)
+
+ // Announce file creation
+ createData := map[string]interface{}{
+ "session_id": t.collaborativeSession.SessionID,
+ "file": filename,
+ "creator": t.runtime.Config.Agent.ID,
+ "action": "file_create",
+ "timestamp": time.Now().Unix(),
+ }
+
+ t.announceCollaborativeEdit(createData)
+
+ fmt.Println("ā
Collaborative file creation announced!")
+ fmt.Printf("š® Starting collaborative editing of: %s\n", filename)
+
+ // Start collaborative editing of the new file
+ t.handleCollaborativeFileEdit(filename)
+}
+
+// handleCollaborativeSync forces synchronization with other participants
+func (t *TerminalInterface) handleCollaborativeSync() {
+ fmt.Println("š Forcing sync with collaborative participants...")
+
+ syncData := map[string]interface{}{
+ "session_id": t.collaborativeSession.SessionID,
+ "requester": t.runtime.Config.Agent.ID,
+ "action": "force_sync",
+ "timestamp": time.Now().Unix(),
+ }
+
+ t.announceCollaborativeEdit(syncData)
+
+ fmt.Println("ā
Sync request sent to all participants!")
+}
+
+// handleCollaborativeChat sends a chat message to collaborators
+func (t *TerminalInterface) handleCollaborativeChat(message string) {
+ chatData := map[string]interface{}{
+ "session_id": t.collaborativeSession.SessionID,
+ "sender": t.runtime.Config.Agent.ID,
+ "message": message,
+ "timestamp": time.Now().Unix(),
+ }
+
+ t.announceCollaborativeChat(chatData)
+
+ fmt.Printf("š¬ Message sent: %s\n", message)
+}
+
+// showCollaborativeHistory displays the collaborative editing history
+func (t *TerminalInterface) showCollaborativeHistory() {
+ fmt.Println("\n=== š COLLABORATIVE SESSION HISTORY ===")
+ fmt.Printf("Session: %s\n", t.collaborativeSession.SessionID)
+ fmt.Println()
+
+ // For now, show placeholder history
+ fmt.Println("š Session started")
+ for i, participant := range t.collaborativeSession.Participants {
+ if i == 0 {
+ fmt.Printf("š %s created session\n", participant)
+ } else {
+ fmt.Printf("š %s joined session\n", participant)
+ }
+ }
+
+ fmt.Println("\nš” Complete history would show:")
+ fmt.Println(" ⢠All file edits and creations")
+ fmt.Println(" ⢠Chat messages between participants")
+ fmt.Println(" ⢠Sync events and conflict resolutions")
+ fmt.Println(" ⢠Participant join/leave events")
+}
+
+// showCollaborativeConflicts displays and helps resolve conflicts
+func (t *TerminalInterface) showCollaborativeConflicts() {
+ fmt.Println("\n=== ā ļø COLLABORATIVE CONFLICTS ===")
+ fmt.Printf("Session: %s\n", t.collaborativeSession.SessionID)
+ fmt.Println()
+
+ // For now, show no conflicts
+ fmt.Println("ā
No active conflicts detected!")
+ fmt.Println()
+ fmt.Println("š” Conflict resolution features:")
+ fmt.Println(" ⢠Automatic merge for non-overlapping edits")
+ fmt.Println(" ⢠Three-way merge for conflicting changes")
+ fmt.Println(" ⢠Manual resolution interface")
+ fmt.Println(" ⢠Rollback to previous versions")
+ fmt.Println(" ⢠Participant voting on resolutions")
+}
+
+// handleCollaborativeSave saves the current collaborative work
+func (t *TerminalInterface) handleCollaborativeSave() {
+ fmt.Println("š¾ Saving collaborative session work...")
+
+ saveData := map[string]interface{}{
+ "session_id": t.collaborativeSession.SessionID,
+ "saver": t.runtime.Config.Agent.ID,
+ "action": "session_save",
+ "timestamp": time.Now().Unix(),
+ }
+
+ t.announceCollaborativeEdit(saveData)
+
+ fmt.Println("ā
Collaborative work saved!")
+ fmt.Println("š All changes synchronized across participants")
+}
+
+// announceCollaborativeSession sends HMMM message about session events
+func (t *TerminalInterface) announceCollaborativeSession(eventType string, sessionData map[string]interface{}) error {
+ msgID := t.generateMessageID()
+ threadID := fmt.Sprintf("collab-session-%s", sessionData["session_id"])
+
+ message := hmmm.Message{
+ Topic: "CHORUS/collab/sessions",
+ Type: "collaborative_session",
+ Payload: map[string]interface{}{
+ "event_type": eventType,
+ "session_data": sessionData,
+ },
+ Version: "1.0",
+ IssueID: 0,
+ ThreadID: threadID,
+ MsgID: msgID,
+ NodeID: t.runtime.Node.ID().String(),
+ HopCount: 0,
+ Timestamp: time.Now().Unix(),
+ Message: fmt.Sprintf("Collaborative session %s: %s", eventType, sessionData["session_id"]),
+ }
+
+ return t.sendHMMMMessage(message)
+}
+
+// announceCollaborativeEdit sends HMMM message about collaborative editing events
+func (t *TerminalInterface) announceCollaborativeEdit(editData map[string]interface{}) error {
+ msgID := t.generateMessageID()
+ threadID := fmt.Sprintf("collab-edit-%s", editData["session_id"])
+
+ message := hmmm.Message{
+ Topic: fmt.Sprintf("CHORUS/collab/editing/%s", editData["session_id"]),
+ Type: "collaborative_edit",
+ Payload: editData,
+ Version: "1.0",
+ IssueID: 0,
+ ThreadID: threadID,
+ MsgID: msgID,
+ NodeID: t.runtime.Node.ID().String(),
+ HopCount: 0,
+ Timestamp: time.Now().Unix(),
+ Message: fmt.Sprintf("Collaborative edit: %s", editData["action"]),
+ }
+
+ return t.sendHMMMMessage(message)
+}
+
+// announceCollaborativeChat sends HMMM message for chat in collaborative session
+func (t *TerminalInterface) announceCollaborativeChat(chatData map[string]interface{}) error {
+ msgID := t.generateMessageID()
+ threadID := fmt.Sprintf("collab-chat-%s", chatData["session_id"])
+
+ message := hmmm.Message{
+ Topic: fmt.Sprintf("CHORUS/collab/chat/%s", chatData["session_id"]),
+ Type: "collaborative_chat",
+ Payload: chatData,
+ Version: "1.0",
+ IssueID: 0,
+ ThreadID: threadID,
+ MsgID: msgID,
+ NodeID: t.runtime.Node.ID().String(),
+ HopCount: 0,
+ Timestamp: time.Now().Unix(),
+ Message: fmt.Sprintf("Chat: %s", chatData["message"]),
+ }
+
+ return t.sendHMMMMessage(message)
+}
+
+// === DECISION TRACKING HELPER FUNCTIONS ===
+
+// getActiveDecisions retrieves all active decisions from DHT
+func (t *TerminalInterface) getActiveDecisions() ([]Decision, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ // In a full implementation, we'd query a decision index
+ // For now, we'll check a few known decision keys and return mock data for demonstration
+ decisions := []Decision{
+ {
+ ID: "DEC-" + generateRandomID(6),
+ Title: "Upgrade libp2p to v0.27",
+ Description: "Proposal to upgrade the libp2p networking library to version 0.27 for improved performance and security features.",
+ Type: "technical",
+ Proposer: "autonomous-agent-7",
+ ProposerType: "agent",
+ CreatedAt: time.Now().Add(-2 * time.Hour),
+ Deadline: time.Now().Add(2*time.Hour + 15*time.Minute),
+ Status: "voting",
+ Votes: make(map[string]DecisionVote),
+ Metadata: map[string]interface{}{
+ "priority": "medium",
+ "category": "infrastructure",
+ },
+ Version: 1,
+ },
+ {
+ ID: "DEC-" + generateRandomID(6),
+ Title: "Adjust task timeout from 30s to 60s",
+ Description: "Increase the default task timeout to accommodate more complex operations while maintaining system responsiveness.",
+ Type: "operational",
+ Proposer: "human-alice",
+ ProposerType: "human",
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ Deadline: time.Now().Add(6*time.Hour + 30*time.Minute),
+ Status: "voting",
+ Votes: make(map[string]DecisionVote),
+ Metadata: map[string]interface{}{
+ "priority": "low",
+ "impact": "moderate",
+ },
+ Version: 1,
+ },
+ }
+
+ // Add some sample votes
+ decisions[0].Votes["agent-monitor-1"] = DecisionVote{
+ VoterID: "agent-monitor-1",
+ VoterType: "agent",
+ Vote: "approve",
+ Reasoning: "Upgrade addresses known security vulnerabilities and improves peer discovery performance.",
+ Timestamp: time.Now().Add(-30 * time.Minute),
+ Confidence: 0.85,
+ }
+
+ decisions[1].Votes["human-bob"] = DecisionVote{
+ VoterID: "human-bob",
+ VoterType: "human",
+ Vote: "defer",
+ Reasoning: "Need more analysis on potential impact to existing workflows before making this change.",
+ Timestamp: time.Now().Add(-15 * time.Minute),
+ Confidence: 0.7,
+ }
+
+ return decisions, nil
+}
+
+// getDecisionByID retrieves a specific decision from DHT
+func (t *TerminalInterface) getDecisionByID(decisionID string) (*Decision, error) {
+ decisions, err := t.getActiveDecisions()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, decision := range decisions {
+ if decision.ID == decisionID {
+ return &decision, nil
+ }
+ }
+
+ return nil, fmt.Errorf("decision %s not found", decisionID)
+}
+
+// saveDecision stores a decision in DHT
+func (t *TerminalInterface) saveDecision(decision *Decision) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ decisionKey := fmt.Sprintf("decision_%s", decision.ID)
+ decisionBytes, err := json.Marshal(decision)
+ if err != nil {
+ return fmt.Errorf("failed to marshal decision: %w", err)
+ }
+
+ return t.runtime.DHT.PutValue(ctx, decisionKey, decisionBytes)
+}
+
+// formatDuration formats a duration into a human-readable string
+func formatDuration(d time.Duration) string {
+ if d < 0 {
+ return "expired"
+ }
+
+ hours := int(d.Hours())
+ minutes := int(d.Minutes()) % 60
+
+ if hours > 24 {
+ days := hours / 24
+ hours = hours % 24
+ return fmt.Sprintf("%dd %dh %dm", days, hours, minutes)
+ } else if hours > 0 {
+ return fmt.Sprintf("%dh %dm", hours, minutes)
+ } else {
+ return fmt.Sprintf("%dm", minutes)
+ }
+}
+
+// generateRandomID generates a random alphanumeric ID
+func generateRandomID(length int) string {
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ result := make([]byte, length)
+ for i := range result {
+ num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
+ result[i] = charset[num.Int64()]
+ }
+ return string(result)
+}
+
+// announceDecisionProposal sends HMMM message about new decision
+func (t *TerminalInterface) announceDecisionProposal(decision *Decision) error {
+ msgID := t.generateMessageID()
+ threadID := fmt.Sprintf("decision-%s", decision.ID)
+
+ message := hmmm.Message{
+ Topic: "CHORUS/decisions/proposals",
+ Type: "decision_proposal",
+ Payload: map[string]interface{}{
+ "decision_id": decision.ID,
+ "title": decision.Title,
+ "description": decision.Description,
+ "type": decision.Type,
+ "proposer": decision.Proposer,
+ "proposer_type": decision.ProposerType,
+ "deadline": decision.Deadline.Unix(),
+ "metadata": decision.Metadata,
+ },
+ Version: "1.0",
+ IssueID: 0,
+ ThreadID: threadID,
+ MsgID: msgID,
+ NodeID: t.runtime.Node.ID().String(),
+ HopCount: 0,
+ Timestamp: time.Now().Unix(),
+ Message: fmt.Sprintf("New decision proposal: %s", decision.Title),
+ }
+
+ return t.sendHMMMMessage(message)
+}
+
+// announceDecisionVote sends HMMM message about vote cast
+func (t *TerminalInterface) announceDecisionVote(decisionID string, vote DecisionVote) error {
+ msgID := t.generateMessageID()
+ threadID := fmt.Sprintf("decision-%s", decisionID)
+
+ message := hmmm.Message{
+ Topic: fmt.Sprintf("CHORUS/decisions/votes/%s", decisionID),
+ Type: "decision_vote",
+ Payload: map[string]interface{}{
+ "decision_id": decisionID,
+ "voter_id": vote.VoterID,
+ "voter_type": vote.VoterType,
+ "vote": vote.Vote,
+ "reasoning": vote.Reasoning,
+ "confidence": vote.Confidence,
+ },
+ Version: "1.0",
+ IssueID: 0,
+ ThreadID: threadID,
+ MsgID: msgID,
+ NodeID: t.runtime.Node.ID().String(),
+ HopCount: 0,
+ Timestamp: time.Now().Unix(),
+ Message: fmt.Sprintf("Vote cast: %s (%s)", vote.Vote, decisionID),
+ }
+
+ return t.sendHMMMMessage(message)
+}
+
+// wrapText wraps text to a specified width
+func wrapText(text string, width int) string {
+ words := strings.Fields(text)
+ if len(words) == 0 {
+ return text
+ }
+
+ var lines []string
+ currentLine := words[0]
+
+ for _, word := range words[1:] {
+ if len(currentLine)+1+len(word) <= width {
+ currentLine += " " + word
+ } else {
+ lines = append(lines, currentLine)
+ currentLine = word
+ }
+ }
+ lines = append(lines, currentLine)
+
+ return strings.Join(lines, "\n")
+}
+
+// === WEB BRIDGE IMPLEMENTATION ===
+
+// startWebBridge starts a web server for browser-based HAP access
+func (t *TerminalInterface) startWebBridge() {
+ fmt.Println("\nš Starting HAP Web Bridge")
+ fmt.Println(strings.Repeat("-", 30))
+
+ // Choose port
+ port := "8090"
+ fmt.Printf("š Starting web server on port %s\n", port)
+
+ // Setup HTTP handlers
+ mux := http.NewServeMux()
+
+ // Static UI endpoints
+ mux.HandleFunc("/", t.webHome)
+ mux.HandleFunc("/status", t.webStatus)
+ mux.HandleFunc("/decisions", t.webDecisions)
+ mux.HandleFunc("/decisions/", t.webDecisionDetails)
+ mux.HandleFunc("/collab", t.webCollaborative)
+ mux.HandleFunc("/patches", t.webPatches)
+ mux.HandleFunc("/hmmm", t.webHMMMMessages)
+
+ // API endpoints
+ mux.HandleFunc("/api/status", t.apiStatus)
+ mux.HandleFunc("/api/decisions", t.apiDecisions)
+ mux.HandleFunc("/api/decisions/vote", t.apiVoteDecision)
+ mux.HandleFunc("/api/decisions/propose", t.apiProposeDecision)
+ mux.HandleFunc("/api/collab/sessions", t.apiCollabSessions)
+ mux.HandleFunc("/api/hmmm/send", t.apiSendHMMMMessage)
+
+ // WebSocket endpoint for real-time updates
+ mux.HandleFunc("/ws", t.webSocket)
+
+ // Create server
+ t.webServer = &http.Server{
+ Addr: ":" + port,
+ Handler: mux,
+ }
+
+ // Start server in background
+ go func() {
+ fmt.Printf("ā
Web interface available at: http://localhost:%s\n", port)
+ fmt.Println("š± Browser HAP interface ready")
+ fmt.Println("š API endpoints available at: /api/*")
+ fmt.Println("ā” Real-time updates via WebSocket: /ws")
+ fmt.Println()
+ fmt.Println("š” Press Enter to return to terminal...")
+
+ if err := t.webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ fmt.Printf("ā Web server error: %v\n", err)
+ }
+ }()
+
+ // Wait for user to return to terminal
+ t.readInput()
+
+ // Optionally stop the server
+ fmt.Print("š Stop web server? (y/n): ")
+ input := t.readInput()
+ if strings.ToLower(strings.TrimSpace(input)) == "y" {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ if err := t.webServer.Shutdown(ctx); err != nil {
+ fmt.Printf("ā Error stopping web server: %v\n", err)
+ } else {
+ fmt.Println("ā
Web server stopped")
+ }
+ t.webServer = nil
+ } else {
+ fmt.Printf("š Web interface continues running at: http://localhost:%s\n", port)
+ }
+}
+
+// webHome serves the main HAP web interface
+func (t *TerminalInterface) webHome(w http.ResponseWriter, r *http.Request) {
+ html := `
+
+
+ CHORUS HAP - Human Agent Portal
+
+
+
+
+
+
+
+
+
+
+
š³ļø Decision Management
+
Participate in network decisions, cast votes, and propose new decisions for community consideration.
+
View Decisions
+
+
+
+
š¤ Collaborative Editing
+
Join collaborative editing sessions, work together on code, and sync changes in real-time.
+
Join Sessions
+
+
+
+
š Patch Management
+
Create patches, submit changes, and track patch reviews through the development workflow.
+
Manage Patches
+
+
+
+
š§ HMMM Network
+
Send reasoning messages, participate in collaborative thinking, and view network discussions.
+
HMMM Messages
+
+
+
+
š Network Status
+
Monitor agent status, view connected peers, and track network health and performance.
+
View Status
+
+
+
+
ā” Live Updates
+
Real-time notifications of network events, decision updates, and collaborative activity.
+
+
+ WebSocket connection: Connecting...
+
+
+
+
+
+
+
+
+
+
+`
+
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(html))
+}
+
+// webStatus serves the status page
+func (t *TerminalInterface) webStatus(w http.ResponseWriter, r *http.Request) {
+ // Get current status
+ nodeID := t.runtime.Node.ID().String()
+ peers := len(t.runtime.Node.Network().Peers())
+
+ html := fmt.Sprintf(`
+
+
+ HAP Status
+
+
+
+
+
+
+
+ Node ID: %s
+
+
+
+ Connected Peers: %d
+
+
+
+ Agent Type: Human Agent (HAP)
+
+
+
+ HMMM Messages Sent: %d
+
+
+
+ Collaborative Session:
+ %s
+
+
+
+ Web Bridge: Active
+
+
+
+
+
+
+
+`,
+ nodeID[:16]+"...",
+ peers,
+ t.hmmmMessageCount,
+ func() string {
+ if t.collaborativeSession != nil {
+ return t.collaborativeSession.SessionID + " (Active)"
+ }
+ return "None"
+ }())
+
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(html))
+}
+
+// webDecisions serves the decisions page
+func (t *TerminalInterface) webDecisions(w http.ResponseWriter, r *http.Request) {
+ decisions, err := t.getActiveDecisions()
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Failed to load decisions: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ html := `
+
+
+ Network Decisions
+
+
+
+
+ `
+
+ for _, decision := range decisions {
+ // Count votes
+ approvals, rejections, deferrals, abstentions := 0, 0, 0, 0
+ for _, vote := range decision.Votes {
+ switch vote.Vote {
+ case "approve": approvals++
+ case "reject": rejections++
+ case "defer": deferrals++
+ case "abstain": abstentions++
+ }
+ }
+
+ timeRemaining := formatDuration(decision.Deadline.Sub(time.Now()))
+
+ html += fmt.Sprintf(`
+
+
%s
+
+ Type: %s | Proposer: %s | Deadline: %s remaining
+
+
+ ā
%d
+ ā %d
+ āøļø %d
+ ā ļø %d
+
+
+
+
`,
+ decision.Title,
+ strings.Title(decision.Type),
+ decision.Proposer,
+ timeRemaining,
+ approvals, rejections, deferrals, abstentions,
+ decision.ID,
+ decision.ID)
+ }
+
+ html += `
+
+
+
+
+
+
+
+`
+
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(html))
+}
+
+// Placeholder web handlers for other interfaces
+func (t *TerminalInterface) webDecisionDetails(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(`Decision Details
Coming soon...
ā Back`))
+}
+
+func (t *TerminalInterface) webCollaborative(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(`Collaborative Editing
Coming soon...
ā Back`))
+}
+
+func (t *TerminalInterface) webPatches(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(`Patch Management
Coming soon...
ā Back`))
+}
+
+func (t *TerminalInterface) webHMMMMessages(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(`HMMM Messages
Coming soon...
ā Back`))
+}
+
+// API handlers
+func (t *TerminalInterface) apiStatus(w http.ResponseWriter, r *http.Request) {
+ status := map[string]interface{}{
+ "node_id": t.runtime.Node.ID().String(),
+ "connected_peers": len(t.runtime.Node.Network().Peers()),
+ "hmmm_messages_sent": t.hmmmMessageCount,
+ "collaborative_session": func() interface{} {
+ if t.collaborativeSession != nil {
+ return t.collaborativeSession.SessionID
+ }
+ return nil
+ }(),
+ "web_bridge_active": true,
+ "timestamp": time.Now().Unix(),
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(status)
+}
+
+func (t *TerminalInterface) apiDecisions(w http.ResponseWriter, r *http.Request) {
+ decisions, err := t.getActiveDecisions()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(decisions)
+}
+
+func (t *TerminalInterface) apiVoteDecision(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ var req struct {
+ DecisionID string `json:"decision_id"`
+ Vote string `json:"vote"`
+ Reasoning string `json:"reasoning"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request", http.StatusBadRequest)
+ return
+ }
+
+ // Validate vote
+ validVotes := map[string]bool{"approve": true, "reject": true, "defer": true, "abstain": true}
+ if !validVotes[req.Vote] {
+ http.Error(w, "Invalid vote option", http.StatusBadRequest)
+ return
+ }
+
+ if req.Reasoning == "" {
+ http.Error(w, "Reasoning is required", http.StatusBadRequest)
+ return
+ }
+
+ // Get and update decision
+ decision, err := t.getDecisionByID(req.DecisionID)
+ if err != nil {
+ http.Error(w, "Decision not found", http.StatusNotFound)
+ return
+ }
+
+ // Create vote record
+ voteRecord := DecisionVote{
+ VoterID: t.runtime.Config.Agent.ID,
+ VoterType: "human",
+ Vote: req.Vote,
+ Reasoning: req.Reasoning,
+ Timestamp: time.Now(),
+ Confidence: 1.0,
+ }
+
+ // Add vote to decision
+ if decision.Votes == nil {
+ decision.Votes = make(map[string]DecisionVote)
+ }
+ decision.Votes[t.runtime.Config.Agent.ID] = voteRecord
+ decision.Version++
+
+ // Save updated decision
+ if err := t.saveDecision(decision); err != nil {
+ http.Error(w, "Failed to save vote", http.StatusInternalServerError)
+ return
+ }
+
+ // Announce vote via HMMM
+ t.announceDecisionVote(req.DecisionID, voteRecord)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "success": true,
+ "message": "Vote submitted successfully",
+ })
+}
+
+// Placeholder API handlers
+func (t *TerminalInterface) apiProposeDecision(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "success": false,
+ "error": "Decision proposal API not yet implemented",
+ })
+}
+
+func (t *TerminalInterface) apiCollabSessions(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "sessions": []interface{}{},
+ "message": "Collaborative sessions API not yet implemented",
+ })
+}
+
+func (t *TerminalInterface) apiSendHMMMMessage(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "success": false,
+ "error": "HMMM message API not yet implemented",
+ })
+}
+
+// webSocket handles WebSocket connections for real-time updates
+func (t *TerminalInterface) webSocket(w http.ResponseWriter, r *http.Request) {
+ // WebSocket upgrade would be implemented here for real-time updates
+ // For now, return a simple message
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write([]byte("WebSocket support not yet implemented"))
}
\ No newline at end of file