opencode-courier 0.13.8 → 0.14.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.
Files changed (163) hide show
  1. package/README.md +7 -9
  2. package/dist/index.d.ts +0 -8
  3. package/dist/index.js +6 -53
  4. package/dist/kernel/protocol.d.ts +0 -14
  5. package/dist/kernel/protocol.js +1 -15
  6. package/dist/kernel/tool.d.ts +0 -2
  7. package/dist/kernel/tool.js +0 -1
  8. package/dist/memory/domain.d.ts +6 -85
  9. package/dist/memory/domain.js +7 -63
  10. package/dist/memory/events.d.ts +0 -11
  11. package/dist/memory/events.js +1 -12
  12. package/dist/memory/forget.d.ts +0 -5
  13. package/dist/memory/forget.js +0 -4
  14. package/dist/memory/index.js +0 -2
  15. package/dist/memory/lessons.d.ts +0 -8
  16. package/dist/memory/lessons.js +0 -9
  17. package/dist/memory/note.d.ts +0 -9
  18. package/dist/memory/note.js +0 -5
  19. package/dist/memory/pin.d.ts +0 -5
  20. package/dist/memory/pin.js +0 -4
  21. package/dist/memory/ports.d.ts +2 -22
  22. package/dist/memory/recall.d.ts +0 -5
  23. package/dist/memory/recall.js +0 -12
  24. package/dist/memory/record.d.ts +0 -4
  25. package/dist/memory/record.js +8 -31
  26. package/dist/memory/rpc-handlers.d.ts +3 -6
  27. package/dist/memory/rpc-handlers.js +0 -6
  28. package/dist/memory/rpc.d.ts +6 -19
  29. package/dist/memory/rpc.js +1 -16
  30. package/dist/memory/store.d.ts +0 -5
  31. package/dist/memory/store.js +1 -16
  32. package/dist/memory/sweep.d.ts +0 -4
  33. package/dist/memory/sweep.js +0 -4
  34. package/dist/memory/text.d.ts +0 -25
  35. package/dist/memory/text.js +6 -36
  36. package/dist/memory/tools.d.ts +0 -2
  37. package/dist/memory/tools.js +0 -2
  38. package/dist/memory/tui.d.ts +1 -35
  39. package/dist/person/events.d.ts +0 -16
  40. package/dist/person/events.js +3 -30
  41. package/dist/person/form.d.ts +0 -9
  42. package/dist/person/form.js +0 -10
  43. package/dist/person/index.d.ts +0 -2
  44. package/dist/person/permission.d.ts +0 -34
  45. package/dist/person/permission.js +2 -28
  46. package/dist/person/ports.d.ts +0 -16
  47. package/dist/person/ports.js +0 -1
  48. package/dist/person/question/answer.d.ts +0 -28
  49. package/dist/person/question/answer.js +0 -49
  50. package/dist/person/question/index.js +0 -2
  51. package/dist/person/question/lifecycle.d.ts +0 -26
  52. package/dist/person/question/lifecycle.js +1 -32
  53. package/dist/person/question/pure.d.ts +0 -11
  54. package/dist/person/question/pure.js +0 -11
  55. package/dist/person/question/relay.d.ts +0 -1
  56. package/dist/person/question/relay.js +5 -67
  57. package/dist/person/question/runtime.d.ts +0 -14
  58. package/dist/person/question/runtime.js +1 -26
  59. package/dist/person/question/shared.d.ts +0 -17
  60. package/dist/person/question/shared.js +0 -20
  61. package/dist/person/question/types.d.ts +0 -44
  62. package/dist/person/rpc-handlers.d.ts +0 -7
  63. package/dist/person/rpc-handlers.js +0 -6
  64. package/dist/person/rpc.d.ts +0 -20
  65. package/dist/person/rpc.js +0 -16
  66. package/dist/person/text.d.ts +0 -45
  67. package/dist/person/text.js +2 -54
  68. package/dist/person/tools.d.ts +0 -3
  69. package/dist/person/tools.js +1 -3
  70. package/dist/person/tui.d.ts +0 -21
  71. package/dist/platform/bounded.d.ts +0 -8
  72. package/dist/platform/bounded.js +0 -8
  73. package/dist/platform/events.d.ts +0 -18
  74. package/dist/platform/events.js +3 -22
  75. package/dist/platform/hub.d.ts +0 -113
  76. package/dist/platform/hub.js +1 -76
  77. package/dist/platform/json.d.ts +0 -3
  78. package/dist/platform/json.js +1 -3
  79. package/dist/platform/storage.d.ts +0 -7
  80. package/dist/platform/storage.js +0 -6
  81. package/dist/platform/version.d.ts +0 -9
  82. package/dist/platform/version.js +0 -8
  83. package/dist/rpc.js +0 -1
  84. package/dist/scheduling/index.d.ts +0 -1
  85. package/dist/scheduling/index.js +0 -1
  86. package/dist/scheduling/later.d.ts +0 -11
  87. package/dist/scheduling/later.js +0 -14
  88. package/dist/scheduling/text.d.ts +0 -1
  89. package/dist/scheduling/text.js +0 -1
  90. package/dist/scheduling/tools.d.ts +0 -1
  91. package/dist/scheduling/tools.js +1 -5
  92. package/dist/skill.d.ts +1 -9
  93. package/dist/skill.js +4 -12
  94. package/dist/tree/cleanup.d.ts +0 -20
  95. package/dist/tree/cleanup.js +0 -26
  96. package/dist/tree/context.d.ts +0 -5
  97. package/dist/tree/context.js +0 -8
  98. package/dist/tree/domain.d.ts +0 -64
  99. package/dist/tree/domain.js +7 -45
  100. package/dist/tree/events.d.ts +0 -36
  101. package/dist/tree/events.js +4 -49
  102. package/dist/tree/forget.d.ts +0 -15
  103. package/dist/tree/forget.js +0 -18
  104. package/dist/tree/group.d.ts +0 -22
  105. package/dist/tree/group.js +1 -37
  106. package/dist/tree/index.d.ts +2 -2
  107. package/dist/tree/index.js +1 -3
  108. package/dist/tree/limits.d.ts +0 -4
  109. package/dist/tree/limits.js +1 -12
  110. package/dist/tree/mascot.d.ts +0 -25
  111. package/dist/tree/mascot.js +1 -32
  112. package/dist/tree/model.d.ts +0 -5
  113. package/dist/tree/model.js +2 -15
  114. package/dist/tree/nudge.d.ts +0 -11
  115. package/dist/tree/nudge.js +3 -16
  116. package/dist/tree/panel.d.ts +0 -8
  117. package/dist/tree/panel.js +3 -16
  118. package/dist/tree/ports.d.ts +2 -138
  119. package/dist/tree/report.d.ts +0 -10
  120. package/dist/tree/report.js +0 -11
  121. package/dist/tree/roster.d.ts +0 -21
  122. package/dist/tree/roster.js +0 -21
  123. package/dist/tree/rpc-handlers.d.ts +6 -1
  124. package/dist/tree/rpc-handlers.js +0 -5
  125. package/dist/tree/rpc.d.ts +9 -109
  126. package/dist/tree/rpc.js +2 -38
  127. package/dist/tree/running.d.ts +0 -6
  128. package/dist/tree/running.js +2 -10
  129. package/dist/tree/send.d.ts +0 -4
  130. package/dist/tree/send.js +4 -31
  131. package/dist/tree/spawn.d.ts +0 -7
  132. package/dist/tree/spawn.js +6 -47
  133. package/dist/tree/standing.d.ts +0 -8
  134. package/dist/tree/standing.js +1 -11
  135. package/dist/tree/status.d.ts +0 -8
  136. package/dist/tree/status.js +1 -9
  137. package/dist/tree/stop.d.ts +2 -9
  138. package/dist/tree/stop.js +58 -25
  139. package/dist/tree/store.d.ts +0 -8
  140. package/dist/tree/store.js +4 -38
  141. package/dist/tree/subtree.d.ts +8 -26
  142. package/dist/tree/subtree.js +37 -35
  143. package/dist/tree/summary.d.ts +0 -4
  144. package/dist/tree/summary.js +0 -8
  145. package/dist/tree/text.d.ts +1 -44
  146. package/dist/tree/text.js +5 -65
  147. package/dist/tree/tools.d.ts +0 -6
  148. package/dist/tree/tools.js +4 -8
  149. package/dist/tree/tui.d.ts +0 -81
  150. package/dist/tree/wake.d.ts +0 -30
  151. package/dist/tree/wake.js +5 -39
  152. package/dist/tui.js +22 -8
  153. package/package.json +8 -9
  154. package/dist/webhook/index.d.ts +0 -5
  155. package/dist/webhook/index.js +0 -3
  156. package/dist/webhook/receiver.d.ts +0 -80
  157. package/dist/webhook/receiver.js +0 -339
  158. package/dist/webhook/subscriptions.d.ts +0 -29
  159. package/dist/webhook/subscriptions.js +0 -52
  160. package/dist/webhook/text.d.ts +0 -23
  161. package/dist/webhook/text.js +0 -82
  162. package/dist/webhook/tools.d.ts +0 -19
  163. package/dist/webhook/tools.js +0 -46
