opencode-courier 0.5.0 → 0.7.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 (43) hide show
  1. package/README.md +39 -30
  2. package/dist/index.js +9 -5
  3. package/dist/person/form.d.ts +4 -1
  4. package/dist/person/form.js +5 -0
  5. package/dist/person/index.d.ts +2 -1
  6. package/dist/person/index.js +1 -0
  7. package/dist/person/permission.d.ts +14 -0
  8. package/dist/person/permission.js +15 -0
  9. package/dist/person/ports.d.ts +8 -2
  10. package/dist/person/rpc-handlers.d.ts +37 -30
  11. package/dist/person/rpc-handlers.js +26 -16
  12. package/dist/person/rpc.d.ts +43 -24
  13. package/dist/person/rpc.js +4 -3
  14. package/dist/person/tui.d.ts +23 -12
  15. package/dist/tree/domain.d.ts +23 -0
  16. package/dist/tree/domain.js +26 -0
  17. package/dist/tree/events.d.ts +2 -2
  18. package/dist/tree/events.js +31 -4
  19. package/dist/tree/index.d.ts +2 -1
  20. package/dist/tree/index.js +2 -1
  21. package/dist/tree/panel.d.ts +7 -2
  22. package/dist/tree/panel.js +29 -17
  23. package/dist/tree/ports.d.ts +36 -0
  24. package/dist/tree/roster.d.ts +5 -0
  25. package/dist/tree/roster.js +5 -3
  26. package/dist/tree/rpc-handlers.d.ts +55 -6
  27. package/dist/tree/rpc-handlers.js +30 -1
  28. package/dist/tree/rpc.d.ts +111 -22
  29. package/dist/tree/rpc.js +30 -9
  30. package/dist/tree/stop.js +2 -0
  31. package/dist/tree/store.d.ts +4 -1
  32. package/dist/tree/store.js +51 -0
  33. package/dist/tree/subtree.d.ts +40 -9
  34. package/dist/tree/subtree.js +40 -19
  35. package/dist/tree/summary.d.ts +2 -2
  36. package/dist/tree/summary.js +17 -16
  37. package/dist/tree/text.js +4 -1
  38. package/dist/tree/tools.js +2 -1
  39. package/dist/tree/tui.d.ts +17 -6
  40. package/dist/tree/wake.d.ts +68 -0
  41. package/dist/tree/wake.js +119 -0
  42. package/dist/tui.js +617 -197
  43. package/package.json +1 -1
@@ -21,7 +21,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
21
21
  readonly members: number;
22
22
  }[];
23
23
  readonly waiting: number;
24
- readonly truncated?: number | undefined;
24
+ readonly stalled?: number | undefined;
25
25
  }, {
26
26
  readonly running: number;
27
27
  readonly idle: number;
@@ -31,17 +31,17 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
31
31
  readonly members: number;
32
32
  }[];
33
33
  readonly waiting: number;
34
- readonly truncated?: number | undefined;
34
+ readonly stalled?: number | undefined;
35
35
  }> & import("effect/Schema").Struct<{
36
36
  readonly running: import("effect/Schema").Int;
37
37
  readonly waiting: import("effect/Schema").Int;
38
38
  readonly idle: import("effect/Schema").Int;
39
+ readonly stalled: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
39
40
  readonly groups: import("effect/Schema").$Array<import("effect/Schema").Struct<{
40
41
  readonly name: import("effect/Schema").String;
41
42
  readonly reported: import("effect/Schema").Int;
42
43
  readonly members: import("effect/Schema").Int;
43
44
  }>>;
44
- readonly truncated: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
45
45
  }>;
46
46
  };
47
47
  readonly tree: {
@@ -60,7 +60,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
60
60
  readonly depth: number;
61
61
  readonly created: number;
62
62
  readonly isolated: boolean;
63
- readonly state: "running" | "idle" | "gone" | "waiting";
63
+ readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
64
64
  readonly waits: readonly ({
65
65
  readonly type: "permission";
66
66
  readonly action: string;
@@ -68,17 +68,22 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
68
68
  } | {
69
69
  readonly type: "question";
70
70
  readonly question: string;
71
+ } | {
72
+ readonly type: "form";
73
+ readonly title: string;
71
74
  })[];
72
75
  readonly group?: {
73
76
  readonly name: string;
74
77
  readonly standing: string;
75
78
  } | undefined;
79
+ readonly stopped?: boolean | undefined;
76
80
  readonly idle?: number | undefined;
77
81
  readonly report?: {
78
82
  readonly progressed: boolean;
79
83
  readonly state: "reported" | "ended" | "owed";
80
84
  readonly status?: string | undefined;
81
85
  } | undefined;
86
+ readonly stalled?: number | undefined;
82
87
  readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
83
88
  readonly tool?: {
84
89
  readonly name: string;
@@ -86,6 +91,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
86
91
  } | undefined;
87
92
  readonly cost?: number | undefined;
88
93
  }[];
