opencode-courier 0.8.0 → 0.10.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 +6 -0
- package/dist/kernel/protocol.js +13 -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/skill.d.ts +4 -1
- package/dist/skill.js +25 -16
- 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/tree/text.js +11 -8
- package/dist/tui.js +80 -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: {}
|
|
@@ -54,10 +61,13 @@ var memoryRpc = Rpc.define({
|
|
|
54
61
|
|
|
55
62
|
// src/kernel/protocol.ts
|
|
56
63
|
var STATUSES = ["done", "partial", "blocked", "failed"];
|
|
64
|
+
var SPLIT_LINE = "split: no, because \u2026; considered: <the parts you weighed> or split: <n> parts: \u2026 (one phrase per part)";
|
|
65
|
+
var ONE_PART_LINE = "split: 1 part: <the task>, to keep my context";
|
|
66
|
+
var ROOT_SPLIT_LINE = `${SPLIT_LINE} or ${ONE_PART_LINE}`;
|
|
57
67
|
|
|
58
68
|
// src/memory/domain.ts
|
|
59
69
|
var RETENTION_MS = 90 * 24 * 60 * 60000;
|
|
60
|
-
var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "stalled", "out"];
|
|
70
|
+
var PART_STATUSES2 = [...STATUSES, "interrupted", "stopped", "gone", "stalled", "out"];
|
|
61
71
|
|
|
62
72
|
// src/memory/text.ts
|
|
63
73
|
function duration(ms) {
|
|
@@ -73,12 +83,13 @@ var NOTE_EMPTY = `Not noted: text is empty. Write one line, ${ONE_LINE}, or noth
|
|
|
73
83
|
|
|
74
84
|
// src/memory/tui.tsx
|
|
75
85
|
var MEMORY_COMMAND = "courier.memory";
|
|
76
|
-
var MEMORY_USAGE = "/courier memory lists this project's tree records, to read or forget";
|
|
86
|
+
var MEMORY_USAGE = "/courier memory lists this project's tree records, to read, pin or forget";
|
|
77
87
|
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
88
|
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
89
|
var LESSON_LINE_MAX = 160;
|
|
80
90
|
var ROW_TITLE_MAX = 46;
|
|
81
91
|
var TODAY = "Today";
|
|
92
|
+
var PINNED = "Pinned";
|
|
82
93
|
var LESSON_ROW = 90;
|
|
83
94
|
var pad = (n) => String(n).padStart(2, "0");
|
|
84
95
|
var localDay = (at) => new Date(at).toDateString();
|
|
@@ -96,6 +107,7 @@ var minute = (at) => {
|
|
|
96
107
|
var oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
|
|
97
108
|
var clip = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
98
109
|
var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
110
|
+
var times = (n) => n === 1 ? "once" : `${n} times`;
|
|
99
111
|
var money = (cost) => `$${cost.toFixed(2)}`;
|
|
100
112
|
var nameOf = (record) => clip(oneLine2(record.title) || record.sessionID, 60);
|
|
101
113
|
function partsCounted(parts) {
|
|
@@ -114,16 +126,21 @@ function recordTitle(record, open) {
|
|
|
114
126
|
return `${clip(oneLine2(record.title) || record.sessionID, ROW_TITLE_MAX)}${record.sessionID === open ? " (this session)" : ""}`;
|
|
115
127
|
}
|
|
116
128
|
function recordCategory(record, now = Date.now()) {
|
|
129
|
+
if (record.pinned)
|
|
130
|
+
return PINNED;
|
|
117
131
|
const moved = localDay(record.movedAt);
|
|
118
132
|
return moved === localDay(now) ? TODAY : moved;
|
|
119
133
|
}
|
|
134
|
+
function readsLine(reads) {
|
|
135
|
+
return [reads.shown ? `shown ${times(reads.shown)}` : "", reads.read ? `opened ${times(reads.read)}` : ""].filter(Boolean).join(", ");
|
|
136
|
+
}
|
|
120
137
|
function lessonHeading(lesson) {
|
|
121
138
|
const from = lesson.depth === undefined ? "" : `, from depth ${lesson.depth}`;
|
|
122
139
|
return `lesson (${day(lesson.at)}${from})`;
|
|
123
140
|
}
|
|
124
141
|
function recordDescription(record) {
|
|
125
142
|
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 ");
|
|
143
|
+
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
144
|
}
|
|
128
145
|
function recordDetails(record) {
|
|
129
146
|
return record.lesson ? [clip(`${lessonHeading(record.lesson)}: "${oneLine2(record.lesson.text)}"`, LESSON_LINE_MAX)] : [];
|
|
@@ -178,6 +195,8 @@ function detailOptions(record) {
|
|
|
178
195
|
});
|
|
179
196
|
const cost = costOf(record);
|
|
180
197
|
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");
|
|
198
|
+
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");
|
|
199
|
+
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
200
|
const lesson = record.lesson ? [{
|
|
182
201
|
title: lessonHeading(record.lesson),
|
|
183
202
|
value: {
|
|
@@ -201,7 +220,7 @@ function detailOptions(record) {
|
|
|
201
220
|
category: "Lesson",
|
|
202
221
|
description: "courier_note writes one line per tree"
|
|
203
222
|
}];
|
|
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) => ({
|
|
223
|
+
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
224
|
title: `${" ".repeat(Math.max(0, part.depth - 1))}${clip(oneLine2(part.title) || part.sessionID, 60)}`,
|
|
206
225
|
value: {
|
|
207
226
|
kind: "part",
|
|
@@ -233,6 +252,14 @@ function forgottenLine(record, outcome) {
|
|
|
233
252
|
return `Forgot the lesson of ${name}; its facts stay`;
|
|
234
253
|
return `Nothing to forget for ${name}: it was already gone`;
|
|
235
254
|
}
|
|
255
|
+
function pinnedLine(record, outcome) {
|
|
256
|
+
const name = `"${nameOf(record)}"`;
|
|
257
|
+
if (outcome === "pinned")
|
|
258
|
+
return `Pinned ${name}: listed first, never swept`;
|
|
259
|
+
if (outcome === "unpinned")
|
|
260
|
+
return `Unpinned ${name}: kept as the others are`;
|
|
261
|
+
return `Nothing to pin for ${name}: it was already gone`;
|
|
262
|
+
}
|
|
236
263
|
function addMemoryView(ctx) {
|
|
237
264
|
const memory = ctx.client.rpc(memoryRpc);
|
|
238
265
|
const openSession = () => {
|
|
@@ -258,7 +285,7 @@ function addMemoryView(ctx) {
|
|
|
258
285
|
message,
|
|
259
286
|
variant: "info"
|
|
260
287
|
});
|
|
261
|
-
function show(options, chosen, forget) {
|
|
288
|
+
function show(options, chosen, forget, pin) {
|
|
262
289
|
let request = () => {
|
|
263
290
|
return;
|
|
264
291
|
};
|
|
@@ -271,6 +298,10 @@ function addMemoryView(ctx) {
|
|
|
271
298
|
bind: "x",
|
|
272
299
|
title: "forget",
|
|
273
300
|
onTrigger: (value) => request(forget(value))
|
|
301
|
+
}, {
|
|
302
|
+
bind: "p",
|
|
303
|
+
title: pin.title,
|
|
304
|
+
onTrigger: (value) => request(pin.ask(value))
|
|
274
305
|
}]
|
|
275
306
|
});
|
|
276
307
|
ctx.ui.dialog.set({
|
|
@@ -303,7 +334,13 @@ function addMemoryView(ctx) {
|
|
|
303
334
|
kind: "forget",
|
|
304
335
|
record,
|
|
305
336
|
lesson: false
|
|
306
|
-
})
|
|
337
|
+
}), {
|
|
338
|
+
title: "pin",
|
|
339
|
+
ask: (record) => ({
|
|
340
|
+
kind: "pin",
|
|
341
|
+
record
|
|
342
|
+
})
|
|
343
|
+
});
|
|
307
344
|
};
|
|
308
345
|
const showRecord = (record, current) => show({
|
|
309
346
|
title: `${localDay(record.movedAt)} ${nameOf(record)}`,
|
|
@@ -319,7 +356,32 @@ function addMemoryView(ctx) {
|
|
|
319
356
|
record,
|
|
320
357
|
lesson: line.kind === "lesson",
|
|
321
358
|
line
|
|
322
|
-
})
|
|
359
|
+
}), {
|
|
360
|
+
title: record.pinned ? "unpin" : "pin",
|
|
361
|
+
ask: (line) => ({
|
|
362
|
+
kind: "pin",
|
|
363
|
+
record,
|
|
364
|
+
line
|
|
365
|
+
})
|
|
366
|
+
});
|
|
367
|
+
async function togglePin(record) {
|
|
368
|
+
const pinned = !record.pinned;
|
|
369
|
+
try {
|
|
370
|
+
const result = await memory.pin({
|
|
371
|
+
sessionID: record.sessionID,
|
|
372
|
+
pinned
|
|
373
|
+
}, {
|
|
374
|
+
location: where()
|
|
375
|
+
});
|
|
376
|
+
ctx.ui.toast.show({
|
|
377
|
+
title: "courier",
|
|
378
|
+
message: pinnedLine(record, result.outcome),
|
|
379
|
+
variant: result.outcome === "none" ? "warning" : "success"
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
fail(pinned ? "Not pinned" : "Not unpinned")(error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
323
385
|
async function confirmForget(record, lesson) {
|
|
324
386
|
const confirmed = await ctx.ui.dialog.confirm({
|
|
325
387
|
title: lesson ? "Forget lesson" : "Forget record",
|
|
@@ -386,6 +448,12 @@ function addMemoryView(ctx) {
|
|
|
386
448
|
viewing = request.record.sessionID;
|
|
387
449
|
continue;
|
|
388
450
|
}
|
|
451
|
+
if (request.kind === "pin") {
|
|
452
|
+
await togglePin(request.record);
|
|
453
|
+
near = [request.record.sessionID];
|
|
454
|
+
line = request.line;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
389
457
|
const forgotten = await confirmForget(request.record, request.lesson);
|
|
390
458
|
near = nearby(listed, request.record.sessionID, forgotten && !request.lesson);
|
|
391
459
|
if (record && !forgotten)
|
|
@@ -404,7 +472,7 @@ function addMemoryView(ctx) {
|
|
|
404
472
|
commands: [{
|
|
405
473
|
id: MEMORY_COMMAND,
|
|
406
474
|
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",
|
|
475
|
+
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
476
|
group: "Courier",
|
|
409
477
|
palette: true,
|
|
410
478
|
run: () => void open()
|
|
@@ -1295,7 +1363,7 @@ function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0, mound =
|
|
|
1295
1363
|
span(head + 2, 8, "k", (column) => !eyeColumns.includes(column), lean(2));
|
|
1296
1364
|
span(head + 4, 2, "a", undefined, lean(4));
|
|
1297
1365
|
span(10, SIZE, ".", (column) => column >= 6 && column <= 9);
|
|
1298
|
-
span(10, top, mound, (column) => column >= 6 && column <= 9, shift);
|
|
1366
|
+
span(10, top, mound, (column) => column + shift >= 6 && column + shift <= 9, shift);
|
|
1299
1367
|
span(11, 10, mound, undefined, shift);
|
|
1300
1368
|
span(12, 12, mound, undefined, shift);
|
|
1301
1369
|
for (const row of [13, 14, 15])
|
|
@@ -1958,6 +2026,7 @@ var digbyColours = (theme) => ({
|
|
|
1958
2026
|
e: theme.text.feedback.error.base,
|
|
1959
2027
|
s: theme.text.feedback.success.base
|
|
1960
2028
|
});
|
|
2029
|
+
var DIGBY_UNDER = RGBA.fromValues(0, 0, 0, 0);
|
|
1961
2030
|
function restBackground(theme, {
|
|
1962
2031
|
selected,
|
|
1963
2032
|
hovered
|
|
@@ -2200,7 +2269,7 @@ function TreePanel(props) {
|
|
|
2200
2269
|
_$insert(_el$5, () => one().char);
|
|
2201
2270
|
_$effect((_$p) => _$setProp(_el$5, "style", {
|
|
2202
2271
|
fg: one().fg && tokens[one().fg],
|
|
2203
|
-
bg: one().bg
|
|
2272
|
+
bg: one().bg ? tokens[one().bg] : DIGBY_UNDER
|
|
2204
2273
|
}, _$p));
|
|
2205
2274
|
return _el$5;
|
|
2206
2275
|
})()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-courier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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>",
|