package/dist/tree/text.js CHANGED
@@ -1,13 +1,8 @@
1
- /** The tree's model-facing texts: the child brief, the role part, refusals, reports, notices and tool results. */
2
1
  import { obj, str } from "../platform/json.js";
3
2
  import { clipText, defuse, defuseBlock, END_TURN, ROOT_SPLIT_LINE, SKILL_ID, SPLIT_LINE, STATUSES } from "../kernel/protocol.js";
4
3
  import { retryDelay } from "./domain.js";
5
- // The envelope and the child brief.
6
- /** How a text names the statuses a report may carry. */
7
4
  const STATUS_LIST = `${STATUSES.slice(0, -1).join(", ")} or ${STATUSES.at(-1)}`;
8
- /** The branch an isolated child commits its work on, and names in the `branch` of its report. */
9
5
  export const childBranch = (sessionID) => `courier/${sessionID}`;
10
- /** What a spawned session at `depth` is told; `depth` 1 is a child of a session nobody spawned. `branch` is for an isolated one. */
11
6
  export function childBrief(parentID, task, depth, limits, branch) {
12
7
  return [
13
8
  `You were started by session ${parentID} through opencode-courier.`,
@@ -63,14 +58,8 @@ function splitRule(parentID, depth, limits) {
63
58
  `session), then send ${parentID} one combined report.`,
64
59
  ];
65
60
  }
66
- /** How every role part begins, in the role entry and in the part the context hook adds without one, so no second copy is added. */
67
61
  export const ROLE_PREFIX = "opencode-courier role:";
68
- /** The key of the session instruction entry carrying a spawned session's role part, `<context key="role">` in its system prompt. */
69
62
  export const ROLE_KEY = "role";
70
- /**
71
- * The system part naming a session's place in its tree: `depth` 0 for a session nobody spawned that
72
- * has started one, `null` for one that has started none, which is pointed to the skill. The same on every request.
73
- */
74
63
  export function rolePart(depth, limits) {
75
64
  const { maxDepth, maxChildren, maxTotal } = limits;
76
65
  const budget = `At most ${maxChildren} of the sessions you start run at once, and ${maxTotal} in the whole tree.`;
@@ -89,14 +78,9 @@ export function rolePart(depth, limits) {
89
78
  return (`${ROLE_PREFIX} leaf, at depth ${depth}, the deepest the session tree goes. You cannot start sessions: do your ` +
90
79
  "task yourself and report with courier_send to the session that started you.");
91
80
  }
92
- // The standing entry: what a parent's system prompt says of the sessions it started.
93
- /** The key of the session instruction entry on a parent listing the sessions it started, `<context key="standing">`. */
94
81
  export const STANDING_KEY = "standing";
95
- /** How every standing entry begins. */
96
82
  export const STANDING_PREFIX = "opencode-courier standing:";
97
- /** How many children the entry lists; older ones past that are counted. */
98
83
  export const STANDING_MAX = 10;
99
- /** The most characters of a child's title the entry shows. */
100
84
  const TITLE_MAX = 40;
101
85
  function stateWords(state) {
102
86
  const progressed = state.progressed ? ", sent progress" : "";
@@ -132,10 +116,6 @@ function groupWords(name, standing) {
132
116
  return `out of ${group} (${LEFT_HOW[standing]})`;
133
117
  }
134
118
  }
135
- /**
136
- * The standing entry: one line per session the parent started, oldest first, with its state and its place in its join group,
137
- * in the same words for the same state, so an unchanged standing is not put again.
138
- */
139
119
  export function standingEntry(rows) {
140
120
  const shown = rows.slice(-STANDING_MAX);
141
121
  const older = rows.length - shown.length;
@@ -150,22 +130,17 @@ export function standingEntry(rows) {
150
130
  ...(older ? [`${count(older, "older session")} not listed; courier_children lists them.`] : []),
151
131
  ].join("\n");
152
132
  }
153
- // courier_spawn's refusals, one per limit.
154
133
  export const depthRefusal = (depth, maxDepth) => `Not started: this session is at depth ${depth} of its session tree, and the tree goes at most ${maxDepth} deep ` +
155
134
  "(maxDepth), so it cannot start sessions. Do the task yourself, and report with courier_send.";
156
- /** How a refusal names sessions that count against a limit: those running, and those that have not reported yet. */
157
135
  const live = (n) => (n === 1 ? "is running or has not reported yet" : "are running or have not reported yet");
158
136
  export const childrenRefusal = (running, maxChildren) => `Not started: ${count(running, "session")} you started ${live(running)}, the most allowed at once ` +
159
137
  `(maxChildren ${maxChildren}). Do this part yourself, or end your turn and start it once one of them has reported.`;
160
138
  export const totalRefusal = (running, maxTotal) => `Not started: ${count(running, "session")} in this session tree ${live(running)}, the most allowed at once ` +
161
139
  `(maxTotal ${maxTotal}). Do this part yourself, or end your turn and try again once a report has arrived.`;
162
- // courier_spawn's refusals of a model or a variant OpenCode does not list.
163
- /** How many models, or providers, a refusal names before it counts the rest. */
164
- const NAMED = 12;
165
- const some = (items) => (items.length > NAMED ? `${items.slice(0, NAMED).join(", ")} and ${items.length - NAMED} more` : items.join(", "));
140
+ const LISTED_MAX = 12;
141
+ const some = (items) => (items.length > LISTED_MAX ? `${items.slice(0, LISTED_MAX).join(", ")} and ${items.length - LISTED_MAX} more` : items.join(", "));
166
142
  export const modelFormatRefusal = (given) => `Not started: model must be provider/model, as OpenCode names them (opencode/gpt-5-nano, anthropic/claude-sonnet-4-5), not ${JSON.stringify(given)}. ` +
167
143
  "Leave it unset to run the session on your own model.";
168
- /** The model is not among those OpenCode lists; `named` says the caller named it, rather than inherited it and named a variant. */
169
144
  export function unknownModelRefusal(model, known, named) {
170
145
  const name = `${model.providerID}/${model.id}`;
171
146
  const ids = known.filter((item) => item.providerID === model.providerID).map((item) => item.id);
@@ -184,19 +159,12 @@ export function unknownVariantRefusal(model, variants) {
184
159
  return (`Not started: the model ${model.providerID}/${model.id} has no variant ${JSON.stringify(model.variant)}. ` +
185
160
  `Its variants are ${listed}; leave variant unset for the model's default.`);
186
161
  }
187
- /** A variant was named, but the model it would apply to could not be read: the named agent's, else the caller's own or OpenCode's default. */
188
162
  export function variantWithoutModelRefusal(variant, agent) {
189
163
  const why = agent ? `the agent ${agent} could not be looked up` : "neither your own model nor OpenCode's default could be read";
190
164
  return `Not started: variant ${JSON.stringify(variant)} needs a model to apply to, and ${why}. Name a model as provider/model, or leave variant unset.`;
191
165
  }
192
- /** The strings of a list given as artifacts, whatever else the model put in it. */
193
166
  const strings = (value) => (Array.isArray(value) ? value.flatMap((item) => (typeof item === "string" ? [item.trim()] : [])) : []).filter(Boolean);
194
- /** A list's line in the artifacts block, or none when it is empty. */
195
167
  const listed = (name, items) => (items.length ? [`- ${name}: ${items.join(", ")}`] : []);
196
- /**
197
- * The body of a report: its text, then the artifacts in a fixed layout, the same for every report,
198
- * so a parent finds the branch, the commits, the files and the checks without reading prose.
199
- */
200
168
  export function reportBody(message, artifacts) {
201
169
  const given = obj(artifacts);
202
170
  const checks = (Array.isArray(given.checks) ? given.checks : []).flatMap((check) => {
@@ -211,8 +179,6 @@ export function reportBody(message, artifacts) {
211
179
  ];
212
180
  return lines.length ? [message, "", "Artifacts:", ...lines].join("\n") : message;
213
181
  }
214
- // Tool results.
215
- /** courier_spawn's refusal of `native: true` with `isolate: true`: OpenCode gives a session a worktree or a parent, never both. */
216
182
  export const nativeIsolateRefusal = () => "Not started: native: true and isolate: true cannot go together. OpenCode runs a session in a worktree of its own or as a " +
217
183
  "subagent of its parent, never both. Start it with isolate: true alone for a worktree of its own, or with native: true alone for a subagent in your directory.";
218
184
  export function spawnText(child) {
@@ -231,7 +197,6 @@ export function spawnText(child) {
231
197
  return (`Started session ${child.sessionID} in ${child.directory}${on}. ${reports} Once you have started every session you need, ` +
232
198
  `end your turn: reply without calling more tools. That does not drop the task; you carry on with it when the reports arrive.${warning}`);
233
199
  }
234
- /** courier_send's result: for a message to the session that started the sender, whether it was its report; below it, the status dropped. */
235
200
  export function sendText(sessionID, sent = {}) {
236
201
  if (sent.dropped)
237
202
  return (`Delivered to ${sessionID}, without the status ${sent.dropped}: a status belongs on a report to the session that ` +
@@ -266,7 +231,6 @@ export function cleanupText(result) {
266
231
  return done;
267
232
  return `${done} Dropped it from group "${result.group}" without a report: the group's other reports reach you once they are in and your turn has ended, the message naming it, and a report it sends after all comes on its own.`;
268
233
  }
269
- /** courier_tree's result: the sessions under a session, drawn depth first, as JSON. */
270
234
  export function treeText(tree) {
271
235
  if (!tree.sessions.length)
272
236
  return "No sessions started with courier_spawn.";
@@ -276,14 +240,13 @@ export function treeText(tree) {
276
240
  const note = `\n${older} not listed: at most maxTotal are, the live ones first, then the newest; courier_tree on a listed session lists what is under it.`;
277
241
  return JSON.stringify(tree, null, 2) + note;
278
242
  }
279
- /** What courier_stop did to each session, and to the messages and worktrees. */
280
243
  export function stopText(result) {
281
244
  const interrupted = result.stopped.filter((one) => one.outcome === "interrupted").length;
282
245
  const failed = result.stopped.filter((one) => one.outcome === "failed");
283
246
  return [
284
247
  `Stopped ${result.sessionID} and ${count(result.stopped.length - 1, "session")} under it, deepest first: ${interrupted} had a turn running and was interrupted, ` +
285
248
  `${result.stopped.filter((one) => one.outcome === "idle" || one.outcome === "gone").length} did not run.`,
286
- ...result.stopped.map((one) => `- ${one.sessionID}: ${one.outcome}${one.error ? " (" + one.error + ")" : ""}`),
249
+ ...result.stopped.map((one) => `- ${one.sessionID}${one.subagent ? " (subagent)" : ""}: ${one.outcome}${one.error ? " (" + one.error + ")" : ""}`),
287
250
  ...(failed.length ? ["Those that failed may still run: try courier_stop again."] : []),
288
251
  result.cancelled.length
289
252
  ? `Cancelled ${count(result.cancelled.length, "scheduled message")}: ${result.cancelled.join(", ")}.`
@@ -293,7 +256,6 @@ export function stopText(result) {
293
256
  ].join("\n");
294
257
  }
295
258
  const FORCE_HINT = " Commit or branch what you want to keep, or call courier_cleanup again with force: true to discard it.";
296
- /** courier_stop's lines on the worktrees it was asked to remove. */
297
259
  function worktreeLines(cleanup) {
298
260
  if (!cleanup.length)
299
261
  return ["None of them ran in a worktree of its own."];
@@ -301,12 +263,10 @@ function worktreeLines(cleanup) {
301
263
  ? `Worktree of ${one.sessionID}: not removed (${one.error}).`
302
264
  : cleanupText(one).replace(FORCE_HINT, " Kept; courier_cleanup with force: true discards it."));
303
265
  }
304
- /** What the parent of a stopped session is told when the stop was ordered by a session above both. */
305
266
  export const stoppedNotice = (title, by) => [
306
267
  `This session, "${title}", which you started with courier_spawn, was stopped by ${by}, a session above you, together with the sessions under it.`,
307
268
  "It was not a failure and it will not report back. Carry on without it, or start a replacement if you still need what it was doing.",
308
269
  ].join("\n");
309
- /** Why a worktree in this state must be kept, or undefined when removing it loses nothing. */
310
270
  export function keepReason(state) {
311
271
  const reasons = [
312
272
  state.changes.length ? `${count(state.changes.length, "uncommitted change")} (${preview(state.changes)})` : "",
@@ -322,8 +282,6 @@ function count(n, noun) {
322
282
  function preview(items) {
323
283
  return items.length > 5 ? `${items.slice(0, 5).join(", ")}, ...` : items.join(", ");
324
284
  }
325
- // Turn failures.
326
- /** A wait in whole minutes, rounded up: "1 minute", "45 minutes", "2 hours 5 minutes". */
327
285
  function wait(ms) {
328
286
  const minutes = Math.ceil(ms / 60_000);
329
287
  if (minutes < 60)
@@ -331,20 +289,14 @@ function wait(ms) {
331
289
  const rest = minutes % 60;
332
290
  return [count(Math.floor(minutes / 60), "hour"), rest ? count(rest, "minute") : ""].filter(Boolean).join(" ");
333
291
  }
334
- /** A time as the notices say it, in UTC, with its date when it is not the day of `from`. */
335
292
  function clock(at, from) {
336
293
  const iso = new Date(at).toISOString();
337
294
  return iso.slice(0, 10) === new Date(from).toISOString().slice(0, 10) ? `${iso.slice(11, 16)} UTC` : `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
338
295
  }
339
- /** The line saying how long the provider asked to wait, and until when if the failure's time `at` is known. */
340
296
  function waitLine(delay, at) {
341
297
  const until = at === undefined ? "" : `, until about ${clock(at + delay, at)}`;
342
298
  return `The provider asked to wait about ${wait(delay)} before trying again${until}.`;
343
299
  }
344
- /**
345
- * What the parent is told when a session it started failed its turn; `at` is when the failure happened, which the provider's
346
- * retry time (`retryAfterMs`) counts from. Message the child before that and it fails again, so the notice says to wait.
347
- */
348
300
  export function failureNotice(title, error, group, at) {
349
301
  const status = error.status === undefined ? "" : `, status ${error.status}`;
350
302
  const delay = retryDelay(error);
@@ -364,7 +316,6 @@ export function failureNotice(title, error, group, at) {
364
316
  : []),
365
317
  ].join("\n");
366
318
  }
367
- /** The one reminder a spawned session gets as it is about to end its turn owing its parent a report; it carries on in the same turn. */
368
319
  export function reportReminder(parentID) {
369
320
  return [
370
321
  `You are about to end your turn without reporting to session ${parentID}, which started you.`,
@@ -373,12 +324,7 @@ export function reportReminder(parentID) {
373
324
  "This reminder comes once: if you end your turn without a report, the session that started you is told.",
374
325
  ].join("\n");
375
326
  }
376
- /** Longest last reply a notice that a session ended its turn without reporting quotes. */
377
- const MAX_REPLY = 2000;
378
- /**
379
- * What the parent is told when a session it started ends its turn without a report to it;
380
- * `progressed` when it messaged the parent without a status since its last prompt.
381
- */
327
+ const QUOTED_REPLY_MAX = 2000;
382
328
  export function silentNotice(title, lastText, progressed = false, group) {
383
329
  const reply = lastText?.trim();
384
330
  return [
@@ -386,7 +332,7 @@ export function silentNotice(title, lastText, progressed = false, group) {
386
332
  ...(progressed
387
333
  ? [`Its last message to you had no status, so it was progress, not its report: a report carries status ${STATUS_LIST}.`]
388
334
  : []),
389
- ...(reply ? ["Its last reply:", defuse(clipText(reply, MAX_REPLY))] : ["It ended without a reply."]),
335
+ ...(reply ? ["Its last reply:", defuse(clipText(reply, QUOTED_REPLY_MAX))] : ["It ended without a reply."]),
390
336
  "",
391
337
  "Decide what it needs: message it with courier_send to have it carry on, or to report if its last reply is what you needed; or start a replacement.",
392
338
  "Until it reports, it counts toward your limits on the sessions you run at once.",
@@ -398,8 +344,6 @@ export function silentNotice(title, lastText, progressed = false, group) {
398
344
  : []),
399
345
  ].join("\n");
400
346
  }
401
- // Join groups.
402
- /** How a release names each status among its reports: `2 done, 1 failed`. */
403
347
  function statusCounts(reports) {
404
348
  return STATUSES.flatMap((status) => {
405
349
  const n = reports.filter((report) => report.status === status).length;
@@ -412,10 +356,6 @@ const LEFT_HOW = {
412
356
  deleted: "it was deleted",
413
357
  dropped: "you dropped it with courier_cleanup",
414
358
  };
415
- /**
416
- * What the parent gets when a group it named is released: every report, with its status, text and artifacts in
417
- * `reportBody`'s layout, and the members that left without one, in the order they joined.
418
- */
419
359
  export function groupNotice(group, reports, left = []) {
420
360
  const total = reports.length + left.length;
421
361
  const listed = reports.flatMap((report, index) => [
@@ -1,10 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
  import { type ToolEditor } from "../kernel/tool.js";
3
3
  import type { CleanupPorts, CourierPorts, StopPorts } from "./ports.js";
4
- /**
5
- * The tree's courier tools: their input schemas, descriptions, and what each returns. The descriptions are
6
- * model-facing like the texts in `text.ts`, and snapshotted with them in `test/notices.test.ts`.
7
- */
8
4
  export declare const SpawnInput: Schema.Struct<{
9
5
  readonly task: Schema.String;
10
6
  readonly title: Schema.optional<Schema.String>;
@@ -44,11 +40,9 @@ export declare const CleanupInput: Schema.Struct<{
44
40
  readonly sessionID: Schema.String;
45
41
  readonly force: Schema.optional<Schema.Boolean>;
46
42
  }>;
47
- /** What the tree's tools run on: one plugin instance's ports. */
48
43
  export interface TreeToolPorts {
49
44
  readonly courier: CourierPorts;
50
45
  readonly cleanup: CleanupPorts;
51
46
  readonly stop: StopPorts;
52
47
  }
53
- /** Adds the tree's courier tools, each a direct tool rather than a code-mode one. */
54
48
  export declare function addTreeTools(tools: ToolEditor, ports: TreeToolPorts): void;
@@ -8,10 +8,6 @@ import { listChildren } from "./status.js";
8
8
  import { stop } from "./stop.js";
9
9
  import { tree } from "./subtree.js";
10
10
  import { childrenText, cleanupText, sendText, spawnText, stopText, treeText } from "./text.js";
11
- /**
12
- * The tree's courier tools: their input schemas, descriptions, and what each returns. The descriptions are
13
- * model-facing like the texts in `text.ts`, and snapshotted with them in `test/notices.test.ts`.
14
- */
15
11
  export const SpawnInput = Schema.Struct({
16
12
  task: Schema.String.annotate({ description: "What the new session should do. It is told who started it and how to report back." }),
17
13
  title: Schema.optional(Schema.String.annotate({ description: "Session title; defaults to the task's first line." })),
@@ -80,7 +76,6 @@ export const CleanupInput = Schema.Struct({
80
76
  description: "Remove it even with uncommitted changes or commits on no branch; that work is lost.",
81
77
  })),
82
78
  });
83
- /** Adds the tree's courier tools, each a direct tool rather than a code-mode one. */
84
79
  export function addTreeTools(tools, ports) {
85
80
  tools.add({
86
81
  name: "courier_spawn",
@@ -132,7 +127,8 @@ export function addTreeTools(tools, ports) {
132
127
  options: { codemode: false },
133
128
  description: "List the whole tree of sessions under this one (or under sessionID): every session started with courier_spawn " +
134
129
  "from it, directly or through others, each with its depth, what courier_children shows, and whether it still " +
135
- "owes a report, sent progress, or ended without one. For a one-off look, not for waiting. A long tree lists at most " +
130
+ "owes a report, sent progress, or ended without one, and the subagents OpenCode's subagent tool started in it, " +
131
+ "marked subagent: true, which owe no report. For a one-off look, not for waiting. A long tree lists at most " +
136
132
  "maxTotal sessions, the live ones first, then the newest, and says how many older ones are left out.",
137
133
  input: TreeInput,
138
134
  execute: async (input, context) => {
@@ -143,8 +139,8 @@ export function addTreeTools(tools, ports) {
143
139
  tools.add({
144
140
  name: "courier_stop",
145
141
  options: { codemode: false },
146
- description: "Stop a session you started, directly or through others, and every session under it: each running turn is " +
147
- "interrupted, deepest first, and the messages scheduled for them with courier_later are cancelled. Use it when a " +
142
+ description: "Stop a session you started, directly or through others, and every session under it, subagents included: each " +
143
+ "running turn is interrupted, deepest first, and the messages scheduled for them with courier_later are cancelled. Use it when a " +
148
144
  "subtree went wrong or is no longer needed. They do not report back, and you are not told of what you stopped; a " +
149
145
  "session between you and the target is. A stopped session can be woken again with courier_send. With " +
150
146
  "cleanup: true their worktrees are removed too, unless they hold work that is on no branch.",
@@ -4,106 +4,62 @@ import { type Accessor } from "solid-js";
4
4
  import { type MascotState, type Token } from "./mascot.js";
5
5
  import { type Forgotten, type Row, type Stopped, type Summary, type Wait } from "./rpc.js";
6
6
  type Context = Plugin.Context;
7
- /**
8
- * The events after which a summary may read differently: a turn started or ended, a request or form, a session made, removed,
9
- * archived or unarchived.
10
- */
11
7
  export declare const CHANGES: Set<string>;
12
- /** How long after the first of a burst of changes the entry asks the server again. */
13
8
  export declare const SETTLE_MS = 250;
14
- /** The panel's settle: each of its asks reads every row's messages, so a busy tree is asked at most once a second. */
15
9
  export declare const PANEL_SETTLE_MS = 1000;
16
- /** The name of the tree panel in the `session.panel` slot. */
17
10
  export declare const PANEL = "courier-tree";
18
- /** How often the panel redraws its running times and how long ago each last tool call was. */
19
11
  export declare const TICK_MS = 1000;
20
- /** The panel's animation frame: the spinner's step, and how often a flash or the bar's easing moves. */
21
12
  export declare const FRAME_MS = 90;
22
- /** How long a row's background takes to fade back from its flash after its report arrives: the report's hold. */
23
13
  export declare const FLASH_MS = 1500;
24
- /** How long the header's bar takes to grow, or shrink, to its new length. */
25
14
  export declare const BAR_MS = 450;
26
- /** Whether the panel animates: unless the TUI part's options say `"animations": false`, as the plugin API has no TUI setting for it. */
27
15
  export declare function animationsOn(options: Readonly<Record<string, unknown>> | undefined): boolean;
28
- /** A value eased over the panel's frames by OpenTUI's `Timeline`; reading it in a computation follows the frames. */
29
16
  export type Tween = {
30
17
  readonly value: () => number;
31
18
  readonly done: () => boolean;
32
19
  };
33
- /**
34
- * The panel's one timer: a frame every `FRAME_MS` while a tween moves or an `animates` predicate holds, every `every` while
35
- * only slower ones do, none otherwise, with animations off or once the panel is gone. `frame` counts in `FRAME_MS`.
36
- */
37
20
  export type Motion = {
38
21
  readonly enabled: boolean;
39
22
  readonly frame: Accessor<number>;
40
23
  readonly animates: (when: Accessor<boolean>, every?: number) => void;
41
24
  readonly tween: (from: number, to: number, duration: number, ease: EasingFunctions) => Tween;
42
25
  };
43
- /** The panel's `Motion`, owned by the computation it is made in. */
44
26
  export declare function createMotion(enabled: boolean): Motion;
45
- /**
46
- * Each row's flash, by session, from a read of the rows to the next that finds its report turned to `reported`; not on
47
- * the first read. `strength` is a row's, 1 when its report arrives down to 0; `reported` holds for `FLASH_MS` after one.
48
- */
49
27
  export declare function createFlashes(motion: Motion, rows: Accessor<ReadonlyArray<Row>>): {
50
28
  strength: (sessionID: string) => number;
51
29
  reported: () => boolean;
52
30
  };
53
- /** The header bar's filled cells, eased over `BAR_MS` to its new length once it is shown, at once when it first is. */
54
31
  export declare function createBarFill(motion: Motion, counts: Accessor<{
55
32
  reported: number;
56
33
  started: number;
57
34
  }>): () => number;
58
- /** True for `FLASH_MS` after a read of the tree finds a session the one before it did not have; not on the first read. */
59
35
  export declare function createSpawns(motion: Motion, rows: Accessor<ReadonlyArray<Row> | undefined>): () => boolean;
60
- /**
61
- * Digby's state for the tree's rows and its `top`, by `summaryLine`'s priority, unless a report or a spawn just arrived;
62
- * gone when every row under the top is.
63
- */
64
36
  export declare function mascotState(rows: ReadonlyArray<Row>, { top, reported, spawned }?: {
65
37
  top?: Row;
66
38
  reported?: boolean;
67
39
  spawned?: boolean;
68
40
  }): MascotState;
69
- /** What the tree's sessions do, as the header's second line says it: "1 waiting on you · 2 running"; empty with none live. */
70
41
  export declare function countsLine(rows: ReadonlyArray<Row>): string;
71
- /** The panel's size below which it shows no header glyph, so the header keeps to one line and its bar is not cut. */
72
42
  export declare const GLYPH_ROWS = 12;
73
43
  export declare const GLYPH_COLUMNS = 54;
74
- /** The empty panel's height from which it shows the 16×16. */
75
44
  export declare const ART_ROWS = 14;
76
- /** The rows left free below the tree's last row from which the 16×16 shows there. */
77
45
  export declare const BELOW_ROWS = 10;
78
- /** `tint` over `rest` by `amount`, from 0 (all `rest`) to 1 (all `tint`). */
79
46
  export declare function blend(tint: RGBA, rest: RGBA, amount: number): RGBA;
80
- /**
81
- * The status entry's text, such as "courier 1 waiting on you · 2 running · group pair 1/2"; empty when there is nothing to show.
82
- * Within `maxWidth` it keeps whole parts, most important first, and counts the groups it drops: "· +2 groups".
83
- */
84
47
  export declare function summaryLine(summary: Summary, maxWidth?: number): string;
85
- /** A span of time, coarsest first: "45s", "3m 05s", "2h 07m". */
86
48
  export declare function duration(ms: number): string;
87
- /** How many of a session's children have reported, out of those it started, among the rows read. */
88
49
  export declare function parts(rows: ReadonlyArray<Row>, parentID: string): {
89
50
  reported: number;
90
51
  started: number;
91
52
  };
92
- /** How many of a bar's `width` cells are filled for the parts reported. */
93
53
  export declare function filledCells({ reported, started }: {
94
54
  reported: number;
95
55
  started: number;
96
56
  }, width?: number): number;
97
- /** A bar of parts reported, `width` cells wide: "[████░░░░░░] 2/5 reported"; `filled` cells of it while it eases to its length. */
98
57
  export declare function bar(counts: {
99
58
  reported: number;
100
59
  started: number;
101
60
  }, width?: number, filled?: number): string;
102
- /** What a paused session waits on, such as "asks permission: shell echo hi" or "shows a form: Web Search". */
103
61
  export declare function waitLine(wait: Wait): string;
104
- /** What a row's session is doing, its report and its group standing: "idle · reported done · group pair held". */
105
62
  export declare function statusLine(row: Row, now?: number): string;
106
- /** The parts of `statusLine`, each with the row part the panel colours it as. */
107
63
  export declare function statusParts(row: Row, now?: number): {
108
64
  text: string;
109
65
  kind: "state" | "report" | "plain";
@@ -115,72 +71,35 @@ type Feedback<Color> = {
115
71
  }>;
116
72
  };
117
73
  };
118
- /** The running mark's frames, one every `FRAME_MS`. */
119
74
  export declare const SPINNER: string[];
120
- /**
121
- * The one cell a row's state shows and its theme colour, the blank cell of one with no mark: this is the only place that
122
- * chooses them. A running row spins through `SPINNER` at the panel's `frame`, and shows `•` with none (animations off).
123
- */
124
75
  export declare function rowMark<Color>(row: Row, theme: Feedback<Color>, frame?: number): {
125
76
  mark: string;
126
77
  color: Color | undefined;
127
78
  };
128
- /** Past this fraction of its window, a session's context use is shown in the warning colour. */
129
79
  export declare const CONTEXT_WARN = 0.8;
130
- /** A leaf's activity: how long it has run, its last tool call and how long ago, its context use and its cost. */
131
80
  export declare function activityParts(row: Row, now: number): string[];
132
81
  export declare const activityLine: (row: Row, now: number) => string;
133
- /** What a stalled session's row, or the panel of one, says of it: "a message waits in its inbox since 2m 05s, woken again in vain". */
134
82
  export declare function stalledLine(since: number, now: number): string;
135
- /** The lines a row shows under its title: an orchestrator's bar, a paused or stalled session's wait, or else a live leaf's activity. */
136
83
  export declare function detailLines(row: Row, rows: ReadonlyArray<Row>, now: number): string[];
137
- /**
138
- * Each row's tree guides, by session, for rows in the order a tree is drawn, below depth `base`: before its mark ("│ ├ "),
139
- * and before its detail lines, which carry a line down to its first child ("│ │ ").
140
- */
141
84
  export declare function guides(rows: ReadonlyArray<Row>, base: number): Map<string, {
142
85
  row: string;
143
86
  detail: string;
144
87
  }>;
145
- /** How often the footer and the panel ask again with no event: the scheduler flags a stalled session without one. */
146
88
  export declare const REREAD_MS = 15000;
147
- /**
148
- * Adds the tree's entry to the prompt's footer: what the sessions under the open session do, asked of the RPC after each
149
- * change and every `REREAD_MS`; nothing on the home screen. Clicked while some wait on the person, it calls `onWaiting`.
150
- */
151
89
  export declare function addTreeStatus(ctx: Context, onWaiting?: (sessionID: string) => void): () => void;
152
- /** The toast after a stop: how many sessions were interrupted, and how many scheduled messages were cancelled. */
153
90
  export declare function stoppedLine({ stopped, cancelled }: Stopped): string;
154
- /** The toast after a forget, as courier_cleanup reports it: forgotten, its worktree removed or already gone, or kept and why. */
155
91
  export declare function forgottenLine(title: string, result: Forgotten): string;
156
- /** The one place Digby's pixel tokens take a theme colour. */
157
92
  export declare const digbyColours: (theme: Context["theme"]) => Record<Token, RGBA>;
158
- /**
159
- * A row's background at rest: the selection's while the panel has the keyboard, else one step quieter under the pointer,
160
- * else none, the panel's.
161
- */
162
93
  export declare function restBackground(theme: Context["theme"], { selected, hovered }: {
163
94
  selected: boolean;
164
95
  hovered: boolean;
165
96
  }): RGBA | undefined;
166
- /** The usage the `/courier` command shows for an input it does not know, before the further verbs' lines. */
167
97
  export declare const USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
168
- /** A further verb of the `/courier` command, which another module's TUI part supplies: what it runs, and its line of the usage. */
169
98
  export type Verb = {
170
99
  readonly run: () => void;
171
100
  readonly usage: string;
172
101
  };
173
- /** The usage with the further verbs' lines. */
174
102
  export declare const usageWith: (verbs: Readonly<Record<string, Verb>>) => string;
175
- /**
176
- * Adds the tree panel to the `session.panel` slot, opened by `/courier tree` or the palette, which follows the person
177
- * between sessions until closed, and `/courier stop <session>`, which stops a session under the open one once confirmed;
178
- * `verbs` are the command's further verbs, by name.
179
- */
180
103
  export declare function addTreePanel(ctx: Context, verbs?: Readonly<Record<string, Verb>>): () => void;
181
- /**
182
- * Draws the person's attention when reports arrive: a join group's reports, released to the session that started them,
183
- * and a child's report to its parent, with the `subagent_done` sound. Read from the courier's own delivery metadata.
184
- */
185
104
  export declare function addTreeAttention(ctx: Context): () => void;
186
105
  export {};
@@ -1,7 +1,5 @@
1
1
  import type { GroupPorts, Wake, WatchPorts } from "./ports.js";
2
- /** How long a courier message may wait in an idle session's inbox before its wake counts as missed. */
3
2
  export declare const WAKE_MS = 30000;
4
- /** OpenCode's `session.inbox.enqueued`, with the item's payload, which carries the courier's metadata. */
5
3
  export interface Enqueued {
6
4
  readonly created?: number;
7
5
  readonly data: {
@@ -20,68 +18,40 @@ export interface Enqueued {
20
18
  };
21
19
  };
22
20
  }
23
- /** OpenCode's `session.inbox.delivered` and `session.inbox.cancelled`. */
24
21
  export interface InboxSettled {
25
22
  readonly data: {
26
23
  readonly sessionID: string;
27
24
  readonly inboxID: string;
28
25
  };
29
26
  }
30
- /** OpenCode's `session.execution.*` events, which frame a session's busy period. */
31
27
  export interface Execution {
32
28
  readonly created?: number;
33
29
  readonly data: {
34
30
  readonly sessionID: string;
35
31
  };
36
32
  }
37
- /**
38
- * Watches a courier message enqueued for a session until it is delivered, noting whether the session runs.
39
- * Returns whether it is watched, which one sent with `resume: false` is not.
40
- */
41
33
  export declare function watchWake(ports: WatchPorts, event: Enqueued): Promise<boolean>;
42
- /**
43
- * Watches a message `courier_send` has just had enqueued, without waiting for its enqueued event, so the limits see a reported child woken
44
- * for it at once; the event leaves this row be. Dropped again if the message is already stored, delivered before it was written.
45
- */
46
34
  export declare function noteSent(ports: Pick<WatchPorts, "storage" | "session" | "now">, sent: {
47
35
  readonly sessionID: string;
48
36
  readonly inboxID: string;
49
37
  readonly text: string;
50
38
  readonly description?: string;
51
39
  }): Promise<void>;
52
- /** Stops watching a message once it is delivered or cancelled. */
53
40
  export declare function settleWake(ports: Pick<WatchPorts, "storage">, event: InboxSettled): Promise<void>;
54
- /** A turn started in a session: the messages waiting there are its to take. */
55
41
  export declare function wakeStarted(ports: Pick<WatchPorts, "storage">, event: Execution): Promise<void>;
56
- /**
57
- * A session's turn ended: a message it took is no longer watched, as its delivery may have been missed by this
58
- * subscription; one still waiting is watched as waiting in an idle session from now.
59
- */
60
42
  export declare function wakeEnded(ports: WatchPorts, event: Execution & {
61
43
  readonly data: {
62
44
  readonly reason?: string;
63
45
  };
64
46
  }): Promise<void>;
65
- /** Forgets the messages watched in a session's inbox: it was deleted, or stopped, which nothing is to wake again. */
66
47
  export declare function forgetWakes(ports: Pick<WatchPorts, "storage">, sessionID: string): Promise<void>;
67
- /** The ports the scheduler's tick wakes sessions through: what every hub-v2 member's `later` ports have, whose session is its `ctx.session`. */
68
48
  export type WakePorts = GroupPorts & {
69
49
  readonly session: Pick<WatchPorts["session"], "get" | "context">;
70
50
  readonly now: () => number;
71
51
  };
72
- /**
73
- * Sends once more, under its inbox id, each courier message an idle session has left waiting for `WAKE_MS`, flags stalled one still
74
- * waiting `WAKE_MS` later, and marks running one a turn it shows will take; run by the scheduler's owner, `claimed` keeping a tick alongside off it.
75
- */
76
52
  export declare function wakeMissed(ports: WakePorts, claimed: Set<string>): Promise<void>;
77
- /** Whether a message has waited in an idle session past `WAKE_MS`, and since a wake again, if one was sent; a stalled one is done. */
78
53
  export declare function missed(wake: Wake, now: number): boolean;
79
- /**
80
- * Whether a courier message sent with a wake waits undelivered in the session's inbox while it is idle, waiting or, unless
81
- * `stalled` is false, stalled; one a running turn is to take is not counted, as `runs` says that turn runs.
82
- */
83
54
  export declare function waitsInInbox(ports: Pick<WatchPorts, "storage">, sessionID: string, { stalled }?: {
84
55
  stalled?: boolean | undefined;
85
56
  }): Promise<boolean>;
86
- /** The sessions among `sessionIDs` flagged stalled, each with when its oldest stalled message was enqueued. */
87
57
  export declare function stalledAmong(ports: Pick<WatchPorts, "storage">, sessionIDs: ReadonlyArray<string>): Promise<Map<string, number>>;