opencode-courier 0.7.3 → 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.
- package/README.md +109 -135
- package/dist/index.js +23 -4
- package/dist/memory/domain.d.ts +119 -0
- package/dist/memory/domain.js +84 -0
- package/dist/memory/events.d.ts +26 -0
- package/dist/memory/events.js +46 -0
- package/dist/memory/forget.d.ts +10 -0
- package/dist/memory/forget.js +20 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/note.d.ts +37 -0
- package/dist/memory/note.js +29 -0
- package/dist/memory/ports.d.ts +44 -0
- package/dist/memory/ports.js +1 -0
- package/dist/memory/recall.d.ts +27 -0
- package/dist/memory/recall.js +48 -0
- package/dist/memory/record.d.ts +7 -0
- package/dist/memory/record.js +92 -0
- package/dist/memory/rpc-handlers.d.ts +134 -0
- package/dist/memory/rpc-handlers.js +23 -0
- package/dist/memory/rpc.d.ts +227 -0
- package/dist/memory/rpc.js +60 -0
- package/dist/memory/store.d.ts +10 -0
- package/dist/memory/store.js +90 -0
- package/dist/memory/sweep.d.ts +4 -0
- package/dist/memory/sweep.js +12 -0
- package/dist/memory/text.d.ts +27 -0
- package/dist/memory/text.js +95 -0
- package/dist/memory/tools.d.ts +16 -0
- package/dist/memory/tools.js +78 -0
- package/dist/memory/tui.d.ts +73 -0
- package/dist/person/rpc.d.ts +4 -4
- package/dist/platform/json.d.ts +1 -0
- package/dist/platform/json.js +1 -0
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.js +1 -0
- package/dist/skill.js +10 -3
- package/dist/tree/context.d.ts +2 -2
- package/dist/tree/context.js +4 -3
- package/dist/tree/index.d.ts +6 -5
- package/dist/tree/index.js +5 -4
- package/dist/tree/ports.d.ts +4 -2
- package/dist/tree/roster.d.ts +17 -0
- package/dist/tree/roster.js +24 -0
- package/dist/tree/rpc-handlers.d.ts +18 -18
- package/dist/tree/rpc.d.ts +18 -18
- package/dist/tree/status.d.ts +5 -5
- package/dist/tree/subtree.d.ts +2 -2
- package/dist/tree/text.js +2 -1
- package/dist/tree/tui.d.ts +11 -3
- package/dist/tui.js +544 -121
- package/package.json +1 -1
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/
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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: {}
|
|
53
|
+
});
|
|
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 }))
|
|
35
436
|
});
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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:
|
|
52
|
-
output:
|
|
464
|
+
input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
|
|
465
|
+
output: Schema2.toStandardSchemaV1(Asks)
|
|
53
466
|
},
|
|
54
467
|
answer: {
|
|
55
|
-
input:
|
|
56
|
-
output:
|
|
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
|
|
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
|
|
118
|
-
return
|
|
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
|
|
537
|
+
return clip2(`Question: ${what}${more}`);
|
|
125
538
|
}
|
|
126
539
|
case "form":
|
|
127
|
-
return
|
|
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
|
|
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}${
|
|
805
|
+
const picked = await choose(`${prompt.header}: ${prompt.question}${count2}`, choices, {
|
|
393
806
|
multiple: !!prompt.multiple,
|
|
394
807
|
own: true
|
|
395
808
|
});
|
|
@@ -947,74 +1360,74 @@ function art(state, frame) {
|
|
|
947
1360
|
}
|
|
948
1361
|
|
|
949
1362
|
// src/tree/rpc.ts
|
|
950
|
-
import { Rpc as
|
|
951
|
-
import { Schema as
|
|
952
|
-
var
|
|
953
|
-
var Group =
|
|
954
|
-
var Summary =
|
|
955
|
-
running:
|
|
956
|
-
waiting:
|
|
957
|
-
idle:
|
|
958
|
-
stalled:
|
|
959
|
-
groups:
|
|
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)
|
|
960
1373
|
});
|
|
961
|
-
var Wait =
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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 })
|
|
965
1378
|
]);
|
|
966
|
-
var Row =
|
|
967
|
-
sessionID:
|
|
968
|
-
parentID:
|
|
969
|
-
title:
|
|
970
|
-
depth:
|
|
971
|
-
state:
|
|
972
|
-
stalled:
|
|
973
|
-
outcome:
|
|
974
|
-
stopped:
|
|
975
|
-
report:
|
|
976
|
-
group:
|
|
977
|
-
isolated:
|
|
978
|
-
created:
|
|
979
|
-
idle:
|
|
980
|
-
cost:
|
|
981
|
-
context:
|
|
982
|
-
tool:
|
|
983
|
-
waits:
|
|
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)
|
|
984
1397
|
});
|
|
985
|
-
var Tree =
|
|
986
|
-
rows:
|
|
987
|
-
truncated:
|
|
988
|
-
stalled:
|
|
1398
|
+
var Tree = Schema3.Struct({
|
|
1399
|
+
rows: Schema3.Array(Row),
|
|
1400
|
+
truncated: Schema3.optionalKey(Count3),
|
|
1401
|
+
stalled: Schema3.optionalKey(Schema3.Number)
|
|
989
1402
|
});
|
|
990
|
-
var Stopped =
|
|
991
|
-
stopped:
|
|
992
|
-
cancelled:
|
|
1403
|
+
var Stopped = Schema3.Struct({
|
|
1404
|
+
stopped: Schema3.Array(Schema3.Struct({ sessionID: Schema3.String, title: Schema3.String, outcome: Schema3.String })),
|
|
1405
|
+
cancelled: Count3
|
|
993
1406
|
});
|
|
994
|
-
var
|
|
995
|
-
|
|
996
|
-
|
|
1407
|
+
var Forgotten2 = Schema3.Union([
|
|
1408
|
+
Schema3.Struct({ outcome: Schema3.Literals(["forgotten", "removed", "gone"]) }),
|
|
1409
|
+
Schema3.Struct({ outcome: Schema3.Literal("kept"), reason: Schema3.String })
|
|
997
1410
|
]);
|
|
998
|
-
var treeRpc =
|
|
1411
|
+
var treeRpc = Rpc3.define({
|
|
999
1412
|
id: "courier",
|
|
1000
1413
|
methods: {
|
|
1001
1414
|
summary: {
|
|
1002
|
-
input:
|
|
1003
|
-
output:
|
|
1415
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
|
|
1416
|
+
output: Schema3.toStandardSchemaV1(Summary)
|
|
1004
1417
|
},
|
|
1005
1418
|
tree: {
|
|
1006
|
-
input:
|
|
1007
|
-
output:
|
|
1419
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
|
|
1420
|
+
output: Schema3.toStandardSchemaV1(Tree)
|
|
1008
1421
|
},
|
|
1009
1422
|
stop: {
|
|
1010
|
-
input:
|
|
1011
|
-
output:
|
|
1012
|
-
errors: { refused:
|
|
1423
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
|
|
1424
|
+
output: Schema3.toStandardSchemaV1(Stopped),
|
|
1425
|
+
errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
|
|
1013
1426
|
},
|
|
1014
1427
|
cleanup: {
|
|
1015
|
-
input:
|
|
1016
|
-
output:
|
|
1017
|
-
errors: { refused:
|
|
1428
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String, by: Schema3.String })),
|
|
1429
|
+
output: Schema3.toStandardSchemaV1(Forgotten2),
|
|
1430
|
+
errors: { refused: Schema3.toStandardSchemaV1(Schema3.Struct({})) }
|
|
1018
1431
|
}
|
|
1019
1432
|
},
|
|
1020
1433
|
events: {}
|
|
@@ -1053,14 +1466,14 @@ function createMotion(enabled) {
|
|
|
1053
1466
|
const now = untrack(moving);
|
|
1054
1467
|
for (const one of now)
|
|
1055
1468
|
one.step(FRAME_MS * every);
|
|
1056
|
-
setFrame((
|
|
1469
|
+
setFrame((count2) => count2 + every);
|
|
1057
1470
|
const left = now.filter((one) => !one.done());
|
|
1058
1471
|
if (left.length !== now.length)
|
|
1059
1472
|
setMoving(left);
|
|
1060
1473
|
}), FRAME_MS * every);
|
|
1061
1474
|
onCleanup(() => clearInterval(timer));
|
|
1062
1475
|
});
|
|
1063
|
-
const tween = (from, to,
|
|
1476
|
+
const tween = (from, to, duration2, ease) => {
|
|
1064
1477
|
if (!enabled || from === to)
|
|
1065
1478
|
return {
|
|
1066
1479
|
value: () => to,
|
|
@@ -1070,11 +1483,11 @@ function createMotion(enabled) {
|
|
|
1070
1483
|
value: from
|
|
1071
1484
|
};
|
|
1072
1485
|
const timeline = new Timeline({
|
|
1073
|
-
duration,
|
|
1486
|
+
duration: duration2,
|
|
1074
1487
|
autoplay: false
|
|
1075
1488
|
});
|
|
1076
1489
|
timeline.add(target, {
|
|
1077
|
-
duration,
|
|
1490
|
+
duration: duration2,
|
|
1078
1491
|
ease,
|
|
1079
1492
|
value: to
|
|
1080
1493
|
});
|
|
@@ -1087,7 +1500,7 @@ function createMotion(enabled) {
|
|
|
1087
1500
|
},
|
|
1088
1501
|
done: () => {
|
|
1089
1502
|
frame();
|
|
1090
|
-
return elapsed >=
|
|
1503
|
+
return elapsed >= duration2;
|
|
1091
1504
|
},
|
|
1092
1505
|
step: (ms) => {
|
|
1093
1506
|
elapsed += ms;
|
|
@@ -1186,8 +1599,8 @@ function mascotState(rows, {
|
|
|
1186
1599
|
return rows.length && rows.every((row) => row.state === "gone") ? "gone" : "idle";
|
|
1187
1600
|
}
|
|
1188
1601
|
function countsLine(rows) {
|
|
1189
|
-
const
|
|
1190
|
-
return [
|
|
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 ");
|
|
1191
1604
|
}
|
|
1192
1605
|
var GLYPH_ROWS = 12;
|
|
1193
1606
|
var GLYPH_COLUMNS = 54;
|
|
@@ -1211,14 +1624,14 @@ function summaryLine(summary, maxWidth = Infinity) {
|
|
|
1211
1624
|
return line(kept);
|
|
1212
1625
|
return parts[0];
|
|
1213
1626
|
}
|
|
1214
|
-
function
|
|
1627
|
+
function duration2(ms) {
|
|
1215
1628
|
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
1216
|
-
const
|
|
1629
|
+
const pad2 = (value) => String(value).padStart(2, "0");
|
|
1217
1630
|
if (seconds < 60)
|
|
1218
1631
|
return `${seconds}s`;
|
|
1219
1632
|
if (seconds < 3600)
|
|
1220
|
-
return `${Math.floor(seconds / 60)}m ${
|
|
1221
|
-
return `${Math.floor(seconds / 3600)}h ${
|
|
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`;
|
|
1222
1635
|
}
|
|
1223
1636
|
function parts(rows, parentID) {
|
|
1224
1637
|
const children = rows.filter((row) => row.parentID === parentID);
|
|
@@ -1324,14 +1737,14 @@ function reportWords(report2, stopped) {
|
|
|
1324
1737
|
return stopped ? "" : "ended without a report";
|
|
1325
1738
|
return ["reported", report2.status].filter(Boolean).join(" ");
|
|
1326
1739
|
}
|
|
1327
|
-
var counted = (
|
|
1740
|
+
var counted = (count2, noun) => `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
|
|
1328
1741
|
var CONTEXT_WARN = 0.8;
|
|
1329
1742
|
function activityParts(row, now) {
|
|
1330
|
-
return [`ran ${
|
|
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);
|
|
1331
1744
|
}
|
|
1332
1745
|
var activityLine = (row, now) => activityParts(row, now).join(" \xB7 ");
|
|
1333
1746
|
function stalledLine(since, now) {
|
|
1334
|
-
return `a message waits in its inbox since ${
|
|
1747
|
+
return `a message waits in its inbox since ${duration2(now - since)}, woken again in vain`;
|
|
1335
1748
|
}
|
|
1336
1749
|
function detailLines(row, rows, now) {
|
|
1337
1750
|
const started = parts(rows, row.sessionID);
|
|
@@ -1354,7 +1767,7 @@ function courierOf(ctx) {
|
|
|
1354
1767
|
var REREAD_MS = 15000;
|
|
1355
1768
|
function changes(ctx, types, settle) {
|
|
1356
1769
|
const [changed, setChanged] = createSignal(0);
|
|
1357
|
-
const reread = setInterval(() => setChanged((
|
|
1770
|
+
const reread = setInterval(() => setChanged((count2) => count2 + 1), REREAD_MS);
|
|
1358
1771
|
let settling;
|
|
1359
1772
|
const unlisten = ctx.data.listen(({
|
|
1360
1773
|
details
|
|
@@ -1363,7 +1776,7 @@ function changes(ctx, types, settle) {
|
|
|
1363
1776
|
return;
|
|
1364
1777
|
settling = setTimeout(() => {
|
|
1365
1778
|
settling = undefined;
|
|
1366
|
-
setChanged((
|
|
1779
|
+
setChanged((count2) => count2 + 1);
|
|
1367
1780
|
}, settle);
|
|
1368
1781
|
});
|
|
1369
1782
|
return [changed, () => {
|
|
@@ -1489,8 +1902,8 @@ async function confirmStop(ctx, courier, location, by, target) {
|
|
|
1489
1902
|
}
|
|
1490
1903
|
return true;
|
|
1491
1904
|
}
|
|
1492
|
-
function
|
|
1493
|
-
const name = `"${
|
|
1905
|
+
function forgottenLine2(title, result) {
|
|
1906
|
+
const name = `"${oneLine3(title)}"`;
|
|
1494
1907
|
if (result.outcome === "kept")
|
|
1495
1908
|
return `Kept ${name} and its worktree: ${result.reason}`;
|
|
1496
1909
|
if (result.outcome === "removed")
|
|
@@ -1501,7 +1914,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
|
|
|
1501
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.";
|
|
1502
1915
|
const confirmed = await ctx.ui.dialog.confirm({
|
|
1503
1916
|
title: "Forget session",
|
|
1504
|
-
message: `Forget "${
|
|
1917
|
+
message: `Forget "${oneLine3(target.title)}" (${target.sessionID})? ${what}`,
|
|
1505
1918
|
label: {
|
|
1506
1919
|
confirm: "Forget",
|
|
1507
1920
|
cancel: "Keep"
|
|
@@ -1518,7 +1931,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
|
|
|
1518
1931
|
});
|
|
1519
1932
|
ctx.ui.toast.show({
|
|
1520
1933
|
title: "courier",
|
|
1521
|
-
message:
|
|
1934
|
+
message: forgottenLine2(target.title, result),
|
|
1522
1935
|
variant: result.outcome === "kept" ? "warning" : "success"
|
|
1523
1936
|
});
|
|
1524
1937
|
if (result.outcome !== "kept")
|
|
@@ -1534,7 +1947,7 @@ async function confirmForget(ctx, courier, location, by, target, done) {
|
|
|
1534
1947
|
}
|
|
1535
1948
|
var FOCUS_PROMPT = "pane.focus.left";
|
|
1536
1949
|
var SPLIT_WIDTH = 108;
|
|
1537
|
-
var
|
|
1950
|
+
var oneLine3 = (title) => title.replace(/[\r\n]+/g, " ");
|
|
1538
1951
|
var digbyColours = (theme) => ({
|
|
1539
1952
|
b: theme.text.base,
|
|
1540
1953
|
k: theme.background.base,
|
|
@@ -1682,7 +2095,7 @@ function TreePanel(props) {
|
|
|
1682
2095
|
if (row.state !== "gone")
|
|
1683
2096
|
return ctx.ui.toast.show({
|
|
1684
2097
|
title: "courier",
|
|
1685
|
-
message: `x forgets only a gone session; "${
|
|
2098
|
+
message: `x forgets only a gone session; "${oneLine3(row.title)}" is ${row.state}.`,
|
|
1686
2099
|
variant: "warning"
|
|
1687
2100
|
});
|
|
1688
2101
|
confirmForget(ctx, courier, location, view.root, {
|
|
@@ -1996,7 +2409,7 @@ function TreePanel(props) {
|
|
|
1996
2409
|
_$setProp(_el$29, "wrapMode", "none");
|
|
1997
2410
|
_$setProp(_el$29, "flexShrink", 1);
|
|
1998
2411
|
_$setProp(_el$29, "minWidth", 0);
|
|
1999
|
-
_$insert(_el$29, () =>
|
|
2412
|
+
_$insert(_el$29, () => oneLine3(row.title), null);
|
|
2000
2413
|
_$insert(_el$29, () => row.sessionID === input.sessionID ? " (on screen)" : "", null);
|
|
2001
2414
|
_$insertNode(_el$30, _$createTextNode(` `));
|
|
2002
2415
|
_$setProp(_el$30, "wrapMode", "none");
|
|
@@ -2115,7 +2528,8 @@ function TreePanel(props) {
|
|
|
2115
2528
|
})();
|
|
2116
2529
|
}
|
|
2117
2530
|
var USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
|
|
2118
|
-
|
|
2531
|
+
var usageWith = (verbs) => [USAGE, ...Object.values(verbs).map((verb) => verb.usage)].join("; ");
|
|
2532
|
+
function addTreePanel(ctx, verbs = {}) {
|
|
2119
2533
|
const {
|
|
2120
2534
|
courier,
|
|
2121
2535
|
location
|
|
@@ -2306,7 +2720,7 @@ function addTreePanel(ctx) {
|
|
|
2306
2720
|
commands: [{
|
|
2307
2721
|
id: "courier.command",
|
|
2308
2722
|
title: "Courier",
|
|
2309
|
-
description:
|
|
2723
|
+
description: usageWith(verbs),
|
|
2310
2724
|
group: "Courier",
|
|
2311
2725
|
slash: {
|
|
2312
2726
|
name: "courier",
|
|
@@ -2318,9 +2732,12 @@ function addTreePanel(ctx) {
|
|
|
2318
2732
|
return openPanel();
|
|
2319
2733
|
if (verb === "stop")
|
|
2320
2734
|
return stopUnder(target);
|
|
2735
|
+
const further = Object.hasOwn(verbs, verb) ? verbs[verb] : undefined;
|
|
2736
|
+
if (further)
|
|
2737
|
+
return further.run();
|
|
2321
2738
|
ctx.ui.toast.show({
|
|
2322
2739
|
title: "courier",
|
|
2323
|
-
message:
|
|
2740
|
+
message: usageWith(verbs),
|
|
2324
2741
|
variant: "warning"
|
|
2325
2742
|
});
|
|
2326
2743
|
}
|
|
@@ -2390,7 +2807,13 @@ var tui_default = Plugin.define({
|
|
|
2390
2807
|
id: "courier.tui",
|
|
2391
2808
|
setup: (ctx) => {
|
|
2392
2809
|
const inbox = addAsksInbox(ctx, CHANGES);
|
|
2393
|
-
const
|
|
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)];
|
|
2394
2817
|
return () => {
|
|
2395
2818
|
for (const cleanup of cleanups.toReversed())
|
|
2396
2819
|
cleanup();
|