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.
- package/README.md +119 -136
- package/dist/index.js +23 -4
- package/dist/kernel/protocol.d.ts +2 -0
- package/dist/kernel/protocol.js +9 -1
- package/dist/memory/domain.d.ts +188 -0
- package/dist/memory/domain.js +209 -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 +11 -0
- package/dist/memory/index.js +12 -0
- package/dist/memory/note.d.ts +37 -0
- package/dist/memory/note.js +29 -0
- package/dist/memory/pin.d.ts +8 -0
- package/dist/memory/pin.js +13 -0
- package/dist/memory/ports.d.ts +55 -0
- package/dist/memory/ports.js +1 -0
- package/dist/memory/recall.d.ts +27 -0
- package/dist/memory/recall.js +67 -0
- package/dist/memory/record.d.ts +7 -0
- package/dist/memory/record.js +124 -0
- package/dist/memory/rpc-handlers.d.ts +179 -0
- package/dist/memory/rpc-handlers.js +25 -0
- package/dist/memory/rpc.d.ts +299 -0
- package/dist/memory/rpc.js +71 -0
- package/dist/memory/store.d.ts +14 -0
- package/dist/memory/store.js +174 -0
- package/dist/memory/sweep.d.ts +7 -0
- package/dist/memory/sweep.js +16 -0
- package/dist/memory/text.d.ts +35 -0
- package/dist/memory/text.js +116 -0
- package/dist/memory/tools.d.ts +16 -0
- package/dist/memory/tools.js +78 -0
- package/dist/memory/tui.d.ts +82 -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/mascot.js +1 -1
- 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 +20 -20
- package/dist/tree/rpc.d.ts +20 -20
- 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 +612 -123
- 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/
|
|
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", "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
|
-
|
|
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
|
+
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 =
|
|
525
|
+
var personRpc = Rpc2.define({
|
|
48
526
|
id: "courier-person",
|
|
49
527
|
methods: {
|
|
50
528
|
asks: {
|
|
51
|
-
input:
|
|
52
|
-
output:
|
|
529
|
+
input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String })),
|
|
530
|
+
output: Schema2.toStandardSchemaV1(Asks)
|
|
53
531
|
},
|
|
54
532
|
answer: {
|
|
55
|
-
input:
|
|
56
|
-
output:
|
|
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
|
|
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
|
|
118
|
-
return
|
|
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
|
|
602
|
+
return clip2(`Question: ${what}${more}`);
|
|
125
603
|
}
|
|
126
604
|
case "form":
|
|
127
|
-
return
|
|
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
|
|
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}${
|
|
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
|
|
951
|
-
import { Schema as
|
|
952
|
-
var
|
|
953
|
-
var Group =
|
|
954
|
-
var Summary =
|
|
955
|
-
running:
|
|
956
|
-
waiting:
|
|
957
|
-
idle:
|
|
958
|
-
stalled:
|
|
959
|
-
groups:
|
|
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 =
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
986
|
-
rows:
|
|
987
|
-
truncated:
|
|
988
|
-
stalled:
|
|
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 =
|
|
991
|
-
stopped:
|
|
992
|
-
cancelled:
|
|
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
|
|
995
|
-
|
|
996
|
-
|
|
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 =
|
|
1476
|
+
var treeRpc = Rpc3.define({
|
|
999
1477
|
id: "courier",
|
|
1000
1478
|
methods: {
|
|
1001
1479
|
summary: {
|
|
1002
|
-
input:
|
|
1003
|
-
output:
|
|
1480
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
|
|
1481
|
+
output: Schema3.toStandardSchemaV1(Summary)
|
|
1004
1482
|
},
|
|
1005
1483
|
tree: {
|
|
1006
|
-
input:
|
|
1007
|
-
output:
|
|
1484
|
+
input: Schema3.toStandardSchemaV1(Schema3.Struct({ sessionID: Schema3.String })),
|
|
1485
|
+
output: Schema3.toStandardSchemaV1(Tree)
|
|
1008
1486
|
},
|
|
1009
1487
|
stop: {
|
|
1010
|
-
input:
|
|
1011
|
-
output:
|
|
1012
|
-
errors: { refused:
|
|
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:
|
|
1016
|
-
output:
|
|
1017
|
-
errors: { refused:
|
|
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((
|
|
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,
|
|
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 >=
|
|
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
|
|
1190
|
-
return [
|
|
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
|
|
1692
|
+
function duration2(ms) {
|
|
1215
1693
|
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
1216
|
-
const
|
|
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 ${
|
|
1221
|
-
return `${Math.floor(seconds / 3600)}h ${
|
|
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 = (
|
|
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 ${
|
|
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 ${
|
|
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((
|
|
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((
|
|
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
|
|
1493
|
-
const name = `"${
|
|
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 "${
|
|
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:
|
|
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
|
|
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; "${
|
|
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
|
|
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, () =>
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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();
|