@ccmsg/cli 0.11.2 → 0.11.4

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.
Files changed (67) hide show
  1. package/README.md +8 -12
  2. package/package.json +1 -1
  3. package/src/auth/auth.ts +23 -23
  4. package/src/auth/http.ts +3 -3
  5. package/src/auth/records.ts +2 -2
  6. package/src/auth/webauthn.ts +1 -1
  7. package/src/cli.ts +78 -23
  8. package/src/daemon/control.ts +1 -1
  9. package/src/daemon/registry.ts +11 -11
  10. package/src/daemon/snapshot.ts +1 -1
  11. package/src/daemon/supervise.ts +4 -4
  12. package/src/dispatch/caller.ts +1 -1
  13. package/src/dispatch/dispatch.ts +4 -4
  14. package/src/dispatch/handler.ts +4 -4
  15. package/src/dispatch/identity.ts +1 -1
  16. package/src/dispatch/result.ts +2 -2
  17. package/src/files/containment.ts +2 -2
  18. package/src/harness/index.ts +1 -1
  19. package/src/instance/client.ts +1 -1
  20. package/src/instance/config.ts +13 -13
  21. package/src/instance/instance.ts +42 -42
  22. package/src/instance/lock.ts +2 -2
  23. package/src/instance/log.ts +1 -1
  24. package/src/instance/paths.ts +6 -6
  25. package/src/instance/socket.ts +1 -1
  26. package/src/kv/store.ts +1 -1
  27. package/src/mesh/instances.ts +2 -2
  28. package/src/mesh/keys.ts +7 -7
  29. package/src/mesh/mesh.ts +67 -67
  30. package/src/mesh/relay.ts +8 -8
  31. package/src/mesh/wire.ts +2 -2
  32. package/src/messaging/delivery.ts +24 -24
  33. package/src/messaging/direct.ts +23 -23
  34. package/src/messaging/inbox.ts +6 -6
  35. package/src/messaging/notify.ts +5 -5
  36. package/src/plugin/codex.ts +1 -1
  37. package/src/sessions/classify.ts +6 -6
  38. package/src/sessions/dump.ts +1 -1
  39. package/src/sessions/handlers.ts +2 -2
  40. package/src/sessions/harness.ts +12 -12
  41. package/src/sessions/last-live.ts +4 -4
  42. package/src/sessions/registry.ts +32 -32
  43. package/src/sessions/status.ts +7 -7
  44. package/src/sessions/terminals.ts +1 -1
  45. package/src/transcript/files.ts +4 -4
  46. package/src/transcript/fold.ts +9 -9
  47. package/src/transcript/items/classify.ts +56 -8
  48. package/src/transcript/read.ts +1 -1
  49. package/src/transcript/tail.ts +3 -3
  50. package/src/transcript/transcripts.ts +7 -7
  51. package/src/translate/helper.ts +2 -2
  52. package/src/transport/conn.ts +3 -3
  53. package/src/transport/dial.ts +1 -1
  54. package/src/transport/driver.ts +2 -2
  55. package/src/transport/entry.ts +1 -1
  56. package/src/transport/framing.ts +1 -1
  57. package/src/transport/listener.ts +3 -3
  58. package/src/transport/uds.ts +2 -2
  59. package/src/transport/ws.ts +2 -2
  60. package/src/upstream/events.ts +3 -3
  61. package/src/upstream/gateway.ts +6 -6
  62. package/src/upstream/json.ts +1 -1
  63. package/src/upstream/requests.ts +6 -6
  64. package/src/upstream/stats.ts +1 -1
  65. package/src/upstream/status.ts +4 -4
  66. package/src/upstream/usage.ts +1 -1
  67. package/src/upstream/webhook.ts +1 -1
@@ -26,13 +26,13 @@ import type { DirectRoute } from "./direct.ts";
26
26
  import type { Inbox } from "./inbox.ts";
27
27
 
28
28
  /** The topic a message reaches its session on, and the only route in use while
29
- * route (a) waits for confirmation (§4.1). */
29
+ * route (a) waits for confirmation (DESIGN §6.5). */
30
30
  const INBOX = "inbox";
31
31
 
