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
package/dist/tui.js CHANGED
@@ -2,58 +2,471 @@
2
2
  // src/tui.tsx
3
3
  import { Plugin } from "@opencode/plugin/tui";
4
4
 
5
- // src/person/rpc.ts
5
+ // src/memory/rpc.ts
6
6
  import { Rpc } from "@opencode/plugin/rpc";
7
7
  import { Schema } from "effect";
8
8
  var Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
9
- var Session = Schema.Struct({
9
+ var PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"];
10
+ var Part = Schema.Struct({
10
11
  sessionID: Schema.String,
11
12
  title: Schema.String,
12
- parentID: Schema.String,
13
13
  depth: Count,
14
- group: Schema.optionalKey(Schema.String)
14
+ isolated: Schema.Boolean,
15
+ group: Schema.optionalKey(Schema.String),
16
+ status: Schema.Literals(PART_STATUSES),
17
+ duration: Schema.optionalKey(Schema.Number),
18
+ cost: Schema.optionalKey(Schema.Number),
19
+ context: Schema.optionalKey(Schema.Number),
20
+ refused: Schema.optionalKey(Count)
15
21
  });
16
- var Permission = Schema.Struct({
22
+ var Lesson = Schema.Struct({ text: Schema.String, at: Schema.Number, depth: Schema.optionalKey(Count) });
23
+ var Record = Schema.Struct({
17
24
  sessionID: Schema.String,
18
- requestID: Schema.String,
19
- action: Schema.String,
20
- resources: Schema.Array(Schema.String),
21
- save: Schema.optionalKey(Schema.Array(Schema.String)),
22
- passesOn: Schema.optionalKey(Schema.Struct({ sessionID: Schema.String, requestID: Schema.String }))
25
+ startedAt: Schema.Number,
26
+ movedAt: Schema.Number,
27
+ title: Schema.String,
28
+ split: Schema.String,
29
+ finished: Schema.Boolean,
30
+ recalled: Schema.optionalKey(Schema.Struct({ at: Schema.optionalKey(Schema.Number), calls: Count })),
31
+ lesson: Schema.optionalKey(Lesson),
32
+ parts: Schema.Array(Part)
23
33
  });
24
- var Prompt = Schema.Struct({
25
- question: Schema.String,
26
- header: Schema.String,
27
- options: Schema.Array(Schema.Struct({ label: Schema.String, description: Schema.String })),
28
- multiple: Schema.optionalKey(Schema.Boolean)
34
+ var Records = Schema.Struct({
35
+ memory: Schema.Boolean,
36
+ projectID: Schema.String,
37
+ records: Schema.Array(Record)
29
38
  });
30
- var Question = Schema.Struct({
31
- sessionID: Schema.String,
32
- requestID: Schema.String,
33
- questions: Schema.Array(Prompt),
34
- stopped: Schema.optionalKey(Schema.Boolean)
39
+ var Forgotten = Schema.Struct({ outcome: Schema.Literals(["record", "lesson", "none"]) });
40
+ var memoryRpc = Rpc.define({
41
+ id: "courier-memory",
42
+ methods: {
43
+ records: {
44
+ input: Schema.toStandardSchemaV1(Schema.Struct({})),
45
+ output: Schema.toStandardSchemaV1(Records)
46
+ },
47
+ forget: {
48
+ input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
49
+ output: Schema.toStandardSchemaV1(Forgotten)
50
+ }
51
+ },
52
+ events: {}
35
53
  });
36
- var Asks = Schema.Struct({
37
- top: Schema.String,
38
- sessions: Schema.Array(Session),
39
- permissions: Schema.Array(Permission),
40
- questions: Schema.Array(Question)
54
+
55
+ // src/kernel/protocol.ts
56
+ var STATUSES = ["done", "partial", "blocked", "failed"];
57
+
58
+ // src/memory/domain.ts
59
+ var RETENTION_MS = 90 * 24 * 60 * 60000;
60
+ var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "stalled", "out"];
61
+
62
+ // src/memory/text.ts
63
+ function duration(ms) {
64
+ const minutes = Math.round(ms / 60000);
65
+ if (minutes < 1)
66
+ return `${Math.max(0, Math.round(ms / 1000))} s`;
67
+ if (minutes < 60)
68
+ return `${minutes} min`;
69
+ return `${Math.floor(minutes / 60)} h ${minutes % 60} min`;
70
+ }
71
+ var ONE_LINE = "what the next orchestrator in this project should know about running a tree like this one, with the job it comes from and the reason";
72
+ var NOTE_EMPTY = `Not noted: text is empty. Write one line, ${ONE_LINE}, or nothing.`;
73
+
74
+ // src/memory/tui.tsx
75
+ var MEMORY_COMMAND = "courier.memory";
76
+ var MEMORY_USAGE = "/courier memory lists this project's tree records, to read or forget";
77
+ var MEMORY_OFF = "Memory is off in this project: the plugin's memory option is false, so no record of a session tree is kept or listed here.";
78
+ var NO_RECORDS = "No record of a session tree in this project: one is written as each turn in a tree ends, and kept 90 days.";
79
+ var LESSON_LINE_MAX = 160;
80
+ var ROW_TITLE_MAX = 46;
81
+ var TODAY = "Today";
82
+ var LESSON_ROW = 90;
83
+ var pad = (n) => String(n).padStart(2, "0");
84
+ var localDay = (at) => new Date(at).toDateString();
85
+ var day = (at) => {
86
+ const date = new Date(at);
87
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
88
+ };
89
+ var zone = (date) => new Intl.DateTimeFormat("en-US", {
90
+ timeZoneName: "short"
91
+ }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "local";
92
+ var minute = (at) => {
93
+ const date = new Date(at);
94
+ return `${day(at)} ${pad(date.getHours())}:${pad(date.getMinutes())} ${zone(date)}`;
95
+ };
96
+ var oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
97
+ var clip = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
98
+ var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
99
+ var money = (cost) => `$${cost.toFixed(2)}`;
100
+ var nameOf = (record) => clip(oneLine2(record.title) || record.sessionID, 60);
101
+ function partsCounted(parts) {
102
+ if (!parts.length)
103
+ return "no parts";
104
+ const statuses = PART_STATUSES.flatMap((status) => {
105
+ const n = parts.filter((part) => part.status === status).length;
106
+ return n ? [`${n} ${status}`] : [];
107
+ }).join(", ");
108
+ return `${count(parts.length, "part")}: ${statuses}`;
109
+ }
110
+ function costOf(record) {
111
+ return record.parts.some((part) => part.cost !== undefined) ? record.parts.reduce((sum, part) => sum + (part.cost ?? 0), 0) : undefined;
112
+ }
113
+ function recordTitle(record, open) {
114
+ return `${clip(oneLine2(record.title) || record.sessionID, ROW_TITLE_MAX)}${record.sessionID === open ? " (this session)" : ""}`;
115
+ }
116
+ function recordCategory(record, now = Date.now()) {
117
+ const moved = localDay(record.movedAt);
118
+ return moved === localDay(now) ? TODAY : moved;
119
+ }
120
+ function lessonHeading(lesson) {
121
+ const from = lesson.depth === undefined ? "" : `, from depth ${lesson.depth}`;
122
+ return `lesson (${day(lesson.at)}${from})`;
123
+ }
124
+ function recordDescription(record) {
125
+ const cost = costOf(record);
126
+ return [partsCounted(record.parts), duration(record.movedAt - record.startedAt), cost === undefined ? "" : money(cost), record.finished ? "" : "not finished"].filter(Boolean).join(" \xB7 ");
127
+ }
128
+ function recordDetails(record) {
129
+ return record.lesson ? [clip(`${lessonHeading(record.lesson)}: "${oneLine2(record.lesson.text)}"`, LESSON_LINE_MAX)] : [];
130
+ }
131
+ function partLine(part) {
132
+ return [part.status, `depth ${part.depth}`, part.isolated ? "isolated" : "", part.group ? `group ${part.group}` : "", part.duration === undefined ? "" : duration(part.duration), part.cost === undefined ? "" : money(part.cost), part.context === undefined ? "" : `ctx ${Math.round(part.context * 100)}%`, part.refused ? `${count(part.refused, "spawn")} refused` : ""].filter(Boolean).join(" \xB7 ");
133
+ }
134
+ function wrap(text, width) {
135
+ const rows = [];
136
+ let row = "";
137
+ for (const word of oneLine2(text).split(" ").filter(Boolean)) {
138
+ if (row && row.length + 1 + word.length <= width) {
139
+ row = `${row} ${word}`;
140
+ continue;
141
+ }
142
+ if (row)
143
+ rows.push(row);
144
+ row = word;
145
+ while (row.length > width) {
146
+ rows.push(row.slice(0, width));
147
+ row = row.slice(width);
148
+ }
149
+ }
150
+ if (row)
151
+ rows.push(row);
152
+ return rows;
153
+ }
154
+ var lessonLine = (record) => record.lesson ? {
155
+ kind: "lesson",
156
+ row: 0
157
+ } : {
158
+ kind: "fact",
159
+ key: "lesson"
160
+ };
161
+ function nearby(sessionIDs, sessionID, forgotten) {
162
+ const at = sessionIDs.indexOf(sessionID);
163
+ if (at < 0)
164
+ return forgotten ? [] : [sessionID];
165
+ return [...forgotten ? [] : [sessionID], ...sessionIDs.slice(at + 1), ...sessionIDs.slice(0, at).reverse()];
166
+ }
167
+ function detailOptions(record) {
168
+ const fact = (key, title, description) => ({
169
+ title,
170
+ value: {
171
+ kind: "fact",
172
+ key
173
+ },
174
+ category: "Tree",
175
+ ...description ? {
176
+ description
177
+ } : {}
178
+ });
179
+ const cost = costOf(record);
180
+ const recalled = record.recalled ? fact("recalled", `Recalled ${count(record.recalled.calls, "time")}`, record.recalled.at === undefined ? "by a part only" : `first by the top ${minute(record.recalled.at)}`) : fact("recalled", "Not recalled");
181
+ const lesson = record.lesson ? [{
182
+ title: lessonHeading(record.lesson),
183
+ value: {
184
+ kind: "lesson",
185
+ row: 0
186
+ },
187
+ category: "Lesson"
188
+ }, ...wrap(record.lesson.text, LESSON_ROW).map((row, at) => ({
189
+ title: row,
190
+ value: {
191
+ kind: "lesson",
192
+ row: at + 1
193
+ },
194
+ category: "Lesson"
195
+ }))] : [{
196
+ title: "No lesson",
197
+ value: {
198
+ kind: "fact",
199
+ key: "lesson"
200
+ },
201
+ category: "Lesson",
202
+ description: "courier_note writes one line per tree"
203
+ }];
204
+ return [fact("top", `Top session ${record.sessionID}`), fact("started", `Started ${minute(record.startedAt)}`, `last moved ${minute(record.movedAt)}, ran ${duration(record.movedAt - record.startedAt)}`), fact("split", clip(oneLine2(record.split), 160)), fact("finished", record.finished ? "Finished" : "Not finished", cost === undefined ? undefined : `${money(cost)} in all`), recalled, ...record.parts.length ? record.parts.map((part) => ({
205
+ title: `${" ".repeat(Math.max(0, part.depth - 1))}${clip(oneLine2(part.title) || part.sessionID, 60)}`,
206
+ value: {
207
+ kind: "part",
208
+ sessionID: part.sessionID
209
+ },
210
+ category: "Parts",
211
+ description: partLine(part)
212
+ })) : [{
213
+ title: "No parts",
214
+ value: {
215
+ kind: "fact",
216
+ key: "parts"
217
+ },
218
+ category: "Parts",
219
+ description: "no session was started in this tree"
220
+ }], ...lesson];
221
+ }
222
+ function forgetMessage(record, lesson) {
223
+ const name = `"${nameOf(record)}" (${day(record.movedAt)}, ${partsCounted(record.parts)})`;
224
+ if (lesson)
225
+ return `Forget the lesson of ${name}? The record keeps its facts; courier_recall no longer lists the lesson: "${clip(oneLine2(record.lesson?.text ?? ""), 120)}".`;
226
+ return `Forget the record of ${name}, top session ${record.sessionID}? It leaves this project's memory and courier_recall${record.lesson ? ", its lesson with it" : ""}; the sessions themselves are untouched.`;
227
+ }
228
+ function forgottenLine(record, outcome) {
229
+ const name = `"${nameOf(record)}"`;
230
+ if (outcome === "record")
231
+ return `Forgot the record of ${name}`;
232
+ if (outcome === "lesson")
233
+ return `Forgot the lesson of ${name}; its facts stay`;
234
+ return `Nothing to forget for ${name}: it was already gone`;
235
+ }
236
+ function addMemoryView(ctx) {
237
+ const memory = ctx.client.rpc(memoryRpc);
238
+ const openSession = () => {
239
+ const route = ctx.ui.router.current();
240
+ return route.type === "session" ? route.sessionID : undefined;
241
+ };
242
+ const where = () => {
243
+ const sessionID = openSession();
244
+ return (sessionID ? ctx.data.session.get(sessionID)?.location : undefined) ?? ctx.location ?? ctx.data.location.default();
245
+ };
246
+ const reason = (error) => {
247
+ if (error instanceof Error)
248
+ return error.message;
249
+ return typeof error === "object" && error && "message" in error ? String(error.message) : String(error);
250
+ };
251
+ const fail = (what) => (error) => ctx.ui.toast.show({
252
+ title: "courier",
253
+ message: `${what}: ${reason(error)}`,
254
+ variant: "error"
255
+ });
256
+ const say = (message) => ctx.ui.toast.show({
257
+ title: "courier",
258
+ message,
259
+ variant: "info"
260
+ });
261
+ function show(options, chosen, forget) {
262
+ let request = () => {
263
+ return;
264
+ };
265
+ const requested = new Promise((resolve) => {
266
+ request = resolve;
267
+ });
268
+ const choosing = ctx.ui.dialog.select({
269
+ ...options,
270
+ actions: [{
271
+ bind: "x",
272
+ title: "forget",
273
+ onTrigger: (value) => request(forget(value))
274
+ }]
275
+ });
276
+ ctx.ui.dialog.set({
277
+ size: "xlarge"
278
+ });
279
+ return Promise.race([choosing.then((value) => value === undefined ? {
280
+ kind: "back"
281
+ } : chosen(value)), requested]);
282
+ }
283
+ const showList = (read, current) => {
284
+ const open2 = openSession();
285
+ const now = Date.now();
286
+ return show({
287
+ title: `Memory (${read.records.length})`,
288
+ placeholder: "Search",
289
+ ...current ? {
290
+ current
291
+ } : {},
292
+ options: read.records.map((record) => ({
293
+ title: recordTitle(record, open2),
294
+ value: record,
295
+ category: recordCategory(record, now),
296
+ description: recordDescription(record),
297
+ details: recordDetails(record)
298
+ }))
299
+ }, (record) => ({
300
+ kind: "open",
301
+ record
302
+ }), (record) => ({
303
+ kind: "forget",
304
+ record,
305
+ lesson: false
306
+ }));
307
+ };
308
+ const showRecord = (record, current) => show({
309
+ title: `${localDay(record.movedAt)} ${nameOf(record)}`,
310
+ placeholder: "Search",
311
+ options: detailOptions(record),
312
+ ...current ? {
313
+ current
314
+ } : {}
315
+ }, () => ({
316
+ kind: "back"
317
+ }), (line) => ({
318
+ kind: "forget",
319
+ record,
320
+ lesson: line.kind === "lesson",
321
+ line
322
+ }));
323
+ async function confirmForget(record, lesson) {
324
+ const confirmed = await ctx.ui.dialog.confirm({
325
+ title: lesson ? "Forget lesson" : "Forget record",
326
+ message: forgetMessage(record, lesson),
327
+ label: {
328
+ confirm: "Forget",
329
+ cancel: "Keep"
330
+ }
331
+ });
332
+ if (!confirmed)
333
+ return false;
334
+ try {
335
+ const result = await memory.forget({
336
+ sessionID: record.sessionID,
337
+ lesson
338
+ }, {
339
+ location: where()
340
+ });
341
+ ctx.ui.toast.show({
342
+ title: "courier",
343
+ message: forgottenLine(record, result.outcome),
344
+ variant: result.outcome === "none" ? "warning" : "success"
345
+ });
346
+ return true;
347
+ } catch (error) {
348
+ fail("Not forgotten")(error);
349
+ return false;
350
+ }
351
+ }
352
+ let opened = false;
353
+ async function open() {
354
+ if (opened)
355
+ return;
356
+ opened = true;
357
+ try {
358
+ let viewing;
359
+ let near = [];
360
+ let line;
361
+ for (;; ) {
362
+ const read = await memory.records({}, {
363
+ location: where()
364
+ }).catch(fail("The records could not be read"));
365
+ if (!read)
366
+ return;
367
+ if (!read.memory)
368
+ return say(MEMORY_OFF);
369
+ if (!read.records.length)
370
+ return say(NO_RECORDS);
371
+ const record = viewing === undefined ? undefined : read.records.find((one) => one.sessionID === viewing);
372
+ viewing = record?.sessionID;
373
+ const listed = read.records.map((one) => one.sessionID);
374
+ const current = near.map((sessionID) => read.records.find((one) => one.sessionID === sessionID)).find((one) => one !== undefined);
375
+ const at = line === "lesson" ? record && lessonLine(record) : line;
376
+ const request = await (record ? showRecord(record, at) : showList(read, current));
377
+ line = undefined;
378
+ if (request.kind === "back") {
379
+ if (!record)
380
+ return;
381
+ near = nearby(listed, record.sessionID, false);
382
+ viewing = undefined;
383
+ continue;
384
+ }
385
+ if (request.kind === "open") {
386
+ viewing = request.record.sessionID;
387
+ continue;
388
+ }
389
+ const forgotten = await confirmForget(request.record, request.lesson);
390
+ near = nearby(listed, request.record.sessionID, forgotten && !request.lesson);
391
+ if (record && !forgotten)
392
+ line = request.line;
393
+ else if (request.lesson)
394
+ line = "lesson";
395
+ else
396
+ viewing = undefined;
397
+ }
398
+ } finally {
399
+ opened = false;
400
+ }
401
+ }
402
+ ctx.keymap.layer(() => ({
403
+ mode: "global",
404
+ commands: [{
405
+ id: MEMORY_COMMAND,
406
+ title: "Courier: memory",
407
+ description: "The records of this project's session trees, newest first under the day each last moved: enter for one in full, x to forget one or its lesson",
408
+ group: "Courier",
409
+ palette: true,
410
+ run: () => void open()
411
+ }]
412
+ }));
413
+ return {
414
+ open
415
+ };
416
+ }
417
+
418
+ // src/person/rpc.ts
419
+ import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
420
+ import { Schema as Schema2 } from "effect";
421
+ var Count2 = Schema2.Int.check(Schema2.isGreaterThanOrEqualTo(0));
422
+ var Session = Schema2.Struct({
423
+ sessionID: Schema2.String,
424
+ title: Schema2.String,
425
+ parentID: Schema2.String,
426
+ depth: Count2,
427
+ group: Schema2.optionalKey(Schema2.String)
428
+ });
429
+ var Permission = Schema2.Struct({
430
+ sessionID: Schema2.String,
431
+ requestID: Schema2.String,
432
+ action: Schema2.String,
433
+ resources: Schema2.Array(Schema2.String),
434
+ save: Schema2.optionalKey(Schema2.Array(Schema2.String)),
435
+ passesOn: Schema2.optionalKey(Schema2.Struct({ sessionID: Schema2.String, requestID: Schema2.String }))
436
+ });
437
+ var Prompt = Schema2.Struct({
438
+ question: Schema2.String,
439
+ header: Schema2.String,
440
+ options: Schema2.Array(Schema2.Struct({ label: Schema2.String, description: Schema2.String })),
441
+ multiple: Schema2.optionalKey(Schema2.Boolean)
442
+ });
443
+ var Question = Schema2.Struct({
444
+ sessionID: Schema2.String,
445
+ requestID: Schema2.String,
446
+ questions: Schema2.Array(Prompt),
447
+ stopped: Schema2.optionalKey(Schema2.Boolean)
41
448
  });
42
- var Answered = Schema.Struct({
43
- answered: Schema.Boolean,
44
- by: Schema.optionalKey(Schema.Literals(["result", "message"])),
45
- problem: Schema.optionalKey(Schema.String)
449
+ var Asks = Schema2.Struct({
450
+ top: Schema2.String,
451
+ sessions: Schema2.Array(Session),
452
+ permissions: Schema2.Array(Permission),
453
+ questions: Schema2.Array(Question)
46
454
  });
47
- var personRpc = Rpc.define({
455
+ var Answered = Schema2.Struct({
456
+ answered: Schema2.Boolean,
457
+ by: Schema2.optionalKey(Schema2.Literals(["result", "message"])),
458
+ problem: Schema2.optionalKey(Schema2.String)
459
+ });
460
+ var personRpc = Rpc2.define({
48
461
  id: "courier-person",
49
462
  methods: {
50
463
  asks: {
51
- input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String })),
52
- output: Schema.toStandardSchemaV1(Asks)
464
+ input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
465
+ output: Schema2.toStandardSchemaV1(Asks)
53
466
  },
54
467
  answer: {
55
- input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, requestID: Schema.String, answers: Schema.Array(Schema.Array(Schema.String)) })),
56
- output: Schema.toStandardSchemaV1(Answered)
468
+ input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, requestID: Schema2.String, answers: Schema2.Array(Schema2.Array(Schema2.String)) })),
469
+ output: Schema2.toStandardSchemaV1(Answered)
57
470
  }
58
471
  },
59
472
  events: {}
@@ -103,7 +516,7 @@ function pathOf(asks, sessionID, topTitle, currentTitle = () => {
103
516
  titles.unshift(currentTitle(at.sessionID) || at.title);
104
517
  return [topTitle, ...titles].join(" \u203A ");
105
518
  }
106
- var clip = (text, max = 80) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
519
+ var clip2 = (text, max = 80) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
107
520
  function askTitle(ask) {
108
521
  switch (ask.kind) {
109
522
  case "permission": {
@@ -114,17 +527,17 @@ function askTitle(ask) {
114
527
  } = ask.request;
115
528
  const what = resources.length ? `${action} ${resources.join(", ")}` : action;
116
529
  if (ask.session.depth > 0)
117
- return clip(`Permission: ${what}`);
118
- return clip(passesOn ? `Permission (top session): courier_answer once for "${what}"` : `Permission (top session): ${what}`);
530
+ return clip2(`Permission: ${what}`);
531
+ return clip2(passesOn ? `Permission (top session): courier_answer once for "${what}"` : `Permission (top session): ${what}`);
119
532
  }
120
533
  case "question": {
121
534
  const [first, ...rest] = ask.question.questions;
122
535
  const what = first ? `${first.header}: ${first.question}` : ask.question.requestID;
123
536
  const more = rest.length ? ` (+${rest.length} more)` : "";
124
- return clip(`Question: ${what}${more}`);
537
+ return clip2(`Question: ${what}${more}`);
125
538
  }
126
539
  case "form":
127
- return clip(`Form: ${ask.form.title}`);
540
+ return clip2(`Form: ${ask.form.title}`);
128
541
  }
129
542
  }
130
543
  function askFooter(ask) {
@@ -384,12 +797,12 @@ function addAsksInbox(ctx, follows) {
384
797
  } = ask;
385
798
  const answers = [];
386
799
  for (const [index, prompt] of question.questions.entries()) {
387
- const count = question.questions.length > 1 ? ` (${index + 1}/${question.questions.length})` : "";
800
+ const count2 = question.questions.length > 1 ? ` (${index + 1}/${question.questions.length})` : "";
388
801
  const choices = prompt.options.map((option) => ({
389
802
  ...option,
390
803
  value: option.label
391
804
  }));
392
- const picked = await choose(`${prompt.header}: ${prompt.question}${count}`, choices, {
805
+ const picked = await choose(`${prompt.header}: ${prompt.question}${count2}`, choices, {
393
806
  multiple: !!prompt.multiple,
394
807
  own: true
395
808
  });
@@ -797,76 +1210,224 @@ import { createTextNode as _$createTextNode } from "@opentui/solid";
797
1210
  import { insertNode as _$insertNode } from "@opentui/solid";
798
1211
  import { setProp as _$setProp } from "@opentui/solid";
799
1212
  import { createElement as _$createElement } from "@opentui/solid";
800
- import { createEffect, createMemo, createResource, createRoot, createSignal, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
1213
+ import { MouseButton, RGBA, Timeline } from "@opentui/core";
1214
+ import { batch, createEffect, createMemo, createResource, createRoot, createSignal, For, Index, on, onCleanup, onMount, Show, untrack } from "solid-js";
1215
+
1216
+ // src/tree/mascot.ts
1217
+ var MASCOT_EVERY = 5;
1218
+ var BLINK_EVERY = 11;
1219
+ var GLYPH = {
1220
+ idle: [["........", "........", "..gggg..", "gggggggg"]],
1221
+ spawn: [
1222
+ ["........", "...bb...", "g.bbbb.g", "gggggggg"],
1223
+ [".g....g.", "...bb...", "ggbbbbgg", "gggggggg"]
1224
+ ],
1225
+ running: [
1226
+ ["..g..g..", "...bb...", ".gbbbbg.", "gggggggg"],
1227
+ [".g....g.", "........", ".gbbbbg.", "gggggggg"]
1228
+ ],
1229
+ waiting: [
1230
+ [".bbbbbbw", ".bkbbkbw", ".bbaabbm", "ggggggg."],
1231
+ [".bbbbbbb", ".bkbbkbb", ".bbaabbm", "ggggggg."]
1232
+ ],
1233
+ stalled: [
1234
+ ["..e.....", ".bbbbbb.", ".bkbbkb.", "gbbaabbg"],
1235
+ ["..e.e...", ".bbbbbb.", ".bkbbkb.", "gbbaabbg"],
1236
+ ["..e.e.e.", ".bbbbbb.", ".bkbbkb.", "gbbaabbg"]
1237
+ ],
1238
+ report: [
1239
+ ["......aa", "bbbbbbaa", "bkbbkbm.", "bbaabbm."],
1240
+ ["s.....aa", "bbbbbbaa", "bkbbkbm.", "bbaabbm."]
1241
+ ],
1242
+ gone: [["........", ".mmmmmm.", ".mkmmkm.", "mmmmmmmm"]]
1243
+ };
1244
+ var OPEN_EYES = new Set(["waiting", "report"]);
1245
+ function glyph(state, frame) {
1246
+ const frames = GLYPH[state];
1247
+ const pixels = frames[(frame ?? 0) % frames.length];
1248
+ if (frame === undefined)
1249
+ return pixels;
1250
+ if (state === "idle" && breathesOut(frame))
1251
+ return [...pixels.slice(0, 2), ".gggggg.", ...pixels.slice(3)];
1252
+ return OPEN_EYES.has(state) && blinks(frame) ? pixels.map((row) => row.replaceAll("k", "b")) : pixels;
1253
+ }
1254
+ var breathesOut = (frame) => Math.floor(frame / 2) % 2 === 1;
1255
+ var blinks = (frame) => frame % BLINK_EVERY === BLINK_EVERY - 1;
1256
+ function cells(pixels) {
1257
+ const lines = [];
1258
+ for (let row = 0;row < pixels.length; row += 2) {
1259
+ const top = pixels[row];
1260
+ const bottom = pixels[row + 1] ?? "";
1261
+ lines.push([...top].map((pixel, column) => cell(token(pixel), token(bottom[column]))));
1262
+ }
1263
+ return lines;
1264
+ }
1265
+ var token = (pixel) => pixel && pixel !== "." ? pixel : undefined;
1266
+ function cell(top, bottom) {
1267
+ if (!top && !bottom)
1268
+ return { char: " " };
1269
+ if (!top)
1270
+ return { char: "\u2584", fg: bottom };
1271
+ if (top === bottom)
1272
+ return { char: "\u2588", fg: top };
1273
+ return bottom ? { char: "\u2580", fg: top, bg: bottom } : { char: "\u2580", fg: top };
1274
+ }
1275
+ var SIZE = 16;
1276
+ var SLUMP = [2, 2, 2, 1, 1, 0, 0];
1277
+ function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0, mound = "g" } = {}) {
1278
+ const grid = Array.from({ length: SIZE }, () => new Array(SIZE).fill("."));
1279
+ const span = (row, width, pixel, skip = (_column) => false, by = 0) => {
1280
+ if (row < 0 || row >= SIZE)
1281
+ return;
1282
+ for (let column = (SIZE - width) / 2;column < (SIZE + width) / 2; column++)
1283
+ if (!skip(column))
1284
+ grid[row][column + by] = pixel;
1285
+ };
1286
+ const head = Math.round(10 - 8 * rise);
1287
+ const lean = (at) => slump ? SLUMP[at] : 0;
1288
+ [4, 6, 8, 8, 8, 6, 4].forEach((width, at) => span(head + at, width, "b", undefined, lean(at)));
1289
+ for (let row = head + 7;row <= 11; row++)
1290
+ span(row, 4, "b");
1291
+ const eyeColumns = eyes === "shut" ? [5, 6, 9, 10] : [6, 9];
1292
+ if (eyes !== "none")
1293
+ span(head + 3, 8, "k", (column) => !eyeColumns.includes(column), lean(3));
1294
+ if (eyes === "wide")
1295
+ span(head + 2, 8, "k", (column) => !eyeColumns.includes(column), lean(2));
1296
+ span(head + 4, 2, "a", undefined, lean(4));
1297
+ span(10, SIZE, ".", (column) => column >= 6 && column <= 9);
1298
+ span(10, top, mound, (column) => column >= 6 && column <= 9, shift);
1299
+ span(11, 10, mound, undefined, shift);
1300
+ span(12, 12, mound, undefined, shift);
1301
+ for (const row of [13, 14, 15])
1302
+ span(row, SIZE, mound);
1303
+ return grid.map((row) => row.join(""));
1304
+ }
1305
+ function paint(pixels, spots) {
1306
+ const grid = pixels.map((row) => [...row]);
1307
+ for (const [row, column, drawn] of spots)
1308
+ [...drawn].forEach((pixel, at) => {
1309
+ if (pixel !== "." && grid[row]?.[column + at] !== undefined)
1310
+ grid[row][column + at] = pixel;
1311
+ });
1312
+ return grid.map((row) => row.join(""));
1313
+ }
1314
+ var post = (row, column, length, pixel) => Array.from({ length }, (_, at) => [row + at, column, pixel]);
1315
+ var headTop = (rise) => Math.round(10 - 8 * rise);
1316
+ var running = (rise, shift, dirt) => paint(digby(rise, { shift }), [[headTop(rise), 7, "aa"], ...dirt]);
1317
+ var peek = (column, up) => [
1318
+ ...up ? [[8, column, ".b."], [9, column, "bkb"]] : [[9, column, ".b."]],
1319
+ [10, column, "gbg"],
1320
+ [11, column, "ggg"]
1321
+ ];
1322
+ var spawn = (up, dirt) => paint(digby(0.1), [...peek(0, up), ...peek(13, up), [12, 0, "gg"], [12, 14, "gg"], ...dirt]);
1323
+ var sign = (shown) => [...shown ? [[2, 13, "www"], [3, 13, "wkw"], [4, 13, "www"]] : [], ...post(5, 14, 7, "m")];
1324
+ var waiting = (shown) => (open) => paint(digby(1, { eyes: open ? "wide" : "none" }), [[8, 4, "mm"], [8, 10, "mm"], ...sign(shown)]);
1325
+ var stalled = (dots) => () => paint(digby(0.7, { eyes: "shut", slump: true }), [[5, 0, dots]]);
1326
+ var report = (rise, spark) => (open) => paint(digby(rise, { eyes: open ? "open" : "none" }), [
1327
+ [0, 12, "aaaa"],
1328
+ [1, 12, "akka"],
1329
+ [2, 12, "aaaa"],
1330
+ ...post(3, 13, 8, "m"),
1331
+ ...spark.map((row, at) => [1 + at, 0, row])
1332
+ ]);
1333
+ var ART = {
1334
+ spawn: [
1335
+ () => spawn(true, [[3, 1, "m"], [5, 3, "m"], [2, 14, "m"], [5, 12, "m"]]),
1336
+ () => spawn(false, [[1, 0, "m"], [4, 2, "m"], [3, 15, "m"], [6, 13, "m"]])
1337
+ ],
1338
+ running: [
1339
+ () => running(0.22, 0, [[5, 2, "m"], [7, 1, "m"], [4, 13, "m"], [6, 14, "m"]]),
1340
+ () => running(0.12, 1, [[3, 1, "m"], [6, 3, "m"], [5, 12, "m"], [3, 15, "m"]])
1341
+ ],
1342
+ waiting: [waiting(true), waiting(false)],
1343
+ stalled: [stalled("e.e.e"), stalled("e"), stalled("e.e")],
1344
+ report: [report(1, [".s.", "sss", ".s."]), report(1.25, ["s.s", ".s.", "s.s"]), report(1.1, [".s.", "sss", ".s."])],
1345
+ gone: [() => digby(-0.3, { mound: "m" })]
1346
+ };
1347
+ var Z_LINES = [3, 2, 1, undefined];
1348
+ function idleArt(frame) {
1349
+ const lines = cells(digby(-0.3, { top: frame !== undefined && breathesOut(frame) ? 8 : 6 }));
1350
+ const line = Z_LINES[(frame ?? 0) % Z_LINES.length];
1351
+ if (line === undefined)
1352
+ return lines;
1353
+ return lines.map((cellsOf, at) => at === line ? cellsOf.map((one, column) => column === 13 ? { char: "z", fg: "m" } : one) : cellsOf);
1354
+ }
1355
+ function art(state, frame) {
1356
+ if (state === "idle")
1357
+ return idleArt(frame);
1358
+ const frames = ART[state];
1359
+ return cells(frames[(frame ?? 0) % frames.length](!(frame !== undefined && OPEN_EYES.has(state) && blinks(frame))));
1360
+ }
801
1361
 
802
1362
  // src/tree/rpc.ts
803
- import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
804
- import { Schema as Schema2 } from "effect";
805
- var Count2 = Schema2.Int.check(Schema2.isGreaterThanOrEqualTo(0));
806
- var Group = Schema2.Struct({ name: Schema2.String, reported: Count2, members: Count2 });
807
- var Summary = Schema2.Struct({
808
- running: Count2,
809
- waiting: Count2,
810
- idle: Count2,
811
- stalled: Schema2.optionalKey(Count2),
812
- groups: Schema2.Array(Group)
1363
+ import { Rpc as Rpc3 } from "@opencode/plugin/rpc";
1364
+ import { Schema as Schema3 } from "effect";
1365
+ var Count3 = Schema3.Int.check(Schema3.isGreaterThanOrEqualTo(0));
1366
+ var Group = Schema3.Struct({ name: Schema3.String, reported: Count3, members: Count3 });
1367
+ var Summary = Schema3.Struct({
1368
+ running: Count3,
1369
+ waiting: Count3,
1370
+ idle: Count3,
1371
+ stalled: Schema3.optionalKey(Count3),
1372
+ groups: Schema3.Array(Group)
813
1373
  });
814
- var Wait = Schema2.Union([
815
- Schema2.Struct({ type: Schema2.Literal("permission"), action: Schema2.String, resources: Schema2.Array(Schema2.String) }),
816
- Schema2.Struct({ type: Schema2.Literal("question"), question: Schema2.String }),
817
- Schema2.Struct({ type: Schema2.Literal("form"), title: Schema2.String })
1374
+ var Wait = Schema3.Union([
1375
+ Schema3.Struct({ type: Schema3.Literal("permission"), action: Schema3.String, resources: Schema3.Array(Schema3.String) }),
1376
+ Schema3.Struct({ type: Schema3.Literal("question"), question: Schema3.String }),
1377
+ Schema3.Struct({ type: Schema3.Literal("form"), title: Schema3.String })
818
1378
  ]);
819
- var Row = Schema2.Struct({
820
- sessionID: Schema2.String,
821
- parentID: Schema2.String,
822
- title: Schema2.String,
823
- depth: Count2,
824
- state: Schema2.Literals(["running", "waiting", "idle", "stalled", "gone"]),
825
- stalled: Schema2.optionalKey(Schema2.Number),
826
- outcome: Schema2.optionalKey(Schema2.Literals(["succeeded", "failed", "interrupted"])),
827
- stopped: Schema2.optionalKey(Schema2.Boolean),
828
- report: Schema2.optionalKey(Schema2.Struct({ state: Schema2.Literals(["owed", "reported", "ended"]), status: Schema2.optionalKey(Schema2.String), progressed: Schema2.Boolean })),
829
- group: Schema2.optionalKey(Schema2.Struct({ name: Schema2.String, standing: Schema2.String })),
830
- isolated: Schema2.Boolean,
831
- created: Schema2.Number,
832
- idle: Schema2.optionalKey(Schema2.Number),
833
- cost: Schema2.optionalKey(Schema2.Number),
834
- tool: Schema2.optionalKey(Schema2.Struct({ name: Schema2.String, at: Schema2.Number })),
835
- waits: Schema2.Array(Wait)
1379
+ var Row = Schema3.Struct({
1380
+ sessionID: Schema3.String,
1381
+ parentID: Schema3.String,
1382
+ title: Schema3.String,
1383
+ depth: Count3,
1384
+ state: Schema3.Literals(["running", "waiting", "idle", "stalled", "gone"]),
1385
+ stalled: Schema3.optionalKey(Schema3.Number),
1386
+ outcome: Schema3.optionalKey(Schema3.Literals(["succeeded", "failed", "interrupted"])),
1387
+ stopped: Schema3.optionalKey(Schema3.Boolean),
1388
+ report: Schema3.optionalKey(Schema3.Struct({ state: Schema3.Literals(["owed", "reported", "ended"]), status: Schema3.optionalKey(Schema3.String), progressed: Schema3.Boolean })),
1389
+ group: Schema3.optionalKey(Schema3.Struct({ name: Schema3.String, standing: Schema3.String })),
1390
+ isolated: Schema3.Boolean,
1391
+ created: Schema3.Number,
1392
+ idle: Schema3.optionalKey(Schema3.Number),
1393
+ cost: Schema3.optionalKey(Schema3.Number),
1394
+ context: Schema3.optionalKey(Schema3.Number),
1395
+ tool: Schema3.optionalKey(Schema3.Struct({ name: Schema3.String, at: Schema3.Number })),
1396
+ waits: Schema3.Array(Wait)
836
1397
  });
837
- var Tree = Schema2.Struct({
838
- rows: Schema2.Array(Row),
839
- truncated: Schema2.optionalKey(Count2),
840
- stalled: Schema2.optionalKey(Schema2.Number)
1398
+ var Tree = Schema3.Struct({
1399
+ rows: Schema3.Array(Row),
1400
+ truncated: Schema3.optionalKey(Count3),
1401
+ stalled: Schema3.optionalKey(Schema3.Number)
841
1402
  });
842
- var Stopped = Schema2.Struct({
843
- stopped: Schema2.Array(Schema2.Struct({ sessionID: Schema2.String, title: Schema2.String, outcome: Schema2.String })),
844
- cancelled: Count2
1403
+ var Stopped = Schema3.Struct({
1404
+ stopped: Schema3.Array(Schema3.Struct({ sessionID: Schema3.String, title: Schema3.String, outcome: Schema3.String })),
1405
+ cancelled: Count3
845
1406
  });
846
- var Forgotten = Schema2.Union([
847
- Schema2.Struct({ outcome: Schema2.Literals(["forgotten", "removed", "gone"]) }),
848
- Schema2.Struct({ outcome: Schema2.Literal("kept"), reason: Schema2.String })
1407
+ var Forgotten2 = Schema3.Union([
1408
+ Schema3.Struct({ outcome: Schema3.Literals(["forgotten", "removed", "gone"]) }),
1409
+ Schema3.Struct({ outcome: Schema3.Literal("kept"), reason: Schema3.String })
849
1410
  ]);
850
- var treeRpc = Rpc2.define({
1411
+ var treeRpc = Rpc3.define({
851
1412
  id: "courier",
852
1413
  methods: {
853
1414
  summary: {
854
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
855
- output: Schema2.toStandardSchemaV1(Summary)
1415
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
1416
+ output: Schema3.toStandardSchemaV1(Summary)
856
1417
  },
857
1418
  tree: {
858
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
859
- output: Schema2.toStandardSchemaV1(Tree)
1419
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
1420
+ output: Schema3.toStandardSchemaV1(Tree)
860
1421
  },
861
1422
  stop: {
862
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
863
- output: Schema2.toStandardSchemaV1(Stopped),
864
- errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
1423
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
1424
+ output: Schema3.toStandardSchemaV1(Stopped),
1425
+ errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
865
1426
  },
866
1427
  cleanup: {
867
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
868
- output: Schema2.toStandardSchemaV1(Forgotten),
869
- errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
1428
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
1429
+ output: Schema3.toStandardSchemaV1(Forgotten2),
1430
+ errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
870
1431
  }
871
1432
  },
872
1433
  events: {}
@@ -879,6 +1440,176 @@ var SETTLE_MS2 = 250;
879
1440
  var PANEL_SETTLE_MS = 1000;
880
1441
  var PANEL = "courier-tree";
881
1442
  var TICK_MS = 1000;
1443
+ var FRAME_MS = 90;
1444
+ var FLASH_MS = 1500;
1445
+ var BAR_MS = 450;
1446
+ function animationsOn(options) {
1447
+ return options?.animations !== false;
1448
+ }
1449
+ function createMotion(enabled) {
1450
+ const [frame, setFrame] = createSignal(0);
1451
+ const [keepers, setKeepers] = createSignal([]);
1452
+ const [moving, setMoving] = createSignal([]);
1453
+ const cadence = createMemo(() => {
1454
+ if (!enabled)
1455
+ return;
1456
+ if (moving().length)
1457
+ return 1;
1458
+ const wanted = keepers().filter((one) => one.when());
1459
+ return wanted.length ? Math.min(...wanted.map((one) => one.every)) : undefined;
1460
+ });
1461
+ createEffect(() => {
1462
+ const every = cadence();
1463
+ if (every === undefined)
1464
+ return;
1465
+ const timer = setInterval(() => batch(() => {
1466
+ const now = untrack(moving);
1467
+ for (const one of now)
1468
+ one.step(FRAME_MS * every);
1469
+ setFrame((count2) => count2 + every);
1470
+ const left = now.filter((one) => !one.done());
1471
+ if (left.length !== now.length)
1472
+ setMoving(left);
1473
+ }), FRAME_MS * every);
1474
+ onCleanup(() => clearInterval(timer));
1475
+ });
1476
+ const tween = (from, to, duration2, ease) => {
1477
+ if (!enabled || from === to)
1478
+ return {
1479
+ value: () => to,
1480
+ done: () => true
1481
+ };
1482
+ const target = {
1483
+ value: from
1484
+ };
1485
+ const timeline = new Timeline({
1486
+ duration: duration2,
1487
+ autoplay: false
1488
+ });
1489
+ timeline.add(target, {
1490
+ duration: duration2,
1491
+ ease,
1492
+ value: to
1493
+ });
1494
+ timeline.play();
1495
+ let elapsed = 0;
1496
+ const one = {
1497
+ value: () => {
1498
+ frame();
1499
+ return target.value;
1500
+ },
1501
+ done: () => {
1502
+ frame();
1503
+ return elapsed >= duration2;
1504
+ },
1505
+ step: (ms) => {
1506
+ elapsed += ms;
1507
+ timeline.update(ms);
1508
+ }
1509
+ };
1510
+ setMoving((all) => [...all, one]);
1511
+ return one;
1512
+ };
1513
+ return {
1514
+ enabled,
1515
+ frame,
1516
+ animates: (when, every = 1) => setKeepers((all) => [...all, {
1517
+ when,
1518
+ every
1519
+ }]),
1520
+ tween
1521
+ };
1522
+ }
1523
+ function createFlashes(motion, rows) {
1524
+ const [flashes, setFlashes] = createSignal(new Map);
1525
+ let seen;
1526
+ createEffect(() => {
1527
+ const read = rows();
1528
+ const before = seen;
1529
+ seen = new Map(read.map((row) => [row.sessionID, row.report?.state]));
1530
+ if (!before || !motion.enabled)
1531
+ return;
1532
+ const arrived = read.filter((row) => row.report?.state === "reported" && before.has(row.sessionID) && before.get(row.sessionID) !== "reported");
1533
+ if (!arrived.length)
1534
+ return;
1535
+ const next = new Map([...untrack(flashes)].filter(([, one]) => !untrack(one.done)));
1536
+ for (const row of arrived)
1537
+ next.set(row.sessionID, motion.tween(1, 0, FLASH_MS, "outQuad"));
1538
+ setFlashes(next);
1539
+ });
1540
+ const strength = (sessionID) => {
1541
+ const one = flashes().get(sessionID);
1542
+ return one && !one.done() ? one.value() : 0;
1543
+ };
1544
+ const reported = () => [...flashes().values()].some((one) => !one.done());
1545
+ return {
1546
+ strength,
1547
+ reported
1548
+ };
1549
+ }
1550
+ function createBarFill(motion, counts) {
1551
+ const target = createMemo(() => filledCells(counts()));
1552
+ const [easing, setEasing] = createSignal();
1553
+ let last;
1554
+ createEffect(() => {
1555
+ const cells2 = target();
1556
+ const shown = counts().started > 0;
1557
+ if (last && cells2 !== last.cells) {
1558
+ const from = untrack(() => easing()?.value()) ?? last.cells;
1559
+ setEasing(last.shown ? motion.tween(from, cells2, BAR_MS, "outQuad") : undefined);
1560
+ }
1561
+ last = {
1562
+ cells: cells2,
1563
+ shown
1564
+ };
1565
+ });
1566
+ return () => easing()?.value() ?? target();
1567
+ }
1568
+ function createSpawns(motion, rows) {
1569
+ const [hold, setHold] = createSignal();
1570
+ let seen;
1571
+ createEffect(() => {
1572
+ const read = rows();
1573
+ if (!read)
1574
+ return;
1575
+ const before = seen;
1576
+ seen = new Set(read.map((row) => row.sessionID));
1577
+ if (before && motion.enabled && read.some((row) => !before.has(row.sessionID)))
1578
+ setHold(motion.tween(1, 0, FLASH_MS, "linear"));
1579
+ });
1580
+ return () => {
1581
+ const one = hold();
1582
+ return one !== undefined && !one.done();
1583
+ };
1584
+ }
1585
+ function mascotState(rows, {
1586
+ reported = false,
1587
+ spawned = false
1588
+ } = {}) {
1589
+ if (reported)
1590
+ return "report";
1591
+ if (spawned)
1592
+ return "spawn";
1593
+ if (rows.some((row) => row.state === "waiting"))
1594
+ return "waiting";
1595
+ if (rows.some((row) => row.state === "stalled"))
1596
+ return "stalled";
1597
+ if (rows.some((row) => row.state === "running"))
1598
+ return "running";
1599
+ return rows.length && rows.every((row) => row.state === "gone") ? "gone" : "idle";
1600
+ }
1601
+ function countsLine(rows) {
1602
+ const count2 = (state) => rows.filter((row) => row.state === state).length;
1603
+ return [count2("waiting") ? `${count2("waiting")} waiting on you` : "", count2("stalled") ? `${count2("stalled")} stalled` : "", count2("running") ? `${count2("running")} running` : "", count2("idle") ? `${count2("idle")} idle` : ""].filter(Boolean).join(" \xB7 ");
1604
+ }
1605
+ var GLYPH_ROWS = 12;
1606
+ var GLYPH_COLUMNS = 54;
1607
+ var ART_ROWS = 14;
1608
+ var BELOW_ROWS = 10;
1609
+ function blend(tint, rest, amount) {
1610
+ const mix = (a, b) => b + (a - b) * amount;
1611
+ return RGBA.fromValues(mix(tint.r, rest.r), mix(tint.g, rest.g), mix(tint.b, rest.b), mix(tint.a, rest.a));
1612
+ }
882
1613
  function summaryLine(summary, maxWidth = Infinity) {
883
1614
  const groups = summary.groups.map((group) => `group ${group.name} ${group.reported}/${group.members}`);
884
1615
  const parts = [summary.waiting ? `${summary.waiting} waiting on you` : "", summary.stalled ? `${summary.stalled} stalled` : "", summary.running ? `${summary.running} running` : "", ...groups, summary.idle ? `${summary.idle} idle` : ""].filter(Boolean);
@@ -893,14 +1624,14 @@ function summaryLine(summary, maxWidth = Infinity) {
893
1624
  return line(kept);
894
1625
  return parts[0];
895
1626
  }
896
- function duration(ms) {
1627
+ function duration2(ms) {
897
1628
  const seconds = Math.max(0, Math.floor(ms / 1000));
898
- const pad = (value) => String(value).padStart(2, "0");
1629
+ const pad2 = (value) => String(value).padStart(2, "0");
899
1630
  if (seconds < 60)
900
1631
  return `${seconds}s`;
901
1632
  if (seconds < 3600)
902
- return `${Math.floor(seconds / 60)}m ${pad(seconds % 60)}s`;
903
- return `${Math.floor(seconds / 3600)}h ${pad(Math.floor(seconds / 60) % 60)}m`;
1633
+ return `${Math.floor(seconds / 60)}m ${pad2(seconds % 60)}s`;
1634
+ return `${Math.floor(seconds / 3600)}h ${pad2(Math.floor(seconds / 60) % 60)}m`;
904
1635
  }
905
1636
  function parts(rows, parentID) {
906
1637
  const children = rows.filter((row) => row.parentID === parentID);
@@ -909,12 +1640,15 @@ function parts(rows, parentID) {
909
1640
  started: children.length
910
1641
  };
911
1642
  }
912
- function bar({
1643
+ function filledCells({
913
1644
  reported,
914
1645
  started
915
1646
  }, width = 10) {
916
- const filled = started ? Math.round(reported / started * width) : 0;
917
- return `[${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}] ${reported}/${started} reported`;
1647
+ return started ? Math.round(reported / started * width) : 0;
1648
+ }
1649
+ function bar(counts, width = 10, filled = filledCells(counts, width)) {
1650
+ const cells2 = Math.max(0, Math.min(width, Math.round(filled)));
1651
+ return `[${"\u2588".repeat(cells2)}${"\u2591".repeat(width - cells2)}] ${counts.reported}/${counts.started} reported`;
918
1652
  }
919
1653
  function waitLine(wait) {
920
1654
  if (wait.type === "permission")
@@ -922,7 +1656,66 @@ function waitLine(wait) {
922
1656
  return wait.type === "form" ? `shows a form: ${wait.title}` : `asks: ${wait.question}`;
923
1657
  }
924
1658
  function statusLine(row) {
925
- return [stateWord(row), reportWords(row.report, row.stopped), row.group ? `group ${row.group.name} ${row.group.standing}` : "", row.isolated ? "isolated" : ""].filter(Boolean).join(" \xB7 ");
1659
+ return statusParts(row).map((part) => part.text).join(" \xB7 ");
1660
+ }
1661
+ function statusParts(row) {
1662
+ const parts2 = [{
1663
+ text: stateWord(row),
1664
+ kind: "state"
1665
+ }, {
1666
+ text: reportWords(row.report, row.stopped),
1667
+ kind: "report"
1668
+ }, {
1669
+ text: row.group ? `group ${row.group.name} ${row.group.standing}` : "",
1670
+ kind: "plain"
1671
+ }, {
1672
+ text: row.isolated ? "isolated" : "",
1673
+ kind: "plain"
1674
+ }];
1675
+ return parts2.filter((part) => part.text);
1676
+ }
1677
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1678
+ function rowMark(row, theme, frame) {
1679
+ const {
1680
+ error,
1681
+ warning,
1682
+ success,
1683
+ info
1684
+ } = theme.text.feedback;
1685
+ if (row.state === "gone")
1686
+ return {
1687
+ mark: " ",
1688
+ color: undefined
1689
+ };
1690
+ if (row.state === "stalled")
1691
+ return {
1692
+ mark: "!",
1693
+ color: error.base
1694
+ };
1695
+ if (row.state === "waiting")
1696
+ return {
1697
+ mark: "\u25CF",
1698
+ color: warning.base
1699
+ };
1700
+ if (row.state === "running")
1701
+ return {
1702
+ mark: frame === undefined ? "\u2022" : SPINNER[frame % SPINNER.length],
1703
+ color: info.base
1704
+ };
1705
+ if (!row.stopped && (row.outcome === "failed" || row.outcome === "interrupted"))
1706
+ return {
1707
+ mark: "\u2717",
1708
+ color: error.base
1709
+ };
1710
+ if (row.report?.state === "reported")
1711
+ return {
1712
+ mark: "\u2713",
1713
+ color: success.base
1714
+ };
1715
+ return {
1716
+ mark: " ",
1717
+ color: undefined
1718
+ };
926
1719
  }
927
1720
  function stateWord(row) {
928
1721
  if (row.state === "waiting")
@@ -935,21 +1728,23 @@ function stateWord(row) {
935
1728
  return row.outcome;
936
1729
  return row.state;
937
1730
  }
938
- function reportWords(report, stopped) {
939
- if (!report)
1731
+ function reportWords(report2, stopped) {
1732
+ if (!report2)
940
1733
  return "";
941
- if (report.state === "owed")
1734
+ if (report2.state === "owed")
942
1735
  return "owes a report";
943
- if (report.state === "ended")
1736
+ if (report2.state === "ended")
944
1737
  return stopped ? "" : "ended without a report";
945
- return ["reported", report.status].filter(Boolean).join(" ");
1738
+ return ["reported", report2.status].filter(Boolean).join(" ");
946
1739
  }
947
- var counted = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
948
- function activityLine(row, now) {
949
- return [`ran ${duration((row.idle ?? now) - row.created)}`, row.tool ? `last ${row.tool.name} ${duration(now - row.tool.at)} ago` : "no tool call yet", row.cost !== undefined ? `$${row.cost.toFixed(2)}` : ""].filter(Boolean).join(" \xB7 ");
1740
+ var counted = (count2, noun) => `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
1741
+ var CONTEXT_WARN = 0.8;
1742
+ function activityParts(row, now) {
1743
+ return [`ran ${duration2((row.idle ?? now) - row.created)}`, row.tool ? `last ${row.tool.name} ${duration2(now - row.tool.at)} ago` : "no tool call yet", row.context !== undefined ? `ctx ${Math.round(row.context * 100)}%` : "", row.cost !== undefined ? `$${row.cost.toFixed(2)}` : ""].filter(Boolean);
950
1744
  }
1745
+ var activityLine = (row, now) => activityParts(row, now).join(" \xB7 ");
951
1746
  function stalledLine(since, now) {
952
- return `a message waits in its inbox since ${duration(now - since)}, woken again in vain`;
1747
+ return `a message waits in its inbox since ${duration2(now - since)}, woken again in vain`;
953
1748
  }
954
1749
  function detailLines(row, rows, now) {
955
1750
  const started = parts(rows, row.sessionID);
@@ -972,7 +1767,7 @@ function courierOf(ctx) {
972
1767
  var REREAD_MS = 15000;
973
1768
  function changes(ctx, types, settle) {
974
1769
  const [changed, setChanged] = createSignal(0);
975
- const reread = setInterval(() => setChanged((count) => count + 1), REREAD_MS);
1770
+ const reread = setInterval(() => setChanged((count2) => count2 + 1), REREAD_MS);
976
1771
  let settling;
977
1772
  const unlisten = ctx.data.listen(({
978
1773
  details
@@ -981,7 +1776,7 @@ function changes(ctx, types, settle) {
981
1776
  return;
982
1777
  settling = setTimeout(() => {
983
1778
  settling = undefined;
984
- setChanged((count) => count + 1);
1779
+ setChanged((count2) => count2 + 1);
985
1780
  }, settle);
986
1781
  });
987
1782
  return [changed, () => {
@@ -1107,8 +1902,8 @@ async function confirmStop(ctx, courier, location, by, target) {
1107
1902
  }
1108
1903
  return true;
1109
1904
  }
1110
- function forgottenLine(title, result) {
1111
- const name = `"${oneLine(title)}"`;
1905
+ function forgottenLine2(title, result) {
1906
+ const name = `"${oneLine3(title)}"`;
1112
1907
  if (result.outcome === "kept")
1113
1908
  return `Kept ${name} and its worktree: ${result.reason}`;
1114
1909
  if (result.outcome === "removed")
@@ -1119,7 +1914,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1119
1914
  const what = target.isolated ? "Its worktree is removed unless it has uncommitted changes or commits found nowhere else." : "It leaves this tree and courier_children.";
1120
1915
  const confirmed = await ctx.ui.dialog.confirm({
1121
1916
  title: "Forget session",
1122
- message: `Forget "${oneLine(target.title)}" (${target.sessionID})? ${what}`,
1917
+ message: `Forget "${oneLine3(target.title)}" (${target.sessionID})? ${what}`,
1123
1918
  label: {
1124
1919
  confirm: "Forget",
1125
1920
  cancel: "Keep"
@@ -1136,7 +1931,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1136
1931
  });
1137
1932
  ctx.ui.toast.show({
1138
1933
  title: "courier",
1139
- message: forgottenLine(target.title, result),
1934
+ message: forgottenLine2(target.title, result),
1140
1935
  variant: result.outcome === "kept" ? "warning" : "success"
1141
1936
  });
1142
1937
  if (result.outcome !== "kept")
@@ -1152,7 +1947,25 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1152
1947
  }
1153
1948
  var FOCUS_PROMPT = "pane.focus.left";
1154
1949
  var SPLIT_WIDTH = 108;
1155
- var oneLine = (title) => title.replace(/[\r\n]+/g, " ");
1950
+ var oneLine3 = (title) => title.replace(/[\r\n]+/g, " ");
1951
+ var digbyColours = (theme) => ({
1952
+ b: theme.text.base,
1953
+ k: theme.background.base,
1954
+ g: theme.background.raised.max,
1955
+ m: theme.text.muted,
1956
+ a: theme.text.action.primary.selected,
1957
+ w: theme.text.feedback.warning.base,
1958
+ e: theme.text.feedback.error.base,
1959
+ s: theme.text.feedback.success.base
1960
+ });
1961
+ function restBackground(theme, {
1962
+ selected,
1963
+ hovered
1964
+ }) {
1965
+ if (selected)
1966
+ return theme.background.raised.high;
1967
+ return hovered ? theme.background.raised.base : undefined;
1968
+ }
1156
1969
  function TreePanel(props) {
1157
1970
  const {
1158
1971
  ctx,
@@ -1246,13 +2059,25 @@ function TreePanel(props) {
1246
2059
  if (next)
1247
2060
  setSelectedID(next.sessionID);
1248
2061
  };
2062
+ const openRow = (row) => ctx.ui.router.navigate({
2063
+ type: "session",
2064
+ sessionID: row.sessionID
2065
+ });
1249
2066
  const open = () => {
1250
2067
  const row = current();
1251
2068
  if (row)
1252
- ctx.ui.router.navigate({
1253
- type: "session",
1254
- sessionID: row.sessionID
1255
- });
2069
+ openRow(row);
2070
+ };
2071
+ const [hovered, setHovered] = createSignal();
2072
+ createEffect(() => {
2073
+ const id = hovered();
2074
+ if (id !== undefined && !rows().some((row) => row.sessionID === id))
2075
+ setHovered(undefined);
2076
+ });
2077
+ let pressed;
2078
+ const click = (row) => {
2079
+ setSelectedID(row.sessionID);
2080
+ openRow(row);
1256
2081
  };
1257
2082
  const stopSelected = () => {
1258
2083
  const row = current();
@@ -1270,7 +2095,7 @@ function TreePanel(props) {
1270
2095
  if (row.state !== "gone")
1271
2096
  return ctx.ui.toast.show({
1272
2097
  title: "courier",
1273
- message: `x forgets only a gone session; "${oneLine(row.title)}" is ${row.state}.`,
2098
+ message: `x forgets only a gone session; "${oneLine3(row.title)}" is ${row.state}.`,
1274
2099
  variant: "warning"
1275
2100
  });
1276
2101
  confirmForget(ctx, courier, location, view.root, {
@@ -1335,6 +2160,61 @@ function TreePanel(props) {
1335
2160
  }]
1336
2161
  }));
1337
2162
  const root = () => parts(rows(), view.root);
2163
+ const motion = createMotion(props.animations);
2164
+ motion.animates(() => rows().some((row) => row.state === "running"));
2165
+ const flashes = createFlashes(motion, rows);
2166
+ const filled = createBarFill(motion, root);
2167
+ const frame = () => motion.enabled ? motion.frame() : undefined;
2168
+ const spawned = createSpawns(motion, () => latest()?.tree?.rows);
2169
+ const [size, setSize] = createSignal({
2170
+ width: 0,
2171
+ height: 0
2172
+ });
2173
+ const height = () => size().height;
2174
+ const empty = () => Boolean(latest()?.tree && !rows().length);
2175
+ const glyphShown = () => height() >= GLYPH_ROWS && size().width >= GLYPH_COLUMNS;
2176
+ const artShown = () => empty() && height() >= ART_ROWS;
2177
+ const [listHeight, setListHeight] = createSignal(0);
2178
+ const treeLines = () => rows().reduce((lines, row) => lines + 1 + detailLines(row, rows(), now()).length, 0);
2179
+ const belowShown = () => rows().length > 0 && listHeight() - treeLines() >= BELOW_ROWS;
2180
+ const headerGlyph = () => glyphShown() && !artShown() && !belowShown();
2181
+ const mascot = createMemo(() => mascotState(rows(), {
2182
+ reported: flashes.reported(),
2183
+ spawned: spawned()
2184
+ }));
2185
+ motion.animates(() => (glyphShown() || artShown() || belowShown()) && mascot() !== "gone", MASCOT_EVERY);
2186
+ const mascotFrame = () => motion.enabled ? Math.floor(motion.frame() / MASCOT_EVERY) : undefined;
2187
+ const header = createMemo(() => cells(glyph(mascot(), mascotFrame())));
2188
+ const art2 = createMemo(() => art(mascot(), mascotFrame()));
2189
+ const tokens = digbyColours(theme);
2190
+ const drawn = (line) => (() => {
2191
+ var _el$4 = _$createElement("text");
2192
+ _$setProp(_el$4, "wrapMode", "none");
2193
+ _$setProp(_el$4, "flexShrink", 0);
2194
+ _$insert(_el$4, _$createComponent(Index, {
2195
+ get each() {
2196
+ return line();
2197
+ },
2198
+ children: (one) => (() => {
2199
+ var _el$5 = _$createElement("span");
2200
+ _$insert(_el$5, () => one().char);
2201
+ _$effect((_$p) => _$setProp(_el$5, "style", {
2202
+ fg: one().fg && tokens[one().fg],
2203
+ bg: one().bg && tokens[one().bg]
2204
+ }, _$p));
2205
+ return _el$5;
2206
+ })()
2207
+ }));
2208
+ return _el$4;
2209
+ })();
2210
+ const rowBackground = (row, chosen) => {
2211
+ const rest = restBackground(theme, {
2212
+ selected: chosen && input.focused,
2213
+ hovered: hovered() === row.sessionID
2214
+ });
2215
+ const strength = flashes.strength(row.sessionID);
2216
+ return strength > 0 ? blend(theme.background.feedback.success.base, rest ?? theme.background.base, strength) : rest;
2217
+ };
1338
2218
  const rowColor = (row) => {
1339
2219
  if (row.state === "stalled")
1340
2220
  return theme.text.feedback.error.base;
@@ -1342,84 +2222,153 @@ function TreePanel(props) {
1342
2222
  return theme.text.feedback.warning.base;
1343
2223
  return row.state === "gone" ? theme.text.muted : theme.text.base;
1344
2224
  };
2225
+ const partColor = (row, kind) => {
2226
+ const marked = rowMark(row, theme);
2227
+ if (kind === "state" && row.state === "running")
2228
+ return marked.color;
2229
+ if (kind === "report" && row.report?.state === "reported")
2230
+ return theme.text.feedback.success.base;
2231
+ return theme.text.muted;
2232
+ };
1345
2233
  return (() => {
1346
- var _el$4 = _$createElement("box"), _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$15 = _$createElement("scrollbox"), _el$16 = _$createElement("text");
1347
- _$insertNode(_el$4, _el$5);
1348
- _$insertNode(_el$4, _el$15);
1349
- _$insertNode(_el$4, _el$16);
1350
- _$setProp(_el$4, "flexDirection", "column");
1351
- _$setProp(_el$4, "flexGrow", 1);
1352
- _$setProp(_el$4, "paddingLeft", 1);
1353
- _$setProp(_el$4, "paddingRight", 1);
1354
- _$insertNode(_el$5, _el$6);
1355
- _$setProp(_el$5, "flexDirection", "row");
1356
- _$setProp(_el$5, "flexShrink", 0);
1357
- _$setProp(_el$5, "gap", 2);
1358
- _$insertNode(_el$6, _$createTextNode(`courier tree`));
1359
- _$setProp(_el$6, "wrapMode", "none");
1360
- _$insert(_el$5, _$createComponent(Show, {
2234
+ var _el$6 = _$createElement("box"), _el$7 = _$createElement("box"), _el$8 = _$createElement("text"), _el$20 = _$createElement("scrollbox"), _el$22 = _$createElement("text");
2235
+ _$insertNode(_el$6, _el$7);
2236
+ _$insertNode(_el$6, _el$20);
2237
+ _$insertNode(_el$6, _el$22);
2238
+ _$setProp(_el$6, "flexDirection", "column");
2239
+ _$setProp(_el$6, "flexGrow", 1);
2240
+ _$setProp(_el$6, "paddingLeft", 1);
2241
+ _$setProp(_el$6, "paddingRight", 1);
2242
+ _$setProp(_el$6, "onSizeChange", function() {
2243
+ setSize({
2244
+ width: this.width,
2245
+ height: this.height
2246
+ });
2247
+ });
2248
+ _$setProp(_el$6, "onMouseDrag", () => pressed = undefined);
2249
+ _$insertNode(_el$7, _el$8);
2250
+ _$setProp(_el$7, "flexDirection", "row");
2251
+ _$setProp(_el$7, "flexShrink", 0);
2252
+ _$setProp(_el$7, "gap", 2);
2253
+ _$insert(_el$7, _$createComponent(Show, {
2254
+ get when() {
2255
+ return headerGlyph();
2256
+ },
2257
+ get children() {
2258
+ return drawn(() => header()[0]);
2259
+ }
2260
+ }), _el$8);
2261
+ _$insertNode(_el$8, _$createTextNode(`courier tree`));
2262
+ _$setProp(_el$8, "wrapMode", "none");
2263
+ _$insert(_el$7, _$createComponent(Show, {
1361
2264
  get when() {
1362
2265
  return root().started;
1363
2266
  },
1364
2267
  get children() {
1365
- var _el$8 = _$createElement("text");
1366
- _$setProp(_el$8, "wrapMode", "none");
1367
- _$insert(_el$8, () => bar(root()));
1368
- _$effect((_$p) => _$setProp(_el$8, "fg", theme.text.muted, _$p));
1369
- return _el$8;
2268
+ var _el$0 = _$createElement("text");
2269
+ _$setProp(_el$0, "wrapMode", "none");
2270
+ _$insert(_el$0, () => bar(root(), 10, filled()));
2271
+ _$effect((_$p) => _$setProp(_el$0, "fg", tokens.a, _$p));
2272
+ return _el$0;
1370
2273
  }
1371
2274
  }), null);
1372
- _$insert(_el$5, _$createComponent(Show, {
2275
+ _$insert(_el$7, _$createComponent(Show, {
1373
2276
  get when() {
1374
2277
  return latest()?.tree?.truncated;
1375
2278
  },
1376
2279
  get children() {
1377
- var _el$9 = _$createElement("text"), _el$0 = _$createTextNode(` older left out`);
1378
- _$insertNode(_el$9, _el$0);
1379
- _$setProp(_el$9, "wrapMode", "none");
1380
- _$insert(_el$9, () => latest()?.tree?.truncated, _el$0);
1381
- _$effect((_$p) => _$setProp(_el$9, "fg", theme.text.feedback.warning.base, _$p));
1382
- return _el$9;
2280
+ var _el$1 = _$createElement("text"), _el$10 = _$createTextNode(` older left out`);
2281
+ _$insertNode(_el$1, _el$10);
2282
+ _$setProp(_el$1, "wrapMode", "none");
2283
+ _$insert(_el$1, () => latest()?.tree?.truncated, _el$10);
2284
+ _$effect((_$p) => _$setProp(_el$1, "fg", theme.text.feedback.warning.base, _$p));
2285
+ return _el$1;
1383
2286
  }
1384
2287
  }), null);
1385
- _$insert(_el$4, _$createComponent(Show, {
2288
+ _$insert(_el$6, _$createComponent(Show, {
2289
+ get when() {
2290
+ return glyphShown();
2291
+ },
2292
+ get children() {
2293
+ var _el$11 = _$createElement("box"), _el$12 = _$createElement("text");
2294
+ _$insertNode(_el$11, _el$12);
2295
+ _$setProp(_el$11, "flexDirection", "row");
2296
+ _$setProp(_el$11, "flexShrink", 0);
2297
+ _$setProp(_el$11, "gap", 2);
2298
+ _$insert(_el$11, _$createComponent(Show, {
2299
+ get when() {
2300
+ return headerGlyph();
2301
+ },
2302
+ get children() {
2303
+ return drawn(() => header()[1]);
2304
+ }
2305
+ }), _el$12);
2306
+ _$setProp(_el$12, "wrapMode", "none");
2307
+ _$insert(_el$12, () => countsLine(rows()));
2308
+ _$effect((_$p) => _$setProp(_el$12, "fg", theme.text.muted, _$p));
2309
+ return _el$11;
2310
+ }
2311
+ }), _el$20);
2312
+ _$insert(_el$6, _$createComponent(Show, {
1386
2313
  get when() {
1387
2314
  return latest()?.error;
1388
2315
  },
1389
2316
  get children() {
1390
- var _el$1 = _$createElement("text"), _el$10 = _$createTextNode(`courier RPC failed: `);
1391
- _$insertNode(_el$1, _el$10);
1392
- _$insert(_el$1, () => latest()?.error, null);
1393
- _$effect((_$p) => _$setProp(_el$1, "fg", theme.text.feedback.error.base, _$p));
1394
- return _el$1;
2317
+ var _el$13 = _$createElement("text"), _el$14 = _$createTextNode(`courier RPC failed: `);
2318
+ _$insertNode(_el$13, _el$14);
2319
+ _$insert(_el$13, () => latest()?.error, null);
2320
+ _$effect((_$p) => _$setProp(_el$13, "fg", theme.text.feedback.error.base, _$p));
2321
+ return _el$13;
1395
2322
  }
1396
- }), _el$15);
1397
- _$insert(_el$4, _$createComponent(Show, {
2323
+ }), _el$20);
2324
+ _$insert(_el$6, _$createComponent(Show, {
1398
2325
  get when() {
1399
2326
  return latest()?.tree?.stalled;
1400
2327
  },
1401
2328
  get children() {
1402
- var _el$11 = _$createElement("text"), _el$12 = _$createTextNode(`This session is stalled: `);
1403
- _$insertNode(_el$11, _el$12);
1404
- _$insert(_el$11, () => stalledLine(latest().tree.stalled, now()), null);
1405
- _$effect((_$p) => _$setProp(_el$11, "fg", theme.text.feedback.error.base, _$p));
1406
- return _el$11;
2329
+ var _el$15 = _$createElement("text"), _el$16 = _$createTextNode(`This session is stalled: `);
2330
+ _$insertNode(_el$15, _el$16);
2331
+ _$insert(_el$15, () => stalledLine(latest().tree.stalled, now()), null);
2332
+ _$effect((_$p) => _$setProp(_el$15, "fg", theme.text.feedback.error.base, _$p));
2333
+ return _el$15;
1407
2334
  }
1408
- }), _el$15);
1409
- _$insert(_el$4, _$createComponent(Show, {
2335
+ }), _el$20);
2336
+ _$insert(_el$6, _$createComponent(Show, {
1410
2337
  get when() {
1411
2338
  return _$memo(() => !!latest()?.tree)() && !rows().length;
1412
2339
  },
1413
2340
  get children() {
1414
- var _el$13 = _$createElement("text");
1415
- _$insertNode(_el$13, _$createTextNode(`No session started with courier_spawn under this one.`));
1416
- _$effect((_$p) => _$setProp(_el$13, "fg", theme.text.muted, _$p));
1417
- return _el$13;
2341
+ return [(() => {
2342
+ var _el$17 = _$createElement("text");
2343
+ _$insertNode(_el$17, _$createTextNode(`No session started with courier_spawn under this one.`));
2344
+ _$effect((_$p) => _$setProp(_el$17, "fg", theme.text.muted, _$p));
2345
+ return _el$17;
2346
+ })(), _$createComponent(Show, {
2347
+ get when() {
2348
+ return artShown();
2349
+ },
2350
+ get children() {
2351
+ var _el$19 = _$createElement("box");
2352
+ _$setProp(_el$19, "flexDirection", "column");
2353
+ _$setProp(_el$19, "flexShrink", 0);
2354
+ _$setProp(_el$19, "paddingTop", 1);
2355
+ _$insert(_el$19, _$createComponent(Index, {
2356
+ get each() {
2357
+ return art2();
2358
+ },
2359
+ children: (line) => drawn(line)
2360
+ }));
2361
+ return _el$19;
2362
+ }
2363
+ })];
1418
2364
  }
1419
- }), _el$15);
1420
- _$use((value) => scroll = value, _el$15);
1421
- _$setProp(_el$15, "flexGrow", 1);
1422
- _$insert(_el$15, _$createComponent(For, {
2365
+ }), _el$20);
2366
+ _$use((value) => scroll = value, _el$20);
2367
+ _$setProp(_el$20, "flexGrow", 1);
2368
+ _$setProp(_el$20, "onSizeChange", function() {
2369
+ setListHeight(this.height);
2370
+ });
2371
+ _$insert(_el$20, _$createComponent(For, {
1423
2372
  get each() {
1424
2373
  return rows();
1425
2374
  },
@@ -1427,77 +2376,165 @@ function TreePanel(props) {
1427
2376
  const chosen = () => index() === selected();
1428
2377
  const indent = " ".repeat(row.depth - 1);
1429
2378
  return (() => {
1430
- var _el$18 = _$createElement("box"), _el$19 = _$createElement("box"), _el$20 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` `);
1431
- _$insertNode(_el$18, _el$19);
1432
- _$setProp(_el$18, "flexDirection", "column");
1433
- _$setProp(_el$18, "flexShrink", 0);
1434
- _$insertNode(_el$19, _el$20);
1435
- _$insertNode(_el$19, _el$21);
1436
- _$setProp(_el$19, "flexDirection", "row");
1437
- _$setProp(_el$19, "flexShrink", 0);
1438
- _$setProp(_el$20, "wrapMode", "none");
1439
- _$setProp(_el$20, "flexShrink", 1);
1440
- _$setProp(_el$20, "minWidth", 0);
1441
- _$insert(_el$20, indent, null);
1442
- _$insert(_el$20, () => chosen() ? "\u25B8 " : " ", null);
1443
- _$insert(_el$20, () => oneLine(row.title), null);
1444
- _$insert(_el$20, () => row.sessionID === input.sessionID ? " (on screen)" : "", null);
1445
- _$insertNode(_el$21, _el$22);
1446
- _$setProp(_el$21, "wrapMode", "none");
1447
- _$setProp(_el$21, "flexShrink", 0);
1448
- _$insert(_el$21, () => statusLine(row), null);
1449
- _$insert(_el$18, _$createComponent(For, {
2379
+ var _el$24 = _$createElement("box"), _el$25 = _$createElement("box"), _el$26 = _$createElement("text"), _el$27 = _$createElement("text"), _el$28 = _$createTextNode(` `), _el$29 = _$createElement("text"), _el$30 = _$createElement("text");
2380
+ _$insertNode(_el$24, _el$25);
2381
+ _$setProp(_el$24, "flexDirection", "column");
2382
+ _$setProp(_el$24, "flexShrink", 0);
2383
+ _$setProp(_el$24, "onMouseOver", () => setHovered(row.sessionID));
2384
+ _$setProp(_el$24, "onMouseOut", () => setHovered((id) => id === row.sessionID ? undefined : id));
2385
+ _$setProp(_el$24, "onMouseDown", (event) => pressed = event.button === MouseButton.LEFT ? row.sessionID : undefined);
2386
+ _$setProp(_el$24, "onMouseUp", (event) => {
2387
+ if (event.button !== MouseButton.LEFT)
2388
+ return;
2389
+ event.stopPropagation();
2390
+ const down = pressed;
2391
+ pressed = undefined;
2392
+ if (down === row.sessionID)
2393
+ click(row);
2394
+ });
2395
+ _$insertNode(_el$25, _el$26);
2396
+ _$insertNode(_el$25, _el$27);
2397
+ _$insertNode(_el$25, _el$29);
2398
+ _$insertNode(_el$25, _el$30);
2399
+ _$setProp(_el$25, "flexDirection", "row");
2400
+ _$setProp(_el$25, "flexShrink", 0);
2401
+ _$setProp(_el$26, "wrapMode", "none");
2402
+ _$setProp(_el$26, "flexShrink", 0);
2403
+ _$insert(_el$26, indent, null);
2404
+ _$insert(_el$26, () => chosen() ? "\u25B8 " : " ", null);
2405
+ _$insertNode(_el$27, _el$28);
2406
+ _$setProp(_el$27, "wrapMode", "none");
2407
+ _$setProp(_el$27, "flexShrink", 0);
2408
+ _$insert(_el$27, () => rowMark(row, theme, frame()).mark, _el$28);
2409
+ _$setProp(_el$29, "wrapMode", "none");
2410
+ _$setProp(_el$29, "flexShrink", 1);
2411
+ _$setProp(_el$29, "minWidth", 0);
2412
+ _$insert(_el$29, () => oneLine3(row.title), null);
2413
+ _$insert(_el$29, () => row.sessionID === input.sessionID ? " (on screen)" : "", null);
2414
+ _$insertNode(_el$30, _$createTextNode(` `));
2415
+ _$setProp(_el$30, "wrapMode", "none");
2416
+ _$setProp(_el$30, "flexShrink", 0);
2417
+ _$insert(_el$25, _$createComponent(For, {
2418
+ get each() {
2419
+ return statusParts(row);
2420
+ },
2421
+ children: (part, at2) => [_$createComponent(Show, {
2422
+ get when() {
2423
+ return at2();
2424
+ },
2425
+ get children() {
2426
+ var _el$32 = _$createElement("text");
2427
+ _$insertNode(_el$32, _$createTextNode(` \xB7 `));
2428
+ _$setProp(_el$32, "wrapMode", "none");
2429
+ _$setProp(_el$32, "flexShrink", 0);
2430
+ _$effect((_$p) => _$setProp(_el$32, "fg", theme.text.muted, _$p));
2431
+ return _el$32;
2432
+ }
2433
+ }), (() => {
2434
+ var _el$34 = _$createElement("text");
2435
+ _$setProp(_el$34, "wrapMode", "none");
2436
+ _$setProp(_el$34, "flexShrink", 0);
2437
+ _$insert(_el$34, () => part.text);
2438
+ _$effect((_$p) => _$setProp(_el$34, "fg", partColor(row, part.kind), _$p));
2439
+ return _el$34;
2440
+ })()]
2441
+ }), null);
2442
+ _$insert(_el$24, _$createComponent(For, {
1450
2443
  get each() {
1451
2444
  return detailLines(row, rows(), now());
1452
2445
  },
1453
2446
  children: (line) => (() => {
1454
- var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` `);
1455
- _$insertNode(_el$23, _el$24);
1456
- _$setProp(_el$23, "wrapMode", "none");
1457
- _$insert(_el$23, indent, _el$24);
1458
- _$insert(_el$23, line, null);
1459
- _$effect((_$p) => _$setProp(_el$23, "fg", theme.text.muted, _$p));
1460
- return _el$23;
2447
+ var _el$35 = _$createElement("text"), _el$36 = _$createTextNode(` `);
2448
+ _$insertNode(_el$35, _el$36);
2449
+ _$setProp(_el$35, "wrapMode", "none");
2450
+ _$insert(_el$35, indent, _el$36);
2451
+ _$insert(_el$35, _$createComponent(Show, {
2452
+ get when() {
2453
+ return _$memo(() => row.context !== undefined)() && Math.round(row.context * 100) > CONTEXT_WARN * 100;
2454
+ },
2455
+ fallback: line,
2456
+ get children() {
2457
+ return _$createComponent(For, {
2458
+ get each() {
2459
+ return line.split(" \xB7 ");
2460
+ },
2461
+ children: (part, at2) => [_$memo(() => at2() ? " \xB7 " : ""), _$memo(() => _$memo(() => part === `ctx ${Math.round(row.context * 100)}%`)() ? (() => {
2462
+ var _el$37 = _$createElement("span");
2463
+ _$insert(_el$37, part);
2464
+ _$effect((_$p) => _$setProp(_el$37, "style", {
2465
+ fg: theme.text.feedback.warning.base
2466
+ }, _$p));
2467
+ return _el$37;
2468
+ })() : part)]
2469
+ });
2470
+ }
2471
+ }), null);
2472
+ _$effect((_$p) => _$setProp(_el$35, "fg", theme.text.muted, _$p));
2473
+ return _el$35;
1461
2474
  })()
1462
2475
  }), null);
1463
2476
  _$effect((_p$) => {
1464
- var _v$3 = `courier-row-${index()}`, _v$4 = chosen() && input.focused ? theme.background.raised.high : undefined, _v$5 = rowColor(row), _v$6 = theme.text.muted;
1465
- _v$3 !== _p$.e && (_p$.e = _$setProp(_el$18, "id", _v$3, _p$.e));
1466
- _v$4 !== _p$.t && (_p$.t = _$setProp(_el$18, "backgroundColor", _v$4, _p$.t));
1467
- _v$5 !== _p$.a && (_p$.a = _$setProp(_el$20, "fg", _v$5, _p$.a));
1468
- _v$6 !== _p$.o && (_p$.o = _$setProp(_el$21, "fg", _v$6, _p$.o));
2477
+ var _v$3 = `courier-row-${index()}`, _v$4 = rowBackground(row, chosen()), _v$5 = rowColor(row), _v$6 = rowMark(row, theme).color, _v$7 = rowColor(row), _v$8 = theme.text.muted;
2478
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$24, "id", _v$3, _p$.e));
2479
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$24, "backgroundColor", _v$4, _p$.t));
2480
+ _v$5 !== _p$.a && (_p$.a = _$setProp(_el$26, "fg", _v$5, _p$.a));
2481
+ _v$6 !== _p$.o && (_p$.o = _$setProp(_el$27, "fg", _v$6, _p$.o));
2482
+ _v$7 !== _p$.i && (_p$.i = _$setProp(_el$29, "fg", _v$7, _p$.i));
2483
+ _v$8 !== _p$.n && (_p$.n = _$setProp(_el$30, "fg", _v$8, _p$.n));
1469
2484
  return _p$;
1470
2485
  }, {
1471
2486
  e: undefined,
1472
2487
  t: undefined,
1473
2488
  a: undefined,
1474
- o: undefined
2489
+ o: undefined,
2490
+ i: undefined,
2491
+ n: undefined
1475
2492
  });
1476
- return _el$18;
2493
+ return _el$24;
1477
2494
  })();
1478
2495
  }
1479
- }));
1480
- _$insertNode(_el$16, _$createTextNode(`\u2191\u2193 select \xB7 enter open \xB7 s stop \xB7 x forget \xB7 r refresh \xB7 f fullscreen \xB7 q close`));
1481
- _$setProp(_el$16, "wrapMode", "none");
1482
- _$setProp(_el$16, "flexShrink", 0);
2496
+ }), null);
2497
+ _$insert(_el$20, _$createComponent(Show, {
2498
+ get when() {
2499
+ return belowShown();
2500
+ },
2501
+ get children() {
2502
+ var _el$21 = _$createElement("box");
2503
+ _$setProp(_el$21, "flexDirection", "column");
2504
+ _$setProp(_el$21, "flexShrink", 0);
2505
+ _$setProp(_el$21, "paddingTop", 1);
2506
+ _$insert(_el$21, _$createComponent(Index, {
2507
+ get each() {
2508
+ return art2();
2509
+ },
2510
+ children: (line) => drawn(line)
2511
+ }));
2512
+ return _el$21;
2513
+ }
2514
+ }), null);
2515
+ _$insertNode(_el$22, _$createTextNode(`\u2191\u2193 select \xB7 enter open \xB7 s stop \xB7 x forget \xB7 r refresh \xB7 f fullscreen \xB7 q close`));
2516
+ _$setProp(_el$22, "wrapMode", "none");
2517
+ _$setProp(_el$22, "flexShrink", 0);
1483
2518
  _$effect((_p$) => {
1484
- var _v$ = theme.text.base, _v$2 = theme.text.muted;
1485
- _v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$, _p$.e));
1486
- _v$2 !== _p$.t && (_p$.t = _$setProp(_el$16, "fg", _v$2, _p$.t));
2519
+ var _v$ = tokens.a, _v$2 = theme.text.muted;
2520
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$8, "fg", _v$, _p$.e));
2521
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$22, "fg", _v$2, _p$.t));
1487
2522
  return _p$;
1488
2523
  }, {
1489
2524
  e: undefined,
1490
2525
  t: undefined
1491
2526
  });
1492
- return _el$4;
2527
+ return _el$6;
1493
2528
  })();
1494
2529
  }
1495
2530
  var USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
1496
- function addTreePanel(ctx) {
2531
+ var usageWith = (verbs) => [USAGE, ...Object.values(verbs).map((verb) => verb.usage)].join("; ");
2532
+ function addTreePanel(ctx, verbs = {}) {
1497
2533
  const {
1498
2534
  courier,
1499
2535
  location
1500
2536
  } = courierOf(ctx);
2537
+ const animations = animationsOn(ctx.options);
1501
2538
  const [changed, stopListening] = changes(ctx, PANEL_CHANGES, PANEL_SETTLE_MS);
1502
2539
  let following;
1503
2540
  let attempt = 0;
@@ -1531,7 +2568,8 @@ function addTreePanel(ctx) {
1531
2568
  },
1532
2569
  courier,
1533
2570
  location,
1534
- changed
2571
+ changed,
2572
+ animations
1535
2573
  });
1536
2574
  }
1537
2575
  })
@@ -1557,7 +2595,7 @@ function addTreePanel(ctx) {
1557
2595
  return;
1558
2596
  });
1559
2597
  const follow = (from, sessionID) => {
1560
- const token = ++attempt;
2598
+ const token2 = ++attempt;
1561
2599
  const under = (tree) => tree?.rows.some((row) => row.sessionID === sessionID) === true;
1562
2600
  Object.assign(from, {
1563
2601
  at: undefined,
@@ -1568,7 +2606,7 @@ function addTreePanel(ctx) {
1568
2606
  (async () => {
1569
2607
  const inside = await treeOf(from.root);
1570
2608
  const own = under(inside) ? undefined : await treeOf(sessionID);
1571
- if (token !== attempt || following !== from)
2609
+ if (token2 !== attempt || following !== from)
1572
2610
  return;
1573
2611
  if (under(inside))
1574
2612
  show(from, sessionID, from.root, inside);
@@ -1682,7 +2720,7 @@ function addTreePanel(ctx) {
1682
2720
  commands: [{
1683
2721
  id: "courier.command",
1684
2722
  title: "Courier",
1685
- description: USAGE,
2723
+ description: usageWith(verbs),
1686
2724
  group: "Courier",
1687
2725
  slash: {
1688
2726
  name: "courier",
@@ -1694,9 +2732,12 @@ function addTreePanel(ctx) {
1694
2732
  return openPanel();
1695
2733
  if (verb === "stop")
1696
2734
  return stopUnder(target);
2735
+ const further = Object.hasOwn(verbs, verb) ? verbs[verb] : undefined;
2736
+ if (further)
2737
+ return further.run();
1697
2738
  ctx.ui.toast.show({
1698
2739
  title: "courier",
1699
- message: USAGE,
2740
+ message: usageWith(verbs),
1700
2741
  variant: "warning"
1701
2742
  });
1702
2743
  }
@@ -1766,7 +2807,13 @@ var tui_default = Plugin.define({
1766
2807
  id: "courier.tui",
1767
2808
  setup: (ctx) => {
1768
2809
  const inbox = addAsksInbox(ctx, CHANGES);
1769
- const cleanups = [inbox.cleanup, addTreeStatus(ctx, (sessionID) => void inbox.open(sessionID)), addTreePanel(ctx), addTreeAttention(ctx)];
2810
+ const memory = addMemoryView(ctx);
2811
+ const cleanups = [inbox.cleanup, addTreeStatus(ctx, (sessionID) => void inbox.open(sessionID)), addTreePanel(ctx, {
2812
+ memory: {
2813
+ run: () => void memory.open(),
2814
+ usage: MEMORY_USAGE
2815
+ }
2816
+ }), addTreeAttention(ctx)];
1770
2817
  return () => {
1771
2818
  for (const cleanup of cleanups.toReversed())
1772
2819
  cleanup();