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.
- package/README.md +7 -9
- package/dist/index.d.ts +0 -8
- package/dist/index.js +7 -54
- package/dist/kernel/protocol.d.ts +3 -17
- package/dist/kernel/protocol.js +3 -17
- package/dist/kernel/tool.d.ts +0 -2
- package/dist/kernel/tool.js +0 -1
- package/dist/memory/domain.d.ts +6 -85
- package/dist/memory/domain.js +7 -63
- package/dist/memory/events.d.ts +0 -11
- package/dist/memory/events.js +1 -12
- package/dist/memory/forget.d.ts +0 -5
- package/dist/memory/forget.js +0 -4
- package/dist/memory/index.js +0 -2
- package/dist/memory/lessons.d.ts +1 -9
- package/dist/memory/lessons.js +2 -11
- package/dist/memory/note.d.ts +2 -11
- package/dist/memory/note.js +5 -11
- package/dist/memory/pin.d.ts +0 -5
- package/dist/memory/pin.js +0 -4
- package/dist/memory/ports.d.ts +2 -22
- package/dist/memory/recall.d.ts +0 -5
- package/dist/memory/recall.js +0 -12
- package/dist/memory/record.d.ts +0 -4
- package/dist/memory/record.js +8 -31
- package/dist/memory/rpc-handlers.d.ts +3 -6
- package/dist/memory/rpc-handlers.js +0 -6
- package/dist/memory/rpc.d.ts +6 -19
- package/dist/memory/rpc.js +1 -16
- package/dist/memory/store.d.ts +0 -5
- package/dist/memory/store.js +1 -16
- package/dist/memory/sweep.d.ts +0 -4
- package/dist/memory/sweep.js +0 -4
- package/dist/memory/text.d.ts +1 -26
- package/dist/memory/text.js +8 -38
- package/dist/memory/tools.d.ts +0 -2
- package/dist/memory/tools.js +4 -6
- package/dist/memory/tui.d.ts +1 -35
- package/dist/person/events.d.ts +0 -16
- package/dist/person/events.js +3 -30
- package/dist/person/form.d.ts +0 -9
- package/dist/person/form.js +0 -10
- package/dist/person/index.d.ts +0 -2
- package/dist/person/permission.d.ts +0 -34
- package/dist/person/permission.js +2 -28
- package/dist/person/ports.d.ts +0 -16
- package/dist/person/ports.js +0 -1
- package/dist/person/question/answer.d.ts +0 -28
- package/dist/person/question/answer.js +0 -49
- package/dist/person/question/index.js +0 -2
- package/dist/person/question/lifecycle.d.ts +0 -26
- package/dist/person/question/lifecycle.js +1 -32
- package/dist/person/question/pure.d.ts +0 -11
- package/dist/person/question/pure.js +0 -11
- package/dist/person/question/relay.d.ts +0 -1
- package/dist/person/question/relay.js +5 -67
- package/dist/person/question/runtime.d.ts +0 -14
- package/dist/person/question/runtime.js +1 -26
- package/dist/person/question/shared.d.ts +0 -17
- package/dist/person/question/shared.js +0 -20
- package/dist/person/question/types.d.ts +0 -44
- package/dist/person/rpc-handlers.d.ts +0 -7
- package/dist/person/rpc-handlers.js +0 -6
- package/dist/person/rpc.d.ts +0 -20
- package/dist/person/rpc.js +0 -16
- package/dist/person/text.d.ts +0 -45
- package/dist/person/text.js +2 -54
- package/dist/person/tools.d.ts +0 -3
- package/dist/person/tools.js +3 -4
- package/dist/person/tui.d.ts +0 -21
- package/dist/platform/bounded.d.ts +0 -8
- package/dist/platform/bounded.js +0 -8
- package/dist/platform/events.d.ts +0 -18
- package/dist/platform/events.js +3 -22
- package/dist/platform/hub.d.ts +0 -113
- package/dist/platform/hub.js +1 -76
- package/dist/platform/json.d.ts +0 -3
- package/dist/platform/json.js +1 -3
- package/dist/platform/storage.d.ts +0 -7
- package/dist/platform/storage.js +0 -6
- package/dist/platform/version.d.ts +0 -9
- package/dist/platform/version.js +0 -8
- package/dist/rpc.js +0 -1
- package/dist/scheduling/index.d.ts +0 -1
- package/dist/scheduling/index.js +0 -1
- package/dist/scheduling/later.d.ts +0 -11
- package/dist/scheduling/later.js +0 -14
- package/dist/scheduling/text.d.ts +0 -1
- package/dist/scheduling/text.js +0 -1
- package/dist/scheduling/tools.d.ts +0 -1
- package/dist/scheduling/tools.js +6 -8
- package/dist/skill.d.ts +1 -9
- package/dist/skill.js +40 -97
- package/dist/tree/cleanup.d.ts +0 -20
- package/dist/tree/cleanup.js +0 -26
- package/dist/tree/context.d.ts +0 -5
- package/dist/tree/context.js +17 -13
- package/dist/tree/domain.d.ts +0 -64
- package/dist/tree/domain.js +7 -45
- package/dist/tree/events.d.ts +5 -36
- package/dist/tree/events.js +23 -55
- package/dist/tree/forget.d.ts +0 -15
- package/dist/tree/forget.js +0 -18
- package/dist/tree/group.d.ts +0 -22
- package/dist/tree/group.js +6 -39
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.js +1 -3
- package/dist/tree/limits.d.ts +1 -4
- package/dist/tree/limits.js +9 -13
- package/dist/tree/mascot.d.ts +0 -25
- package/dist/tree/mascot.js +1 -32
- package/dist/tree/model.d.ts +0 -5
- package/dist/tree/model.js +2 -15
- package/dist/tree/nudge.d.ts +0 -11
- package/dist/tree/nudge.js +3 -16
- package/dist/tree/panel.d.ts +0 -8
- package/dist/tree/panel.js +3 -16
- package/dist/tree/ports.d.ts +5 -139
- package/dist/tree/report.d.ts +1 -10
- package/dist/tree/report.js +4 -11
- package/dist/tree/roster.d.ts +0 -21
- package/dist/tree/roster.js +0 -21
- package/dist/tree/rpc-handlers.d.ts +6 -1
- package/dist/tree/rpc-handlers.js +0 -5
- package/dist/tree/rpc.d.ts +9 -109
- package/dist/tree/rpc.js +2 -38
- package/dist/tree/running.d.ts +0 -6
- package/dist/tree/running.js +2 -10
- package/dist/tree/send.d.ts +0 -4
- package/dist/tree/send.js +4 -31
- package/dist/tree/spawn.d.ts +1 -7
- package/dist/tree/spawn.js +15 -56
- package/dist/tree/standing.d.ts +0 -8
- package/dist/tree/standing.js +1 -11
- package/dist/tree/status.d.ts +0 -8
- package/dist/tree/status.js +1 -9
- package/dist/tree/stop.d.ts +2 -9
- package/dist/tree/stop.js +58 -25
- package/dist/tree/store.d.ts +0 -8
- package/dist/tree/store.js +4 -38
- package/dist/tree/subtree.d.ts +8 -26
- package/dist/tree/subtree.js +37 -35
- package/dist/tree/summary.d.ts +0 -4
- package/dist/tree/summary.js +0 -8
- package/dist/tree/text.d.ts +4 -46
- package/dist/tree/text.js +44 -83
- package/dist/tree/tools.d.ts +0 -6
- package/dist/tree/tools.js +15 -15
- package/dist/tree/tui.d.ts +0 -81
- package/dist/tree/wake.d.ts +0 -30
- package/dist/tree/wake.js +5 -39
- package/dist/tui.js +24 -10
- package/package.json +8 -9
- package/dist/webhook/index.d.ts +0 -5
- package/dist/webhook/index.js +0 -3
- package/dist/webhook/receiver.d.ts +0 -80
- package/dist/webhook/receiver.js +0 -339
- package/dist/webhook/subscriptions.d.ts +0 -29
- package/dist/webhook/subscriptions.js +0 -52
- package/dist/webhook/text.d.ts +0 -23
- package/dist/webhook/text.js +0 -82
- package/dist/webhook/tools.d.ts +0 -19
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const some = (items) => (items.length >
|
|
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
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
?
|
|
357
|
-
:
|
|
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
|
-
|
|
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,
|
|
354
|
+
...(reply ? ["Its last reply:", defuse(clipText(reply, QUOTED_REPLY_MAX))] : ["It ended without a reply."]),
|
|
390
355
|
"",
|
|
391
|
-
|
|
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) => [
|
package/dist/tree/tools.d.ts
CHANGED
|
@@ -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;
|
package/dist/tree/tools.js
CHANGED
|
@@ -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.
|
|
29
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
95
|
-
"check-in for yourself
|
|
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
|
|
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
|
|
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.",
|
package/dist/tree/tui.d.ts
CHANGED
|
@@ -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 {};
|