opencode-courier 0.7.3 → 0.9.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 (57) hide show
  1. package/README.md +119 -136
  2. package/dist/index.js +23 -4
  3. package/dist/kernel/protocol.d.ts +2 -0
  4. package/dist/kernel/protocol.js +9 -1
  5. package/dist/memory/domain.d.ts +188 -0
  6. package/dist/memory/domain.js +209 -0
  7. package/dist/memory/events.d.ts +26 -0
  8. package/dist/memory/events.js +46 -0
  9. package/dist/memory/forget.d.ts +10 -0
  10. package/dist/memory/forget.js +20 -0
  11. package/dist/memory/index.d.ts +11 -0
  12. package/dist/memory/index.js +12 -0
  13. package/dist/memory/note.d.ts +37 -0
  14. package/dist/memory/note.js +29 -0
  15. package/dist/memory/pin.d.ts +8 -0
  16. package/dist/memory/pin.js +13 -0
  17. package/dist/memory/ports.d.ts +55 -0
  18. package/dist/memory/ports.js +1 -0
  19. package/dist/memory/recall.d.ts +27 -0
  20. package/dist/memory/recall.js +67 -0
  21. package/dist/memory/record.d.ts +7 -0
  22. package/dist/memory/record.js +124 -0
  23. package/dist/memory/rpc-handlers.d.ts +179 -0
  24. package/dist/memory/rpc-handlers.js +25 -0
  25. package/dist/memory/rpc.d.ts +299 -0
  26. package/dist/memory/rpc.js +71 -0
  27. package/dist/memory/store.d.ts +14 -0
  28. package/dist/memory/store.js +174 -0
  29. package/dist/memory/sweep.d.ts +7 -0
  30. package/dist/memory/sweep.js +16 -0
  31. package/dist/memory/text.d.ts +35 -0
  32. package/dist/memory/text.js +116 -0
  33. package/dist/memory/tools.d.ts +16 -0
  34. package/dist/memory/tools.js +78 -0
  35. package/dist/memory/tui.d.ts +82 -0
  36. package/dist/person/rpc.d.ts +4 -4
  37. package/dist/platform/json.d.ts +1 -0
  38. package/dist/platform/json.js +1 -0
  39. package/dist/rpc.d.ts +1 -0
  40. package/dist/rpc.js +1 -0
  41. package/dist/skill.js +10 -3
  42. package/dist/tree/context.d.ts +2 -2
  43. package/dist/tree/context.js +4 -3
  44. package/dist/tree/index.d.ts +6 -5
  45. package/dist/tree/index.js +5 -4
  46. package/dist/tree/mascot.js +1 -1
  47. package/dist/tree/ports.d.ts +4 -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 +20 -20
  51. package/dist/tree/rpc.d.ts +20 -20
  52. package/dist/tree/status.d.ts +5 -5
  53. package/dist/tree/subtree.d.ts +2 -2
  54. package/dist/tree/text.js +2 -1
  55. package/dist/tree/tui.d.ts +11 -3
  56. package/dist/tui.js +612 -123
  57. package/package.json +1 -1
package/dist/tui.js CHANGED
@@ -2,58 +2,536 @@
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", "gone", "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
+ reads: Schema.optionalKey(Schema.Struct({ shown: Count, read: Count, at: Schema.Number })),
33
+ pinned: Schema.optionalKey(Schema.Struct({ at: Schema.Number })),
34
+ parts: Schema.Array(Part)
23
35
  });
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)
36
+ var Records = Schema.Struct({
37
+ memory: Schema.Boolean,
38
+ projectID: Schema.String,
39
+ records: Schema.Array(Record)
29
40
  });
30
- var Question = Schema.Struct({
31
- sessionID: Schema.String,
32
- requestID: Schema.String,
33
- questions: Schema.Array(Prompt),
34
- stopped: Schema.optionalKey(Schema.Boolean)
41
+ var Forgotten = Schema.Struct({ outcome: Schema.Literals(["record", "lesson", "none"]) });
42
+ var Pinned = Schema.Struct({ outcome: Schema.Literals(["pinned", "unpinned", "none"]) });
43
+ var memoryRpc = Rpc.define({
44
+ id: "courier-memory",
45
+ methods: {
46
+ records: {
47
+ input: Schema.toStandardSchemaV1(Schema.Struct({})),
48
+ output: Schema.toStandardSchemaV1(Records)
49
+ },
50
+ forget: {
51
+ input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
52
+ output: Schema.toStandardSchemaV1(Forgotten)
53
+ },
54
+ pin: {
55
+ input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, pinned: Schema.Boolean })),
56
+ output: Schema.toStandardSchemaV1(Pinned)
57
+ }
58
+ },
59
+ events: {}
35
60
  });
