opencode-courier 0.7.2 → 0.8.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 (58) hide show
  1. package/README.md +122 -135
  2. package/dist/index.js +24 -4
  3. package/dist/memory/domain.d.ts +119 -0
  4. package/dist/memory/domain.js +84 -0
  5. package/dist/memory/events.d.ts +26 -0
  6. package/dist/memory/events.js +46 -0
  7. package/dist/memory/forget.d.ts +10 -0
  8. package/dist/memory/forget.js +20 -0
  9. package/dist/memory/index.d.ts +10 -0
  10. package/dist/memory/index.js +11 -0
  11. package/dist/memory/note.d.ts +37 -0
  12. package/dist/memory/note.js +29 -0
  13. package/dist/memory/ports.d.ts +44 -0
  14. package/dist/memory/ports.js +1 -0
  15. package/dist/memory/recall.d.ts +27 -0
  16. package/dist/memory/recall.js +48 -0
  17. package/dist/memory/record.d.ts +7 -0
  18. package/dist/memory/record.js +92 -0
  19. package/dist/memory/rpc-handlers.d.ts +134 -0
  20. package/dist/memory/rpc-handlers.js +23 -0
  21. package/dist/memory/rpc.d.ts +227 -0
  22. package/dist/memory/rpc.js +60 -0
  23. package/dist/memory/store.d.ts +10 -0
  24. package/dist/memory/store.js +90 -0
  25. package/dist/memory/sweep.d.ts +4 -0
  26. package/dist/memory/sweep.js +12 -0
  27. package/dist/memory/text.d.ts +27 -0
  28. package/dist/memory/text.js +95 -0
  29. package/dist/memory/tools.d.ts +16 -0
  30. package/dist/memory/tools.js +78 -0
  31. package/dist/memory/tui.d.ts +73 -0
  32. package/dist/person/rpc.d.ts +4 -4
  33. package/dist/platform/json.d.ts +1 -0
  34. package/dist/platform/json.js +1 -0
  35. package/dist/platform/version.js +2 -2
  36. package/dist/rpc.d.ts +1 -0
  37. package/dist/rpc.js +1 -0
  38. package/dist/skill.js +10 -3
  39. package/dist/tree/context.d.ts +2 -2
  40. package/dist/tree/context.js +4 -3
  41. package/dist/tree/index.d.ts +6 -5
  42. package/dist/tree/index.js +5 -4
  43. package/dist/tree/mascot.d.ts +50 -0
  44. package/dist/tree/mascot.js +171 -0
  45. package/dist/tree/panel.d.ts +3 -1
  46. package/dist/tree/panel.js +17 -3
  47. package/dist/tree/ports.d.ts +12 -2
  48. package/dist/tree/roster.d.ts +17 -0
  49. package/dist/tree/roster.js +24 -0
  50. package/dist/tree/rpc-handlers.d.ts +21 -18
  51. package/dist/tree/rpc.d.ts +26 -18
  52. package/dist/tree/rpc.js +2 -0
  53. package/dist/tree/status.d.ts +5 -5
  54. package/dist/tree/subtree.d.ts +2 -2
  55. package/dist/tree/text.js +2 -1
  56. package/dist/tree/tui.d.ts +115 -8
  57. package/dist/tui.js +1269 -222
  58. package/package.json +4 -4