94
+ readonly stalled?: number | undefined;
89
95
  readonly truncated?: number | undefined;
90
96
  }, {
91
97
  readonly rows: readonly {
@@ -95,7 +101,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
95
101
  readonly depth: number;
96
102
  readonly created: number;
97
103
  readonly isolated: boolean;
98
- readonly state: "running" | "idle" | "gone" | "waiting";
104
+ readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
99
105
  readonly waits: readonly ({
100
106
  readonly type: "permission";
101
107
  readonly action: string;
@@ -103,17 +109,22 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
103
109
  } | {
104
110
  readonly type: "question";
105
111
  readonly question: string;
112
+ } | {
113
+ readonly type: "form";
114
+ readonly title: string;
106
115
  })[];
107
116
  readonly group?: {
108
117
  readonly name: string;
109
118
  readonly standing: string;
110
119
  } | undefined;
120
+ readonly stopped?: boolean | undefined;
111
121
  readonly idle?: number | undefined;
112
122
  readonly report?: {
113
123
  readonly progressed: boolean;
114
124
  readonly state: "reported" | "ended" | "owed";
115
125
  readonly status?: string | undefined;
116
126
  } | undefined;
127
+ readonly stalled?: number | undefined;
117
128
  readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
118
129
  readonly tool?: {
119
130
  readonly name: string;
@@ -121,6 +132,7 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
121
132
  } | undefined;
122
133
  readonly cost?: number | undefined;
123
134
  }[];
135
+ readonly stalled?: number | undefined;
124
136
  readonly truncated?: number | undefined;
125
137
  }> & import("effect/Schema").Struct<{
126
138
  readonly rows: import("effect/Schema").$Array<import("effect/Schema").Struct<{
@@ -128,8 +140,10 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
128
140
  readonly parentID: import("effect/Schema").String;
129
141
  readonly title: import("effect/Schema").String;
130
142
  readonly depth: import("effect/Schema").Int;
131
- readonly state: import("effect/Schema").Literals<readonly ["running", "waiting", "idle", "gone"]>;
143
+ readonly state: import("effect/Schema").Literals<readonly ["running", "waiting", "idle", "stalled", "gone"]>;
144
+ readonly stalled: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
132
145
  readonly outcome: import("effect/Schema").optionalKey<import("effect/Schema").Literals<readonly ["succeeded", "failed", "interrupted"]>>;
146
+ readonly stopped: import("effect/Schema").optionalKey<import("effect/Schema").Boolean>;
133
147
  readonly report: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
134
148
  readonly state: import("effect/Schema").Literals<readonly ["owed", "reported", "ended"]>;
135
149
  readonly status: import("effect/Schema").optionalKey<import("effect/Schema").String>;
@@ -154,9 +168,13 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
154
168
  }>, import("effect/Schema").Struct<{
155
169
  readonly type: import("effect/Schema").Literal<"question">;
156
170
  readonly question: import("effect/Schema").String;
171
+ }>, import("effect/Schema").Struct<{
172
+ readonly type: import("effect/Schema").Literal<"form">;
173
+ readonly title: import("effect/Schema").String;
157
174
  }>]>>;
158
175
  }>>;
159
176
  readonly truncated: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
177
+ readonly stalled: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
160
178
  }>;
161
179
  };
162
180
  readonly stop: {
@@ -196,6 +214,37 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
196
214
  readonly refused: import("effect/StandardSchema").StandardSchemaV1<{}, {}> & import("effect/Schema").Struct<{}>;
197
215
  };
198
216
  };