36
- var Asks = Schema.Struct({
37
- top: Schema.String,
38
- sessions: Schema.Array(Session),
39
- permissions: Schema.Array(Permission),
40
- questions: Schema.Array(Question)
61
+
62
+ // src/kernel/protocol.ts
63
+ var STATUSES = ["done", "partial", "blocked", "failed"];
64
+
65
+ // src/memory/domain.ts
66
+ var RETENTION_MS = 90 * 24 * 60 * 60000;
67
+ var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "gone", "stalled", "out"];
68
+
69
+ // src/memory/text.ts
70
+ function duration(ms) {
71
+ const minutes = Math.round(ms / 60000);
72
+ if (minutes < 1)
73
+ return `${Math.max(0, Math.round(ms / 1000))} s`;
74
+ if (minutes < 60)
75
+ return `${minutes} min`;
76
+ return `${Math.floor(minutes / 60)} h ${minutes % 60} min`;
77
+ }
78
+ 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";
79
+ var NOTE_EMPTY = `Not noted: text is empty. Write one line, ${ONE_LINE}, or nothing.`;
80
+
81
+ // src/memory/tui.tsx
82
+ var MEMORY_COMMAND = "courier.memory";
83
+ var MEMORY_USAGE = "/courier memory lists this project's tree records, to read, pin or forget";
84
+ 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.";
85
+ 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.";
86
+ var LESSON_LINE_MAX = 160;
87
+ var ROW_TITLE_MAX = 46;
88
+ var TODAY = "Today";
89
+ var PINNED = "Pinned";
90
+ var LESSON_ROW = 90;
91
+ var pad = (n) => String(n).padStart(2, "0");
92
+ var localDay = (at) => new Date(at).toDateString();
93
+ var day = (at) => {
94
+ const date = new Date(at);
95
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
96
+ };
97
+ var zone = (date) => new Intl.DateTimeFormat("en-US", {
98
+ timeZoneName: "short"
99
+ }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "local";
100
+ var minute = (at) => {
101
+ const date = new Date(at);
102
+ return `${day(at)} ${pad(date.getHours())}:${pad(date.getMinutes())} ${zone(date)}`;
103
+ };
104
+ var oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
105
+ var clip = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
106
+ var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
107
+ var times = (n) => n === 1 ? "once" : `${n} times`;
108
+ var money = (cost) => `$${cost.toFixed(2)}`;
109
+ var nameOf = (record) => clip(oneLine2(record.title) || record.sessionID, 60);
110
+ function partsCounted(parts) {
111
+ if (!parts.length)
112
+ return "no parts";
113
+ const statuses = PART_STATUSES.flatMap((status) => {
114
+ const n = parts.filter((part) => part.status === status).length;
115
+ return n ? [`${n} ${status}`] : [];
116
+ }).join(", ");
117
+ return `${count(parts.length, "part")}: ${statuses}`;
118
+ }
119
+ function costOf(record) {
120
+ return record.parts.some((part) => part.cost !== undefined) ? record.parts.reduce((sum, part) => sum + (part.cost ?? 0), 0) : undefined;
121
+ }
122
+ function recordTitle(record, open) {
123
+ return `${clip(oneLine2(record.title) || record.sessionID, ROW_TITLE_MAX)}${record.sessionID === open ? " (this session)" : ""}`;
124
+ }
125
+ function recordCategory(record, now = Date.now()) {
126
+ if (record.pinned)
127
+ return PINNED;
128
+ const moved = localDay(record.movedAt);
129
+ return moved === localDay(now) ? TODAY : moved;
130
+ }
131
+ function readsLine(reads) {
132
+ return [reads.shown ? `shown ${times(reads.shown)}` : "", reads.read ? `opened ${times(reads.read)}` : ""].filter(Boolean).join(", ");
133
+ }
134
+ function lessonHeading(lesson) {
135
+ const from = lesson.depth === undefined ? "" : `, from depth ${lesson.depth}`;
136
+ return `lesson (${day(lesson.at)}${from})`;
137
+ }
138
+ function recordDescription(record) {
139
+ const cost = costOf(record);
140
+ return [partsCounted(record.parts), duration(record.movedAt - record.startedAt), cost === undefined ? "" : money(cost), record.finished ? "" : "not finished", record.reads ? readsLine(record.reads) : ""].filter(Boolean).join(" \xB7 ");
141
+ }
142
+ function recordDetails(record) {
143
+ return record.lesson ? [clip(`${lessonHeading(record.lesson)}: "${oneLine2(record.lesson.text)}"`, LESSON_LINE_MAX)] : [];
144
+ }
145
+ function partLine(part) {
146
+ 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 ");
147
+ }
148
+ function wrap(text, width) {
149
+ const rows = [];
150
+ let row = "";
151
+ for (const word of oneLine2(text).split(" ").filter(Boolean)) {
152
+ if (row && row.length + 1 + word.length <= width) {
153
+ row = `${row} ${word}`;
154
+ continue;
155
+ }
156
+ if (row)
157
+ rows.push(row);
158
+ row = word;
159
+ while (row.length > width) {
160
+ rows.push(row.slice(0, width));
161
+ row = row.slice(width);
162
+ }
163
+ }
164
+ if (row)
165
+ rows.push(row);
166
+ return rows;
167
+ }
168
+ var lessonLine = (record) => record.lesson ? {
169
+ kind: "lesson",
170
+ row: 0
171
+ } : {
172
+ kind: "fact",
173
+ key: "lesson"
174
+ };
175
+ function nearby(sessionIDs, sessionID, forgotten) {
176
+ const at = sessionIDs.indexOf(sessionID);
177
+ if (at < 0)
178
+ return forgotten ? [] : [sessionID];
179
+ return [...forgotten ? [] : [sessionID], ...sessionIDs.slice(at + 1), ...sessionIDs.slice(0, at).reverse()];
180
+ }
181
+ function detailOptions(record) {
182
+ const fact = (key, title, description) => ({
183
+ title,
184
+ value: {
185
+ kind: "fact",
186
+ key
187
+ },
188
+ category: "Tree",
189
+ ...description ? {
190
+ description
191
+ } : {}
192
+ });
193
+ const cost = costOf(record);
194
+ 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");
195
+ const reads = record.reads ? fact("reads", `${readsLine(record.reads).replace(/^./, (first) => first.toUpperCase())}`, `by other trees' courier_recall, last ${minute(record.reads.at)}`) : fact("reads", "Never shown", "no other tree's courier_recall has listed it yet");
196
+ const pinned = record.pinned ? fact("pinned", "Pinned", `since ${minute(record.pinned.at)}: listed first, never swept; p unpins`) : fact("pinned", "Not pinned", "p pins it: listed first, never swept");
197
+ const lesson = record.lesson ? [{
198
+ title: lessonHeading(record.lesson),
199
+ value: {
200
+ kind: "lesson",
201
+ row: 0
202
+ },
203
+ category: "Lesson"
204
+ }, ...wrap(record.lesson.text, LESSON_ROW).map((row, at) => ({
205
+ title: row,
206
+ value: {
207
+ kind: "lesson",
208
+ row: at + 1
209
+ },
210
+ category: "Lesson"
211
+ }))] : [{
212
+ title: "No lesson",
213
+ value: {
214
+ kind: "fact",
215
+ key: "lesson"
216
+ },
217
+ category: "Lesson",
218
+ description: "courier_note writes one line per tree"
219
+ }];
220
+ 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, reads, pinned, ...record.parts.length ? record.parts.map((part) => ({
221
+ title: `${" ".repeat(Math.max(0, part.depth - 1))}${clip(oneLine2(part.title) || part.sessionID, 60)}`,
222
+ value: {
223
+ kind: "part",
224
+ sessionID: part.sessionID
225
+ },
226
+ category: "Parts",
227
+ description: partLine(part)
228
+ })) : [{
229
+ title: "No parts",
230
+ value: {
231
+ kind: "fact",
232
+ key: "parts"
233
+ },
234
+ category: "Parts",
235
+ description: "no session was started in this tree"
236
+ }], ...lesson];
237
+ }
238
+ function forgetMessage(record, lesson) {
239
+ const name = `"${nameOf(record)}" (${day(record.movedAt)}, ${partsCounted(record.parts)})`;
240
+ if (lesson)
241
+ return `Forget the lesson of ${name}? The record keeps its facts; courier_recall no longer lists the lesson: "${clip(oneLine2(record.lesson?.text ?? ""), 120)}".`;
242
+ 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.`;
243
+ }
244
+ function forgottenLine(record, outcome) {
245
+ const name = `"${nameOf(record)}"`;
246
+ if (outcome === "record")
247
+ return `Forgot the record of ${name}`;
248
+ if (outcome === "lesson")
249
+ return `Forgot the lesson of ${name}; its facts stay`;
250
+ return `Nothing to forget for ${name}: it was already gone`;
251
+ }
252
+ function pinnedLine(record, outcome) {
253
+ const name = `"${nameOf(record)}"`;
254
+ if (outcome === "pinned")
255
+ return `Pinned ${name}: listed first, never swept`;
256
+ if (outcome === "unpinned")
257
+ return `Unpinned ${name}: kept as the others are`;
258
+ return `Nothing to pin for ${name}: it was already gone`;
259
+ }
260
+ function addMemoryView(ctx) {
261
+ const memory = ctx.client.rpc(memoryRpc);
262
+ const openSession = () => {
263
+ const route = ctx.ui.router.current();
264
+ return route.type === "session" ? route.sessionID : undefined;
265
+ };
266
+ const where = () => {
267
+ const sessionID = openSession();
268
+ return (sessionID ? ctx.data.session.get(sessionID)?.location : undefined) ?? ctx.location ?? ctx.data.location.default();
269
+ };
270
+ const reason = (error) => {
271
+ if (error instanceof Error)
272
+ return error.message;
273
+ return typeof error === "object" && error && "message" in error ? String(error.message) : String(error);
274
+ };
275
+ const fail = (what) => (error) => ctx.ui.toast.show({
276
+ title: "courier",
277
+ message: `${what}: ${reason(error)}`,
278
+ variant: "error"
279
+ });
280
+ const say = (message) => ctx.ui.toast.show({
281
+ title: "courier",
282
+ message,
283
+ variant: "info"
284
+ });
285
+ function show(options, chosen, forget, pin) {
286
+ let request = () => {
287
+ return;
288
+ };
289
+ const requested = new Promise((resolve) => {
290
+ request = resolve;
291
+ });
292
+ const choosing = ctx.ui.dialog.select({
293
+ ...options,
294
+ actions: [{
295
+ bind: "x",
296
+ title: "forget",
297
+ onTrigger: (value) => request(forget(value))
298
+ }, {
299
+ bind: "p",
300
+ title: pin.title,
301
+ onTrigger: (value) => request(pin.ask(value))
302
+ }]
303
+ });
304
+ ctx.ui.dialog.set({
305
+ size: "xlarge"
306
+ });
307
+ return Promise.race([choosing.then((value) => value === undefined ? {
308
+ kind: "back"
309
+ } : chosen(value)), requested]);
310
+ }
311
+ const showList = (read, current) => {
312
+ const open2 = openSession();
313
+ const now = Date.now();
314
+ return show({
315
+ title: `Memory (${read.records.length})`,
316
+ placeholder: "Search",
317
+ ...current ? {
318
+ current
319
+ } : {},
320
+ options: read.records.map((record) => ({
321
+ title: recordTitle(record, open2),
322
+ value: record,
323
+ category: recordCategory(record, now),
324
+ description: recordDescription(record),
325
+ details: recordDetails(record)
326
+ }))
327
+ }, (record) => ({
328
+ kind: "open",
329
+ record
330
+ }), (record) => ({
331
+ kind: "forget",
332
+ record,
333
+ lesson: false
334
+ }), {
335
+ title: "pin",
336
+ ask: (record) => ({
337
+ kind: "pin",
338
+ record
339
+ })
340
+ });
341
+ };
342
+ const showRecord = (record, current) => show({
343
+ title: `${localDay(record.movedAt)} ${nameOf(record)}`,
344
+ placeholder: "Search",
345
+ options: detailOptions(record),
346
+ ...current ? {
347
+ current
348
+ } : {}
349
+ }, () => ({
350
+ kind: "back"
351
+ }), (line) => ({
352
+ kind: "forget",
353
+ record,
354
+ lesson: line.kind === "lesson",
355
+ line
356
+ }), {
357
+ title: record.pinned ? "unpin" : "pin",
358
+ ask: (line) => ({
359
+ kind: "pin",
360
+ record,
361
+ line
362
+ })
363
+ });
364
+ async function togglePin(record) {
365
+ const pinned = !record.pinned;
366
+ try {
367
+ const result = await memory.pin({
368
+ sessionID: record.sessionID,
369
+ pinned
370
+ }, {
371
+ location: where()
372
+ });
373
+ ctx.ui.toast.show({
374
+ title: "courier",
375
+ message: pinnedLine(record, result.outcome),
376
+ variant: result.outcome === "none" ? "warning" : "success"
377
+ });
378
+ } catch (error) {
379
+ fail(pinned ? "Not pinned" : "Not unpinned")(error);
380
+ }
381
+ }
382
+ async function confirmForget(record, lesson) {
383
+ const confirmed = await ctx.ui.dialog.confirm({
384
+ title: lesson ? "Forget lesson" : "Forget record",
385
+ message: forgetMessage(record, lesson),
386
+ label: {
387
+ confirm: "Forget",
388
+ cancel: "Keep"
389
+ }
390
+ });
391
+ if (!confirmed)
392
+ return false;
393
+ try {
394
+ const result = await memory.forget({
395
+ sessionID: record.sessionID,
396
+ lesson
397
+ }, {
398
+ location: where()
399
+ });
400
+ ctx.ui.toast.show({
401
+ title: "courier",
402
+ message: forgottenLine(record, result.outcome),
403
+ variant: result.outcome === "none" ? "warning" : "success"
404
+ });
405
+ return true;
406
+ } catch (error) {
407
+ fail("Not forgotten")(error);
408
+ return false;
409
+ }
410
+ }
411
+ let opened = false;
412
+ async function open() {
413
+ if (opened)
414
+ return;
415
+ opened = true;
416
+ try {
417
+ let viewing;
418
+ let near = [];
419
+ let line;
420
+ for (;; ) {
421
+ const read = await memory.records({}, {
422
+ location: where()
423
+ }).catch(fail("The records could not be read"));
424
+ if (!read)
425
+ return;
426
+ if (!read.memory)
427
+ return say(MEMORY_OFF);
428
+ if (!read.records.length)
429
+ return say(NO_RECORDS);
430
+ const record = viewing === undefined ? undefined : read.records.find((one) => one.sessionID === viewing);
431
+ viewing = record?.sessionID;
432
+ const listed = read.records.map((one) => one.sessionID);
433
+ const current = near.map((sessionID) => read.records.find((one) => one.sessionID === sessionID)).find((one) => one !== undefined);
434
+ const at = line === "lesson" ? record && lessonLine(record) : line;
435
+ const request = await (record ? showRecord(record, at) : showList(read, current));
436
+ line = undefined;
437
+ if (request.kind === "back") {
438
+ if (!record)
439
+ return;
440
+ near = nearby(listed, record.sessionID, false);
441
+ viewing = undefined;
442
+ continue;
443
+ }
444
+ if (request.kind === "open") {
445
+ viewing = request.record.sessionID;
446
+ continue;
447
+ }
448
+ if (request.kind === "pin") {
449
+ await togglePin(request.record);
450
+ near = [request.record.sessionID];
451
+ line = request.line;
452
+ continue;
453
+ }
454
+ const forgotten = await confirmForget(request.record, request.lesson);
455
+ near = nearby(listed, request.record.sessionID, forgotten && !request.lesson);
456
+ if (record && !forgotten)
457
+ line = request.line;
458
+ else if (request.lesson)
459
+ line = "lesson";
460
+ else
461
+ viewing = undefined;
462
+ }
463
+ } finally {
464
+ opened = false;
465
+ }
466
+ }
467
+ ctx.keymap.layer(() => ({
468
+ mode: "global",
469
+ commands: [{
470
+ id: MEMORY_COMMAND,
471
+ title: "Courier: memory",
472
+ description: "The records of this project's session trees, pinned first, then newest first under the day each last moved: enter for one in full, p to pin or unpin one, x to forget one or its lesson",
473
+ group: "Courier",
474
+ palette: true,
475
+ run: () => void open()
476
+ }]
477
+ }));
478
+ return {
479
+ open
480
+ };
481
+ }
482
+
483
+ // src/person/rpc.ts
484
+ import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
485
+ import { Schema as Schema2 } from "effect";
486
+ var Count2 = Schema2.Int.check(Schema2.isGreaterThanOrEqualTo(0));
487
+ var Session = Schema2.Struct({
488
+ sessionID: Schema2.String,
489
+ title: Schema2.String,
490
+ parentID: Schema2.String,
491
+ depth: Count2,
492
+ group: Schema2.optionalKey(Schema2.String)
493
+ });
494
+ var Permission = Schema2.Struct({
495
+ sessionID: Schema2.String,
496
+ requestID: Schema2.String,
497
+ action: Schema2.String,
498
+ resources: Schema2.Array(Schema2.String),
499
+ save: Schema2.optionalKey(Schema2.Array(Schema2.String)),
500
+ passesOn: Schema2.optionalKey(Schema2.Struct({ sessionID: Schema2.String, requestID: Schema2.String }))
501
+ });
502
+ var Prompt = Schema2.Struct({
503
+ question: Schema2.String,
504
+ header: Schema2.String,
505
+ options: Schema2.Array(Schema2.Struct({ label: Schema2.String, description: Schema2.String })),
506
+ multiple: Schema2.optionalKey(Schema2.Boolean)
41
507
  });
42
- var Answered = Schema.Struct({
43
- answered: Schema.Boolean,
44
- by: Schema.optionalKey(Schema.Literals(["result", "message"])),
45
- problem: Schema.optionalKey(Schema.String)
508
+ var Question = Schema2.Struct({
509
+ sessionID: Schema2.String,
510
+ requestID: Schema2.String,
511
+ questions: Schema2.Array(Prompt),
512
+ stopped: Schema2.optionalKey(Schema2.Boolean)
513
+ });
514
+ var Asks = Schema2.Struct({
515
+ top: Schema2.String,
516
+ sessions: Schema2.Array(Session),
517
+ permissions: Schema2.Array(Permission),
518
+ questions: Schema2.Array(Question)
519
+ });
520
+ var Answered = Schema2.Struct({
521
+ answered: Schema2.Boolean,
522
+ by: Schema2.optionalKey(Schema2.Literals(["result", "message"])),
523
+ problem: Schema2.optionalKey(Schema2.String)
46
524
  });
47
- var personRpc = Rpc.define({
525
+ var personRpc = Rpc2.define({
48
526
  id: "courier-person",
49
527
  methods: {
50
528
  asks: {
51
- input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String })),
52
- output: Schema.toStandardSchemaV1(Asks)
529
+ input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
530
+ output: Schema2.toStandardSchemaV1(Asks)
53
531
  },
54
532
  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)
533
+ input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, requestID: Schema2.String, answers: Schema2.Array(Schema2.Array(Schema2.String)) })),
534
+ output: Schema2.toStandardSchemaV1(Answered)
57
535
  }
58
536
  },
59
537
  events: {}
@@ -103,7 +581,7 @@ function pathOf(asks, sessionID, topTitle, currentTitle = () => {
103
581
  titles.unshift(currentTitle(at.sessionID) || at.title);
104
582
  return [topTitle, ...titles].join(" \u203A ");
105
583
  }
106
- var clip = (text, max = 80) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
584
+ var clip2 = (text, max = 80) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
107
585
  function askTitle(ask) {
108
586
  switch (ask.kind) {
109
587
  case "permission": {
@@ -114,17 +592,17 @@ function askTitle(ask) {
114
592
  } = ask.request;
115
593
  const what = resources.length ? `${action} ${resources.join(", ")}` : action;
116
594
  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}`);
595
+ return clip2(`Permission: ${what}`);
596
+ return clip2(passesOn ? `Permission (top session): courier_answer once for "${what}"` : `Permission (top session): ${what}`);
119
597
  }