@@ -164,6 +164,8 @@ export interface CourierPorts {
164
164
  readonly pending: (sessionID: string) => Promise<ReadonlyArray<Pending>>;
165
165
  /** The forms a session shows, which only the person can answer: web search's, a plugin's; not the question tool's. */
166
166
  readonly forms: (sessionID: string) => Promise<ReadonlyArray<ShownForm>>;
167
+ /** The context windows of the models available, by provider and model id, as the panel reads a session's context use. */
168
+ readonly windows: () => Promise<ReadonlyArray<ModelWindow>>;
167
169
  readonly limits: Limits;
168
170
  /** The `nativeChildren` option: a child started without isolate is created with OpenCode's own `parentID`. */
169
171
  readonly nativeChildren?: boolean;
@@ -174,6 +176,12 @@ export interface CourierPorts {
174
176
  readonly nudge: () => void;
175
177
  readonly log: (message: string) => void;
176
178
  }
179
+ /** A model's context window in tokens, as `CourierPorts.windows` lists it. */
180
+ export interface ModelWindow {
181
+ readonly providerID: string;
182
+ readonly id: string;
183
+ readonly context: number;
184
+ }
177
185
  /** A form a session shows, as `CourierPorts.forms` lists it; `person/` keeps them. */
178
186
  export interface ShownForm {
179
187
  readonly id: string;
@@ -278,8 +286,8 @@ export interface ExecutionError {
278
286
  }
279
287
  export interface WatchPorts {
280
288
  readonly storage: RosterStorage;
281
- /** `context` reads the last reply of a session that ended its turn without reporting. */
282
- readonly session: Pick<Context["session"], "synthetic" | "context">;
289
+ /** `context` reads the last reply of a session that ended its turn without reporting; `get` is what every member joins with too. */
290
+ readonly session: Pick<Context["session"], "synthetic" | "context" | "get">;
283
291
  readonly event: Pick<Context["event"], "subscribe">;
284
292
  /** The permission domains of every loaded location, which the `Waits` suppliers ask; an isolated child's are in its worktree's. */
285
293
  readonly permissions: () => Iterable<Pick<Context["permission"], "list">>;
@@ -308,5 +316,7 @@ export interface ContextPorts {
308
316
  readonly limits: Limits;
309
317
  /** Each session's depth once looked up: null for one nobody spawned that has started none, until it starts one. */
310
318
  readonly roles: Map<string, number | null>;
319
+ /** Tools of other modules hidden from a leaf alongside courier_spawn; `src/index.ts` names courier_recall and courier_note. */
320
+ readonly hiddenFromLeaf?: ReadonlyArray<string>;
311
321
  readonly log: (message: string) => void;
312
322
  }
@@ -1,3 +1,4 @@
1
+ import { type Status } from "../kernel/protocol.js";
1
2
  import type { RosterEntry, RosterStorage } from "./ports.js";
2
3
  /**
3
4
  * Entries older than this are dropped when their parent's roster is read, and on plugin setup,
@@ -11,6 +12,22 @@ export declare function allEntries(storage: RosterStorage): Promise<RosterEntry[
11
12
  * courier_spawn did not start; empty when courier_spawn did not start the session.
12
13
  */
13
14
  export declare function lineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
15
+ /** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
16
+ export declare function indexedLineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
17
+ /** The sessions a session started, oldest first, expired entries included. */
18
+ export declare function childrenOf(storage: RosterStorage, parentID: string): Promise<RosterEntry[]>;
19
+ /**
20
+ * What another module reads of a spawned session's report: `known` once a prompt was noted, whether it `owes` one, how
21
+ * its wait `ended` when not by a report, the `status` of its last report, and whether courier_stop `stopped` it since its last prompt.
22
+ */
23
+ export interface ReportState {
24
+ readonly known: boolean;
25
+ readonly owes: boolean;
26
+ readonly ended?: "failed" | "interrupted";
27
+ readonly status?: Status;
28
+ readonly stopped: boolean;
29
+ }
30
+ export declare function reportState(storage: RosterStorage, sessionID: string): Promise<ReportState>;
14
31
  /**
15
32
  * The top of a spawned session's lineage, where its permission requests and questions go, since the
16
33
  * person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
@@ -1,5 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { isStatus } from "../kernel/protocol.js";
2
3
  import { forgetChild, forgetReports } from "./forget.js";
4
+ import { stoppedSincePrompt } from "./report.js";
3
5
  import { childStore } from "./store.js";
4
6
  /**
5
7
  * Entries older than this are dropped when their parent's roster is read, and on plugin setup,
@@ -17,6 +19,28 @@ export async function allEntries(storage) {
17
19
  export async function lineage(storage, sessionID) {
18
20
  return childStore(storage).lineage(sessionID);
19
21
  }
22
+ /** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
23
+ export async function indexedLineage(storage, sessionID) {
24
+ return childStore(storage).indexedLineage(sessionID);
25
+ }
26
+ /** The sessions a session started, oldest first, expired entries included. */
27
+ export async function childrenOf(storage, parentID) {
28
+ return childStore(storage).children(parentID);
29
+ }
30
+ export async function reportState(storage, sessionID) {
31
+ const children = childStore(storage);
32
+ const [{ prompt, settled }, stopped] = await Promise.all([children.notes(sessionID, ["prompt", "settled"]), stoppedSincePrompt(children, sessionID)]);
33
+ const known = prompt.at !== undefined;
34
+ const done = known && settled.at !== undefined && settled.at >= prompt.at;
35
+ const by = settled.by;
36
+ return {
37
+ known,
38
+ owes: known && !done,
39
+ ...(done && (by === "failed" || by === "interrupted") ? { ended: by } : {}),
40
+ ...(isStatus(settled.status) ? { status: settled.status } : {}),
41
+ stopped,
42
+ };
43
+ }
20
44
  /**
21
45
  * The top of a spawned session's lineage, where its permission requests and questions go, since the
22
46
  * person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
@@ -56,11 +56,11 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
56
56
  readonly rows: readonly {
57
57
  readonly sessionID: string;
58
58
  readonly title: string;
59
- readonly parentID: string;
60
59
  readonly depth: number;
61
- readonly created: number;
62
60
  readonly isolated: boolean;
63
- readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
61
+ readonly parentID: string;
62
+ readonly created: number;
63
+ readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
64
64
  readonly waits: readonly ({
65
65
  readonly type: "permission";
66
66
  readonly action: string;
@@ -72,24 +72,25 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
72
72
  readonly type: "form";
73
73
  readonly title: string;
74
74
  })[];
75
+ readonly stopped?: boolean | undefined;
76
+ readonly stalled?: number | undefined;
75
77
  readonly group?: {
76
78
  readonly name: string;
77
79
  readonly standing: string;
78
80
  } | undefined;
79
- readonly stopped?: boolean | undefined;
80
- readonly idle?: number | undefined;
81
+ readonly cost?: number | undefined;
82
+ readonly context?: number | undefined;
83
+ readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
81
84
  readonly report?: {
82
85
  readonly progressed: boolean;
83
- readonly state: "reported" | "ended" | "owed";
86
+ readonly state: "ended" | "reported" | "owed";
84
87
  readonly status?: string | undefined;
85
88
  } | undefined;
86
- readonly stalled?: number | undefined;
87
- readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
89
+ readonly idle?: number | undefined;
88
90
  readonly tool?: {
89
91
  readonly name: string;
90
92
  readonly at: number;
91
93
  } | undefined;
92
- readonly cost?: number | undefined;
93
94
  }[];
94
95
  readonly stalled?: number | undefined;
95
96
  readonly truncated?: number | undefined;
@@ -97,11 +98,11 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
97
98
  readonly rows: readonly {
98
99
  readonly sessionID: string;
99
100
  readonly title: string;
100
- readonly parentID: string;
101
101
  readonly depth: number;
102
- readonly created: number;
103
102
  readonly isolated: boolean;
104
- readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
103
+ readonly parentID: string;
104
+ readonly created: number;
105
+ readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
105
106
  readonly waits: readonly ({
106
107
  readonly type: "permission";
107
108
  readonly action: string;
@@ -113,24 +114,25 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
113
114
  readonly type: "form";
114
115
  readonly title: string;
115
116
  })[];
117
+ readonly stopped?: boolean | undefined;
118
+ readonly stalled?: number | undefined;
116
119
  readonly group?: {
117
120
  readonly name: string;
118
121
  readonly standing: string;
119
122
  } | undefined;
120
- readonly stopped?: boolean | undefined;
121
- readonly idle?: number | undefined;
123
+ readonly cost?: number | undefined;
124
+ readonly context?: number | undefined;
125
+ readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
122
126
  readonly report?: {
123
127
  readonly progressed: boolean;
124
- readonly state: "reported" | "ended" | "owed";
128
+ readonly state: "ended" | "reported" | "owed";
125
129
  readonly status?: string | undefined;
126
130
  } | undefined;
127
- readonly stalled?: number | undefined;
128
- readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
131
+ readonly idle?: number | undefined;
129
132
  readonly tool?: {
130
133
  readonly name: string;
131
134
  readonly at: number;
132
135
  } | undefined;
133
- readonly cost?: number | undefined;
134
136
  }[];
135
137
  readonly stalled?: number | undefined;
136
138
  readonly truncated?: number | undefined;
@@ -157,6 +159,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
157
159
  readonly created: import("effect/Schema").Number;
158
160
  readonly idle: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
159
161
  readonly cost: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
162
+ readonly context: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
160
163
  readonly tool: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
161
164
  readonly name: import("effect/Schema").String;
162
165
  readonly at: import("effect/Schema").Number;
@@ -63,6 +63,8 @@ declare const Row: Schema.Struct<{
63
63
  readonly idle: Schema.optionalKey<Schema.Number>;
64
64
  /** What it has cost so far, in US dollars. */
65
65
  readonly cost: Schema.optionalKey<Schema.Number>;
66
+ /** How full its context is, as a fraction of the model's window; left out when the model or its limit cannot be read. */
67
+ readonly context: Schema.optionalKey<Schema.Number>;
66
68
  /** Its last tool call, and when it was made. */
67
69
  readonly tool: Schema.optionalKey<Schema.Struct<{
68
70
  readonly name: Schema.String;
@@ -116,6 +118,8 @@ declare const Tree: Schema.Struct<{
116
118
  readonly idle: Schema.optionalKey<Schema.Number>;
117
119
  /** What it has cost so far, in US dollars. */
118
120
  readonly cost: Schema.optionalKey<Schema.Number>;
121
+ /** How full its context is, as a fraction of the model's window; left out when the model or its limit cannot be read. */
122
+ readonly context: Schema.optionalKey<Schema.Number>;
119
123
  /** Its last tool call, and when it was made. */
120
124
  readonly tool: Schema.optionalKey<Schema.Struct<{
121
125
  readonly name: Schema.String;
@@ -219,11 +223,11 @@ export declare const treeRpc: {
219
223
  readonly rows: readonly {
220
224
  readonly sessionID: string;
221
225
  readonly title: string;
222
- readonly parentID: string;
223
226
  readonly depth: number;
224
- readonly created: number;
225
227
  readonly isolated: boolean;
226
- readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
228
+ readonly parentID: string;
229
+ readonly created: number;
230
+ readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
227
231
  readonly waits: readonly ({
228
232
  readonly type: "permission";
229
233
  readonly action: string;
@@ -235,24 +239,25 @@ export declare const treeRpc: {
235
239
  readonly type: "form";
236
240
  readonly title: string;
237
241
  })[];
242
+ readonly stopped?: boolean | undefined;
243
+ readonly stalled?: number | undefined;
238
244
  readonly group?: {
239
245
  readonly name: string;
240
246
  readonly standing: string;
241
247
  } | undefined;
242
- readonly stopped?: boolean | undefined;
243
- readonly idle?: number | undefined;
248
+ readonly cost?: number | undefined;
249
+ readonly context?: number | undefined;
250
+ readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
244
251
  readonly report?: {
245
252
  readonly progressed: boolean;
246
- readonly state: "reported" | "ended" | "owed";
253
+ readonly state: "ended" | "reported" | "owed";
247
254
  readonly status?: string | undefined;
248
255
  } | undefined;
249
- readonly stalled?: number | undefined;
250
- readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
256
+ readonly idle?: number | undefined;
251
257
  readonly tool?: {
252
258
  readonly name: string;
253
259
  readonly at: number;
254
260
  } | undefined;
255
- readonly cost?: number | undefined;
256
261
  }[];
257
262
  readonly stalled?: number | undefined;
258
263
  readonly truncated?: number | undefined;
@@ -260,11 +265,11 @@ export declare const treeRpc: {
260
265
  readonly rows: readonly {
261
266
  readonly sessionID: string;
262
267
  readonly title: string;
263
- readonly parentID: string;
264
268
  readonly depth: number;
265
- readonly created: number;
266
269
  readonly isolated: boolean;
267
- readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
270
+ readonly parentID: string;
271
+ readonly created: number;
272
+ readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
268
273
  readonly waits: readonly ({
269
274
  readonly type: "permission";
270
275
  readonly action: string;
@@ -276,24 +281,25 @@ export declare const treeRpc: {
276
281
  readonly type: "form";
277
282
  readonly title: string;
278
283
  })[];
284
+ readonly stopped?: boolean | undefined;
285
+ readonly stalled?: number | undefined;
279
286
  readonly group?: {
280
287
  readonly name: string;
281
288
  readonly standing: string;
282
289
  } | undefined;
283
- readonly stopped?: boolean | undefined;
284
- readonly idle?: number | undefined;
290
+ readonly cost?: number | undefined;
291
+ readonly context?: number | undefined;
292
+ readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
285
293
  readonly report?: {
286
294
  readonly progressed: boolean;
287
- readonly state: "reported" | "ended" | "owed";
295
+ readonly state: "ended" | "reported" | "owed";
288
296
  readonly status?: string | undefined;
289
297
  } | undefined;
290
- readonly stalled?: number | undefined;
291
- readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
298
+ readonly idle?: number | undefined;
292
299
  readonly tool?: {
293
300
  readonly name: string;
294
301
  readonly at: number;
295
302
  } | undefined;
296
- readonly cost?: number | undefined;
297
303
  }[];
298
304
  readonly stalled?: number | undefined;
299
305
  readonly truncated?: number | undefined;
@@ -332,6 +338,8 @@ export declare const treeRpc: {
332
338
  readonly idle: Schema.optionalKey<Schema.Number>;
333
339
  /** What it has cost so far, in US dollars. */
334
340
  readonly cost: Schema.optionalKey<Schema.Number>;
341
+ /** How full its context is, as a fraction of the model's window; left out when the model or its limit cannot be read. */
342
+ readonly context: Schema.optionalKey<Schema.Number>;
335
343
  /** Its last tool call, and when it was made. */
336
344
  readonly tool: Schema.optionalKey<Schema.Struct<{
337
345
  readonly name: Schema.String;
package/dist/tree/rpc.js CHANGED
@@ -53,6 +53,8 @@ const Row = Schema.Struct({
53
53
  idle: Schema.optionalKey(Schema.Number),
54
54
  /** What it has cost so far, in US dollars. */
55
55
  cost: Schema.optionalKey(Schema.Number),
56
+ /** How full its context is, as a fraction of the model's window; left out when the model or its limit cannot be read. */
57
+ context: Schema.optionalKey(Schema.Number),
56
58
  /** Its last tool call, and when it was made. */
57
59
  tool: Schema.optionalKey(Schema.Struct({ name: Schema.String, at: Schema.Number })),
58
60
  waits: Schema.Array(Wait),
@@ -27,7 +27,7 @@ export declare function lastReply(messages: Awaited<ReturnType<Context["session"
27
27
  export declare function listChildren(ports: CourierPorts, parentID: string): Promise<({
28
28
  group: {
29
29
  name: string;
30
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
30
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
31
31
  };
32
32
  directory: string;
33
33
  isolated: boolean;
@@ -57,7 +57,7 @@ export declare function listChildren(ports: CourierPorts, parentID: string): Pro
57
57
  error: string;
58
58
  group: {
59
59
  name: string;
60
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
60
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
61
61
  };
62
62
  directory: string;
63
63
  isolated: boolean;
@@ -77,7 +77,7 @@ export declare function listChildren(ports: CourierPorts, parentID: string): Pro
77
77
  export declare function described(ports: CourierPorts, entry: RosterEntry): Promise<{
78
78
  group: {
79
79
  name: string;
80
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
80
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
81
81
  };
82
82
  directory: string;
83
83
  isolated: boolean;
@@ -107,7 +107,7 @@ export declare function described(ports: CourierPorts, entry: RosterEntry): Prom
107
107
  error: string;
108
108
  group: {
109
109
  name: string;
110
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
110
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
111
111
  };
112
112
  directory: string;
113
113
  isolated: boolean;
@@ -124,5 +124,5 @@ export declare function described(ports: CourierPorts, entry: RosterEntry): Prom
124
124
  title: string;
125
125
  }>;
126
126
  /** A child's standing in the join group its roster entry names, `unknown` when that cannot be read. */
127
- export declare function standingIn(ports: Pick<CourierPorts, "storage">, entry: RosterEntry, group: string): Promise<"failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown">;
127
+ export declare function standingIn(ports: Pick<CourierPorts, "storage">, entry: RosterEntry, group: string): Promise<"failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released">;
128
128
  export {};
@@ -60,7 +60,7 @@ export declare function tree(ports: CourierPorts, rootID: string): Promise<{
60
60
  depth: number;
61
61
  group: {
62
62
  name: string;
63
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
63
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
64
64
  };
65
65
  directory: string;
66
66
  isolated: boolean;
@@ -104,7 +104,7 @@ export declare function tree(ports: CourierPorts, rootID: string): Promise<{
104
104
  error: string;
105
105
  group: {
106
106
  name: string;
107
- report: "failed" | "interrupted" | "deleted" | "dropped" | "held" | "out" | "released" | "unknown";
107
+ report: "failed" | "interrupted" | "out" | "unknown" | "deleted" | "dropped" | "held" | "released";
108
108
  };
109
109
  directory: string;
110
110
  isolated: boolean;
package/dist/tree/text.js CHANGED
@@ -64,7 +64,8 @@ export function rolePart(depth, limits) {
64
64
  const budget = `At most ${maxChildren} of the sessions you start run at once, and ${maxTotal} in the whole tree.`;
65
65
  if (depth === null)
66
66
  return (`${ROLE_PREFIX} root, no sessions started yet. Before a task that touches several files or areas, load the ` +
67
- `${SKILL_ID} skill and decide whether to split it across sessions (doing it yourself is fine), and say ` +
67
+ `${SKILL_ID} skill, call courier_recall once for how earlier trees in this project were split and how they went, ` +
68
+ `and decide whether to split it across sessions (doing it yourself is fine), and say ` +
68
69
  `which in one line of your reply: ${SPLIT_LINE}`);
69
70
  if (depth === 0)
70
71
  return (`${ROLE_PREFIX} root orchestrator. You started sessions with courier_spawn, and they may start their own, ` +
@@ -1,4 +1,7 @@
1
1
  import type { Plugin } from "@opencode/plugin/tui";
2
+ import { RGBA, type EasingFunctions } from "@opentui/core";
3
+ import { type Accessor } from "solid-js";
4
+ import { type MascotState, type Token } from "./mascot.js";
2
5
  import { type Forgotten, type Row, type Stopped, type Summary, type Wait } from "./rpc.js";
3
6
  type Context = Plugin.Context;
4
7
  /** The events after which a summary may read differently: a turn started or ended, a request or form, a session made or removed. */
@@ -11,6 +14,62 @@ export declare const PANEL_SETTLE_MS = 1000;
11
14
  export declare const PANEL = "courier-tree";
12
15
  /** How often the panel redraws its running times and how long ago each last tool call was. */
13
16
  export declare const TICK_MS = 1000;
17
+ /** The panel's animation frame: the spinner's step, and how often a flash or the bar's easing moves. */
18
+ export declare const FRAME_MS = 90;
19
+ /** How long a row's background takes to fade back from its flash after its report arrives: the report's hold. */
20
+ export declare const FLASH_MS = 1500;
21
+ /** How long the header's bar takes to grow, or shrink, to its new length. */
22
+ export declare const BAR_MS = 450;
23
+ /** Whether the panel animates: unless the TUI part's options say `"animations": false`, as the plugin API has no TUI setting for it. */
24
+ export declare function animationsOn(options: Readonly<Record<string, unknown>> | undefined): boolean;
25
+ /** A value eased over the panel's frames by OpenTUI's `Timeline`; reading it in a computation follows the frames. */
26
+ export type Tween = {
27
+ readonly value: () => number;
28
+ readonly done: () => boolean;
29
+ };
30
+ /**
31
+ * The panel's one timer: a frame every `FRAME_MS` while a tween moves or an `animates` predicate holds, every `every` while
32
+ * only slower ones do, none otherwise, with animations off or once the panel is gone. `frame` counts in `FRAME_MS`.
33
+ */
34
+ export type Motion = {
35
+ readonly enabled: boolean;
36
+ readonly frame: Accessor<number>;
37
+ readonly animates: (when: Accessor<boolean>, every?: number) => void;
38
+ readonly tween: (from: number, to: number, duration: number, ease: EasingFunctions) => Tween;
39
+ };
40
+ /** The panel's `Motion`, owned by the computation it is made in. */
41
+ export declare function createMotion(enabled: boolean): Motion;
42
+ /**
43
+ * Each row's flash, by session, from a read of the rows to the next that finds its report turned to `reported`; not on
44
+ * the first read. `strength` is a row's, 1 when its report arrives down to 0; `reported` holds for `FLASH_MS` after one.
45
+ */
46
+ export declare function createFlashes(motion: Motion, rows: Accessor<ReadonlyArray<Row>>): {
47
+ strength: (sessionID: string) => number;
48
+ reported: () => boolean;
49
+ };
50
+ /** The header bar's filled cells, eased over `BAR_MS` to its new length once it is shown, at once when it first is. */
51
+ export declare function createBarFill(motion: Motion, counts: Accessor<{
52
+ reported: number;
53
+ started: number;
54
+ }>): () => number;
55
+ /** 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. */
56
+ export declare function createSpawns(motion: Motion, rows: Accessor<ReadonlyArray<Row> | undefined>): () => boolean;
57
+ /** Digby's state for the tree's rows, by `summaryLine`'s priority, unless a report or a spawn just arrived. */
58
+ export declare function mascotState(rows: ReadonlyArray<Row>, { reported, spawned }?: {
59
+ reported?: boolean | undefined;
60
+ spawned?: boolean | undefined;
61
+ }): MascotState;
62
+ /** What the tree's sessions do, as the header's second line says it: "1 waiting on you · 2 running"; empty with none live. */
63
+ export declare function countsLine(rows: ReadonlyArray<Row>): string;
64
+ /** The panel's size below which it shows no header glyph, so the header keeps to one line and its bar is not cut. */
65
+ export declare const GLYPH_ROWS = 12;
66
+ export declare const GLYPH_COLUMNS = 54;
67
+ /** The empty panel's height from which it shows the 16×16. */
68
+ export declare const ART_ROWS = 14;
69
+ /** The rows left free below the tree's last row from which the 16×16 shows there. */
70
+ export declare const BELOW_ROWS = 10;
71
+ /** `tint` over `rest` by `amount`, from 0 (all `rest`) to 1 (all `tint`). */
72
+ export declare function blend(tint: RGBA, rest: RGBA, amount: number): RGBA;
14
73
  /**
15
74
  * 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.
16
75
  * Within `maxWidth` it keeps whole parts, most important first, and counts the groups it drops: "· +2 groups".
@@ -23,17 +82,47 @@ export declare function parts(rows: ReadonlyArray<Row>, parentID: string): {
23
82
  reported: number;
24
83
  started: number;
25
84
  };
26
- /** A bar of parts reported, `width` cells wide: "[████░░░░░░] 2/5 reported". */
27
- export declare function bar({ reported, started }: {
85
+ /** How many of a bar's `width` cells are filled for the parts reported. */
86
+ export declare function filledCells({ reported, started }: {
28
87
  reported: number;
29
88
  started: number;
30
- }, width?: number): string;
89
+ }, width?: number): number;
90
+ /** A bar of parts reported, `width` cells wide: "[████░░░░░░] 2/5 reported"; `filled` cells of it while it eases to its length. */
91
+ export declare function bar(counts: {
92
+ reported: number;
93
+ started: number;
94
+ }, width?: number, filled?: number): string;
31
95
  /** What a paused session waits on, such as "asks permission: shell echo hi" or "shows a form: Web Search". */
32
96
  export declare function waitLine(wait: Wait): string;
33
97
  /** What a row's session is doing, its report and its group standing: "idle · reported done · group pair held". */
34
98
  export declare function statusLine(row: Row): string;
35
- /** A leaf's activity: how long it has run, its last tool call and how long ago, and its cost. */
36
- export declare function activityLine(row: Row, now: number): string;
99
+ /** The parts of `statusLine`, each with the row part the panel colours it as. */
100
+ export declare function statusParts(row: Row): {
101
+ text: string;
102
+ kind: "state" | "report" | "plain";
103
+ }[];
104
+ type Feedback<Color> = {
105
+ text: {
106
+ feedback: Record<"error" | "warning" | "success" | "info", {
107
+ base: Color;
108
+ }>;
109
+ };
110
+ };
111
+ /** The running mark's frames, one every `FRAME_MS`. */
112
+ export declare const SPINNER: string[];
113
+ /**
114
+ * 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
115
+ * chooses them. A running row spins through `SPINNER` at the panel's `frame`, and shows `•` with none (animations off).
116
+ */
117
+ export declare function rowMark<Color>(row: Row, theme: Feedback<Color>, frame?: number): {
118
+ mark: string;
119
+ color: Color | undefined;
120
+ };
121
+ /** Past this fraction of its window, a session's context use is shown in the warning colour. */
122
+ export declare const CONTEXT_WARN = 0.8;
123
+ /** A leaf's activity: how long it has run, its last tool call and how long ago, its context use and its cost. */
124
+ export declare function activityParts(row: Row, now: number): string[];
125
+ export declare const activityLine: (row: Row, now: number) => string;
37
126
  /** 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". */
38
127
  export declare function stalledLine(since: number, now: number): string;
39
128
  /** 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. */
@@ -49,13 +138,31 @@ export declare function addTreeStatus(ctx: Context, onWaiting?: (sessionID: stri
49
138
  export declare function stoppedLine({ stopped, cancelled }: Stopped): string;
50
139
  /** The toast after a forget, as courier_cleanup reports it: forgotten, its worktree removed or already gone, or kept and why. */
51
140
  export declare function forgottenLine(title: string, result: Forgotten): string;
52
- /** The usage the `/courier` command shows for an input it does not know. */
141
+ /** The one place Digby's pixel tokens take a theme colour. */
142
+ export declare const digbyColours: (theme: Context["theme"]) => Record<Token, RGBA>;
143
+ /**
144
+ * A row's background at rest: the selection's while the panel has the keyboard, else one step quieter under the pointer,
145
+ * else none, the panel's.
146
+ */
147
+ export declare function restBackground(theme: Context["theme"], { selected, hovered }: {
148
+ selected: boolean;
149
+ hovered: boolean;
150
+ }): RGBA | undefined;
151
+ /** The usage the `/courier` command shows for an input it does not know, before the further verbs' lines. */
53
152
  export declare const USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
153
+ /** A further verb of the `/courier` command, which another module's TUI part supplies: what it runs, and its line of the usage. */
154
+ export type Verb = {
155
+ readonly run: () => void;
156
+ readonly usage: string;
157
+ };
158
+ /** The usage with the further verbs' lines. */
159
+ export declare const usageWith: (verbs: Readonly<Record<string, Verb>>) => string;
54
160
  /**
55
161
  * Adds the tree panel to the `session.panel` slot, opened by `/courier tree` or the palette, which follows the person
56
- * between sessions until closed, and `/courier stop <session>`, which stops a session under the open one once confirmed.
162
+ * between sessions until closed, and `/courier stop <session>`, which stops a session under the open one once confirmed;
163
+ * `verbs` are the command's further verbs, by name.
57
164
  */
58
- export declare function addTreePanel(ctx: Context): () => void;
165
+ export declare function addTreePanel(ctx: Context, verbs?: Readonly<Record<string, Verb>>): () => void;
59
166
  /**
60
167
  * Draws the person's attention when reports arrive: a join group's reports, released to the session that started them,
61
168
  * and a child's report to its parent, with the `subagent_done` sound. Read from the courier's own delivery metadata.