217
+ readonly cleanup: {
218
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
219
+ readonly sessionID: string;
220
+ readonly by: string;
221
+ }, {
222
+ readonly sessionID: string;
223
+ readonly by: string;
224
+ }> & import("effect/Schema").Struct<{
225
+ readonly sessionID: import("effect/Schema").String;
226
+ readonly by: import("effect/Schema").String;
227
+ }>;
228
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
229
+ readonly outcome: "removed" | "gone" | "forgotten";
230
+ } | {
231
+ readonly outcome: "kept";
232
+ readonly reason: string;
233
+ }, {
234
+ readonly outcome: "removed" | "gone" | "forgotten";
235
+ } | {
236
+ readonly outcome: "kept";
237
+ readonly reason: string;
238
+ }> & import("effect/Schema").Union<readonly [import("effect/Schema").Struct<{
239
+ readonly outcome: import("effect/Schema").Literals<readonly ["forgotten", "removed", "gone"]>;
240
+ }>, import("effect/Schema").Struct<{
241
+ readonly outcome: import("effect/Schema").Literal<"kept">;
242
+ readonly reason: import("effect/Schema").String;
243
+ }>]>;
244
+ readonly errors: {
245
+ readonly refused: import("effect/StandardSchema").StandardSchemaV1<{}, {}> & import("effect/Schema").Struct<{}>;
246
+ };
247
+ };
199
248
  };
200
249
  readonly events: {};
201
250
  }>>;
@@ -1,9 +1,14 @@
1
+ import { describeFailure } from "../kernel/protocol.js";
2
+ import { isNotFound } from "../platform/json.js";
3
+ import { cleanup } from "./cleanup.js";
1
4
  import { panel } from "./panel.js";
2
5
  import { treeRpc } from "./rpc.js";
3
6
  import { stop } from "./stop.js";
7
+ import { childStore } from "./store.js";
4
8
  import { summary } from "./summary.js";
5
9
  /** Serves the tree's RPC at the plugin's location, through the same use cases as the tools; disposed with the plugin. */
6
10
  export function registerTreeRpc(rpc, ports) {
11
+ const refused = (cause) => (cause instanceof Error ? cause.message : String(cause));
7
12
  return rpc.register(treeRpc, {
8
13
  summary: ({ sessionID }) => summary(ports.courier, sessionID),
9
14
  tree: ({ sessionID }) => panel(ports.courier, sessionID),
@@ -13,8 +18,32 @@ export function registerTreeRpc(rpc, ports) {
13
18
  return { stopped: stopped.map(({ sessionID, title, outcome }) => ({ sessionID, title, outcome })), cancelled: cancelled.length };
14
19
  }
15
20
  catch (cause) {
16
- return error("refused", cause instanceof Error ? cause.message : String(cause), {});
21
+ return error("refused", refused(cause), {});
22
+ }
23
+ },
24
+ cleanup: async ({ sessionID, by }, { error }) => {
25
+ try {
26
+ return await forget(ports, by, sessionID);
27
+ }
28
+ catch (cause) {
29
+ return error("refused", refused(cause), {});
17
30
  }
18
31
  },
19
32
  });
20
33
  }
34
+ /**
35
+ * Forgets a session OpenCode no longer knows as courier_cleanup called from its own parent, once `by` is found above it,
36
+ * as courier_stop checks. A session OpenCode still has, or cannot say it lost, is refused: forgetting it loses its report.
37
+ */
38
+ async function forget(ports, by, sessionID) {
39
+ const chain = await childStore(ports.courier.storage).lineage(sessionID);
40
+ if (!chain.length)
41
+ throw new Error(`${sessionID} was not started with courier_spawn, so there is nothing to forget.`);
42
+ if (!chain.some((entry) => entry.parentID === by))
43
+ throw new Error(`${by} is not above ${sessionID}: only a session above it, in the line of sessions that started it with courier_spawn, can forget it.`);
44
+ const known = await ports.courier.session.get({ sessionID }).then(() => `${sessionID} is still known to OpenCode: only a gone session is forgotten here.`, (error) => (isNotFound(error) ? undefined : describeFailure("session.get", error).message));
45
+ if (known)
46
+ throw new Error(known);
47
+ const result = await cleanup(ports.cleanup, chain[0].parentID, { sessionID });
48
+ return result.outcome === "kept" ? { outcome: "kept", reason: result.reason } : { outcome: result.outcome };
49
+ }
@@ -1,22 +1,22 @@
1
1
  import { Schema } from "effect";
2
2
  /** The sessions started under a session, as the prompt footer's status entry shows them. */
3
3
  declare const Summary: Schema.Struct<{
4
- /** Running, and waiting on no request. */
4
+ /** Running, and waiting on no request or form. */
5
5
  readonly running: Schema.Int;
6
- /** Waiting on a permission request or a question, which go to the person. */
6
+ /** Waiting on a permission request, a question or a form it shows, which go to the person. */
7
7
  readonly waiting: Schema.Int;
8
8
  /** Not running: done, or waiting for a message. */
9
9
  readonly idle: Schema.Int;
10
+ /** Left a courier message waiting while idle, and did not take it once woken again; the session itself counts too. Absent at 0. */
11
+ readonly stalled: Schema.optionalKey<Schema.Int>;
10
12
  readonly groups: Schema.$Array<Schema.Struct<{
11
13
  readonly name: Schema.String;
12
14
  readonly reported: Schema.Int;
13
15
  readonly members: Schema.Int;
14
16
  }>>;
15
- /** Set when the subtree had more sessions than `maxTotal`, which were not counted. */
16
- readonly truncated: Schema.optionalKey<Schema.Int>;
17
17
  }>;