120
598
  case "question": {
121
599
  const [first, ...rest] = ask.question.questions;
122
600
  const what = first ? `${first.header}: ${first.question}` : ask.question.requestID;
123
601
  const more = rest.length ? ` (+${rest.length} more)` : "";
124
- return clip(`Question: ${what}${more}`);
602
+ return clip2(`Question: ${what}${more}`);
125
603
  }
126
604
  case "form":
127
- return clip(`Form: ${ask.form.title}`);
605
+ return clip2(`Form: ${ask.form.title}`);
128
606
  }
129
607
  }
130
608
  function askFooter(ask) {
@@ -384,12 +862,12 @@ function addAsksInbox(ctx, follows) {
384
862
  } = ask;
385
863
  const answers = [];
386
864
  for (const [index, prompt] of question.questions.entries()) {
387
- const count = question.questions.length > 1 ? ` (${index + 1}/${question.questions.length})` : "";
865
+ const count2 = question.questions.length > 1 ? ` (${index + 1}/${question.questions.length})` : "";
388
866
  const choices = prompt.options.map((option) => ({
389
867
  ...option,
390
868
  value: option.label
391
869
  }));
392
- const picked = await choose(`${prompt.header}: ${prompt.question}${count}`, choices, {
870
+ const picked = await choose(`${prompt.header}: ${prompt.question}${count2}`, choices, {
393
871
  multiple: !!prompt.multiple,
394
872
  own: true
395
873
  });
@@ -882,7 +1360,7 @@ function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0, mound =
882
1360
  span(head + 2, 8, "k", (column) => !eyeColumns.includes(column), lean(2));
883
1361
  span(head + 4, 2, "a", undefined, lean(4));
884
1362
  span(10, SIZE, ".", (column) => column >= 6 && column <= 9);
885
- span(10, top, mound, (column) => column >= 6 && column <= 9, shift);
1363
+ span(10, top, mound, (column) => column + shift >= 6 && column + shift <= 9, shift);
886
1364
  span(11, 10, mound, undefined, shift);
887
1365
  span(12, 12, mound, undefined, shift);
888
1366
  for (const row of [13, 14, 15])
@@ -947,74 +1425,74 @@ function art(state, frame) {
947
1425
  }
948
1426
 
949
1427
  // src/tree/rpc.ts
950
- import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
951
- import { Schema as Schema2 } from "effect";
952
- var Count2 = Schema2.Int.check(Schema2.isGreaterThanOrEqualTo(0));
953
- var Group = Schema2.Struct({ name: Schema2.String, reported: Count2, members: Count2 });
954
- var Summary = Schema2.Struct({
955
- running: Count2,
956
- waiting: Count2,
957
- idle: Count2,
958
- stalled: Schema2.optionalKey(Count2),
959
- groups: Schema2.Array(Group)
1428
+ import { Rpc as Rpc3 } from "@opencode/plugin/rpc";
1429
+ import { Schema as Schema3 } from "effect";
1430
+ var Count3 = Schema3.Int.check(Schema3.isGreaterThanOrEqualTo(0));
1431
+ var Group = Schema3.Struct({ name: Schema3.String, reported: Count3, members: Count3 });
1432
+ var Summary = Schema3.Struct({
1433
+ running: Count3,
1434
+ waiting: Count3,
1435
+ idle: Count3,
1436
+ stalled: Schema3.optionalKey(Count3),
1437
+ groups: Schema3.Array(Group)
960
1438
  });
961
- var Wait = Schema2.Union([
962
- Schema2.Struct({ type: Schema2.Literal("permission"), action: Schema2.String, resources: Schema2.Array(Schema2.String) }),
963
- Schema2.Struct({ type: Schema2.Literal("question"), question: Schema2.String }),
964
- Schema2.Struct({ type: Schema2.Literal("form"), title: Schema2.String })
1439
+ var Wait = Schema3.Union([
1440
+ Schema3.Struct({ type: Schema3.Literal("permission"), action: Schema3.String, resources: Schema3.Array(Schema3.String) }),
1441
+ Schema3.Struct({ type: Schema3.Literal("question"), question: Schema3.String }),
1442
+ Schema3.Struct({ type: Schema3.Literal("form"), title: Schema3.String })
965
1443
  ]);
966
- var Row = Schema2.Struct({
967
- sessionID: Schema2.String,
968
- parentID: Schema2.String,
969
- title: Schema2.String,
970
- depth: Count2,
971
- state: Schema2.Literals(["running", "waiting", "idle", "stalled", "gone"]),
972
- stalled: Schema2.optionalKey(Schema2.Number),
973
- outcome: Schema2.optionalKey(Schema2.Literals(["succeeded", "failed", "interrupted"])),
974
- stopped: Schema2.optionalKey(Schema2.Boolean),
975
- report: Schema2.optionalKey(Schema2.Struct({ state: Schema2.Literals(["owed", "reported", "ended"]), status: Schema2.optionalKey(Schema2.String), progressed: Schema2.Boolean })),
976
- group: Schema2.optionalKey(Schema2.Struct({ name: Schema2.String, standing: Schema2.String })),
977
- isolated: Schema2.Boolean,
978
- created: Schema2.Number,
979
- idle: Schema2.optionalKey(Schema2.Number),
980
- cost: Schema2.optionalKey(Schema2.Number),
981
- context: Schema2.optionalKey(Schema2.Number),
982
- tool: Schema2.optionalKey(Schema2.Struct({ name: Schema2.String, at: Schema2.Number })),
983
- waits: Schema2.Array(Wait)
1444
+ var Row = Schema3.Struct({
1445
+ sessionID: Schema3.String,
1446
+ parentID: Schema3.String,
1447
+ title: Schema3.String,
1448
+ depth: Count3,
1449
+ state: Schema3.Literals(["running", "waiting", "idle", "stalled", "gone"]),
1450
+ stalled: Schema3.optionalKey(Schema3.Number),
1451
+ outcome: Schema3.optionalKey(Schema3.Literals(["succeeded", "failed", "interrupted"])),
1452
+ stopped: Schema3.optionalKey(Schema3.Boolean),
1453
+ report: Schema3.optionalKey(Schema3.Struct({ state: Schema3.Literals(["owed", "reported", "ended"]), status: Schema3.optionalKey(Schema3.String), progressed: Schema3.Boolean })),
1454
+ group: Schema3.optionalKey(Schema3.Struct({ name: Schema3.String, standing: Schema3.String })),
1455
+ isolated: Schema3.Boolean,
1456
+ created: Schema3.Number,
1457
+ idle: Schema3.optionalKey(Schema3.Number),
1458
+ cost: Schema3.optionalKey(Schema3.Number),
1459
+ context: Schema3.optionalKey(Schema3.Number),
1460
+ tool: Schema3.optionalKey(Schema3.Struct({ name: Schema3.String, at: Schema3.Number })),
1461
+ waits: Schema3.Array(Wait)
984
1462
  });
985
- var Tree = Schema2.Struct({
986
- rows: Schema2.Array(Row),
987
- truncated: Schema2.optionalKey(Count2),
988
- stalled: Schema2.optionalKey(Schema2.Number)
1463
+ var Tree = Schema3.Struct({
1464
+ rows: Schema3.Array(Row),
1465
+ truncated: Schema3.optionalKey(Count3),
1466
+ stalled: Schema3.optionalKey(Schema3.Number)
989
1467
  });
990
- var Stopped = Schema2.Struct({
991
- stopped: Schema2.Array(Schema2.Struct({ sessionID: Schema2.String, title: Schema2.String, outcome: Schema2.String })),
992
- cancelled: Count2
1468
+ var Stopped = Schema3.Struct({
1469
+ stopped: Schema3.Array(Schema3.Struct({ sessionID: Schema3.String, title: Schema3.String, outcome: Schema3.String })),
1470
+ cancelled: Count3
993
1471
  });
994
- var Forgotten = Schema2.Union([
995
- Schema2.Struct({ outcome: Schema2.Literals(["forgotten", "removed", "gone"]) }),
996
- Schema2.Struct({ outcome: Schema2.Literal("kept"), reason: Schema2.String })
1472
+ var Forgotten2 = Schema3.Union([
1473
+ Schema3.Struct({ outcome: Schema3.Literals(["forgotten", "removed", "gone"]) }),
1474
+ Schema3.Struct({ outcome: Schema3.Literal("kept"), reason: Schema3.String })
997
1475
  ]);
998
- var treeRpc = Rpc2.define({
1476
+ var treeRpc = Rpc3.define({
999
1477
  id: "courier",
1000
1478
  methods: {
1001
1479
  summary: {
1002
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
1003
- output: Schema2.toStandardSchemaV1(Summary)
1480
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
1481
+ output: Schema3.toStandardSchemaV1(Summary)
1004
1482
  },
1005
1483
  tree: {
1006
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
1007
- output: Schema2.toStandardSchemaV1(Tree)
1484
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
1485
+ output: Schema3.toStandardSchemaV1(Tree)
1008
1486
  },
1009
1487
  stop: {
1010
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
1011
- output: Schema2.toStandardSchemaV1(Stopped),
1012
- errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
1488
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
1489
+ output: Schema3.toStandardSchemaV1(Stopped),
1490
+ errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
1013
1491
  },
1014
1492
  cleanup: {
1015
- input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
1016
- output: Schema2.toStandardSchemaV1(Forgotten),
1017
- errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
1493
+ input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
1494
+ output: Schema3.toStandardSchemaV1(Forgotten2),
1495
+ errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
1018
1496
  }
1019
1497
  },
1020
1498
  events: {}
@@ -1053,14 +1531,14 @@ function createMotion(enabled) {
1053
1531
  const now = untrack(moving);
1054
1532
  for (const one of now)
1055
1533
  one.step(FRAME_MS * every);
1056
- setFrame((count) => count + every);
1534
+ setFrame((count2) => count2 + every);
1057
1535
  const left = now.filter((one) => !one.done());
1058
1536
  if (left.length !== now.length)
1059
1537
  setMoving(left);
1060
1538
  }), FRAME_MS * every);
1061
1539
  onCleanup(() => clearInterval(timer));
1062
1540
  });
1063
- const tween = (from, to, duration, ease) => {
1541
+ const tween = (from, to, duration2, ease) => {
1064
1542
  if (!enabled || from === to)
1065
1543
  return {
1066
1544
  value: () => to,
@@ -1070,11 +1548,11 @@ function createMotion(enabled) {
1070
1548
  value: from
1071
1549
  };
1072
1550
  const timeline = new Timeline({
1073
- duration,
1551
+ duration: duration2,
1074
1552
  autoplay: false
1075
1553
  });
1076
1554
  timeline.add(target, {
1077
- duration,
1555
+ duration: duration2,
1078
1556
  ease,
1079
1557
  value: to
1080
1558
  });
@@ -1087,7 +1565,7 @@ function createMotion(enabled) {
1087
1565
  },
1088
1566
  done: () => {
1089
1567
  frame();
1090
- return elapsed >= duration;
1568
+ return elapsed >= duration2;
1091
1569
  },
1092
1570
  step: (ms) => {
1093
1571
  elapsed += ms;
@@ -1186,8 +1664,8 @@ function mascotState(rows, {
1186
1664
  return rows.length && rows.every((row) => row.state === "gone") ? "gone" : "idle";
1187
1665
  }
1188
1666
  function countsLine(rows) {
1189
- const count = (state) => rows.filter((row) => row.state === state).length;
1190
- return [count("waiting") ? `${count("waiting")} waiting on you` : "", count("stalled") ? `${count("stalled")} stalled` : "", count("running") ? `${count("running")} running` : "", count("idle") ? `${count("idle")} idle` : ""].filter(Boolean).join(" \xB7 ");
1667
+ const count2 = (state) => rows.filter((row) => row.state === state).length;
1668
+ 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 ");
1191
1669
  }
1192
1670
  var GLYPH_ROWS = 12;
1193
1671
  var GLYPH_COLUMNS = 54;
@@ -1211,14 +1689,14 @@ function summaryLine(summary, maxWidth = Infinity) {
1211
1689
  return line(kept);
1212
1690
  return parts[0];
1213
1691
  }
1214
- function duration(ms) {
1692
+ function duration2(ms) {
1215
1693
  const seconds = Math.max(0, Math.floor(ms / 1000));
1216
- const pad = (value) => String(value).padStart(2, "0");
1694
+ const pad2 = (value) => String(value).padStart(2, "0");
1217
1695
  if (seconds < 60)
1218
1696
  return `${seconds}s`;
1219
1697
  if (seconds < 3600)
1220
- return `${Math.floor(seconds / 60)}m ${pad(seconds % 60)}s`;
1221
- return `${Math.floor(seconds / 3600)}h ${pad(Math.floor(seconds / 60) % 60)}m`;
1698
+ return `${Math.floor(seconds / 60)}m ${pad2(seconds % 60)}s`;
1699
+ return `${Math.floor(seconds / 3600)}h ${pad2(Math.floor(seconds / 60) % 60)}m`;
1222
1700
  }
1223
1701
  function parts(rows, parentID) {
1224
1702
  const children = rows.filter((row) => row.parentID === parentID);
@@ -1324,14 +1802,14 @@ function reportWords(report2, stopped) {
1324
1802
  return stopped ? "" : "ended without a report";
1325
1803
  return ["reported", report2.status].filter(Boolean).join(" ");
1326
1804
  }
1327
- var counted = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
1805
+ var counted = (count2, noun) => `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
1328
1806
  var CONTEXT_WARN = 0.8;
1329
1807
  function activityParts(row, now) {
1330
- 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.context !== undefined ? `ctx ${Math.round(row.context * 100)}%` : "", row.cost !== undefined ? `$${row.cost.toFixed(2)}` : ""].filter(Boolean);
1808
+ 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);
1331
1809
  }
1332
1810
  var activityLine = (row, now) => activityParts(row, now).join(" \xB7 ");
1333
1811
  function stalledLine(since, now) {
1334
- return `a message waits in its inbox since ${duration(now - since)}, woken again in vain`;
1812
+ return `a message waits in its inbox since ${duration2(now - since)}, woken again in vain`;
1335
1813
  }
1336
1814
  function detailLines(row, rows, now) {
1337
1815
  const started = parts(rows, row.sessionID);
@@ -1354,7 +1832,7 @@ function courierOf(ctx) {
1354
1832
  var REREAD_MS = 15000;
1355
1833
  function changes(ctx, types, settle) {
1356
1834
  const [changed, setChanged] = createSignal(0);
1357
- const reread = setInterval(() => setChanged((count) => count + 1), REREAD_MS);
1835
+ const reread = setInterval(() => setChanged((count2) => count2 + 1), REREAD_MS);
1358
1836
  let settling;
1359
1837
  const unlisten = ctx.data.listen(({
1360
1838
  details
@@ -1363,7 +1841,7 @@ function changes(ctx, types, settle) {
1363
1841
  return;
1364
1842
  settling = setTimeout(() => {
1365
1843
  settling = undefined;
1366
- setChanged((count) => count + 1);
1844
+ setChanged((count2) => count2 + 1);
1367
1845
  }, settle);
1368
1846
  });
1369
1847
  return [changed, () => {
@@ -1489,8 +1967,8 @@ async function confirmStop(ctx, courier, location, by, target) {
1489
1967
  }
1490
1968
  return true;
1491
1969
  }
1492
- function forgottenLine(title, result) {
1493
- const name = `"${oneLine(title)}"`;
1970
+ function forgottenLine2(title, result) {
1971
+ const name = `"${oneLine3(title)}"`;
1494
1972
  if (result.outcome === "kept")
1495
1973
  return `Kept ${name} and its worktree: ${result.reason}`;
1496
1974
  if (result.outcome === "removed")
@@ -1501,7 +1979,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1501
1979
  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.";
1502
1980
  const confirmed = await ctx.ui.dialog.confirm({
1503
1981
  title: "Forget session",
1504
- message: `Forget "${oneLine(target.title)}" (${target.sessionID})? ${what}`,
1982
+ message: `Forget "${oneLine3(target.title)}" (${target.sessionID})? ${what}`,
1505
1983
  label: {
1506
1984
  confirm: "Forget",
1507
1985
  cancel: "Keep"
@@ -1518,7 +1996,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1518
1996
  });
1519
1997
  ctx.ui.toast.show({
1520
1998
  title: "courier",
1521
- message: forgottenLine(target.title, result),
1999
+ message: forgottenLine2(target.title, result),
1522
2000
  variant: result.outcome === "kept" ? "warning" : "success"
1523
2001
  });
1524
2002
  if (result.outcome !== "kept")
@@ -1534,7 +2012,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
1534
2012
  }
1535
2013
  var FOCUS_PROMPT = "pane.focus.left";
1536
2014
  var SPLIT_WIDTH = 108;
1537
- var oneLine = (title) => title.replace(/[\r\n]+/g, " ");
2015
+ var oneLine3 = (title) => title.replace(/[\r\n]+/g, " ");
1538
2016
  var digbyColours = (theme) => ({
1539
2017
  b: theme.text.base,
1540
2018
  k: theme.background.base,
@@ -1545,6 +2023,7 @@ var digbyColours = (theme) => ({
1545
2023
  e: theme.text.feedback.error.base,
1546
2024
  s: theme.text.feedback.success.base
1547
2025
  });
2026
+ var DIGBY_UNDER = RGBA.fromValues(0, 0, 0, 0);
1548
2027
  function restBackground(theme, {
1549
2028
  selected,
1550
2029
  hovered
@@ -1682,7 +2161,7 @@ function TreePanel(props) {
1682
2161
  if (row.state !== "gone")
1683
2162
  return ctx.ui.toast.show({
1684
2163
  title: "courier",
1685
- message: `x forgets only a gone session; "${oneLine(row.title)}" is ${row.state}.`,
2164
+ message: `x forgets only a gone session; "${oneLine3(row.title)}" is ${row.state}.`,
1686
2165
  variant: "warning"
1687
2166
  });
1688
2167
  confirmForget(ctx, courier, location, view.root, {
@@ -1787,7 +2266,7 @@ function TreePanel(props) {
1787
2266
  _$insert(_el$5, () => one().char);
1788
2267
  _$effect((_$p) => _$setProp(_el$5, "style", {
1789
2268
  fg: one().fg && tokens[one().fg],
1790
- bg: one().bg && tokens[one().bg]
2269
+ bg: one().bg ? tokens[one().bg] : DIGBY_UNDER
1791
2270
  }, _$p));
1792
2271
  return _el$5;
1793
2272
  })()
@@ -1996,7 +2475,7 @@ function TreePanel(props) {
1996
2475
  _$setProp(_el$29, "wrapMode", "none");
1997
2476
  _$setProp(_el$29, "flexShrink", 1);
1998
2477
  _$setProp(_el$29, "minWidth", 0);
1999
- _$insert(_el$29, () => oneLine(row.title), null);
2478
+ _$insert(_el$29, () => oneLine3(row.title), null);
2000
2479
  _$insert(_el$29, () => row.sessionID === input.sessionID ? " (on screen)" : "", null);
2001
2480
  _$insertNode(_el$30, _$createTextNode(` `));
2002
2481
  _$setProp(_el$30, "wrapMode", "none");
@@ -2115,7 +2594,8 @@ function TreePanel(props) {
2115
2594
  })();
2116
2595
  }
2117
2596
  var USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
2118
- function addTreePanel(ctx) {
2597
+ var usageWith = (verbs) => [USAGE, ...Object.values(verbs).map((verb) => verb.usage)].join("; ");
2598
+ function addTreePanel(ctx, verbs = {}) {
2119
2599
  const {
2120
2600
  courier,
2121
2601
  location
@@ -2306,7 +2786,7 @@ function addTreePanel(ctx) {
2306
2786
  commands: [{
2307
2787
  id: "courier.command",
2308
2788
  title: "Courier",
2309
- description: USAGE,
2789
+ description: usageWith(verbs),
2310
2790
  group: "Courier",
2311
2791
  slash: {
2312
2792
  name: "courier",
@@ -2318,9 +2798,12 @@ function addTreePanel(ctx) {
2318
2798
  return openPanel();
2319
2799
  if (verb === "stop")
2320
2800
  return stopUnder(target);
2801
+ const further = Object.hasOwn(verbs, verb) ? verbs[verb] : undefined;
2802
+ if (further)
2803
+ return further.run();
2321
2804
  ctx.ui.toast.show({
2322
2805
  title: "courier",
2323
- message: USAGE,
2806
+ message: usageWith(verbs),
2324
2807
  variant: "warning"
2325
2808
  });
2326
2809
  }
@@ -2390,7 +2873,13 @@ var tui_default = Plugin.define({
2390
2873
  id: "courier.tui",
2391
2874
  setup: (ctx) => {
2392
2875
  const inbox = addAsksInbox(ctx, CHANGES);
2393
- const cleanups = [inbox.cleanup, addTreeStatus(ctx, (sessionID) => void inbox.open(sessionID)), addTreePanel(ctx), addTreeAttention(ctx)];
2876
+ const memory = addMemoryView(ctx);
2877
+ const cleanups = [inbox.cleanup, addTreeStatus(ctx, (sessionID) => void inbox.open(sessionID)), addTreePanel(ctx, {
2878
+ memory: {
2879
+ run: () => void memory.open(),
2880
+ usage: MEMORY_USAGE
2881
+ }
2882
+ }), addTreeAttention(ctx)];
2394
2883
  return () => {
2395
2884
  for (const cleanup of cleanups.toReversed())
2396
2885
  cleanup();