From: Anderson A. Giacomolli Date: Sat, 7 Nov 2026 13:12:48 +0000 (-0300) Subject: Show the user messages of the other clients X-Git-Url: http://git.giacomolli.uk/onechat/commitdiff_plain/b2873a2a0bf8b0ba648aebac51759e5fed89a2e2 Show the user messages of the other clients Each user message event names its source client. The socket clients submit as terminal, and the terminal client shows a user message from any other source with the source as a mark, such as "[telegram] text". With more than one client on one agent, each client then shows the full conversation, so a reply always comes with its question. The log does not keep the source. Issue: a143bd8 --- diff --git a/chat.go b/chat.go index 8bc5285..1e96b24 100644 --- a/chat.go +++ b/chat.go @@ -143,12 +143,19 @@ func showEvents(conn net.Conn, out io.Writer) error { } // eventText returns the text that the client shows for event, or "" for none. -// The client shows no user message, because the user typed it. A tool result -// shows its first resultLines lines. +// The client shows a user message only from another source, such as telegram, +// because the user typed the others. A tool result shows its first resultLines +// lines. func eventText(event Event) string { switch event.Kind { case "ready": return "[connected to " + event.Text + "]" + case "user": + if event.Source == terminalSource { + return "" + } + + return "[" + event.Source + "] " + event.Text case "reply": return event.Text case "tool": diff --git a/daemon.go b/daemon.go index 58bf502..68073d0 100644 --- a/daemon.go +++ b/daemon.go @@ -34,6 +34,10 @@ const ( // socketPathBytes is the longest path of a Unix socket on Linux. socketPathBytes = 107 + + // terminalSource is the source of the user messages from the clients of + // the socket. + terminalSource = "terminal" ) // errTokensInUse tells that another onechat process uses the token file. @@ -305,7 +309,7 @@ func runRequest(l *loop, request clientRequest) error { return errors.New("empty message") } - _, err := l.Submit(request.Text) + _, err := l.Submit(terminalSource, request.Text) return err case "interrupt": diff --git a/daemon_test.go b/daemon_test.go index fe40929..00680e6 100644 --- a/daemon_test.go +++ b/daemon_test.go @@ -154,10 +154,15 @@ func TestEventText(t *testing.T) { text: "Done.", }, { - name: "user message not shown", - event: Event{Kind: "user", Text: "typed"}, + name: "own user message not shown", + event: Event{Kind: "user", Text: "typed", Source: terminalSource}, text: "", }, + { + name: "user message of another source marked", + event: Event{Kind: "user", Text: "from the phone", Source: "telegram"}, + text: "[telegram] from the phone", + }, { name: "long result cut", event: Event{Kind: "echo", Text: strings.Repeat("line\n", 12)}, diff --git a/turn.go b/turn.go index f8faad8..0b2b29e 100644 --- a/turn.go +++ b/turn.go @@ -18,11 +18,14 @@ type modelClient interface { // Event tells the clients what the agent does as it happens. Kind is the kind // of a logged message (user, reply, tool, echo), or one of ready, busy, idle, -// and error. ID is the ID of a logged message. +// and error. ID is the ID of a logged message. Source names the client that +// sent a user message, such as terminal or telegram, so that each client can +// show the messages of the others. type Event struct { - Kind string `json:"kind"` - ID int `json:"id,omitempty"` - Text string `json:"text,omitempty"` + Kind string `json:"kind"` + ID int `json:"id,omitempty"` + Text string `json:"text,omitempty"` + Source string `json:"source,omitempty"` } // loop runs the turns of one agent. A user message goes to the log when it @@ -98,10 +101,11 @@ func newLoop(agent *Agent, memory *Memory, client modelClient, clock func() time return l, nil } -// Submit logs a user message and wakes the loop. The message joins the next -// request: the first one of a new turn, or the next one of a running turn. -func (l *loop) Submit(text string) (Message, error) { - message, err := l.Append("user", text) +// Submit logs a user message from the client source and wakes the loop. The +// message joins the next request: the first one of a new turn, or the next one +// of a running turn. +func (l *loop) Submit(source, text string) (Message, error) { + message, err := l.record("user", source, text) if err != nil { return Message{}, err } @@ -144,15 +148,22 @@ func (l *loop) Busy() bool { // Append logs a message and tells the clients. The toolbox logs its calls and // results through it. func (l *loop) Append(kind, text string) (Message, error) { + return l.record(kind, "", text) +} + +// record logs a message and tells the clients, with the client source of a user +// message. +func (l *loop) record(kind, source, text string) (Message, error) { message, err := l.memory.Append(kind, text) if err != nil { return Message{}, err } l.notify(Event{ - Kind: kind, - ID: message.ID, - Text: text, + Kind: kind, + ID: message.ID, + Text: text, + Source: source, }) return message, nil diff --git a/turn_test.go b/turn_test.go index f1295e9..0de7dd7 100644 --- a/turn_test.go +++ b/turn_test.go @@ -30,7 +30,7 @@ func TestTurnRunsToolsUntilTheFinalAnswer(t *testing.T) { } test := newTestLoop(t, server) - _, err := test.loop.Submit("Run echo hi.") + _, err := test.loop.Submit(terminalSource, "Run echo hi.") require.NoError(t, err) test.events.WaitFor(t, "idle", 1) require.Equal(t, 2, server.Calls()) @@ -74,7 +74,7 @@ func TestFirstRequestHoldsTheViewTheStateAndTheMessage(t *testing.T) { _, err := test.memory.Append("user", "an earlier message") require.NoError(t, err) - _, err = test.loop.Submit("The new message.") + _, err = test.loop.Submit(terminalSource, "The new message.") require.NoError(t, err) test.events.WaitFor(t, "idle", 1) @@ -102,11 +102,11 @@ func TestMessageDuringATurnJoinsTheNextRequest(t *testing.T) { } test := newTestLoop(t, server) - _, err := test.loop.Submit("first") + _, err := test.loop.Submit(terminalSource, "first") require.NoError(t, err) <-arrived - _, err = test.loop.Submit("second, during the turn") + _, err = test.loop.Submit(terminalSource, "second, during the turn") require.NoError(t, err) close(release) @@ -136,11 +136,11 @@ func TestMessageDuringTheFinalRequestStartsANewTurn(t *testing.T) { } test := newTestLoop(t, server) - _, err := test.loop.Submit("first") + _, err := test.loop.Submit(terminalSource, "first") require.NoError(t, err) <-arrived - _, err = test.loop.Submit("second") + _, err = test.loop.Submit(terminalSource, "second") require.NoError(t, err) close(release) @@ -161,7 +161,7 @@ func TestTurnWaitsForTheSummaries(t *testing.T) { _, err := test.memory.Append("echo", strings.Repeat("long output ", 100)) require.NoError(t, err) - _, err = test.loop.Submit("go on") + _, err = test.loop.Submit(terminalSource, "go on") require.NoError(t, err) time.Sleep(50 * time.Millisecond) @@ -187,7 +187,7 @@ func TestInterruptEndsTheTurn(t *testing.T) { } test := newTestLoop(t, server) - _, err := test.loop.Submit("a long task") + _, err := test.loop.Submit(terminalSource, "a long task") require.NoError(t, err) <-arrived @@ -199,7 +199,7 @@ func TestInterruptEndsTheTurn(t *testing.T) { require.Equal(t, []string{"user: a long task"}, test.Log(t)) require.False(t, test.loop.Interrupt()) - _, err = test.loop.Submit("again") + _, err = test.loop.Submit(terminalSource, "again") require.NoError(t, err) test.events.WaitFor(t, "idle", 2) require.Equal(t, "reply: answer", test.Log(t)[2]) @@ -212,7 +212,7 @@ func TestFailedCallEndsTheTurnWithoutARetry(t *testing.T) { } test := newTestLoop(t, server) - _, err := test.loop.Submit("hello") + _, err := test.loop.Submit(terminalSource, "hello") require.NoError(t, err) test.events.WaitFor(t, "idle", 1) @@ -225,7 +225,7 @@ func TestViewMessagesOnlyGrowBetweenTurns(t *testing.T) { server := newFakeResponses(t) test := newTestLoop(t, server) for n, text := range []string{"one", "two", "three"} { - _, err := test.loop.Submit(text) + _, err := test.loop.Submit(terminalSource, text) require.NoError(t, err) test.events.WaitFor(t, "idle", n+1) }