opencode-courier 0.13.8 → 0.15.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 +7 -54
  4. package/dist/kernel/protocol.d.ts +3 -17
  5. package/dist/kernel/protocol.js +3 -17
  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 +1 -9
  16. package/dist/memory/lessons.js +2 -11
  17. package/dist/memory/note.d.ts +2 -11
  18. package/dist/memory/note.js +5 -11
  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 +1 -26
  35. package/dist/memory/text.js +8 -38
  36. package/dist/memory/tools.d.ts +0 -2
  37. package/dist/memory/tools.js +4 -6
  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 +3 -4
  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 +6 -8
  92. package/dist/skill.d.ts +1 -9
  93. package/dist/skill.js +40 -97
  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 +17 -13
  98. package/dist/tree/domain.d.ts +0 -64
  99. package/dist/tree/domain.js +7 -45
  100. package/dist/tree/events.d.ts +5 -36
  101. package/dist/tree/events.js +23 -55
  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 +6 -39
  106. package/dist/tree/index.d.ts +2 -2
  107. package/dist/tree/index.js +1 -3
  108. package/dist/tree/limits.d.ts +1 -4
  109. package/dist/tree/limits.js +9 -13
  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 +5 -139
  119. package/dist/tree/report.d.ts +1 -10
  120. package/dist/tree/report.js +4 -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 +1 -7
  132. package/dist/tree/spawn.js +15 -56
  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 +4 -46
  146. package/dist/tree/text.js +44 -83
  147. package/dist/tree/tools.d.ts +0 -6
  148. package/dist/tree/tools.js +15 -15
  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 +24 -10
  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.`,
@@ -21,10 +16,12 @@ export function childBrief(parentID, task, depth, limits, branch) {
21
16
  "A message without a status is progress, not your report: if you end your turn without a report, the parent is told so.",
22
17
  "If you find something the other parts of the job need (an assumption of the split that is wrong, a shared file or API " +
23
18
  "that behaves otherwise), send it to the parent at once with courier_send without a status, then carry on: it passes " +
24
- "it on to the sessions it affects. Progress alone is not worth a message.",
19
+ "it on to the sessions it affects. Do the same with one a session you started sends you, when it reaches past your own " +
20
+ "part. Progress alone is not worth a message.",
25
21
  "A message the person writes in your session, outside a <courier> envelope, is for the whole job: before you act on it, " +
26
- "send it to the parent at once with courier_send without a status, quoting it as the person's, then carry on.",
27
- "If you need the person to decide something, use your question tool; it reaches them through the session that started you.",
22
+ "send it to the parent at once with courier_send without a status, quoting it as the person's, then carry on. The " +
23
+ "person's words a session you started passes you are for the whole job too: send them on to the parent the same way.",
24
+ "If you need the person to decide something, use your question tool; it reaches them through the top session of the tree.",
28
25
  "",
29
26
  ...(branch ? [...isolationRule(branch, depth < limits.maxDepth), ""] : []),
30
27
  ...splitRule(parentID, depth, limits),
@@ -63,14 +60,8 @@ function splitRule(parentID, depth, limits) {
63
60
  `session), then send ${parentID} one combined report.`,
64
61
  ];
65
62
  }
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
63
  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
64
  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
65
  export function rolePart(depth, limits) {
75
66
  const { maxDepth, maxChildren, maxTotal } = limits;
76
67
  const budget = `At most ${maxChildren} of the sessions you start run at once, and ${maxTotal} in the whole tree.`;
@@ -79,24 +70,20 @@ export function rolePart(depth, limits) {
79
70
  `${SKILL_ID} skill, call courier_recall once for how earlier trees in this project were split and how they went, ` +
80
71
  `and decide whether to split it across sessions (doing it yourself is fine; so is handing a heavy task, with long ` +
81
72
  `reading, many edits or long runs, whole to one session to keep your context, but never a small one), and say ` +
82
- `which in one line of your reply: ${ROOT_SPLIT_LINE}`);
73
+ `which in one line of your reply: ${ROOT_SPLIT_LINE}.`);
83
74
  if (depth === 0)
84
75
  return (`${ROLE_PREFIX} root orchestrator. You started sessions with courier_spawn, and they may start their own, ` +
85
- `down to ${maxDepth} levels below you. ${budget} Check and integrate what each one reports yourself.`);
76
+ `down to ${maxDepth} levels below you. ${budget} Check and integrate what each one reports yourself. ` +
77
+ `Load the ${SKILL_ID} skill if it is not in your context.`);
86
78
  if (depth < maxDepth)
87
- return (`${ROLE_PREFIX} sub-orchestrator, at depth ${depth} of at most ${maxDepth}. Before you split, load the ${SKILL_ID} ` +
79
+ return (`${ROLE_PREFIX} sub-orchestrator, at depth ${depth} of at most ${maxDepth}. Before you decide whether to split, load the ${SKILL_ID} ` +
88
80
  `skill. Split your task with courier_spawn or do it yourself, as your brief says; sessions can be started ${count(maxDepth - depth, "level")} below you. ${budget}`);
89
81
  return (`${ROLE_PREFIX} leaf, at depth ${depth}, the deepest the session tree goes. You cannot start sessions: do your ` +
90
82
  "task yourself and report with courier_send to the session that started you.");
91
83
  }
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
84
  export const STANDING_KEY = "standing";
95
- /** How every standing entry begins. */
96
85
  export const STANDING_PREFIX = "opencode-courier standing:";
97
- /** How many children the entry lists; older ones past that are counted. */
98
86
  export const STANDING_MAX = 10;
99
- /** The most characters of a child's title the entry shows. */
100
87
  const TITLE_MAX = 40;
101
88
  function stateWords(state) {
102
89
  const progressed = state.progressed ? ", sent progress" : "";
@@ -132,10 +119,6 @@ function groupWords(name, standing) {
132
119
  return `out of ${group} (${LEFT_HOW[standing]})`;
133
120
  }
134
121
  }
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
122
  export function standingEntry(rows) {
140
123
  const shown = rows.slice(-STANDING_MAX);
141
124
  const older = rows.length - shown.length;
@@ -150,22 +133,19 @@ export function standingEntry(rows) {
150
133
  ...(older ? [`${count(older, "older session")} not listed; courier_children lists them.`] : []),
151
134
  ].join("\n");
152
135
  }
153
- // courier_spawn's refusals, one per limit.
154
136
  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
137
  "(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
138
  const live = (n) => (n === 1 ? "is running or has not reported yet" : "are running or have not reported yet");
158
139
  export const childrenRefusal = (running, maxChildren) => `Not started: ${count(running, "session")} you started ${live(running)}, the most allowed at once ` +
159
- `(maxChildren ${maxChildren}). Do this part yourself, or end your turn and start it once one of them has reported.`;
160
- export const totalRefusal = (running, maxTotal) => `Not started: ${count(running, "session")} in this session tree ${live(running)}, the most allowed at once ` +
161
- `(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
+ `(maxChildren ${maxChildren}). Do this part yourself, or end your turn and start it once one of them has reported, not in a loop.`;
141
+ export const totalRefusal = (running, maxTotal, yours) => `Not started: ${count(running, "session")} in this session tree ${live(running)}, the most allowed at once (maxTotal ${maxTotal}). ` +
142
+ (yours === 0
143
+ ? "Do this part yourself: none of the sessions you started is running, so no report will wake you."
144
+ : "Do this part yourself, or end your turn and try again once a report has arrived, not in a loop.");
145
+ const LISTED_MAX = 12;
146
+ const some = (items) => (items.length > LISTED_MAX ? `${items.slice(0, LISTED_MAX).join(", ")} and ${items.length - LISTED_MAX} more` : items.join(", "));
166
147
  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
148
  "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
149
  export function unknownModelRefusal(model, known, named) {
170
150
  const name = `${model.providerID}/${model.id}`;
171
151
  const ids = known.filter((item) => item.providerID === model.providerID).map((item) => item.id);
@@ -184,19 +164,12 @@ export function unknownVariantRefusal(model, variants) {
184
164
  return (`Not started: the model ${model.providerID}/${model.id} has no variant ${JSON.stringify(model.variant)}. ` +
185
165
  `Its variants are ${listed}; leave variant unset for the model's default.`);
186
166
  }
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
167
  export function variantWithoutModelRefusal(variant, agent) {
189
168
  const why = agent ? `the agent ${agent} could not be looked up` : "neither your own model nor OpenCode's default could be read";
190
169
  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
170
  }
192
- /** The strings of a list given as artifacts, whatever else the model put in it. */
193
171
  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
172
  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
173
  export function reportBody(message, artifacts) {
201
174
  const given = obj(artifacts);
202
175
  const checks = (Array.isArray(given.checks) ? given.checks : []).flatMap((check) => {
@@ -211,27 +184,27 @@ export function reportBody(message, artifacts) {
211
184
  ];
212
185
  return lines.length ? [message, "", "Artifacts:", ...lines].join("\n") : message;
213
186
  }
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
187
  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
188
  "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
189
  export function spawnText(child) {
219
190
  const warning = (child.fromParent ? " Its worktree starts from the commit your own worktree is on." : "") +
220
191
  (child.uncommitted
221
- ? " Your worktree has uncommitted changes, which it does not see: commit them and tell it, or send them to it, if it needs them."
192
+ ? " Your working tree has uncommitted changes, which it does not see: commit them and tell it, or send them to it, if it needs them."
222
193
  : "") +
223
194
  (child.rosterError ? ` It is not on your courier_children list: ${child.rosterError}` : "") +
224
195
  (child.groupError ? ` It is in no group, so its report comes on its own: ${child.groupError}` : "");
196
+ const asks = child.belowTop
197
+ ? " Its permission requests and questions go to the top session of the tree, not to you, so a child slow to report may be waiting on the person."
198
+ : "";
225
199
  const reports = child.group
226
200
  ? `Its report is held with group "${child.group}": once every session you started in that group has reported and your ` +
227
- "turn has ended, you get their reports in one message, which starts a new turn for you; a blocked report, a question " +
228
- "or a failed turn still reaches you at once."
229
- : "It will report back with courier_send, which starts a new turn for you.";
201
+ `turn has ended, you get their reports in one message, which starts a new turn for you; a blocked report${child.belowTop ? "" : ", a question"} ` +
202
+ `or a failed turn still reaches you at once.${asks}`
203
+ : `It will report back with courier_send, which starts a new turn for you.${asks}`;
230
204
  const on = child.model ? `, on ${child.model}` : "";
231
205
  return (`Started session ${child.sessionID} in ${child.directory}${on}. ${reports} Once you have started every session you need, ` +
232
206
  `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
207
  }
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
208
  export function sendText(sessionID, sent = {}) {
236
209
  if (sent.dropped)
237
210
  return (`Delivered to ${sessionID}, without the status ${sent.dropped}: a status belongs on a report to the session that ` +
@@ -266,7 +239,6 @@ export function cleanupText(result) {
266
239
  return done;
267
240
  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
241
  }
269
- /** courier_tree's result: the sessions under a session, drawn depth first, as JSON. */
270
242
  export function treeText(tree) {
271
243
  if (!tree.sessions.length)
272
244
  return "No sessions started with courier_spawn.";
@@ -276,15 +248,18 @@ export function treeText(tree) {
276
248
  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
249
  return JSON.stringify(tree, null, 2) + note;
278
250
  }
279
- /** What courier_stop did to each session, and to the messages and worktrees. */
251
+ const IDLE_STOPPED = "Stopping an idle session does not settle it: one that owed a report still owes it, stays in its join group and " +
252
+ "counts toward maxChildren and maxTotal until the session that started it drops it with courier_cleanup.";
280
253
  export function stopText(result) {
281
254
  const interrupted = result.stopped.filter((one) => one.outcome === "interrupted").length;
282
255
  const failed = result.stopped.filter((one) => one.outcome === "failed");
256
+ const idle = result.stopped.some((one) => one.outcome === "idle" && !one.subagent);
283
257
  return [
284
258
  `Stopped ${result.sessionID} and ${count(result.stopped.length - 1, "session")} under it, deepest first: ${interrupted} had a turn running and was interrupted, ` +
285
259
  `${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 + ")" : ""}`),
260
+ ...result.stopped.map((one) => `- ${one.sessionID}${one.subagent ? " (subagent)" : ""}: ${one.outcome}${one.error ? " (" + one.error + ")" : ""}`),
287
261
  ...(failed.length ? ["Those that failed may still run: try courier_stop again."] : []),
262
+ ...(idle ? [IDLE_STOPPED] : []),
288
263
  result.cancelled.length
289
264
  ? `Cancelled ${count(result.cancelled.length, "scheduled message")}: ${result.cancelled.join(", ")}.`
290
265
  : "No scheduled messages were pending for them.",
@@ -293,7 +268,6 @@ export function stopText(result) {
293
268
  ].join("\n");
294
269
  }
295
270
  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
271
  function worktreeLines(cleanup) {
298
272
  if (!cleanup.length)
299
273
  return ["None of them ran in a worktree of its own."];
@@ -301,12 +275,10 @@ function worktreeLines(cleanup) {
301
275
  ? `Worktree of ${one.sessionID}: not removed (${one.error}).`
302
276
  : cleanupText(one).replace(FORCE_HINT, " Kept; courier_cleanup with force: true discards it."));
303
277
  }
304
- /** What the parent of a stopped session is told when the stop was ordered by a session above both. */
305
278
  export const stoppedNotice = (title, by) => [
306
279
  `This session, "${title}", which you started with courier_spawn, was stopped by ${by}, a session above you, together with the sessions under it.`,
307
280
  "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
281
  ].join("\n");
309
- /** Why a worktree in this state must be kept, or undefined when removing it loses nothing. */
310
282
  export function keepReason(state) {
311
283
  const reasons = [
312
284
  state.changes.length ? `${count(state.changes.length, "uncommitted change")} (${preview(state.changes)})` : "",
@@ -322,8 +294,6 @@ function count(n, noun) {
322
294
  function preview(items) {
323
295
  return items.length > 5 ? `${items.slice(0, 5).join(", ")}, ...` : items.join(", ");
324
296
  }
325
- // Turn failures.
326
- /** A wait in whole minutes, rounded up: "1 minute", "45 minutes", "2 hours 5 minutes". */
327
297
  function wait(ms) {
328
298
  const minutes = Math.ceil(ms / 60_000);
329
299
  if (minutes < 60)
@@ -331,31 +301,32 @@ function wait(ms) {
331
301
  const rest = minutes % 60;
332
302
  return [count(Math.floor(minutes / 60), "hour"), rest ? count(rest, "minute") : ""].filter(Boolean).join(" ");
333
303
  }
334
- /** A time as the notices say it, in UTC, with its date when it is not the day of `from`. */
335
304
  function clock(at, from) {
336
305
  const iso = new Date(at).toISOString();
337
306
  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
307
  }
339
- /** The line saying how long the provider asked to wait, and until when if the failure's time `at` is known. */
340
308
  function waitLine(delay, at) {
341
309
  const until = at === undefined ? "" : `, until about ${clock(at + delay, at)}`;
342
310
  return `The provider asked to wait about ${wait(delay)} before trying again${until}.`;
343
311
  }
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
- export function failureNotice(title, error, group, at) {
312
+ function belowLine(below) {
313
+ const titles = below.map((title) => `"${title}"`).join(", ");
314
+ return (`It has ${count(below.length, "session")} of its own still running (${titles}), whose reports will wake it, and it still counts ` +
315
+ "toward your limits: message it rather than replace it, or stop it with courier_stop first, which stops the sessions under it too.");
316
+ }
317
+ export function failureNotice(title, error, group, at, below = []) {
349
318
  const status = error.status === undefined ? "" : `, status ${error.status}`;
350
319
  const delay = retryDelay(error);
320
+ const replace = below.length ? "" : " start a replacement,";
351
321
  return [
352
322
  `This session, "${title}", which you started with courier_spawn, failed: ${error.message} (${error.type}${status}).`,
353
323
  ...(delay === undefined ? [] : [waitLine(delay, at)]),
354
- "Its turn ended without finishing, so it will not report back on its own.",
324
+ below.length ? "Its turn ended without finishing." : "Its turn ended without finishing, so it will not report back on its own.",
355
325
  delay === undefined
356
- ? "Message it with courier_send to have it try again, start a replacement, or carry on without it."
357
- : "Do not message it with courier_send before then, as the turn would fail again. Message it after, start a replacement, or carry on without it; " +
326
+ ? `Message it with courier_send to have it try again,${replace} or carry on without it.`
327
+ : `Do not message it with courier_send before then, as the turn would fail again. Message it after,${replace} or carry on without it; ` +
358
328
  "courier_later can remind you when the wait is over.",
329
+ ...(below.length ? [belowLine(below)] : []),
359
330
  ...(group
360
331
  ? [
361
332
  `It is a member of group "${group}", which no longer waits for it: its other members' reports reach you once they have ` +
@@ -364,7 +335,6 @@ export function failureNotice(title, error, group, at) {
364
335
  : []),
365
336
  ].join("\n");
366
337
  }
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
338
  export function reportReminder(parentID) {
369
339
  return [
370
340
  `You are about to end your turn without reporting to session ${parentID}, which started you.`,
@@ -373,12 +343,7 @@ export function reportReminder(parentID) {
373
343
  "This reminder comes once: if you end your turn without a report, the session that started you is told.",
374
344
  ].join("\n");
375
345
  }
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
- */
346
+ const QUOTED_REPLY_MAX = 2000;
382
347
  export function silentNotice(title, lastText, progressed = false, group) {
383
348
  const reply = lastText?.trim();
384
349
  return [
@@ -386,9 +351,11 @@ export function silentNotice(title, lastText, progressed = false, group) {
386
351
  ...(progressed
387
352
  ? [`Its last message to you had no status, so it was progress, not its report: a report carries status ${STATUS_LIST}.`]
388
353
  : []),
389
- ...(reply ? ["Its last reply:", defuse(clipText(reply, MAX_REPLY))] : ["It ended without a reply."]),
354
+ ...(reply ? ["Its last reply:", defuse(clipText(reply, QUOTED_REPLY_MAX))] : ["It ended without a reply."]),
390
355
  "",
391
- "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.",
356
+ reply
357
+ ? "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."
358
+ : "Decide what it needs: message it with courier_send to have it carry on; or start a replacement.",
392
359
  "Until it reports, it counts toward your limits on the sessions you run at once.",
393
360
  ...(group
394
361
  ? [
@@ -398,8 +365,6 @@ export function silentNotice(title, lastText, progressed = false, group) {
398
365
  : []),
399
366
  ].join("\n");
400
367
  }
401
- // Join groups.
402
- /** How a release names each status among its reports: `2 done, 1 failed`. */
403
368
  function statusCounts(reports) {
404
369
  return STATUSES.flatMap((status) => {
405
370
  const n = reports.filter((report) => report.status === status).length;
@@ -412,10 +377,6 @@ const LEFT_HOW = {
412
377
  deleted: "it was deleted",
413
378
  dropped: "you dropped it with courier_cleanup",
414
379
  };
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
380
  export function groupNotice(group, reports, left = []) {
420
381
  const total = reports.length + left.length;
421
382
  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." })),
@@ -25,21 +21,23 @@ export const SpawnInput = Schema.Struct({
25
21
  "it unset for the chosen model's own; with a model of your own choosing, that is the model's default. Refused when " +
26
22
  "the model has no such variant.",
27
23
  })),
28
- isolate: Schema.optional(Schema.Boolean.annotate({ description: "Run the session in its own git worktree so parallel sessions don't share files. From a session that is itself " +
29
- "isolated, the worktree starts from your current commit, not your uncommitted changes: commit first. The session " +
24
+ isolate: Schema.optional(Schema.Boolean.annotate({ description: "Run the session in its own git worktree so parallel sessions don't share files. Commit first: its worktree " +
25
+ "starts from your last commit and sees only committed files and project skills, not your uncommitted changes. The session " +
30
26
  "commits on a branch named courier/<its session ID> and names it in its report; merge that branch yourself.",
31
27
  })),
32
28
  native: Schema.optional(Schema.Boolean.annotate({
33
29
  description: "true starts the session as your OpenCode subagent: it leaves OpenCode's session lists for your subagent tab, the " +
34
30
  "person answers its permission requests and questions in the view of the nearest session above it that is not a " +
35
- "subagent, usually the top one, and deleting your session deletes it. false starts a standalone session, whatever the " +
31
+ "subagent, usually the top one (only OpenCode's TUI or paired app shows them; under opencode run without --auto " +
32
+ "or a bare API client they are rejected or wait unseen), and deleting your session deletes it. false starts a standalone session, whatever the " +
36
33
  "plugin's nativeChildren option says; unset, that option decides. Refused with isolate: true, as a subagent runs in " +
37
34
  "its parent's directory.",
38
35
  })),
39
36
  group: Schema.optional(Schema.String.annotate({
40
37
  description: "A join group: start the sessions of one batch with the same name, and their reports are held and delivered " +
41
38
  "to you in one message once every one of them has reported and your turn has ended, instead of one turn per " +
42
- "report. A blocked report, a question, a permission request or a failed turn still reaches you at once. Letters, " +
39
+ "report. A blocked report or a failed turn still reaches you at once, and so do a question and a permission request " +
40
+ "(at the top of the tree; below it they go to the top). Letters, " +
43
41
  "digits, dots, dashes and underscores, up to 60; the name is free again once the group has been delivered.",
44
42
  })),
45
43
  });
@@ -80,7 +78,6 @@ export const CleanupInput = Schema.Struct({
80
78
  description: "Remove it even with uncommitted changes or commits on no branch; that work is lost.",
81
79
  })),
82
80
  });
83
- /** Adds the tree's courier tools, each a direct tool rather than a code-mode one. */
84
81
  export function addTreeTools(tools, ports) {
85
82
  tools.add({
86
83
  name: "courier_spawn",
@@ -88,11 +85,13 @@ export function addTreeTools(tools, ports) {
88
85
  description: "Start a new OpenCode session on a task and return immediately. It runs on your model, unless you name a model or a " +
89
86
  "variant (reasoning effort) fit for the task. The session reports " +
90
87
  "back with courier_send, which wakes this session, and you are told if its turn fails or ends without a report " +
91
- "instead, it waits for a permission or it asks a question. Give a batch of parallel sessions one group name to " +
88
+ "instead, or it waits for a permission or asks a question (at the top of the tree; below it they go to the top). " +
89
+ "Give a batch of parallel sessions one group name to " +
92
90
  "be woken once, with all their reports, instead of once per report. DO NOT poll it or call courier_children in a " +
93
91
  "loop: once you have started the sessions you need, end your turn by replying without calling more tools; each " +
94
- "report, or each group's reports, starts a new turn in which you carry on. For long tasks, also courier_later a " +
95
- "check-in for yourself in case it never reports, and courier_cancel it when it does.",
92
+ "report, or each group's reports, starts a new turn in which you carry on. For a long task or batch, also " +
93
+ "courier_later one check-in for yourself per group (per session when ungrouped) in case the reports never come, " +
94
+ "and courier_cancel it when they arrive: a report does not cancel it.",
96
95
  input: SpawnInput,
97
96
  execute: async (input, context) => {
98
97
  const child = await spawn(ports.courier, context.sessionID, input).catch(rethrow("courier_spawn"));
@@ -132,7 +131,8 @@ export function addTreeTools(tools, ports) {
132
131
  options: { codemode: false },
133
132
  description: "List the whole tree of sessions under this one (or under sessionID): every session started with courier_spawn " +
134
133
  "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 " +
134
+ "owes a report, sent progress, or ended without one, and the subagents OpenCode's subagent tool started in it, " +
135
+ "marked subagent: true, which owe no report. For a one-off look, not for waiting. A long tree lists at most " +
136
136
  "maxTotal sessions, the live ones first, then the newest, and says how many older ones are left out.",
137
137
  input: TreeInput,
138
138
  execute: async (input, context) => {
@@ -143,8 +143,8 @@ export function addTreeTools(tools, ports) {
143
143
  tools.add({
144
144
  name: "courier_stop",
145
145
  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 " +
146
+ description: "Stop a session you started, directly or through others, and every session under it, subagents included: each " +
147
+ "running turn is interrupted, deepest first, and the messages scheduled for them with courier_later are cancelled. Use it when a " +
148
148
  "subtree went wrong or is no longer needed. They do not report back, and you are not told of what you stopped; a " +
149
149
  "session between you and the target is. A stopped session can be woken again with courier_send. With " +
150
150
  "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 {};