32
32
  /** What delivery reads about a session. Two questions, both answered by the
33
- * sessions domain from the inputs of §5.1: where a session stands, and which
33
+ * sessions domain from the inputs of DESIGN §4.2: where a session stands, and which
34
34
  * sessions are around — neither is asked of anything else, which is what keeps
35
- * the reasons of §4.2 from growing a source per reason. */
35
+ * the reasons of DESIGN §6.6 from growing a source per reason. */
36
36
  export interface SessionLookup {
37
37
  classify(sid: Sid): SessionState | undefined;
38
38
  peerRows(): PeerInfo[];
@@ -43,7 +43,7 @@ export interface SessionLookup {
43
43
  * `message.send` is a `cluster` op — any instance may be asked — but a message
44
44
  * reaches a session through the session's own connections, which are held by
45
45
  * the instance it greeted. So the op is answered here by carrying it there
46
- * (§3.2 step 6 is about `instance-local` ops; this is the same forwarding for
46
+ * (DESIGN §2.2 step 6 is about `instance-local` ops; this is the same forwarding for
47
47
  * the one op whose subject is elsewhere while its op is not). */
48
48
  export interface Cluster {
49
49
  /** Which instance holds this session, or nothing when the cluster has not
@@ -67,20 +67,20 @@ export interface DeliveryDeps {
67
67
  readonly cluster?: Cluster;
68
68
  readonly inbox: Inbox;
69
69
  /** Route (a). Off until it is confirmed against a running harness, which is
70
- * condition 0 of §4.1 and is why this is handed in rather than built here. */
70
+ * condition 0 of DESIGN §6.5 and is why this is handed in rather than built here. */
71
71
  readonly direct: DirectRoute;
72
- /** The one way a value reaches subscribers (§6.1), narrowed to the session a
72
+ /** The one way a value reaches subscribers (DESIGN §6.1), narrowed to the session a
73
73
  * message is for. */
74
74
  readonly publish: (topic: string, data: unknown, instance: InstanceId, to: Sid) => PublishOutcome;
75
75
  /** How many of that session's connections are listening on `inbox`. */
76
76
  readonly listeners: (topic: string, to: Sid) => number;
77
77
  }
78
78
 
79
- /** Delivery, and the inbox topic it delivers on (§4).
79
+ /** Delivery, and the inbox topic it delivers on (DESIGN §6.5-6.8).
80
80
  *
81
- * Two things, as §4 splits them: the route a message takes, and what the sender
81
+ * Two things, as DESIGN §6.5-6.8 splits them: the route a message takes, and what the sender
82
82
  * is told when it took none. The second reads the classification and the inbox
83
- * and nothing else (§4.2) — a reason is a name for a state that was already
83
+ * and nothing else (DESIGN §6.6) — a reason is a name for a state that was already
84
84
  * there, never a state of its own. */
85
85
  export class Delivery implements UpstreamResource {
86
86
  #counter: number;
@@ -92,7 +92,7 @@ export class Delivery implements UpstreamResource {
92
92
  /** The messages an offer has taken responsibility for, per session. They are
93
93
  * still in the inbox — an offer that does not reach the end leaves them
94
94
  * there — but they are spoken for, so the snapshot below hands them to
95
- * nobody: one message goes out on one route (§4.3). */
95
+ * nobody: one message goes out on one route (DESIGN §6.7). */
96
96
  readonly #claimed = new Map<Sid, Set<Mid>>();
97
97
 
98
98
  constructor(private readonly deps: DeliveryDeps) {
@@ -115,14 +115,14 @@ export class Delivery implements UpstreamResource {
115
115
  const direct = await this.deps.direct.send(to, message);
116
116
  if (direct === "delivered") {
117
117
  // Route (a) reaching this session is the session being able to receive,
118
- // which is what the inbox waits for (§4.3). Whatever is still held for it
118
+ // which is what the inbox waits for (DESIGN §6.7). Whatever is still held for it
119
119
  // is offered now, on the route that just worked.
120
120
  await this.#offer(to);
121
121
  return { delivered: true };
122
122
  }
123
123
  if (direct === "refused") {
124
124
  // Turned away for now, which is neither delivered nor undeliverable: it
125
- // waits in the inbox and is offered again (§4.4).
125
+ // waits in the inbox and is offered again (DESIGN §6.8).
126
126
  this.deps.inbox.hold(to, message);
127
127
  return { delivered: false, reason: "throttled" };
128
128
  }
@@ -133,7 +133,7 @@ export class Delivery implements UpstreamResource {
133
133
  }
134
134
  // The session is listening but is behind on what it has already been
135
135
  // offered, which is the same standing as route (a) turning the message
136
- // away: it waits in the inbox and is offered again (§4.4).
136
+ // away: it waits in the inbox and is offered again (DESIGN §6.8).
137
137
  this.deps.inbox.hold(to, message);
138
138
  return { delivered: false, reason: "throttled" };
139
139
  }
@@ -146,11 +146,11 @@ export class Delivery implements UpstreamResource {
146
146
  * or named as one the cluster cannot answer for right now.
147
147
  *
148
148
  * Nothing when the cluster has no such session anywhere and every instance
149
- * could be asked — which is the only case `session_not_found` covers (§4.2).
149
+ * could be asked — which is the only case `session_not_found` covers (DESIGN §6.6).
150
150
  * While an instance is out of reach the sid may well be its, so the sender is
151
151
  * told the reason rather than that the session does not exist. The message is
152
152
  * not held here either: the inbox that would offer it again is the one on the
153
- * instance that owns the session (§4.3). */
153
+ * instance that owns the session (DESIGN §6.7). */
154
154
  async #elsewhere(to: Sid, input: HandlerInput): Promise<MessageSendResult | undefined> {
155
155
  const cluster = this.deps.cluster;
156
156
  if (cluster === undefined) return undefined;
@@ -162,7 +162,7 @@ export class Delivery implements UpstreamResource {
162
162
  }
163
163
  // The sender, as the owning instance will run the op as: the identity the
164
164
  // connection greeted with, which is the same thing `message.send` reads to
165
- // decide who a message is from (§4.1).
165
+ // decide who a message is from (DESIGN §6.5).
166
166
  const answer = await cluster.forward(owner, input.args, callerOf(input));
167
167
  if (answer.kind === "reply") {
168
168
  const { ok: _ok, request_id: _id, ...body } = answer.response;
@@ -197,7 +197,7 @@ export class Delivery implements UpstreamResource {
197
197
  /** Hand a session what it is owed, oldest first, over route (a).
198
198
  *
199
199
  * Stops at the first message the route does not carry, whatever it answered:
200
- * a refusal means the session is taking nothing more for now (§4.4), and an
200
+ * a refusal means the session is taking nothing more for now (DESIGN §6.8), and an
201
201
  * unavailable route means route (b) is the one that applies — either way the
202
202
  * rest stay held, in order, for the next time this session becomes able to
203
203
  * receive. */
@@ -223,7 +223,7 @@ export class Delivery implements UpstreamResource {
223
223
  }
224
224
  }
225
225
 
226
- // --- UpstreamResource (§6.3)
226
+ // --- UpstreamResource (DESIGN §6.3)
227
227
 
228
228
  /** Nothing upstream to run: what is undelivered is already in hand, and the
229
229
  * messages that arrive later come through `send`. */
@@ -232,12 +232,12 @@ export class Delivery implements UpstreamResource {
232
232
  stop(): void {}
233
233
 
234
234
  /** The current value of `inbox` for whoever subscribed: everything still
235
- * undelivered for that session (§6.2, element granularity — the snapshot is
235
+ * undelivered for that session (DESIGN §6.2, element granularity — the snapshot is
236
236
  * every element, a later frame is one).
237
237
  *
238
238
  * Subscribing is receiving, so the snapshot empties the inbox: the frame is
239
239
  * queued on the connection before this returns, and a message the session has
240
- * been handed is not one that is still waiting for it (§4.3). A connection
240
+ * been handed is not one that is still waiting for it (DESIGN §6.7). A connection
241
241
  * with no session — a person watching — is handed nothing, because the topic
242
242
  * carries what was said to a session and they are not one.
243
243
  *
@@ -260,7 +260,7 @@ export class Delivery implements UpstreamResource {
260
260
  }
261
261
 
262
262
  /** The reason a message is waiting, named from the classification alone
263
- * (§4.2). `preparing` is the live session with nowhere to put it: it is there,
263
+ * (DESIGN §6.6). `preparing` is the live session with nowhere to put it: it is there,
264
264
  * route (a) did not carry it, and nothing of its is listening yet.
265
265
  *
266
266
  * `instance_unreachable` is not here: it is the mesh's answer about an
@@ -280,7 +280,7 @@ export class Delivery implements UpstreamResource {
280
280
 
281
281
  /** The answer for a message that went to the inbox. Candidates ride along
282
282
  * when the addressee is gone, since that is when sending somewhere else is
283
- * the sender's next move (§4.2). */
283
+ * the sender's next move (DESIGN §6.6). */
284
284
  #undelivered(to: Sid, reason: UndeliveredReason): MessageSendResult {
285
285
  if (reason !== "paused" && reason !== "disappeared") return { delivered: false, reason };
286
286
  const candidates = this.#candidates(to);
@@ -289,7 +289,7 @@ export class Delivery implements UpstreamResource {
289
289
  : { delivered: false, reason, candidates };
290
290
  }
291
291
 
292
- /** Sessions live now in the repository the addressee belongs to (§4.2).
292
+ /** Sessions live now in the repository the addressee belongs to (DESIGN §6.6).
293
293
  *
294
294
  * The rows are one list of sessions, connected and lost alike, so which of
295
295
  * them can be written to is the classification — asked of the domain, the
@@ -319,7 +319,7 @@ export class Delivery implements UpstreamResource {
319
319
  }
320
320
 
321
321
  /** Who the message is from: the identity the connection greeted as, never
322
- * anything the caller put in the arguments (§4.1).
322
+ * anything the caller put in the arguments (DESIGN §6.5).
323
323
  *
324
324
  * A session names itself with its sid. A person greets without one, which is
325
325
  * what the sender literal stands for — spelled out so a reader tells "a
@@ -5,30 +5,30 @@ import { dirname, join } from "node:path";
5
5
  import { type InboxMessage, renderDirectDelivery, type Sid } from "@ccmsg/protocol";
6
6
  import { HARNESS, HARNESSES } from "../harness/index.ts";
7
7
 
8
- /** What route (a) answered (§4.1).
8
+ /** What route (a) answered (DESIGN §6.5).
9
9
  *
10
10
  * `unavailable` is every way the route does not apply — the flag is off, the
11
11
  * harness names no messaging socket, the generation is one we do not speak, the
12
- * key cannot be read, the acknowledgement did not come. §4.1 gives them one
12
+ * key cannot be read, the acknowledgement did not come. DESIGN §6.5 gives them one
13
13
  * outcome on purpose: the route either carried the message or it did not, and
14
14
  * route (b) is tried the same way in each case.
15
15
  *
16
16
  * `refused` is separate because it is not "the route does not apply": the
17
17
  * session is there and turned this message away for now, which is the one
18
- * outcome that reaches the sender as its own reason (§4.4). */
18
+ * outcome that reaches the sender as its own reason (DESIGN §6.8). */
19
19
  export type DirectOutcome = "delivered" | "unavailable" | "refused";
20
20
 
21
21
  /** Route (a): the harness's own messaging socket. */
22
22
  export interface DirectRoute {
23
23
  send(sid: Sid, message: InboxMessage): Promise<DirectOutcome>;
24
24
  /** Let go of what the route holds open. The status inbox below is a bound
25
- * socket with a name on disk, and it leaves when the instance does (§8.5). */
25
+ * socket with a name on disk, and it leaves when the instance does (DESIGN §8.5). */
26
26
  close(): void;
27
27
  }
28
28
 
29
- /** Route (a) turned off by config (§4.1 condition 0). Delivery is unchanged by
29
+ /** Route (a) turned off by config (DESIGN §6.5 condition 0). Delivery is unchanged by
30
30
  * this: route (b) is the fallback, and a fallback that always runs is still the
31
- * same semantics (§4.1). */
31
+ * same semantics (DESIGN §6.5). */
32
32
  export class DisabledDirectRoute implements DirectRoute {
33
33
  send(): Promise<DirectOutcome> {
34
34
  return Promise.resolve("unavailable");
@@ -39,7 +39,7 @@ export class DisabledDirectRoute implements DirectRoute {
39
39
 
40
40
  /** The `peerProtocol` generation this speaks. One value, because one is what
41
41
  * has been read off a running harness (2.1.263); any other generation is a
42
- * protocol nobody here has seen, which is condition 1 of §4.1. */
42
+ * protocol nobody here has seen, which is condition 1 of DESIGN §6.5. */
43
43
  export const PEER_PROTOCOL = 1;
44
44
 
45
45
  /** How long one attempt has to reach the point where the harness holds our
@@ -52,7 +52,7 @@ export const PEER_PROTOCOL = 1;
52
52
  export const DIRECT_ACK_MS = 2_000;
53
53
 
54
54
  /** How long the status inbox is watched for word about this message before the
55
- * send is taken to have landed (§4.1 condition 3).
55
+ * send is taken to have landed (DESIGN §6.5 condition 3).
56
56
  *
57
57
  * Provisional. What is known from the harness (2.1.263) is where the receipt
58
58
  * is raised, not how long it takes to arrive: the receiving session decides a
@@ -74,11 +74,11 @@ export const DIRECT_STATUS_MS = 250;
74
74
  *
75
75
  * `held` is among them because a parked message is not delivered yet: it waits
76
76
  * on somebody's approval there, which is the same "there, and not taking it
77
- * now" that §4.4 keeps in our inbox and offers again. */
77
+ * now" that DESIGN §6.8 keeps in our inbox and offers again. */
78
78
  const REFUSING = new Set(["refused", "denied", "dropped", "expired", "held"]);
79
79
 
80
80
  /** The socket this daemon offers so the receiving session can say what became
81
- * of a message (§4.1 condition 3).
81
+ * of a message (DESIGN §6.5 condition 3).
82
82
  *
83
83
  * It lives in the directory the target's own socket is in, and not in this
84
84
  * instance's state directory, because the receiving harness vets the address it
@@ -213,7 +213,7 @@ export interface SocketRouteOptions {
213
213
  readonly statusMs?: number;
214
214
  }
215
215
 
216
- /** Route (a) against the harness's messaging socket (§4.1).
216
+ /** Route (a) against the harness's messaging socket (DESIGN §6.5).
217
217
  *
218
218
  * The path is `sessions/<pid>.json` of this instance's own config home, which
219
219
  * is also the answer to condition 2: a key beside it that this uid can read is
@@ -222,7 +222,7 @@ export interface SocketRouteOptions {
222
222
  * instance cannot see a state file for is simply not reachable this way.
223
223
  *
224
224
  * The directory is read per send rather than taken from the sessions domain's
225
- * watch: that watch runs only while a topic is subscribed (§6.3), and route (a)
225
+ * watch: that watch runs only while a topic is subscribed (DESIGN §6.3), and route (a)
226
226
  * exists precisely for the session that subscribes to nothing. */
227
227
  export class ClaudeCodeSocketRoute implements DirectRoute {
228
228
  readonly #sessionsDir: string;
@@ -243,7 +243,7 @@ export class ClaudeCodeSocketRoute implements DirectRoute {
243
243
  *
244
244
  * The message is written, and then the receipt channel is watched for word
245
245
  * about it. What can arrive is a session saying it did not take the message
246
- * (§4.4); what cannot is a session saying it did, because none is sent for
246
+ * (DESIGN §6.8); what cannot is a session saying it did, because none is sent for
247
247
  * the ordinary case. So the outcome is refusal if it says so in time, and
248
248
  * delivery if it says nothing — which is the same shape as the acknowledged
249
249
  * send it stands in for, decided on a channel that carries the refusals
@@ -281,7 +281,7 @@ export class ClaudeCodeSocketRoute implements DirectRoute {
281
281
  }
282
282
 
283
283
  /** The state file naming this session, if it names a socket of a generation
284
- * we speak (§4.1 conditions 1). */
284
+ * we speak (DESIGN §6.5 conditions 1). */
285
285
  async #target(sid: Sid): Promise<HarnessTarget | undefined> {
286
286
  let names: string[];
287
287
  try {
@@ -303,7 +303,7 @@ export class ClaudeCodeSocketRoute implements DirectRoute {
303
303
  return undefined;
304
304
  }
305
305
 
306
- /** The `peerToken` the harness wrote for this session (§4.1 condition 2).
306
+ /** The `peerToken` the harness wrote for this session (DESIGN §6.5 condition 2).
307
307
  *
308
308
  * Found by the pid the key is named after rather than by rebuilding the rest
309
309
  * of the name: the digest in `<pid>.<digest>.key` is stated to be over the
@@ -341,7 +341,7 @@ type Env = Record<string, string>;
341
341
  * Passed through, they would tell the Codex CLI about a config home this
342
342
  * instance is not about and a session that is not the one being written to.
343
343
  * The home this route means is named explicitly, and the rest is dropped
344
- * (§3.8). */
344
+ * (DESIGN §4.1). */
345
345
  const DROPPED = HARNESSES.filter((harness) => harness !== "codex").flatMap((harness) => [
346
346
  HARNESS[harness].homeEnv,
347
347
  ...HARNESS[harness].sessionEnv,
@@ -360,7 +360,7 @@ const DROPPED = HARNESSES.filter((harness) => harness !== "codex").flatMap((harn
360
360
  * The budget is here for what is not being predicted: a child that never
361
361
  * answers would hold `message.send` open for as long as it lived, and route
362
362
  * (b) exists exactly so a route that does not come through costs a message
363
- * nothing (§4.1). It is generous next to a call that has been measured to
363
+ * nothing (DESIGN §6.5). It is generous next to a call that has been measured to
364
364
  * return at once. */
365
365
  export const QUEUE_MS = 10_000;
366
366
 
@@ -380,7 +380,7 @@ const runCodex: RunCodex = async (args, env) => {
380
380
  });
381
381
  } catch {
382
382
  // No `codex` on `PATH`, which is the same as the route not applying: the
383
- // message goes by route (b) and nothing about it is lost (§4.1).
383
+ // message goes by route (b) and nothing about it is lost (DESIGN §6.5).
384
384
  return { code: 127 };
385
385
  }
386
386
  // The timer is held so it can be cleared: a send that answered in a
@@ -408,7 +408,7 @@ export interface QueueRouteOptions {
408
408
  readonly run?: RunCodex;
409
409
  }
410
410
 
411
- /** Route (a) against a Codex thread's queue (§4.1).
411
+ /** Route (a) against a Codex thread's queue (DESIGN §6.5).
412
412
  *
413
413
  * Codex has no socket a message can be written to: what it has is a queue per
414
414
  * thread, held by the app-server the thread belongs to, and `codex queue` is
@@ -460,7 +460,7 @@ export class CodexQueueRoute implements DirectRoute {
460
460
  * into a message nobody receives rather than one the wrong session does.
461
461
  *
462
462
  * `from` is the address of our own status inbox, and is fixed by ccmsg rather
463
- * than taken from the caller (§4.1). It is what the receiving session answers
463
+ * than taken from the caller (DESIGN §6.5). It is what the receiving session answers
464
464
  * to about this message, and the message's `mid` is what it answers about — so
465
465
  * the two travel together, and a route with no inbox to offer sends neither
466
466
  * rather than naming an address nothing is listening on. */
@@ -476,7 +476,7 @@ function frames(sid: Sid, token: string, message: InboxMessage, from?: string):
476
476
  return `${JSON.stringify(auth)}\n${JSON.stringify(user)}\n`;
477
477
  }
478
478
 
479
- /** Connect and write, and answer whether the harness holds our bytes (§4.1
479
+ /** Connect and write, and answer whether the harness holds our bytes (DESIGN §6.5
480
480
  * condition 3).
481
481
  *
482
482
  * That is the whole of what this can decide. The connection carries nothing
@@ -520,7 +520,7 @@ async function write(path: string, payload: string, ackMs: number): Promise<Dire
520
520
  });
521
521
  } catch {
522
522
  // No socket at the path, or nothing listening on it: the session ended and
523
- // took its socket with it, or never had one (§4.1 condition 1).
523
+ // took its socket with it, or never had one (DESIGN §6.5 condition 1).
524
524
  return "unavailable";
525
525
  }
526
526
 
@@ -543,7 +543,7 @@ async function readJson(path: string): Promise<Record<string, unknown> | undefin
543
543
  return document as Record<string, unknown>;
544
544
  } catch {
545
545
  // Missing, unreadable by this uid, or half written — all of them are
546
- // "route (a) does not apply here" (§4.1 conditions 1 and 2).
546
+ // "route (a) does not apply here" (DESIGN §6.5 conditions 1 and 2).
547
547
  return undefined;
548
548
  }
549
549
  }
@@ -33,13 +33,13 @@ type Record_ =
33
33
 
34
34
  /** What was said to a session and has not reached it.
35
35
  *
36
- * The one thing here that nothing else can reconstruct (§3.6): the sender's
36
+ * The one thing here that nothing else can reconstruct (DESIGN §2.5): the sender's
37
37
  * `message.send` has already been answered, no transcript holds a message that
38
38
  * was never handed over, and the text lives nowhere else. Losing this file
39
39
  * loses the words.
40
40
  *
41
41
  * One file rather than one per sid. Both are append-only and both mean the same
42
- * thing for removal and expiry (§4.3); a single file makes the write path one
42
+ * thing for removal and expiry (DESIGN §6.7); a single file makes the write path one
43
43
  * open handle and makes "what is undelivered right now" one replay. */
44
44
  export class Inbox {
45
45
  readonly #held = new Map<Sid, InboxMessage[]>();
@@ -100,7 +100,7 @@ export class Inbox {
100
100
  }
101
101
 
102
102
  /** Note that messages reached their session, which is what takes them out of
103
- * the inbox (§4.3). */
103
+ * the inbox (DESIGN §6.7). */
104
104
  delivered(sid: Sid, mids: readonly string[]): void {
105
105
  const held = this.#held.get(sid);
106
106
  if (held === undefined || mids.length === 0) return;
@@ -112,7 +112,7 @@ export class Inbox {
112
112
  }
113
113
 
114
114
  /** Every session something is waiting for. What reads it is the offer of
115
- * §4.3: when a session becomes able to receive, what it is owed has to be
115
+ * DESIGN §6.7: when a session becomes able to receive, what it is owed has to be
116
116
  * findable without asking about each sid in turn. */
117
117
  sids(): Sid[] {
118
118
  return [...this.#held.keys()];
@@ -146,7 +146,7 @@ export class Inbox {
146
146
  else this.#held.set(record.sid, left);
147
147
  }
148
148
 
149
- /** Drop what is past the window the contract sets (DV-Q4). Nothing is
149
+ /** Drop what is past the window the contract sets (DR-0008). Nothing is
150
150
  * appended for an expiry: the same clock reaches the same verdict on the next
151
151
  * replay, so writing it down would record a conclusion rather than an event. */
152
152
  #expire(now: Timestamp, only?: Sid): void {
@@ -185,7 +185,7 @@ export class Inbox {
185
185
  }
186
186
 
187
187
  /** Where the inbox lives for an instance whose state directory is `stateDir`
188
- * (§8.1: every per-instance path is derived from its config home). */
188
+ * (DESIGN §8.1: every per-instance path is derived from its config home). */
189
189
  export function inboxPath(stateDir: string): string {
190
190
  return join(stateDir, INBOX_FILE);
191
191
  }
@@ -21,7 +21,7 @@ export interface NotifyDeps {
21
21
  /** How a session is shown. The contract has the issuing instance resolve it,
22
22
  * so the label is decided here rather than carried in the arguments. */
23
23
  readonly label: (sid: Sid) => string;
24
- /** The one way a value reaches subscribers (§6.1). No `to`: a notification is
24
+ /** The one way a value reaches subscribers (DESIGN §6.1). No `to`: a notification is
25
25
  * for whoever is watching, not for one session. */
26
26
  readonly publish: (topic: string, data: unknown, instance: InstanceId) => PublishOutcome;
27
27
  }
@@ -34,7 +34,7 @@ export interface NotifyDeps {
34
34
  * as the same frame, which is what keeps "a notification" from meaning two
35
35
  * shapes depending on which op raised it.
36
36
  *
37
- * The topic is `event` granularity (§6.2): nothing is held, so there is no
37
+ * The topic is `event` granularity (DESIGN §6.2): nothing is held, so there is no
38
38
  * snapshot and no suppression — two identical notifications are two things
39
39
  * that happened. */
40
40
  export class Notify implements UpstreamResource {
@@ -79,7 +79,7 @@ export class Notify implements UpstreamResource {
79
79
  return [...this.#unread];
80
80
  }
81
81
 
82
- // --- UpstreamResource (§6.3)
82
+ // --- UpstreamResource (DESIGN §6.3)
83
83
 
84
84
  /** Nothing upstream to run: a notification exists because an op raised it. */
85
85
  start(): void {}
@@ -87,7 +87,7 @@ export class Notify implements UpstreamResource {
87
87
  stop(): void {}
88
88
 
89
89
  /** An event topic has no current value, so a subscriber starts at the next
90
- * thing that happens (§6.2). */
90
+ * thing that happens (DESIGN §6.2). */
91
91
  snapshot(): readonly TopicValue[] {
92
92
  return [];
93
93
  }
@@ -102,7 +102,7 @@ export class Notify implements UpstreamResource {
102
102
  // A notification is an occurrence, so nothing folds it away and a watcher
103
103
  // that cannot keep up is what stops it. The caller hears that rather than
104
104
  // the notification going nowhere: it is the one that decides whether to
105
- // raise another (§6.4).
105
+ // raise another (DESIGN §6.4).
106
106
  if (this.deps.publish(NOTIFY, notification, this.deps.self) === "rate_limited") {
107
107
  throw new OpError(
108
108
  "rate_limited",
@@ -86,7 +86,7 @@ const SKILL_FILE = join("skills", "ccmsg", "SKILL.md");
86
86
  * started against the default home has no variable saying so, and a Codex
87
87
  * session started from inside a Claude Code session inherits that session's
88
88
  * `CLAUDE_CONFIG_DIR` and session id — so a hook that only added its own would
89
- * still greet the other instance, as the other session (§3.8, measured). What
89
+ * still greet the other instance, as the other session (DESIGN §4.1, measured). What
90
90
  * is dropped is named here rather than left to the shell: the hook has to
91
91
  * speak for the session it fired for.
92
92
  *
@@ -2,23 +2,23 @@ import type { SessionState, Timestamp } from "@ccmsg/protocol";
2
2
 
3
3
  /** What the harness's own row says, for a session that has one. */
4
4
  export interface HarnessPresence {
5
- /** Its status is `waiting`, so a dialog is open (§5.1). */
5
+ /** Its status is `waiting`, so a dialog is open (DESIGN §4.2). */
6
6
  waiting: boolean;
7
7
  /** The terminal it runs in, when one could be read. Absent means unknown,
8
8
  * which is what makes a live session unmanaged. */
9
9
  terminal_id?: string;
10
10
  }
11
11
 
12
- /** Everything the classification reads, and nothing else (§5.1). */
12
+ /** Everything the classification reads, and nothing else (DESIGN §4.2). */
13
13
  export interface SessionInputs {
14
14
  /** A connection of this session is open to us right now. */
15
15
  connected: boolean;
16
16
  /** Present when the harness's `sessions/` has a row for it. */
17
17
  harness?: HarnessPresence;
18
- /** The last time the gateway saw inference for it (§5.1). Absent on an
18
+ /** The last time the gateway saw inference for it (DESIGN §4.2). Absent on an
19
19
  * instance with no gateway configured, and for a session it has not seen. */
20
20
  gateway_active_at?: Timestamp;
21
- /** Its transcript's last turn ended on an API error (§5.1, from the fold).
21
+ /** Its transcript's last turn ended on an API error (DESIGN §4.2, from the fold).
22
22
  * Absent for a session whose transcript nothing is following. */
23
23
  api_error_stopped?: boolean;
24
24
  /** Present when it is in `last_live`. */
@@ -30,12 +30,12 @@ export interface SessionInputs {
30
30
  export const GATEWAY_LIVE_WINDOW_MS = 5 * 60 * 1000;
31
31
 
32
32
  /** The classification is the contract's `SessionState`, derived here rather
33
- * than by whoever displays it (§5.2): a client combining raw values of its own
33
+ * than by whoever displays it (DESIGN §4.3): a client combining raw values of its own
34
34
  * would read two instances' lists by two rules.
35
35
  *
36
36
  * Pinned is not one of them. A person pins a row and the instance holds the
37
37
  * mark beside the classification, but the mark never decides which state the
38
- * row is in (§5.2).
38
+ * row is in (DESIGN §4.3).
39
39
  *
40
40
  * Busy and idle are not among them either, and not by omission: a live session
41
41
  * carries how busy it is as an attribute of its row, so an instance with no
@@ -21,7 +21,7 @@ import type { TranscriptFiles } from "../transcript/index.ts";
21
21
 
22
22
  /** Where dumps land: one directory under this instance's own state, named
23
23
  * after the config home it answers for like every other per-instance path
24
- * (§8.1). The caller never supplies a path, so there is none to contain. */
24
+ * (DESIGN §8.1). The caller never supplies a path, so there is none to contain. */
25
25
  export const DUMPS = "dumps";
26
26
 
27
27
  /** What a dump file is called. Two extensions rather than one so that a reader
@@ -62,7 +62,7 @@ export interface SessionOpsDeps {
62
62
  * instance last saw them. The sessions domain owns the list; this op only
63
63
  * asks it to forget a row. */
64
64
  readonly forget: (sid: Sid) => boolean;
65
- /** The named selections this instance is configured with (§3.6). */
65
+ /** The named selections this instance is configured with (DESIGN §2.5). */
66
66
  readonly presets: readonly DumpPreset[];
67
67
  }
68
68
 
@@ -138,7 +138,7 @@ export function sessionHandlers(deps: SessionOpsDeps) {
138
138
  "transcript.read": (input: HandlerInput): TranscriptReadResult => {
139
139
  const args = input.args as unknown as TranscriptReadArgs;
140
140
  if (!sees(args.sid, viewer(input))) {
141
- // The role sets the visible range, not the permission (§3.2): outside
141
+ // The role sets the visible range, not the permission (DESIGN §2.2): outside
142
142
  // it there is no transcript to speak of, which is the one code this op
143
143
  // declares. A refusal that named the session would answer a question
144
144
  // the caller was not entitled to ask.
@@ -7,7 +7,7 @@ import type { Harness } from "../harness/index.ts";
7
7
  * an answer, alongside a `waitingFor` naming what it waits on.
8
8
  *
9
9
  * Read out of the harness binary (2.1.263): `{status:"waiting",waitingFor:…}`.
10
- * This is the one thing the raw status decides (§5.1 / DV-Q5) — busy and idle
10
+ * This is the one thing the raw status decides (DESIGN §4.2 / DR-0009) — busy and idle
11
11
  * are the gateway's to say, so no other value of it is read here. */
12
12
  const WAITING = "waiting";
13
13
 
@@ -16,9 +16,9 @@ const WAITING = "waiting";
16
16
  * `fs.watch` is the route; this is not. macOS/Bun delivers FSEvents tens of
17
17
  * seconds late under load (measured in the old daemon while many test children
18
18
  * ran), and the poll exists so a change the watch is sitting on is picked up
19
- * before a person notices it is missing (§5.1). Five seconds is the interval
19
+ * before a person notices it is missing (DESIGN §4.2). Five seconds is the interval
20
20
  * the old daemon's `claude agents` poller ran at as its only route, and this
21
- * one replaces it as a backstop (DV-Q6), so it cannot be the slower of the
21
+ * one replaces it as a backstop (DR-0009), so it cannot be the slower of the
22
22
  * two. */
23
23
  export const CONFIRM_POLL_MS = 5_000;
24
24
 
@@ -32,11 +32,11 @@ const STATE_FILE = /^\d+\.json$/;
32
32
  * states and what the `agents` topic is; Codex says only that a thread has a
33
33
  * live writer, which answers "is it there" and nothing else. So `rows` is what
34
34
  * can be reported and `present` is what the classification reads, and a harness
35
- * that reports nothing still has its sessions classified (§5.1). */
35
+ * that reports nothing still has its sessions classified (DESIGN §4.2). */
36
36
  export interface OwnSessions {
37
37
  readonly running: boolean;
38
38
  /** Begins watching. Called when the first subscriber arrives and not before
39
- * (§6.3 / §8.3: no upstream is read until somebody is listening). */
39
+ * (DESIGN §6.3 / §8.3: no upstream is read until somebody is listening). */
40
40
  start(): void;
41
41
  stop(): void;
42
42
  /** The harness's own rows, as `agents` answers with them. Empty for a
@@ -46,7 +46,7 @@ export interface OwnSessions {
46
46
  present(): ReadonlySet<Sid>;
47
47
  }
48
48
 
49
- /** The one this config home runs (§3.8). */
49
+ /** The one this config home runs (DESIGN §4.1). */
50
50
  export function ownSessions(
51
51
  harness: Harness,
52
52
  configHome: string,
@@ -117,11 +117,11 @@ class CodexThreads implements OwnSessions {
117
117
  /** The sessions the harness itself reports, read from one config home.
118
118
  *
119
119
  * The directory is the whole input: it says which sessions exist and which is
120
- * waiting on a dialog (§5.1). Only the config home this instance was given is
120
+ * waiting on a dialog (DESIGN §4.2). Only the config home this instance was given is
121
121
  * ever opened (M6) — the path is handed in, and nothing here searches for
122
122
  * another one.
123
123
  *
124
- * Two things live here, and §6.3 separates them. Reading the directory answers
124
+ * Two things live here, and DESIGN §6.3 separates them. Reading the directory answers
125
125
  * a question, and is done whenever one is asked. Watching it says the answer
126
126
  * may have changed, which is only worth knowing while somebody is subscribed —
127
127
  * so the watch is what the subscription drives, and no answer waits on it. */
@@ -163,7 +163,7 @@ export class HarnessSessions implements OwnSessions {
163
163
  /** The directory as it is at this instant.
164
164
  *
165
165
  * Every answer comes from here rather than from anything the watch left
166
- * behind. Which sessions exist is an input to the classification (§5.1), and
166
+ * behind. Which sessions exist is an input to the classification (DESIGN §4.2), and
167
167
  * classifying happens inside `message.send`'s decision and inside the
168
168
  * recompute that writes `last_live` — neither of which can hand back a
169
169
  * promise without changing what it means, and neither of which may depend on
@@ -209,7 +209,7 @@ export class HarnessSessions implements OwnSessions {
209
209
  /** One directory that says what the harness's sessions are, watched while
210
210
  * somebody is subscribed and read whenever an answer is wanted.
211
211
  *
212
- * The two things §6.3 separates live here. Reading the directory answers a
212
+ * The two things DESIGN §6.3 separates live here. Reading the directory answers a
213
213
  * question, and is done whenever one is asked. Watching it says the answer may
214
214
  * have changed, which is only worth knowing while somebody is listening — so
215
215
  * the watch is what the subscription drives, and no answer waits on it. */
@@ -274,7 +274,7 @@ type RowResult =
274
274
  | { readonly complete: true; readonly row?: AgentInfo };
275
275
 
276
276
  /** The conversion of one upstream document into the contract's spelling
277
- * (§3.5): renamed to snake_case, instants in Unix ms, and nothing carried over
277
+ * (DESIGN §2.4): renamed to snake_case, instants in Unix ms, and nothing carried over
278
278
  * that the contract does not name.
279
279
  *
280
280
  * A row whose process is gone is dropped: the file outlives a session that did
@@ -316,7 +316,7 @@ function alive(pid: number): boolean {
316
316
  return true;
317
317
  } catch {
318
318
  // EPERM would mean alive but ours to signal — impossible here, since the
319
- // daemon and the sessions of its config home run as one uid (§2 A4).
319
+ // daemon and the sessions of its config home run as one uid (DESIGN §1.4 A4).
320
320
  return false;
321
321
  }
322
322
  }