opencode-courier 0.8.0 → 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 +19 -10
- package/dist/kernel/protocol.d.ts +2 -0
- package/dist/kernel/protocol.js +9 -1
- package/dist/memory/domain.d.ts +82 -13
- package/dist/memory/domain.js +139 -14
- package/dist/memory/index.d.ts +3 -2
- package/dist/memory/index.js +1 -0
- package/dist/memory/pin.d.ts +8 -0
- package/dist/memory/pin.js +13 -0
- package/dist/memory/ports.d.ts +14 -3
- package/dist/memory/recall.d.ts +2 -2
- package/dist/memory/recall.js +24 -5
- package/dist/memory/record.d.ts +1 -1
- package/dist/memory/record.js +50 -18
- package/dist/memory/rpc-handlers.d.ts +54 -9
- package/dist/memory/rpc-handlers.js +8 -6
- package/dist/memory/rpc.d.ts +80 -8
- package/dist/memory/rpc.js +13 -2
- package/dist/memory/store.d.ts +5 -1
- package/dist/memory/store.js +99 -15
- package/dist/memory/sweep.d.ts +4 -1
- package/dist/memory/sweep.js +6 -2
- package/dist/memory/text.d.ts +12 -4
- package/dist/memory/text.js +33 -12
- package/dist/memory/tools.js +2 -2
- package/dist/memory/tui.d.ts +14 -5
- package/dist/rpc.d.ts +1 -1
- package/dist/tree/mascot.js +1 -1
- package/dist/tree/rpc-handlers.d.ts +4 -4
- package/dist/tree/rpc.d.ts +4 -4
- package/dist/tui.js +77 -11
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Plugin } from "@opencode/plugin/tui";
|
|
|
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 PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"];
|
|
9
|
+
var PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"];
|
|
10
10
|
var Part = Schema.Struct({
|
|
11
11
|
sessionID: Schema.String,
|
|
12
12
|
title: Schema.String,
|
|
@@ -29,6 +29,8 @@ var Record = Schema.Struct({
|
|
|
29
29
|
finished: Schema.Boolean,
|
|
30
30
|
recalled: Schema.optionalKey(Schema.Struct({ at: Schema.optionalKey(Schema.Number), calls: Count })),
|
|
31
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 })),
|
|
32
34
|
parts: Schema.Array(Part)
|
|
33
35
|
});
|
|
34
36
|
var Records = Schema.Struct({
|
|
@@ -37,6 +39,7 @@ var Records = Schema.Struct({
|
|
|
37
39
|
records: Schema.Array(Record)
|
|
38
40
|
});
|
|
39
41
|
var Forgotten = Schema.Struct({ outcome: Schema.Literals(["record", "lesson", "none"]) });
|
|
42
|
+
var Pinned = Schema.Struct({ outcome: Schema.Literals(["pinned", "unpinned", "none"]) });
|
|
40
43
|
var memoryRpc = Rpc.define({
|
|
41
44
|
id: "courier-memory",
|
|
42
45
|
methods: {
|
|
@@ -47,6 +50,10 @@ var memoryRpc = Rpc.define({
|
|
|
47
50
|
forget: {
|
|
48
51
|
input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
|
|
49
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)
|
|
50
57
|
}
|
|
51
58
|
},
|
|
52
59
|
events: {}
|
|
@@ -57,7 +64,7 @@ var STATUSES = ["done", "partial", "blocked", "failed"];
|
|
|
57
64
|
|
|
58
65
|
// src/memory/domain.ts
|
|
59
66
|
var RETENTION_MS = 90 * 24 * 60 * 60000;
|
|
60
|
-
var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "stalled", "out"];
|
|
67
|
+
var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "gone", "stalled", "out"];
|
|
61
68
|
|
|
62
69
|
// src/memory/text.ts
|
|
63
70
|
function duration(ms) {
|
|
@@ -73,12 +80,13 @@ var NOTE_EMPTY = `Not noted: text is empty. Write one line, ${ONE_LINE}, or noth
|
|
|
73
80
|
|
|
74
81
|
// src/memory/tui.tsx
|
|
75
82
|
var MEMORY_COMMAND = "courier.memory";
|
|
76
|
-
var MEMORY_USAGE = "/courier memory lists this project's tree records, to read or forget";
|
|
83
|
+
var MEMORY_USAGE = "/courier memory lists this project's tree records, to read, pin or forget";
|
|
77
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.";
|
|
78
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.";
|
|
79
86
|
var LESSON_LINE_MAX = 160;
|
|
80
87
|
var ROW_TITLE_MAX = 46;
|
|
81
88
|
var TODAY = "Today";
|
|
89
|
+
var PINNED = "Pinned";
|
|
82
90
|
var LESSON_ROW = 90;
|
|
83
91
|
var pad = (n) => String(n).padStart(2, "0");
|
|
84
92
|
var localDay = (at) => new Date(at).toDateString();
|
|
@@ -96,6 +104,7 @@ var minute = (at) => {
|
|
|
96
104
|
var oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
|
|
97
105
|
var clip = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
98
106
|
var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
107
|
+
var times = (n) => n === 1 ? "once" : `${n} times`;
|
|
99
108
|
var money = (cost) => `$${cost.toFixed(2)}`;
|
|
100
109
|
var nameOf = (record) => clip(oneLine2(record.title) || record.sessionID, 60);
|
|
101
110
|
function partsCounted(parts) {
|
|
@@ -114,16 +123,21 @@ function recordTitle(record, open) {
|
|
|
114
123
|
return `${clip(oneLine2(record.title) || record.sessionID, ROW_TITLE_MAX)}${record.sessionID === open ? " (this session)" : ""}`;
|
|
115
124
|
}
|
|
116
125
|
function recordCategory(record, now = Date.now()) {
|
|
126
|
+
if (record.pinned)
|
|
127
|
+
return PINNED;
|
|
117
128
|
const moved = localDay(record.movedAt);
|
|
118
129
|
return moved === localDay(now) ? TODAY : moved;
|
|
119
130
|
}
|
|
131
|
+
function readsLine(reads) {
|
|
132
|
+
return [reads.shown ? `shown ${times(reads.shown)}` : "", reads.read ? `opened ${times(reads.read)}` : ""].filter(Boolean).join(", ");
|
|
133
|
+
}
|
|
120
134
|
function lessonHeading(lesson) {
|
|
121
135
|
const from = lesson.depth === undefined ? "" : `, from depth ${lesson.depth}`;
|
|
122
136
|
return `lesson (${day(lesson.at)}${from})`;
|
|
123
137
|
}
|
|
124
138
|
function recordDescription(record) {
|
|
125
139
|
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 ");
|
|
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 ");
|
|
127
141
|
}
|
|
128
142
|
function recordDetails(record) {
|
|
129
143
|
return record.lesson ? [clip(`${lessonHeading(record.lesson)}: "${oneLine2(record.lesson.text)}"`, LESSON_LINE_MAX)] : [];
|
|
@@ -178,6 +192,8 @@ function detailOptions(record) {
|
|
|
178
192
|
});
|
|
179
193
|
const cost = costOf(record);
|
|
180
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");
|
|
181
197
|
const lesson = record.lesson ? [{
|
|
182
198
|
title: lessonHeading(record.lesson),
|
|
183
199
|
value: {
|
|
@@ -201,7 +217,7 @@ function detailOptions(record) {
|
|
|
201
217
|
category: "Lesson",
|
|
202
218
|
description: "courier_note writes one line per tree"
|
|
203
219
|
}];
|
|
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) => ({
|
|
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) => ({
|
|
205
221
|
title: `${" ".repeat(Math.max(0, part.depth - 1))}${clip(oneLine2(part.title) || part.sessionID, 60)}`,
|
|
206
222
|
value: {
|
|
207
223
|
kind: "part",
|
|
@@ -233,6 +249,14 @@ function forgottenLine(record, outcome) {
|
|
|
233
249
|
return `Forgot the lesson of ${name}; its facts stay`;
|
|
234
250
|
return `Nothing to forget for ${name}: it was already gone`;
|
|
235
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
|
+
}
|
|
236
260
|
function addMemoryView(ctx) {
|
|
237
261
|
const memory = ctx.client.rpc(memoryRpc);
|
|
238
262
|
const openSession = () => {
|
|
@@ -258,7 +282,7 @@ function addMemoryView(ctx) {
|
|
|
258
282
|
message,
|
|
259
283
|
variant: "info"
|
|
260
284
|
});
|
|
261
|
-
function show(options, chosen, forget) {
|
|
285
|
+
function show(options, chosen, forget, pin) {
|
|
262
286
|
let request = () => {
|
|
263
287
|
return;
|
|
264
288
|
};
|
|
@@ -271,6 +295,10 @@ function addMemoryView(ctx) {
|
|
|
271
295
|
bind: "x",
|
|
272
296
|
title: "forget",
|
|
273
297
|
onTrigger: (value) => request(forget(value))
|
|
298
|
+
}, {
|
|
299
|
+
bind: "p",
|
|
300
|
+
title: pin.title,
|
|
301
|
+
onTrigger: (value) => request(pin.ask(value))
|
|
274
302
|
}]
|
|
275
303
|
});
|
|
276
304
|
ctx.ui.dialog.set({
|
|
@@ -303,7 +331,13 @@ function addMemoryView(ctx) {
|
|
|
303
331
|
kind: "forget",
|
|
304
332
|
record,
|
|
305
333
|
lesson: false
|
|
306
|
-
})
|
|
334
|
+
}), {
|
|
335
|
+
title: "pin",
|
|
336
|
+
ask: (record) => ({
|
|
337
|
+
kind: "pin",
|
|
338
|
+
record
|
|
339
|
+
})
|
|
340
|
+
});
|
|
307
341
|
};
|
|
308
342
|
const showRecord = (record, current) => show({
|
|
309
343
|
title: `${localDay(record.movedAt)} ${nameOf(record)}`,
|
|
@@ -319,7 +353,32 @@ function addMemoryView(ctx) {
|
|
|
319
353
|
record,
|
|
320
354
|
lesson: line.kind === "lesson",
|
|
321
355
|
line
|
|
322
|
-
})
|
|
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
|
+
}
|
|
323
382
|
async function confirmForget(record, lesson) {
|
|
324
383
|
const confirmed = await ctx.ui.dialog.confirm({
|
|
325
384
|
title: lesson ? "Forget lesson" : "Forget record",
|
|
@@ -386,6 +445,12 @@ function addMemoryView(ctx) {
|
|
|
386
445
|
viewing = request.record.sessionID;
|
|
387
446
|
continue;
|
|
388
447
|
}
|
|
448
|
+
if (request.kind === "pin") {
|
|
449
|
+
await togglePin(request.record);
|
|
450
|
+
near = [request.record.sessionID];
|
|
451
|
+
line = request.line;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
389
454
|
const forgotten = await confirmForget(request.record, request.lesson);
|
|
390
455
|
near = nearby(listed, request.record.sessionID, forgotten && !request.lesson);
|
|
391
456
|
if (record && !forgotten)
|
|
@@ -404,7 +469,7 @@ function addMemoryView(ctx) {
|
|
|
404
469
|
commands: [{
|
|
405
470
|
id: MEMORY_COMMAND,
|
|
406
471
|
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",
|
|
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",
|
|
408
473
|
group: "Courier",
|
|
409
474
|
palette: true,
|
|
410
475
|
run: () => void open()
|
|
@@ -1295,7 +1360,7 @@ function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0, mound =
|
|
|
1295
1360
|
span(head + 2, 8, "k", (column) => !eyeColumns.includes(column), lean(2));
|
|
1296
1361
|
span(head + 4, 2, "a", undefined, lean(4));
|
|
1297
1362
|
span(10, SIZE, ".", (column) => column >= 6 && column <= 9);
|
|
1298
|
-
span(10, top, mound, (column) => column >= 6 && column <= 9, shift);
|
|
1363
|
+
span(10, top, mound, (column) => column + shift >= 6 && column + shift <= 9, shift);
|
|
1299
1364
|
span(11, 10, mound, undefined, shift);
|
|
1300
1365
|
span(12, 12, mound, undefined, shift);
|
|
1301
1366
|
for (const row of [13, 14, 15])
|
|
@@ -1958,6 +2023,7 @@ var digbyColours = (theme) => ({
|
|
|
1958
2023
|
e: theme.text.feedback.error.base,
|
|
1959
2024
|
s: theme.text.feedback.success.base
|
|
1960
2025
|
});
|
|
2026
|
+
var DIGBY_UNDER = RGBA.fromValues(0, 0, 0, 0);
|
|
1961
2027
|
function restBackground(theme, {
|
|
1962
2028
|
selected,
|
|
1963
2029
|
hovered
|
|
@@ -2200,7 +2266,7 @@ function TreePanel(props) {
|
|
|
2200
2266
|
_$insert(_el$5, () => one().char);
|
|
2201
2267
|
_$effect((_$p) => _$setProp(_el$5, "style", {
|
|
2202
2268
|
fg: one().fg && tokens[one().fg],
|
|
2203
|
-
bg: one().bg
|
|
2269
|
+
bg: one().bg ? tokens[one().bg] : DIGBY_UNDER
|
|
2204
2270
|
}, _$p));
|
|
2205
2271
|
return _el$5;
|
|
2206
2272
|
})()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-courier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Multi-session orchestration for OpenCode V2: spawn child sessions, be woken by them. No polling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivo Pogace <71701666+ivopogace@users.noreply.github.com>",
|