@ccmsg/protocol 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/common/topics.ts +12 -2
- package/src/control/agents.ts +23 -4
- package/src/control/dump.ts +28 -1
- package/src/control/instances.ts +21 -0
- package/src/control/peers.ts +52 -68
- package/src/fixtures/control.ts +68 -2
- package/src/fixtures/index.ts +1 -0
- package/src/fixtures/topics.ts +31 -10
- package/src/index.ts +1 -0
- package/src/schemas.ts +2 -0
package/package.json
CHANGED
package/src/common/topics.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const PLAIN_TOPICS = [
|
|
|
7
7
|
"inbox",
|
|
8
8
|
"notify",
|
|
9
9
|
"peers",
|
|
10
|
+
"instances",
|
|
10
11
|
"agents",
|
|
11
12
|
"session_errors",
|
|
12
13
|
"llm_requests",
|
|
@@ -89,8 +90,17 @@ export interface TopicAttributes {
|
|
|
89
90
|
export const TOPIC_ATTRIBUTES = {
|
|
90
91
|
inbox: { roles: ["session", "user"], granularity: "element" },
|
|
91
92
|
notify: { roles: ["session", "user"], granularity: "event" },
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
// A row here changes on its own — one session becomes busy while the rest
|
|
94
|
+
// stand still — so a frame carries the rows that changed rather than every
|
|
95
|
+
// row the instance knows.
|
|
96
|
+
peers: { roles: ["session", "user"], granularity: "element" },
|
|
97
|
+
// One instance's whole reading of its links, taken together, which is why it
|
|
98
|
+
// is apart from the rows of `peers`.
|
|
99
|
+
instances: { roles: ["session", "user"], granularity: "per_instance_whole" },
|
|
100
|
+
agents: { roles: ["user"], granularity: "element" },
|
|
101
|
+
// A set the instance derives whole, by folding one error pattern over its
|
|
102
|
+
// sessions: it learns which sessions are stopped, not that one of them
|
|
103
|
+
// changed, so each frame is that reading entire.
|
|
94
104
|
session_errors: { roles: ["user"], granularity: "per_instance_whole" },
|
|
95
105
|
llm_requests: {
|
|
96
106
|
roles: ["user"],
|
package/src/control/agents.ts
CHANGED
|
@@ -50,15 +50,34 @@ export const AgentInfo = Type.Object(
|
|
|
50
50
|
);
|
|
51
51
|
export type AgentInfo = Static<typeof AgentInfo>;
|
|
52
52
|
|
|
53
|
-
/**
|
|
53
|
+
/** A row that is gone: the harness no longer reports this session, or the
|
|
54
|
+
* instance that polled it stopped. Marked rather than absent, since a frame
|
|
55
|
+
* carries only what changed. */
|
|
56
|
+
export const AgentRemoved = Type.Object(
|
|
57
|
+
{
|
|
58
|
+
sid: Sid,
|
|
59
|
+
instance: InstanceId,
|
|
60
|
+
removed: Type.Literal(true),
|
|
61
|
+
},
|
|
62
|
+
{ $id: "AgentRemoved" },
|
|
63
|
+
);
|
|
64
|
+
export type AgentRemoved = Static<typeof AgentRemoved>;
|
|
65
|
+
|
|
66
|
+
export const AgentElement = Type.Union([AgentInfo, AgentRemoved], { $id: "AgentElement" });
|
|
67
|
+
export type AgentElement = Static<typeof AgentElement>;
|
|
68
|
+
|
|
69
|
+
/** The `agents` topic. Elements, like `peers`: the rows that changed since the
|
|
70
|
+
* last frame, matched by their `instance` and `sid`.
|
|
54
71
|
*
|
|
55
72
|
* The instance polls the harness only while somebody is listening here, so the
|
|
56
|
-
* list is as fresh as the subscription is old
|
|
73
|
+
* list is as fresh as the subscription is old — and a poll that finds one
|
|
74
|
+
* session's status changed says that, rather than restating every row it read.
|
|
75
|
+
*/
|
|
57
76
|
export const AgentsFrame = topicFrame(
|
|
58
77
|
"agents",
|
|
59
78
|
Type.Object({
|
|
60
|
-
agents: Type.Array(
|
|
61
|
-
/** When the poll behind
|
|
79
|
+
agents: Type.Array(AgentElement),
|
|
80
|
+
/** When the poll behind these rows ran. Absent before the first one. */
|
|
62
81
|
polled_at: Type.Optional(Timestamp),
|
|
63
82
|
}),
|
|
64
83
|
);
|
package/src/control/dump.ts
CHANGED
|
@@ -79,6 +79,27 @@ export const TranscriptItemSelector = Type.String({
|
|
|
79
79
|
});
|
|
80
80
|
export type TranscriptItemSelector = Static<typeof TranscriptItemSelector>;
|
|
81
81
|
|
|
82
|
+
/** Whose transcript an item was read from, said as a standing rather than a
|
|
83
|
+
* name: `main` is a session's own transcript, `sub` a throwaway agent's — one
|
|
84
|
+
* started to answer once — and `team` a teammate's, an agent that was named and
|
|
85
|
+
* goes on standing.
|
|
86
|
+
*
|
|
87
|
+
* It is what the relations are read from. `message:parent:in` under a `sub` is
|
|
88
|
+
* an errand's brief and under a `team` is what its lead wrote, and an item that
|
|
89
|
+
* does not say which of the two it stood in can only be placed by whoever
|
|
90
|
+
* remembers the request that fetched it. Carrying it on the item is what lets
|
|
91
|
+
* several transcripts be drawn as one, and what lets the table of relations be
|
|
92
|
+
* checked against items rather than against the request that asked for them.
|
|
93
|
+
*
|
|
94
|
+
* It is not `harness_name`: that is the other party's literal name, this is the
|
|
95
|
+
* subject's own standing, and a subject has one whether or not anything it
|
|
96
|
+
* talked to was named. */
|
|
97
|
+
export const TranscriptSubject = Type.Union(
|
|
98
|
+
[Type.Literal("main"), Type.Literal("team"), Type.Literal("sub")],
|
|
99
|
+
{ $id: "TranscriptSubject" },
|
|
100
|
+
);
|
|
101
|
+
export type TranscriptSubject = Static<typeof TranscriptSubject>;
|
|
102
|
+
|
|
82
103
|
/** Where in the transcript the record an item was read from begins, and how far
|
|
83
104
|
* it runs.
|
|
84
105
|
*
|
|
@@ -115,10 +136,16 @@ export type TranscriptItemId = Static<typeof TranscriptItemId>;
|
|
|
115
136
|
* `id` is what the links below point with — never a position in the array,
|
|
116
137
|
* since a selection or a range decides which items exist in a given read — and
|
|
117
138
|
* `uuid` stays beside it as the record the item came out of, which is what a
|
|
118
|
-
* reader groups by when it wants the whole of one line.
|
|
139
|
+
* reader groups by when it wants the whole of one line.
|
|
140
|
+
*
|
|
141
|
+
* `subject` is on every item because an item is read out of one transcript and
|
|
142
|
+
* ends up among items from others: a client draws a session beside the agents
|
|
143
|
+
* below it, and the standing it was read from is then the item's own to state
|
|
144
|
+
* and not something to be inferred from the call that fetched it. */
|
|
119
145
|
const BASE_FIELDS = {
|
|
120
146
|
id: TranscriptItemId,
|
|
121
147
|
uuid: Type.String({ minLength: 1 }),
|
|
148
|
+
subject: TranscriptSubject,
|
|
122
149
|
source: TranscriptItemSource,
|
|
123
150
|
/** The item's own instant. A call and its result each keep their own. */
|
|
124
151
|
at: Timestamp,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { InstanceInfo } from "../common/hello.ts";
|
|
3
|
+
import { topicFrame } from "../envelope.ts";
|
|
4
|
+
|
|
5
|
+
/** The `instances` topic: the mesh as one instance sees it, itself included,
|
|
6
|
+
* as `hello` answers it.
|
|
7
|
+
*
|
|
8
|
+
* Carried as a topic so that a link going down is something a subscriber
|
|
9
|
+
* learns where it is already listening, rather than by greeting again to find
|
|
10
|
+
* out.
|
|
11
|
+
*
|
|
12
|
+
* Whole-value per instance: `reachable` is one instance's reading of every
|
|
13
|
+
* link it has, taken together, and two instances may legitimately disagree
|
|
14
|
+
* about the same link — so a frame states one sender's whole view and leaves
|
|
15
|
+
* every other sender's alone. It is apart from `peers` for the same reason it
|
|
16
|
+
* is whole: a mesh view is one value, while a session row is a row, and an
|
|
17
|
+
* entry here may also stand before its instance has an id to be matched by. */
|
|
18
|
+
export const InstancesFrame = topicFrame(
|
|
19
|
+
"instances",
|
|
20
|
+
Type.Object({ instances: Type.Array(InstanceInfo) }),
|
|
21
|
+
);
|
package/src/control/peers.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Static, Type } from "@sinclair/typebox";
|
|
2
|
-
import { InstanceInfo } from "../common/hello.ts";
|
|
3
2
|
import { topicFrame } from "../envelope.ts";
|
|
4
3
|
import { InstanceId, Sid, Timestamp } from "../identifiers.ts";
|
|
5
4
|
import { SessionMetaFields } from "../session-meta.ts";
|
|
@@ -31,8 +30,9 @@ export const SessionState = Type.Union(
|
|
|
31
30
|
);
|
|
32
31
|
export type SessionState = Static<typeof SessionState>;
|
|
33
32
|
|
|
34
|
-
/** How long a session
|
|
35
|
-
*
|
|
33
|
+
/** How long a lost session's row is kept after it was last seen, before the
|
|
34
|
+
* instance forgets it and the row leaves as a removal. The same window the inbox
|
|
35
|
+
* keeps undelivered messages for: what a person comes back to
|
|
36
36
|
* is one thing — the session and what was said to it — so the two cannot expire
|
|
37
37
|
* at different times. */
|
|
38
38
|
export const LAST_LIVE_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -57,11 +57,20 @@ export const StaleClientInfo = Type.Object(
|
|
|
57
57
|
);
|
|
58
58
|
export type StaleClientInfo = Static<typeof StaleClientInfo>;
|
|
59
59
|
|
|
60
|
-
/** One session an instance
|
|
60
|
+
/** One session an instance knows of, connected or lost.
|
|
61
61
|
*
|
|
62
62
|
* The paths are the host's, so the entry names the instance holding them:
|
|
63
63
|
* `peers` carries every instance's sessions in one list, and two hosts' paths
|
|
64
|
-
* would otherwise be indistinguishable.
|
|
64
|
+
* would otherwise be indistinguishable. The pair `instance` and `sid` is also
|
|
65
|
+
* what a later row is matched against, since one session lives on one
|
|
66
|
+
* instance.
|
|
67
|
+
*
|
|
68
|
+
* Connected and lost sessions are one kind of row rather than two lists: a
|
|
69
|
+
* session registering or going quiet moves it between the two, and a row that
|
|
70
|
+
* changed lists while keeping its identity is an update of that row. Which it
|
|
71
|
+
* is now is the `state` below, and the fields that only a lost session has
|
|
72
|
+
* (`last_seen_at`, `stopped_at`, and what it must resume as) are stated
|
|
73
|
+
* alongside the connection fields it kept. */
|
|
65
74
|
export const PeerInfo = Type.Object(
|
|
66
75
|
{
|
|
67
76
|
sid: Sid,
|
|
@@ -81,10 +90,10 @@ export const PeerInfo = Type.Object(
|
|
|
81
90
|
* notification's `sid_label` — so the material for those is on the row that
|
|
82
91
|
* every client already holds. */
|
|
83
92
|
title: Type.Optional(SessionMetaFields.title),
|
|
84
|
-
/** How this session stands
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
93
|
+
/** How this session stands, which is also what separates a connected row
|
|
94
|
+
* from one its instance has lost. Absent from an instance that states no
|
|
95
|
+
* classification, and a client then shows the session without grouping it
|
|
96
|
+
* rather than guessing one. */
|
|
88
97
|
state: Type.Optional(SessionState),
|
|
89
98
|
/** Set while a person has pinned this session. Absent means not pinned. */
|
|
90
99
|
pinned: Type.Optional(Type.Boolean()),
|
|
@@ -129,77 +138,52 @@ export const PeerInfo = Type.Object(
|
|
|
129
138
|
protocol_version: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
130
139
|
/** Set while some client of this session is being refused. */
|
|
131
140
|
stale_client: Type.Optional(StaleClientInfo),
|
|
141
|
+
/** The newest instant this session is known to have been alive. Stated on a
|
|
142
|
+
* row its instance has lost, where it is what the retention window above is
|
|
143
|
+
* measured from; a connected row is alive now and states none. */
|
|
144
|
+
last_seen_at: Type.Optional(Timestamp),
|
|
145
|
+
/** When the session said it was stopping. Its presence is what makes a lost
|
|
146
|
+
* session a pause rather than a disappearance: one that goes without a word
|
|
147
|
+
* leaves nothing to stamp here. */
|
|
148
|
+
stopped_at: Type.Optional(Timestamp),
|
|
149
|
+
/** What its last turn ran as, in the transcript's own spelling, read back
|
|
150
|
+
* from the transcript rather than copied from the connection: what a lost
|
|
151
|
+
* session must resume as is a property of where it actually stopped. A
|
|
152
|
+
* resume must not quietly switch the session to something else. */
|
|
153
|
+
model: Type.Optional(SessionMetaFields.model),
|
|
154
|
+
effort: Type.Optional(SessionMetaFields.effort),
|
|
132
155
|
},
|
|
133
156
|
{ $id: "PeerInfo" },
|
|
134
157
|
);
|
|
135
158
|
export type PeerInfo = Static<typeof PeerInfo>;
|
|
136
159
|
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
160
|
+
/** A row that is gone: the session has been forgotten by the instance that
|
|
161
|
+
* held it, whether its retention window ran out or a person dropped it.
|
|
139
162
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
|
|
145
|
-
* written. An entry leaves this list the moment its session registers again, so
|
|
146
|
-
* a fully recovered host shows none. */
|
|
147
|
-
export const LastLiveSession = Type.Object(
|
|
163
|
+
* A removal has to be a marked element rather than an absence, since a frame
|
|
164
|
+
* carries only what changed and an absence in it says nothing. It names the
|
|
165
|
+
* same pair every row is matched by and nothing else — there is no row left to
|
|
166
|
+
* describe. */
|
|
167
|
+
export const PeerRemoved = Type.Object(
|
|
148
168
|
{
|
|
149
169
|
sid: Sid,
|
|
150
170
|
instance: InstanceId,
|
|
151
|
-
|
|
152
|
-
ws: SessionMetaFields.ws,
|
|
153
|
-
cwd: SessionMetaFields.cwd,
|
|
154
|
-
/** Also where the model and effort below were read from. */
|
|
155
|
-
transcript_path: Type.Optional(SessionMetaFields.transcript_path),
|
|
156
|
-
repo_root: Type.Optional(SessionMetaFields.repo_root),
|
|
157
|
-
branch: Type.Optional(SessionMetaFields.branch),
|
|
158
|
-
title: Type.Optional(SessionMetaFields.title),
|
|
159
|
-
/** How this session stands: `paused` or `disappeared`, which the
|
|
160
|
-
* `stopped_at` below is what separates. Absent from an instance that states
|
|
161
|
-
* no classification. */
|
|
162
|
-
state: Type.Optional(SessionState),
|
|
163
|
-
/** Set while a person has pinned this session. Absent means not pinned. */
|
|
164
|
-
pinned: Type.Optional(Type.Boolean()),
|
|
165
|
-
connected_at: Type.Optional(Timestamp),
|
|
166
|
-
/** The newest instant this session is known to have been alive. */
|
|
167
|
-
last_seen_at: Timestamp,
|
|
168
|
-
/** When the session said it was stopping. Its presence is what makes this a
|
|
169
|
-
* pause rather than a disappearance: a session that goes without a word
|
|
170
|
-
* leaves nothing to stamp here. */
|
|
171
|
-
stopped_at: Type.Optional(Timestamp),
|
|
172
|
-
/** What its last turn ran as, in the transcript's own spelling. A resume
|
|
173
|
-
* must not quietly switch the session to something else. */
|
|
174
|
-
model: Type.Optional(SessionMetaFields.model),
|
|
175
|
-
effort: Type.Optional(SessionMetaFields.effort),
|
|
171
|
+
removed: Type.Literal(true),
|
|
176
172
|
},
|
|
177
|
-
{ $id: "
|
|
173
|
+
{ $id: "PeerRemoved" },
|
|
178
174
|
);
|
|
179
|
-
export type
|
|
175
|
+
export type PeerRemoved = Static<typeof PeerRemoved>;
|
|
176
|
+
|
|
177
|
+
export const PeerElement = Type.Union([PeerInfo, PeerRemoved], { $id: "PeerElement" });
|
|
178
|
+
export type PeerElement = Static<typeof PeerElement>;
|
|
180
179
|
|
|
181
180
|
/** The `peers` topic.
|
|
182
181
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
182
|
+
* Elements: each frame carries the rows that changed, matched by their
|
|
183
|
+
* `instance` and `sid`, and rows it does not name are left as they were. A row
|
|
184
|
+
* changes on its own — one session becoming busy, another going quiet — and
|
|
185
|
+
* restating every row an instance knows would send a list to report one field.
|
|
186
186
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
export const PeersFrame = topicFrame(
|
|
190
|
-
"peers",
|
|
191
|
-
Type.Object({
|
|
192
|
-
peers: Type.Array(PeerInfo),
|
|
193
|
-
last_live: Type.Array(LastLiveSession),
|
|
194
|
-
/** The instances the sending one can see, itself included, as `hello`
|
|
195
|
-
* answers it. Carried here so that a link going down is something a
|
|
196
|
-
* subscriber learns from the topic it is already on, rather than by
|
|
197
|
-
* greeting again to find out. It is the sender's own view like the two
|
|
198
|
-
* lists above — `reachable` says whether that instance can reach the one it
|
|
199
|
-
* names, which two instances may legitimately disagree about.
|
|
200
|
-
*
|
|
201
|
-
* Absent from an instance that states no view; a client then keeps what it
|
|
202
|
-
* last knew rather than reading the omission as nothing being reachable. */
|
|
203
|
-
instances: Type.Optional(Type.Array(InstanceInfo)),
|
|
204
|
-
}),
|
|
205
|
-
);
|
|
187
|
+
* The opening `snapshot: true` frame carries every row the instance holds,
|
|
188
|
+
* connected and lost alike. */
|
|
189
|
+
export const PeersFrame = topicFrame("peers", Type.Object({ peers: Type.Array(PeerElement) }));
|
package/src/fixtures/control.ts
CHANGED
|
@@ -230,6 +230,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
230
230
|
{
|
|
231
231
|
id: "3f9a21c4:0",
|
|
232
232
|
uuid: "3f9a21c4",
|
|
233
|
+
subject: "main",
|
|
233
234
|
source: { offset: 180_000, bytes: 420 },
|
|
234
235
|
type: "message:user:in",
|
|
235
236
|
at: FIXTURE_NOW - 3_600_000,
|
|
@@ -239,6 +240,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
239
240
|
{
|
|
240
241
|
id: "f10b6d43:0",
|
|
241
242
|
uuid: "f10b6d43",
|
|
243
|
+
subject: "main",
|
|
242
244
|
source: { offset: 180_420, bytes: 980 },
|
|
243
245
|
type: "thinking",
|
|
244
246
|
at: FIXTURE_NOW - 3_500_000,
|
|
@@ -247,6 +249,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
247
249
|
{
|
|
248
250
|
id: "f10b6d43:1",
|
|
249
251
|
uuid: "f10b6d43",
|
|
252
|
+
subject: "main",
|
|
250
253
|
source: { offset: 180_420, bytes: 980 },
|
|
251
254
|
type: "tool:Bash",
|
|
252
255
|
at: FIXTURE_NOW - 3_400_000,
|
|
@@ -259,6 +262,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
259
262
|
{
|
|
260
263
|
id: "18d6f2c9:0",
|
|
261
264
|
uuid: "18d6f2c9",
|
|
265
|
+
subject: "main",
|
|
262
266
|
source: { offset: 181_400, bytes: 260 },
|
|
263
267
|
type: "tool:Bash",
|
|
264
268
|
at: FIXTURE_NOW - 3_399_000,
|
|
@@ -271,6 +275,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
271
275
|
{
|
|
272
276
|
id: "b7e41d09:0",
|
|
273
277
|
uuid: "b7e41d09",
|
|
278
|
+
subject: "main",
|
|
274
279
|
source: { offset: 181_660, bytes: 640 },
|
|
275
280
|
type: "message:sub:out",
|
|
276
281
|
at: FIXTURE_NOW - 3_300_000,
|
|
@@ -284,6 +289,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
284
289
|
{
|
|
285
290
|
id: "92e6d4f5:0",
|
|
286
291
|
uuid: "92e6d4f5",
|
|
292
|
+
subject: "main",
|
|
287
293
|
source: { offset: 182_300, bytes: 310 },
|
|
288
294
|
type: "hook:PreToolUse",
|
|
289
295
|
at: FIXTURE_NOW - 3_200_000,
|
|
@@ -295,6 +301,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
295
301
|
{
|
|
296
302
|
id: "81d5c3e4:0",
|
|
297
303
|
uuid: "81d5c3e4",
|
|
304
|
+
subject: "main",
|
|
298
305
|
source: { offset: 182_610, bytes: 190 },
|
|
299
306
|
type: "system:attachment:queued_command",
|
|
300
307
|
at: FIXTURE_NOW - 3_100_000,
|
|
@@ -303,6 +310,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
303
310
|
{
|
|
304
311
|
id: "c8a2f371:0",
|
|
305
312
|
uuid: "c8a2f371",
|
|
313
|
+
subject: "main",
|
|
306
314
|
source: { offset: 182_800, bytes: 520 },
|
|
307
315
|
type: "message:team:out",
|
|
308
316
|
at: FIXTURE_NOW - 3_000_000,
|
|
@@ -317,6 +325,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
317
325
|
{
|
|
318
326
|
id: "e5b70c93:0",
|
|
319
327
|
uuid: "e5b70c93",
|
|
328
|
+
subject: "main",
|
|
320
329
|
source: { offset: 183_320, bytes: 300 },
|
|
321
330
|
type: "message:team:in",
|
|
322
331
|
at: FIXTURE_NOW - 2_900_000,
|
|
@@ -326,6 +335,7 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
326
335
|
{
|
|
327
336
|
id: "d4c1a0b2:0",
|
|
328
337
|
uuid: "d4c1a0b2",
|
|
338
|
+
subject: "main",
|
|
329
339
|
source: { offset: 183_620, bytes: 410 },
|
|
330
340
|
type: "message:team:in",
|
|
331
341
|
at: FIXTURE_NOW - 2_800_000,
|
|
@@ -339,15 +349,18 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
|
|
|
339
349
|
},
|
|
340
350
|
];
|
|
341
351
|
|
|
342
|
-
/** The same vocabulary read with
|
|
352
|
+
/** The same vocabulary read with a throwaway agent as the subject.
|
|
343
353
|
*
|
|
344
354
|
* Nothing here is a new type: the brief an agent opens with and the answer it
|
|
345
355
|
* closes with are what `message:parent` names from wherever it is read, and the
|
|
346
|
-
* answer comes as prose with no call behind it.
|
|
356
|
+
* answer comes as prose with no call behind it. What says these were read from
|
|
357
|
+
* an errand's transcript rather than a session's is `subject`, which every item
|
|
358
|
+
* carries. */
|
|
347
359
|
export const TRANSCRIPT_ITEMS_AGENT_SUBJECT: Static<typeof TranscriptItem>[] = [
|
|
348
360
|
{
|
|
349
361
|
id: "a9f30d15:0",
|
|
350
362
|
uuid: "a9f30d15",
|
|
363
|
+
subject: "sub",
|
|
351
364
|
source: { offset: 0, bytes: 1_240 },
|
|
352
365
|
type: "message:parent:in",
|
|
353
366
|
at: FIXTURE_NOW - 3_290_000,
|
|
@@ -357,6 +370,7 @@ export const TRANSCRIPT_ITEMS_AGENT_SUBJECT: Static<typeof TranscriptItem>[] = [
|
|
|
357
370
|
{
|
|
358
371
|
id: "b0e41c26:0",
|
|
359
372
|
uuid: "b0e41c26",
|
|
373
|
+
subject: "sub",
|
|
360
374
|
source: { offset: 1_240, bytes: 380 },
|
|
361
375
|
type: "message:parent:out",
|
|
362
376
|
at: FIXTURE_NOW - 3_260_000,
|
|
@@ -369,6 +383,7 @@ export const TRANSCRIPT_ITEMS_AGENT_SUBJECT: Static<typeof TranscriptItem>[] = [
|
|
|
369
383
|
{
|
|
370
384
|
id: "c1f52d37:0",
|
|
371
385
|
uuid: "c1f52d37",
|
|
386
|
+
subject: "sub",
|
|
372
387
|
source: { offset: 1_620, bytes: 690 },
|
|
373
388
|
type: "message:parent:out",
|
|
374
389
|
at: FIXTURE_NOW - 3_200_000,
|
|
@@ -376,6 +391,57 @@ export const TRANSCRIPT_ITEMS_AGENT_SUBJECT: Static<typeof TranscriptItem>[] = [
|
|
|
376
391
|
},
|
|
377
392
|
];
|
|
378
393
|
|
|
394
|
+
/** The same vocabulary again, read with a teammate as the subject.
|
|
395
|
+
*
|
|
396
|
+
* A teammate stands where neither of the other two does: it answers a lead, as
|
|
397
|
+
* an errand does, and a person can also type at it directly, as a session's own
|
|
398
|
+
* transcript has. The relations are the ones already spelled out — what tells
|
|
399
|
+
* these apart from the errand's above is `subject`, and it is what makes the
|
|
400
|
+
* three columns of the table checkable against items. */
|
|
401
|
+
export const TRANSCRIPT_ITEMS_TEAM_SUBJECT: Static<typeof TranscriptItem>[] = [
|
|
402
|
+
{
|
|
403
|
+
id: "d2a63e48:0",
|
|
404
|
+
uuid: "d2a63e48",
|
|
405
|
+
subject: "team",
|
|
406
|
+
source: { offset: 0, bytes: 1_480 },
|
|
407
|
+
type: "message:parent:in",
|
|
408
|
+
at: FIXTURE_NOW - 3_000_000,
|
|
409
|
+
turn: 1,
|
|
410
|
+
text: "契約に message:parent と message:team を足して",
|
|
411
|
+
harness_name: "main",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "e3b74f59:0",
|
|
415
|
+
uuid: "e3b74f59",
|
|
416
|
+
subject: "team",
|
|
417
|
+
source: { offset: 1_480, bytes: 260 },
|
|
418
|
+
type: "message:user:in",
|
|
419
|
+
at: FIXTURE_NOW - 2_950_000,
|
|
420
|
+
text: "fixtures も忘れずに",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: "f4c8506a:0",
|
|
424
|
+
uuid: "f4c8506a",
|
|
425
|
+
subject: "team",
|
|
426
|
+
source: { offset: 1_740, bytes: 340 },
|
|
427
|
+
type: "message:team:out",
|
|
428
|
+
at: FIXTURE_NOW - 2_920_000,
|
|
429
|
+
role: "use",
|
|
430
|
+
tool_use_id: "toolu_01Wc7Zdq",
|
|
431
|
+
text: "daemon 側の分類はどこまで進んでいる",
|
|
432
|
+
harness_name: "daemon-dump-items",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: "a5d9617b:0",
|
|
436
|
+
uuid: "a5d9617b",
|
|
437
|
+
subject: "team",
|
|
438
|
+
source: { offset: 2_080, bytes: 410 },
|
|
439
|
+
type: "message:parent:out",
|
|
440
|
+
at: FIXTURE_NOW - 2_800_000,
|
|
441
|
+
text: "4 型を足して 1.17.0 を切った",
|
|
442
|
+
},
|
|
443
|
+
];
|
|
444
|
+
|
|
379
445
|
export const TRANSCRIPT_READ_REQUEST: Static<typeof TranscriptReadRequest> = {
|
|
380
446
|
request_id,
|
|
381
447
|
op: "transcript_read",
|
package/src/fixtures/index.ts
CHANGED
|
@@ -163,6 +163,7 @@ export const TOPIC_FIXTURES = {
|
|
|
163
163
|
inbox: topics.INBOX_FRAME,
|
|
164
164
|
notify: topics.NOTIFY_FRAME,
|
|
165
165
|
peers: topics.PEERS_FRAME,
|
|
166
|
+
instances: topics.INSTANCES_FRAME,
|
|
166
167
|
agents: topics.AGENTS_FRAME,
|
|
167
168
|
session_status: topics.SESSION_STATUS_FRAME,
|
|
168
169
|
transcript: topics.TRANSCRIPT_FRAME,
|
package/src/fixtures/topics.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Static } from "@sinclair/typebox";
|
|
2
2
|
import type { AuthRecordsFrame } from "../common/auth.ts";
|
|
3
3
|
import type { AgentsFrame } from "../control/agents.ts";
|
|
4
|
+
import type { InstancesFrame } from "../control/instances.ts";
|
|
4
5
|
import type { KvFrame } from "../control/kv.ts";
|
|
5
6
|
import type { LlmRequestsFrame, LlmStatusFrame } from "../control/llm.ts";
|
|
6
7
|
import type { PeersFrame } from "../control/peers.ts";
|
|
@@ -94,16 +95,6 @@ export const PEERS_FRAME: Static<typeof PeersFrame> = {
|
|
|
94
95
|
repo: "ccmsg",
|
|
95
96
|
ws: "daemon-v2",
|
|
96
97
|
cwd: "/repos/kawaz/ccmsg/daemon-v2",
|
|
97
|
-
state: "live_unmanaged",
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
last_live: [
|
|
101
|
-
{
|
|
102
|
-
sid: other_sid,
|
|
103
|
-
instance,
|
|
104
|
-
repo: "ccmsg",
|
|
105
|
-
ws: "daemon-v2",
|
|
106
|
-
cwd: "/repos/kawaz/ccmsg/daemon-v2",
|
|
107
98
|
state: "paused",
|
|
108
99
|
last_seen_at: FIXTURE_NOW - 1_200_000,
|
|
109
100
|
stopped_at: FIXTURE_NOW - 1_000_000,
|
|
@@ -111,6 +102,28 @@ export const PEERS_FRAME: Static<typeof PeersFrame> = {
|
|
|
111
102
|
effort: "high",
|
|
112
103
|
},
|
|
113
104
|
],
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** A later frame: one row moved on, and one the instance forgot. */
|
|
109
|
+
export const PEERS_CHANGE_FRAME: Static<typeof PeersFrame> = {
|
|
110
|
+
ev: "topic",
|
|
111
|
+
topic: "peers",
|
|
112
|
+
instance,
|
|
113
|
+
data: {
|
|
114
|
+
peers: [
|
|
115
|
+
{ ...PEER, state: "live", gateway_active_at: FIXTURE_NOW + 1_000 },
|
|
116
|
+
{ sid: "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", instance, removed: true },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const INSTANCES_FRAME: Static<typeof InstancesFrame> = {
|
|
122
|
+
ev: "topic",
|
|
123
|
+
topic: "instances",
|
|
124
|
+
snapshot: true,
|
|
125
|
+
instance,
|
|
126
|
+
data: {
|
|
114
127
|
instances: [
|
|
115
128
|
{ id: instance, endpoint, host: "mba", reachable: true },
|
|
116
129
|
{ id: other_instance, endpoint: other_endpoint, host: "nuc", reachable: false },
|
|
@@ -144,6 +157,14 @@ export const AGENTS_FRAME: Static<typeof AgentsFrame> = {
|
|
|
144
157
|
},
|
|
145
158
|
};
|
|
146
159
|
|
|
160
|
+
/** A later frame: the harness no longer reports one of the rows above. */
|
|
161
|
+
export const AGENTS_CHANGE_FRAME: Static<typeof AgentsFrame> = {
|
|
162
|
+
ev: "topic",
|
|
163
|
+
topic: "agents",
|
|
164
|
+
instance,
|
|
165
|
+
data: { agents: [{ sid: other_sid, instance, removed: true }], polled_at: FIXTURE_NOW + 5_000 },
|
|
166
|
+
};
|
|
167
|
+
|
|
147
168
|
export const SESSION_STATUS_FRAME: Static<typeof SessionStatusFrame> = {
|
|
148
169
|
ev: "topic",
|
|
149
170
|
topic: `session_status:${sid}`,
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./common/topics.ts";
|
|
|
7
7
|
export * from "./control/agents.ts";
|
|
8
8
|
export * from "./control/dump.ts";
|
|
9
9
|
export * from "./control/files.ts";
|
|
10
|
+
export * from "./control/instances.ts";
|
|
10
11
|
export * from "./control/kv.ts";
|
|
11
12
|
export * from "./control/launcher.ts";
|
|
12
13
|
export * from "./control/llm.ts";
|
package/src/schemas.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
LlmUsageReadRequest,
|
|
78
78
|
LlmUsageReadResponse,
|
|
79
79
|
} from "./control/llm.ts";
|
|
80
|
+
import { InstancesFrame } from "./control/instances.ts";
|
|
80
81
|
import { PeersFrame } from "./control/peers.ts";
|
|
81
82
|
import {
|
|
82
83
|
SandboxGrantRequest,
|
|
@@ -201,6 +202,7 @@ export const TOPIC_SCHEMAS = {
|
|
|
201
202
|
inbox: InboxFrame,
|
|
202
203
|
notify: NotifyFrame,
|
|
203
204
|
peers: PeersFrame,
|
|
205
|
+
instances: InstancesFrame,
|
|
204
206
|
agents: AgentsFrame,
|
|
205
207
|
session_status: SessionStatusFrame,
|
|
206
208
|
transcript: TranscriptFrame,
|