18
18
  export type Summary = typeof Summary.Type;
19
- /** A request a session waits on until the person answers it: a permission request, or its first question. */
19
+ /** A request a session waits on until the person answers it: a permission request, its first question, or a form it shows. */
20
20
  declare const Wait: Schema.Union<readonly [Schema.Struct<{
21
21
  readonly type: Schema.Literal<"permission">;
22
22
  readonly action: Schema.String;
@@ -24,6 +24,9 @@ declare const Wait: Schema.Union<readonly [Schema.Struct<{
24
24
  }>, Schema.Struct<{
25
25
  readonly type: Schema.Literal<"question">;
26
26
  readonly question: Schema.String;
27
+ }>, Schema.Struct<{
28
+ readonly type: Schema.Literal<"form">;
29
+ readonly title: Schema.String;
27
30
  }>]>;
28
31
  /** One session of the tree panel: what courier_tree shows of it, and what it is doing, in epoch milliseconds. */
29
32
  declare const Row: Schema.Struct<{
@@ -32,10 +35,17 @@ declare const Row: Schema.Struct<{
32
35
  readonly title: Schema.String;
33
36
  /** A child of the panel's session is at depth 1. */
34
37
  readonly depth: Schema.Int;
35
- /** `waiting` on a request to the person; `gone` when OpenCode no longer knows it. */
36
- readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "gone"]>;
38
+ /**
39
+ * `waiting` on a request to the person or a form it shows them; `stalled` when a courier message waits in its inbox and
40
+ * waking it again did not start a turn; `gone` when OpenCode no longer knows it.
41
+ */
42
+ readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "stalled", "gone"]>;
43
+ /** Since when its oldest message that did not wake it has waited, when it is stalled. */
44
+ readonly stalled: Schema.optionalKey<Schema.Number>;
37
45
  /** How its last turn ended. */
38
46
  readonly outcome: Schema.optionalKey<Schema.Literals<readonly ["succeeded", "failed", "interrupted"]>>;
47
+ /** Set when courier_stop stopped it since its last prompt. */
48
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
39
49
  /** Its report since its last prompt: still owed, arrived (held with its group or delivered), or its turn ended without one. */
40
50
  readonly report: Schema.optionalKey<Schema.Struct<{
41
51
  readonly state: Schema.Literals<readonly ["owed", "reported", "ended"]>;
@@ -65,9 +75,12 @@ declare const Row: Schema.Struct<{
65
75
  }>, Schema.Struct<{
66
76
  readonly type: Schema.Literal<"question">;
67
77
  readonly question: Schema.String;
78
+ }>, Schema.Struct<{
79
+ readonly type: Schema.Literal<"form">;
80
+ readonly title: Schema.String;
68
81
  }>]>>;
69
82
  }>;
70
- /** The sessions under a session, as courier_tree reads them, in the order a tree is drawn. */
83
+ /** The sessions under a session, as courier_tree lists them, in the order a tree is drawn. */
71
84
  declare const Tree: Schema.Struct<{
72
85
  readonly rows: Schema.$Array<Schema.Struct<{
73
86
  readonly sessionID: Schema.String;
@@ -75,10 +88,17 @@ declare const Tree: Schema.Struct<{
75
88
  readonly title: Schema.String;
76
89
  /** A child of the panel's session is at depth 1. */
77
90
  readonly depth: Schema.Int;
78
- /** `waiting` on a request to the person; `gone` when OpenCode no longer knows it. */
79
- readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "gone"]>;
91
+ /**
92
+ * `waiting` on a request to the person or a form it shows them; `stalled` when a courier message waits in its inbox and
93
+ * waking it again did not start a turn; `gone` when OpenCode no longer knows it.
94
+ */
95
+ readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "stalled", "gone"]>;
96
+ /** Since when its oldest message that did not wake it has waited, when it is stalled. */
97
+ readonly stalled: Schema.optionalKey<Schema.Number>;
80
98
  /** How its last turn ended. */
81
99
  readonly outcome: Schema.optionalKey<Schema.Literals<readonly ["succeeded", "failed", "interrupted"]>>;
100
+ /** Set when courier_stop stopped it since its last prompt. */
101
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
82
102
  /** Its report since its last prompt: still owed, arrived (held with its group or delivered), or its turn ended without one. */
83
103
  readonly report: Schema.optionalKey<Schema.Struct<{
84
104
  readonly state: Schema.Literals<readonly ["owed", "reported", "ended"]>;
@@ -108,10 +128,15 @@ declare const Tree: Schema.Struct<{
108
128
  }>, Schema.Struct<{
109
129
  readonly type: Schema.Literal<"question">;
110
130
  readonly question: Schema.String;
131
+ }>, Schema.Struct<{
132
+ readonly type: Schema.Literal<"form">;
133
+ readonly title: Schema.String;
111
134
  }>]>>;
112
135
  }>>;
113
- /** Set when the subtree had more sessions than `maxTotal`, which were left out. */
136
+ /** How many older sessions the listing left out, when it had more than `maxTotal`: the live ones come first, then the newest. */
114
137
  readonly truncated: Schema.optionalKey<Schema.Int>;
138
+ /** Since when a courier message has waited in the panel's own session, when that session is stalled. */
139
+ readonly stalled: Schema.optionalKey<Schema.Number>;
115
140
  }>;
