@ccmsg/protocol 1.16.0 → 1.18.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccmsg/protocol",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "Wire contract (schema + types + op attribute table) shared by the ccmsg daemon and web UI",
5
5
  "license": "MIT",
6
6
  "author": "kawaz",
@@ -24,7 +24,17 @@ import { Sid, Timestamp } from "../identifiers.ts";
24
24
  * Segments after the first carry the spelling of whatever named them, which is
25
25
  * why they are not held to snake_case: `tool:Bash` and `hook:PreToolUse` are
26
26
  * the harness's words, and rewriting them would leave the reader unable to
27
- * match what it sees against what it ran. */
27
+ * match what it sees against what it ran.
28
+ *
29
+ * Under `message`, the second segment is **a relation read from the subject**:
30
+ * `parent` is whoever started this agent, `sub` a throwaway agent it started,
31
+ * `team` a named counterpart that goes on standing, `session` another session
32
+ * over ccmsg. The one exception is `user` — a person is not a relation to
33
+ * anyone, but the user, standing alone. A harness's own name for a party is
34
+ * never a type: `message:main` would read as the main session's traffic being
35
+ * overheard wherever it happens, when what is meant is the party this subject
36
+ * answers to — which is what `parent` says. The literal names (`main`, a
37
+ * lead's, a teammate's) are kept in `harness_name` on the item. */
28
38
  export const TranscriptItemType = Type.String({
29
39
  pattern: "^[a-z]+(?::[A-Za-z0-9_.-]+)*$",
30
40
  $id: "TranscriptItemType",
@@ -38,8 +48,12 @@ export type TranscriptItemType = Static<typeof TranscriptItemType>;
38
48
  export const TRANSCRIPT_ITEM_TYPES = [
39
49
  "message:user:in",
40
50
  "message:user:out",
51
+ "message:parent:in",
52
+ "message:parent:out",
41
53
  "message:sub:in",
42
54
  "message:sub:out",
55
+ "message:team:in",
56
+ "message:team:out",
43
57
  "message:session:in",
44
58
  "message:session:out",
45
59
  "thinking",
@@ -156,12 +170,97 @@ const Text = Type.String();
156
170
 
157
171
  /** The subject is a session by default, or one agent below it when the dump
158
172
  * names an agent, and `in` / `out` are read from wherever the subject stands.
159
- * A dump of an agent therefore reads `message:user:in` as the brief its parent
160
- * gave it, on the same type names a dump of the session uses which is what
161
- * lets one preset be carried down a chain of agents. */
173
+ * What moves when the subject moves is who the counterpart is, not the names:
174
+ * the same preset reads a session's talk with a person and an agent's talk with
175
+ * whoever started it, which is what lets one be carried down a chain of agents.
176
+ *
177
+ * The counterpart is named by the kind of party it is — a person, the one above,
178
+ * the throwaway agents below, a teammate that stays, another session — because a
179
+ * dump is read to find out who was talking, and a subject's own position is the
180
+ * one thing it cannot ask about itself. Hence `parent` rather than `user` for
181
+ * the one above: an agent's parent is a session or another agent, and calling it
182
+ * `user` would have a reader take a machine for a person.
183
+ *
184
+ * `message:user` is a person and nobody else. Both directions occur under a
185
+ * session and under a teammate, which someone can type at directly; under a
186
+ * throwaway agent neither does. A combination a subject is not expected to show
187
+ * — `team` below an agent, say — is not refused: an unexpected line is still a
188
+ * line, and it is emitted under the name it fits. */
162
189
  const MessageUserIn = item(Type.Literal("message:user:in"), { text: Text });
163
190
  const MessageUserOut = item(Type.Literal("message:user:out"), { text: Text });
164
191
 
192
+ /** What the one above said, and what was said back to it.
193
+ *
194
+ * An agent's first line is the brief it was started with, and its last is the
195
+ * answer that brief is discharged by; in between it may hand its parent
196
+ * something mid-flight. The answer is plain prose the harness collects, with no
197
+ * call behind it, so `parent:out` is prose-or-call and not a call alone:
198
+ * addressed to the parent is what the two forms have in common, and requiring a
199
+ * `tool_use_id` would leave the one message an agent is certain to send
200
+ * unnameable. */
201
+ const MessageParentIn = item(Type.Literal("message:parent:in"), {
202
+ text: Text,
203
+ /** The harness's own name for the parent — `main`, a lead's name, the agent
204
+ * above. Left out when the record says only that it came from above, which is
205
+ * the case for the brief an agent opens with. */
206
+ harness_name: Type.Optional(Type.String()),
207
+ msg_id: Type.Optional(Type.String()),
208
+ });
209
+
210
+ /** Sent to the parent through a call, which the parent's own transcript has the
211
+ * other half of. */
212
+ const MessageParentOutSent = item(Type.Literal("message:parent:out"), {
213
+ ...USE_FIELDS,
214
+ text: Text,
215
+ /** The harness's own name for the parent, as the subject addressed it. */
216
+ harness_name: Type.Optional(Type.String()),
217
+ summary: Type.Optional(Type.String()),
218
+ });
219
+
220
+ /** Answered to the parent as prose — the agent's reply, final or interim. */
221
+ const MessageParentOutSaid = item(Type.Literal("message:parent:out"), { text: Text });
222
+
223
+ /** A teammate is an agent that was given a name and goes on standing, so what
224
+ * passes between the subject and one is a correspondence rather than an errand:
225
+ * a reply comes back as its own message, addressed and arriving whenever it is
226
+ * written, and not as the answer to the call that sent it.
227
+ *
228
+ * That is the whole of what separates `team` from `sub`. A throwaway agent is
229
+ * started, answers once and is done, which is why `sub:in` is the result of the
230
+ * `sub:out` that started it. Here the two halves of a round trip are two
231
+ * messages, and only the start of a teammate has a result to pair with. */
232
+ const MessageTeamOut = item(Type.Literal("message:team:out"), {
233
+ ...USE_FIELDS,
234
+ text: Text,
235
+ /** The teammate addressed, by the name it stands under. */
236
+ harness_name: Type.Optional(Type.String()),
237
+ summary: Type.Optional(Type.String()),
238
+ agent_id: Type.Optional(Type.String()),
239
+ /** Present on the call that started the teammate, absent on the ones that
240
+ * write to it afterwards. */
241
+ subagent_type: Type.Optional(Type.String()),
242
+ description: Type.Optional(Type.String()),
243
+ });
244
+
245
+ /** A teammate writing to the subject, arriving under its own name whenever it
246
+ * was written. */
247
+ const MessageTeamInSaid = item(Type.Literal("message:team:in"), {
248
+ text: Text,
249
+ /** The name the teammate stands under. */
250
+ harness_name: Type.Optional(Type.String()),
251
+ msg_id: Type.Optional(Type.String()),
252
+ });
253
+
254
+ /** A teammate's run ending, which answers the call that started it. */
255
+ const MessageTeamInDone = item(Type.Literal("message:team:in"), {
256
+ ...RESULT_FIELDS,
257
+ text: Text,
258
+ harness_name: Type.Optional(Type.String()),
259
+ agent_id: Type.Optional(Type.String()),
260
+ status: Type.Optional(Type.String()),
261
+ duration_ms: Type.Optional(Type.Integer({ minimum: 0 })),
262
+ });
263
+
165
264
  const MessageSubOut = item(Type.Literal("message:sub:out"), {
166
265
  ...USE_FIELDS,
167
266
  prompt: Text,
@@ -373,8 +472,14 @@ export const TranscriptItem = Type.Union(
373
472
  [
374
473
  MessageUserIn,
375
474
  MessageUserOut,
475
+ MessageParentIn,
476
+ MessageParentOutSent,
477
+ MessageParentOutSaid,
376
478
  MessageSubOut,
377
479
  MessageSubIn,
480
+ MessageTeamOut,
481
+ MessageTeamInSaid,
482
+ MessageTeamInDone,
378
483
  MessageSessionOut,
379
484
  MessageSessionIn,
380
485
  Thinking,
@@ -213,7 +213,9 @@ export const DUMP_PRESETS_READ_RESPONSE: Static<typeof DumpPresetsReadResponse>
213
213
  {
214
214
  name: "howto",
215
215
  description: "how the work was done: what was thought, run, read and written",
216
- opts: { types: ["thinking", "message:user", "message:sub", "tool:Bash", "@file"] },
216
+ opts: {
217
+ types: ["thinking", "message:user", "message:parent", "message:sub", "tool:Bash", "@file"],
218
+ },
217
219
  },
218
220
  ],
219
221
  };
@@ -298,6 +300,80 @@ export const TRANSCRIPT_ITEMS: Static<typeof TranscriptItem>[] = [
298
300
  at: FIXTURE_NOW - 3_100_000,
299
301
  attachment: { type: "queued_command", command: "/pre-clear" },
300
302
  },
303
+ {
304
+ id: "c8a2f371:0",
305
+ uuid: "c8a2f371",
306
+ source: { offset: 182_800, bytes: 520 },
307
+ type: "message:team:out",
308
+ at: FIXTURE_NOW - 3_000_000,
309
+ role: "use",
310
+ result_item: "d4c1a0b2:0",
311
+ tool_use_id: "toolu_01Tm5XYp",
312
+ text: "契約に message:parent と message:team を足して",
313
+ harness_name: "contract-dump-items",
314
+ agent_id: "b83e0f114",
315
+ subagent_type: "opus5-worker-high",
316
+ },
317
+ {
318
+ id: "e5b70c93:0",
319
+ uuid: "e5b70c93",
320
+ source: { offset: 183_320, bytes: 300 },
321
+ type: "message:team:in",
322
+ at: FIXTURE_NOW - 2_900_000,
323
+ text: "fixtures まで通ったので ci を回す",
324
+ harness_name: "contract-dump-items",
325
+ },
326
+ {
327
+ id: "d4c1a0b2:0",
328
+ uuid: "d4c1a0b2",
329
+ source: { offset: 183_620, bytes: 410 },
330
+ type: "message:team:in",
331
+ at: FIXTURE_NOW - 2_800_000,
332
+ role: "result",
333
+ parent_item: "c8a2f371:0",
334
+ parent_tool_use_id: "toolu_01Tm5XYp",
335
+ text: "4 型を足して 1.17.0 を切った",
336
+ agent_id: "b83e0f114",
337
+ status: "ok",
338
+ duration_ms: 240_000,
339
+ },
340
+ ];
341
+
342
+ /** The same vocabulary read with an agent as the subject.
343
+ *
344
+ * Nothing here is a new type: the brief an agent opens with and the answer it
345
+ * closes with are what `message:parent` names from wherever it is read, and the
346
+ * answer comes as prose with no call behind it. */
347
+ export const TRANSCRIPT_ITEMS_AGENT_SUBJECT: Static<typeof TranscriptItem>[] = [
348
+ {
349
+ id: "a9f30d15:0",
350
+ uuid: "a9f30d15",
351
+ source: { offset: 0, bytes: 1_240 },
352
+ type: "message:parent:in",
353
+ at: FIXTURE_NOW - 3_290_000,
354
+ turn: 1,
355
+ text: "docs/design/dump-kinds.md を書き直す",
356
+ },
357
+ {
358
+ id: "b0e41c26:0",
359
+ uuid: "b0e41c26",
360
+ source: { offset: 1_240, bytes: 380 },
361
+ type: "message:parent:out",
362
+ at: FIXTURE_NOW - 3_260_000,
363
+ role: "use",
364
+ tool_use_id: "toolu_01Qz8Vbn",
365
+ text: "型一覧は 4 群に分けた。preset の例まで直してよいか",
366
+ harness_name: "main",
367
+ summary: "型一覧の分け方を確認",
368
+ },
369
+ {
370
+ id: "c1f52d37:0",
371
+ uuid: "c1f52d37",
372
+ source: { offset: 1_620, bytes: 690 },
373
+ type: "message:parent:out",
374
+ at: FIXTURE_NOW - 3_200_000,
375
+ text: "型一覧を 4 群に整理し、preset の例も揃えた",
376
+ },
301
377
  ];
302
378
 
303
379
  export const TRANSCRIPT_READ_REQUEST: Static<typeof TranscriptReadRequest> = {