116
141
  export type Row = typeof Row.Type;
117
142
  export type Tree = typeof Tree.Type;
@@ -127,6 +152,14 @@ declare const Stopped: Schema.Struct<{
127
152
  readonly cancelled: Schema.Int;
128
153
  }>;
129
154
  export type Stopped = typeof Stopped.Type;
155
+ /** What courier_cleanup did: forgot it, removed its worktree, found that already gone, or kept the worktree, saying why. */
156
+ declare const Forgotten: Schema.Union<readonly [Schema.Struct<{
157
+ readonly outcome: Schema.Literals<readonly ["forgotten", "removed", "gone"]>;
158
+ }>, Schema.Struct<{
159
+ readonly outcome: Schema.Literal<"kept">;
160
+ readonly reason: Schema.String;
161
+ }>]>;
162
+ export type Forgotten = typeof Forgotten.Type;
130
163
  export declare const treeRpc: {
131
164
  readonly id: "courier";
132
165
  readonly methods: {
@@ -147,7 +180,7 @@ export declare const treeRpc: {
147
180
  readonly members: number;
148
181
  }[];
149
182
  readonly waiting: number;
150
- readonly truncated?: number | undefined;
183
+ readonly stalled?: number | undefined;
151
184
  }, {
152
185
  readonly running: number;
153
186
  readonly idle: number;
@@ -157,21 +190,21 @@ export declare const treeRpc: {
157
190
  readonly members: number;
158
191
  }[];
159
192
  readonly waiting: number;
160
- readonly truncated?: number | undefined;
193
+ readonly stalled?: number | undefined;
161
194
  }> & Schema.Struct<{
162
- /** Running, and waiting on no request. */
195
+ /** Running, and waiting on no request or form. */
163
196
  readonly running: Schema.Int;
164
- /** Waiting on a permission request or a question, which go to the person. */
197
+ /** Waiting on a permission request, a question or a form it shows, which go to the person. */
165
198
  readonly waiting: Schema.Int;
166
199
  /** Not running: done, or waiting for a message. */
167
200
  readonly idle: Schema.Int;
201
+ /** Left a courier message waiting while idle, and did not take it once woken again; the session itself counts too. Absent at 0. */
202
+ readonly stalled: Schema.optionalKey<Schema.Int>;
168
203
  readonly groups: Schema.$Array<Schema.Struct<{
169
204
  readonly name: Schema.String;
170
205
  readonly reported: Schema.Int;
171
206
  readonly members: Schema.Int;
172
207
  }>>;
173
- /** Set when the subtree had more sessions than `maxTotal`, which were not counted. */
174
- readonly truncated: Schema.optionalKey<Schema.Int>;
175
208
  }>;
176
209
  };
177
210
  readonly tree: {
@@ -190,7 +223,7 @@ export declare const treeRpc: {
190
223
  readonly depth: number;
191
224
  readonly created: number;
192
225
  readonly isolated: boolean;
193
- readonly state: "running" | "idle" | "gone" | "waiting";
226
+ readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
194
227
  readonly waits: readonly ({
195
228
  readonly type: "permission";
196
229
  readonly action: string;
@@ -198,17 +231,22 @@ export declare const treeRpc: {
198
231
  } | {
199
232
  readonly type: "question";
200
233
  readonly question: string;
234
+ } | {
235
+ readonly type: "form";
236
+ readonly title: string;
201
237
  })[];
202
238
  readonly group?: {
203
239
  readonly name: string;
204
240
  readonly standing: string;
205
241
  } | undefined;
242
+ readonly stopped?: boolean | undefined;
206
243
  readonly idle?: number | undefined;
207
244
  readonly report?: {
208
245
  readonly progressed: boolean;
209
246
  readonly state: "reported" | "ended" | "owed";
210
247
  readonly status?: string | undefined;
211
248
  } | undefined;
249
+ readonly stalled?: number | undefined;
212
250
  readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
213
251
  readonly tool?: {
214
252
  readonly name: string;
@@ -216,6 +254,7 @@ export declare const treeRpc: {
216
254
  } | undefined;
217
255
  readonly cost?: number | undefined;
218
256
  }[];
257
+ readonly stalled?: number | undefined;
219
258
  readonly truncated?: number | undefined;
220
259
  }, {
221
260
  readonly rows: readonly {
@@ -225,7 +264,7 @@ export declare const treeRpc: {
225
264
  readonly depth: number;
226
265
  readonly created: number;
227
266
  readonly isolated: boolean;
228
- readonly state: "running" | "idle" | "gone" | "waiting";
267
+ readonly state: "running" | "idle" | "gone" | "stalled" | "waiting";
229
268
  readonly waits: readonly ({
230
269
  readonly type: "permission";
231
270
  readonly action: string;
@@ -233,17 +272,22 @@ export declare const treeRpc: {
233
272
  } | {
234
273
  readonly type: "question";
235
274
  readonly question: string;
275
+ } | {
276
+ readonly type: "form";
277
+ readonly title: string;
236
278
  })[];
237
279
  readonly group?: {
238
280
  readonly name: string;
239
281
  readonly standing: string;
240
282
  } | undefined;
283
+ readonly stopped?: boolean | undefined;
241
284
  readonly idle?: number | undefined;
242
285
  readonly report?: {
243
286
  readonly progressed: boolean;
244
287
  readonly state: "reported" | "ended" | "owed";
245
288
  readonly status?: string | undefined;
246
289
  } | undefined;
290
+ readonly stalled?: number | undefined;
247
291
  readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
248
292
  readonly tool?: {
249
293
  readonly name: string;
@@ -251,6 +295,7 @@ export declare const treeRpc: {
251
295
  } | undefined;
252
296
  readonly cost?: number | undefined;
253
297
  }[];
298
+ readonly stalled?: number | undefined;
254
299
  readonly truncated?: number | undefined;
255
300
  }> & Schema.Struct<{
256
301
  readonly rows: Schema.$Array<Schema.Struct<{
@@ -259,10 +304,17 @@ export declare const treeRpc: {
259
304
  readonly title: Schema.String;
260
305
  /** A child of the panel's session is at depth 1. */
261
306
  readonly depth: Schema.Int;
262
- /** `waiting` on a request to the person; `gone` when OpenCode no longer knows it. */
263
- readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "gone"]>;
307
+ /**
308
+ * `waiting` on a request to the person or a form it shows them; `stalled` when a courier message waits in its inbox and
309
+ * waking it again did not start a turn; `gone` when OpenCode no longer knows it.
310
+ */
311
+ readonly state: Schema.Literals<readonly ["running", "waiting", "idle", "stalled", "gone"]>;
312
+ /** Since when its oldest message that did not wake it has waited, when it is stalled. */
313
+ readonly stalled: Schema.optionalKey<Schema.Number>;
264
314
  /** How its last turn ended. */
265
315
  readonly outcome: Schema.optionalKey<Schema.Literals<readonly ["succeeded", "failed", "interrupted"]>>;
316
+ /** Set when courier_stop stopped it since its last prompt. */
317
+ readonly stopped: Schema.optionalKey<Schema.Boolean>;
266
318
  /** Its report since its last prompt: still owed, arrived (held with its group or delivered), or its turn ended without one. */
267
319
  readonly report: Schema.optionalKey<Schema.Struct<{
268
320
  readonly state: Schema.Literals<readonly ["owed", "reported", "ended"]>;
@@ -292,10 +344,15 @@ export declare const treeRpc: {
292
344
  }>, Schema.Struct<{
293
345
  readonly type: Schema.Literal<"question">;
294
346
  readonly question: Schema.String;
347
+ }>, Schema.Struct<{
348
+ readonly type: Schema.Literal<"form">;
349
+ readonly title: Schema.String;
295
350
  }>]>>;
296
351
  }>>;
297
- /** Set when the subtree had more sessions than `maxTotal`, which were left out. */
352
+ /** How many older sessions the listing left out, when it had more than `maxTotal`: the live ones come first, then the newest. */
298
353
  readonly truncated: Schema.optionalKey<Schema.Int>;
354
+ /** Since when a courier message has waited in the panel's own session, when that session is stalled. */
355
+ readonly stalled: Schema.optionalKey<Schema.Number>;
299
356
  }>;
300
357
  };
301
358
  /** Stops `sessionID` and everything under it, as courier_stop called from `by`, which must be above it. */
@@ -337,6 +394,38 @@ export declare const treeRpc: {
337
394
  readonly refused: import("effect/StandardSchema").StandardSchemaV1<{}, {}> & Schema.Struct<{}>;
338
395
  };
339
396
  };
397
+ /** Forgets `sessionID` as courier_cleanup called from its parent, without `force`; `by` must be above it. */
398
+ readonly cleanup: {
399
+ readonly input: import("effect/StandardSchema").StandardSchemaV1<{
400
+ readonly sessionID: string;
401
+ readonly by: string;
402
+ }, {
403
+ readonly sessionID: string;
404
+ readonly by: string;
405
+ }> & Schema.Struct<{
406
+ readonly sessionID: Schema.String;
407
+ readonly by: Schema.String;
408
+ }>;
409
+ readonly output: import("effect/StandardSchema").StandardSchemaV1<{
410
+ readonly outcome: "removed" | "gone" | "forgotten";
411
+ } | {
412
+ readonly outcome: "kept";
413
+ readonly reason: string;
414
+ }, {
415
+ readonly outcome: "removed" | "gone" | "forgotten";
416
+ } | {
417
+ readonly outcome: "kept";
418
+ readonly reason: string;
419
+ }> & Schema.Union<readonly [Schema.Struct<{
420
+ readonly outcome: Schema.Literals<readonly ["forgotten", "removed", "gone"]>;
421
+ }>, Schema.Struct<{
422
+ readonly outcome: Schema.Literal<"kept">;
423
+ readonly reason: Schema.String;
424
+ }>]>;
425
+ readonly errors: {
426
+ readonly refused: import("effect/StandardSchema").StandardSchemaV1<{}, {}> & Schema.Struct<{}>;
427
+ };
428
+ };
340
429
  };
341
430
  readonly events: {};
342
431
  };
package/dist/tree/rpc.js CHANGED
@@ -9,20 +9,21 @@ const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
9
9
  const Group = Schema.Struct({ name: Schema.String, reported: Count, members: Count });
10
10
  /** The sessions started under a session, as the prompt footer's status entry shows them. */
11
11
  const Summary = Schema.Struct({
12
- /** Running, and waiting on no request. */
12
+ /** Running, and waiting on no request or form. */
13
13
  running: Count,
14
- /** Waiting on a permission request or a question, which go to the person. */
14
+ /** Waiting on a permission request, a question or a form it shows, which go to the person. */
15
15
  waiting: Count,
16
16
  /** Not running: done, or waiting for a message. */
17
17
  idle: Count,
18
+ /** Left a courier message waiting while idle, and did not take it once woken again; the session itself counts too. Absent at 0. */
19
+ stalled: Schema.optionalKey(Count),
18
20
  groups: Schema.Array(Group),
19
- /** Set when the subtree had more sessions than `maxTotal`, which were not counted. */
20
- truncated: Schema.optionalKey(Count),
21
21
  });
22
- /** A request a session waits on until the person answers it: a permission request, or its first question. */
22
+ /** A request a session waits on until the person answers it: a permission request, its first question, or a form it shows. */
23
23
  const Wait = Schema.Union([
24
24
  Schema.Struct({ type: Schema.Literal("permission"), action: Schema.String, resources: Schema.Array(Schema.String) }),
25
25
  Schema.Struct({ type: Schema.Literal("question"), question: Schema.String }),
26
+ Schema.Struct({ type: Schema.Literal("form"), title: Schema.String }),
26
27
  ]);
27
28
  /** One session of the tree panel: what courier_tree shows of it, and what it is doing, in epoch milliseconds. */
28
29
  const Row = Schema.Struct({
@@ -31,10 +32,17 @@ const Row = Schema.Struct({
31
32
  title: Schema.String,
32
33
  /** A child of the panel's session is at depth 1. */
33
34
  depth: Count,
34
- /** `waiting` on a request to the person; `gone` when OpenCode no longer knows it. */
35
- state: Schema.Literals(["running", "waiting", "idle", "gone"]),
35
+ /**
36
+ * `waiting` on a request to the person or a form it shows them; `stalled` when a courier message waits in its inbox and
37
+ * waking it again did not start a turn; `gone` when OpenCode no longer knows it.
38
+ */
39
+ state: Schema.Literals(["running", "waiting", "idle", "stalled", "gone"]),
40
+ /** Since when its oldest message that did not wake it has waited, when it is stalled. */
41
+ stalled: Schema.optionalKey(Schema.Number),
36
42
  /** How its last turn ended. */
37
43
  outcome: Schema.optionalKey(Schema.Literals(["succeeded", "failed", "interrupted"])),
44
+ /** Set when courier_stop stopped it since its last prompt. */
45
+ stopped: Schema.optionalKey(Schema.Boolean),
38
46
  /** Its report since its last prompt: still owed, arrived (held with its group or delivered), or its turn ended without one. */
39
47
  report: Schema.optionalKey(Schema.Struct({ state: Schema.Literals(["owed", "reported", "ended"]), status: Schema.optionalKey(Schema.String), progressed: Schema.Boolean })),
40
48
  /** Its join group, and its standing there: `held`, `out`, how it left, or `released`. */
@@ -49,11 +57,13 @@ const Row = Schema.Struct({
49
57
  tool: Schema.optionalKey(Schema.Struct({ name: Schema.String, at: Schema.Number })),
50
58
  waits: Schema.Array(Wait),
51
59
  });
52
- /** The sessions under a session, as courier_tree reads them, in the order a tree is drawn. */
60
+ /** The sessions under a session, as courier_tree lists them, in the order a tree is drawn. */
53
61
  const Tree = Schema.Struct({
54
62
  rows: Schema.Array(Row),
55
- /** Set when the subtree had more sessions than `maxTotal`, which were left out. */
63
+ /** How many older sessions the listing left out, when it had more than `maxTotal`: the live ones come first, then the newest. */
56
64
  truncated: Schema.optionalKey(Count),
65
+ /** Since when a courier message has waited in the panel's own session, when that session is stalled. */
66
+ stalled: Schema.optionalKey(Schema.Number),
57
67
  });
58
68
  /** What a stop did to each session, deepest first, as courier_stop reports it. */
59
69
  const Stopped = Schema.Struct({
@@ -61,6 +71,11 @@ const Stopped = Schema.Struct({
61
71
  /** How many scheduled messages were cancelled. */
62
72
  cancelled: Count,
63
73
  });
74
+ /** What courier_cleanup did: forgot it, removed its worktree, found that already gone, or kept the worktree, saying why. */
75
+ const Forgotten = Schema.Union([
76
+ Schema.Struct({ outcome: Schema.Literals(["forgotten", "removed", "gone"]) }),
77
+ Schema.Struct({ outcome: Schema.Literal("kept"), reason: Schema.String }),
78
+ ]);
64
79
  export const treeRpc = Rpc.define({
65
80
  id: "courier",
66
81
  methods: {
@@ -78,6 +93,12 @@ export const treeRpc = Rpc.define({
78
93
  output: Schema.toStandardSchemaV1(Stopped),
79
94
  errors: { refused: Schema.toStandardSchemaV1(Schema.Struct({})) },
80
95
  },
96
+ /** Forgets `sessionID` as courier_cleanup called from its parent, without `force`; `by` must be above it. */
97
+ cleanup: {
98
+ input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, by: Schema.String })),
99
+ output: Schema.toStandardSchemaV1(Forgotten),
100
+ errors: { refused: Schema.toStandardSchemaV1(Schema.Struct({})) },
101
+ },
81
102
  },
82
103
  events: {},
83
104
  });
package/dist/tree/stop.js CHANGED
@@ -4,6 +4,7 @@ import { cleanup } from "./cleanup.js";
4
4
  import { childStore } from "./store.js";
5
5
  import { subtree } from "./subtree.js";
6
6
  import { stoppedNotice } from "./text.js";
7
+ import { forgetWakes } from "./wake.js";
7
8
  /** Rounds of looking again for sessions started while others were being stopped. */
8
9
  const ROUNDS = 3;
9
10
  /**
@@ -62,6 +63,7 @@ async function silence(ports, sessionID, cancelled) {
62
63
  catch (error) {
63
64
  log(`courier_stop: could not cancel the messages scheduled for ${sessionID}: ${String(error)}`);
64
65
  }
66
+ await forgetWakes(ports.courier, sessionID).catch((error) => log(`courier_stop: could not drop the wake watch of ${sessionID}: ${String(error)}`));
65
67
  }
66
68
  async function interrupt(ports, { entry, depth }) {
67
69
  const { sessionID, title } = entry;