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/memory/record.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { finite, isNotFound, millis } from "../platform/json.js";
|
|
2
2
|
import { busy, contextOf, reportState, stalledAmong, subtree } from "../tree/index.js";
|
|
3
|
-
import { mergeParts, NOT_STATED, partStatus,
|
|
3
|
+
import { isSettled, isStated, mergeParts, NOT_STATED, partStatus, replacingMention, revise, splitLinesOf, splitOf, unreadPart } from "./domain.js";
|
|
4
4
|
import { memoryStore } from "./store.js";
|
|
5
|
-
/**
|
|
6
|
-
const repliesOf = (messages) => messages
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/** A session's replies, in order, each with when it was created, for its split lines; only the `finished` ones when asked. */
|
|
6
|
+
const repliesOf = (messages, finished) => messages.flatMap((message) => message.type === "assistant" && (!finished || message.time?.completed !== undefined)
|
|
7
|
+
? [{ text: message.content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"), at: finite(millis(message.time?.created)) }]
|
|
8
|
+
: []);
|
|
9
|
+
/** The lines in the replies that begin with `split:`, each with its reply's time. */
|
|
10
|
+
const statedIn = (replies) => replies.flatMap(({ text, at }) => splitLinesOf(text).map((line) => (at === undefined ? { text: line } : { text: line, at })));
|
|
9
11
|
/** How many courier_spawn calls of a session failed, refused by a limit mostly. */
|
|
10
12
|
const refusedIn = (messages) => messages.reduce((n, message) => (message.type === "assistant" ? n + message.content.filter((part) => part.type === "tool" && part.name === "courier_spawn" && part.state.status === "error").length : n), 0);
|
|
11
13
|
/** A part's context use and refused spawns, as its messages show them. */
|
|
@@ -19,14 +21,16 @@ const keptUse = (last) => ({
|
|
|
19
21
|
...(last?.context !== undefined ? { context: last.context } : {}),
|
|
20
22
|
...(last?.refused ? { refused: last.refused } : {}),
|
|
21
23
|
});
|
|
24
|
+
/** A session's info, or `gone` when OpenCode no longer knows it; neither when it could not be read for another reason. */
|
|
25
|
+
const infoOf = (ports, sessionID) => ports.session.get({ sessionID }).then((info) => ({ info, gone: false }), (error) => ({ gone: isNotFound(error) }));
|
|
22
26
|
/**
|
|
23
27
|
* One session of the tree as its record keeps it, without its use when its messages were not read (only when its turn
|
|
24
|
-
* `ended` or it is `known` to no record yet), and whether it runs now.
|
|
28
|
+
* `ended` or it is `known` to no record yet), without a duration when its info was not, and whether it runs now.
|
|
25
29
|
*/
|
|
26
30
|
async function part(ports, windows, { entry, depth }, stalled, at, known, ended) {
|
|
27
31
|
const { sessionID } = entry;
|
|
28
|
-
const [info, use, report] = await Promise.all([
|
|
29
|
-
ports
|
|
32
|
+
const [{ info, gone }, use, report] = await Promise.all([
|
|
33
|
+
infoOf(ports, sessionID),
|
|
30
34
|
ended || !known ? ports.session.context({ sessionID }).then((messages) => useOf(messages, windows), () => undefined) : undefined,
|
|
31
35
|
reportState(ports.storage, sessionID).catch(() => undefined),
|
|
32
36
|
]);
|
|
@@ -41,14 +45,20 @@ async function part(ports, windows, { entry, depth }, stalled, at, known, ended)
|
|
|
41
45
|
isolated: entry.isolated,
|
|
42
46
|
...(entry.group ? { group: entry.group } : {}),
|
|
43
47
|
status: partStatus({ stopped: report?.stopped ?? false, stalled, report }),
|
|
44
|
-
duration: Math.max(0, end - created),
|
|
48
|
+
...(info ? { duration: Math.max(0, end - created) } : {}),
|
|
45
49
|
...(cost !== undefined ? { cost } : {}),
|
|
46
50
|
};
|
|
47
|
-
return { record, use, running };
|
|
51
|
+
return { record, use, running, unread: info === undefined, gone };
|
|
52
|
+
}
|
|
53
|
+
/** Of the parts last recorded, those the roster has forgotten before they settled whose session OpenCode no longer knows. */
|
|
54
|
+
async function goneSince(ports, parts, onRoster) {
|
|
55
|
+
const unsettled = parts.filter((one) => !onRoster.has(one.sessionID) && !isSettled(one.status));
|
|
56
|
+
const looked = await Promise.all(unsettled.map(async ({ sessionID }) => ({ sessionID, ...(await infoOf(ports, sessionID)) })));
|
|
57
|
+
return new Set(looked.flatMap((one) => (one.gone ? [one.sessionID] : [])));
|
|
48
58
|
}
|
|
49
59
|
/**
|
|
50
60
|
* Writes the record of the tree under `top` as it stands at `at`, after the turn of `ended`; a part the roster has forgotten
|
|
51
|
-
* keeps its last recorded state. Nothing for a top unknown to OpenCode or
|
|
61
|
+
* keeps its last recorded state, unless found gone before it settled. Nothing for a top unknown to OpenCode or with memory off.
|
|
52
62
|
*/
|
|
53
63
|
export async function refresh(ports, state, top, at, ended) {
|
|
54
64
|
let info;
|
|
@@ -64,25 +74,47 @@ export async function refresh(ports, state, top, at, ended) {
|
|
|
64
74
|
return undefined;
|
|
65
75
|
const store = memoryStore(ports.storage);
|
|
66
76
|
const [previous, { nodes }] = await Promise.all([store.get(info.projectID, top), subtree(ports.storage, top)]);
|
|
67
|
-
// The split
|
|
68
|
-
|
|
77
|
+
// The split lines are in the top's own replies, so only its own turn end can revise one stated already; another's reads
|
|
78
|
+
// them while none is stated, and only the replies finished, never a line the top is still writing.
|
|
79
|
+
const own = ended === top;
|
|
80
|
+
const replies = own || !isStated(previous?.split ?? NOT_STATED)
|
|
81
|
+
? ports.session.context({ sessionID: top }).then((messages) => repliesOf(messages, !own), () => [])
|
|
82
|
+
: [];
|
|
69
83
|
const stalled = await stalledAmong(ports, nodes.map((node) => node.entry.sessionID));
|
|
70
84
|
const windows = [...state.windows.values()];
|
|
71
85
|
const known = new Set(previous?.parts.map((one) => one.sessionID));
|
|
72
|
-
const read = await Promise.all(
|
|
73
|
-
|
|
86
|
+
const [read, lost] = await Promise.all([
|
|
87
|
+
Promise.all(nodes.map((node) => part(ports, windows, node, stalled.has(node.entry.sessionID), at, known.has(node.entry.sessionID), node.entry.sessionID === ended))),
|
|
88
|
+
goneSince(ports, previous?.parts ?? [], new Set(nodes.map((node) => node.entry.sessionID))),
|
|
89
|
+
]);
|
|
90
|
+
const said = await replies;
|
|
74
91
|
// Read again just before the write: a courier_recall or courier_note call, or another turn end's refresh, wrote meanwhile.
|
|
75
92
|
const latest = (await store.get(info.projectID, top).catch(() => previous)) ?? previous;
|
|
76
93
|
const last = new Map(latest?.parts.map((one) => [one.sessionID, one]));
|
|
77
|
-
const
|
|
94
|
+
const current = read.map(({ record, use, unread, gone }) => {
|
|
95
|
+
const before = last.get(record.sessionID);
|
|
96
|
+
return { ...(unread ? unreadPart(record, before, gone) : record), ...(use ?? keptUse(before)) };
|
|
97
|
+
});
|
|
98
|
+
const forgotten = (latest?.parts ?? []).filter((one) => lost.has(one.sessionID)).map((one) => unreadPart(one, one, true));
|
|
99
|
+
const parts = mergeParts(latest?.parts ?? [], [...current, ...forgotten]);
|
|
78
100
|
const recalled = latest?.recalled ?? previous?.recalled;
|
|
79
101
|
const lesson = latest?.lesson ?? previous?.lesson;
|
|
102
|
+
// The first stated line stays even when compaction has dropped its reply; a later one is a revision.
|
|
103
|
+
const first = latest?.split ?? NOT_STATED;
|
|
104
|
+
const found = splitOf(first, said.map((reply) => reply.text).join("\n"));
|
|
105
|
+
const stated = statedIn(said);
|
|
106
|
+
const replaced = found !== first;
|
|
107
|
+
const { split, revisions: recorded } = replaced ? replacingMention(found, latest?.revisions) : { split: first, revisions: latest?.revisions };
|
|
108
|
+
// The line that replaced a mention is the first stated, so beside revisions already recorded it is not one of them too.
|
|
109
|
+
const since = replaced && recorded ? stated.slice(1) : stated;
|
|
110
|
+
const revisions = split === NOT_STATED ? undefined : revise({ split, revisions: recorded }, since, at, parts.length);
|
|
80
111
|
const record = {
|
|
81
112
|
startedAt: finite(millis(info.time.created)) ?? previous?.startedAt ?? at,
|
|
82
113
|
movedAt: at,
|
|
83
114
|
title: info.title ?? previous?.title ?? "",
|
|
84
|
-
split
|
|
85
|
-
|
|
115
|
+
split,
|
|
116
|
+
...(revisions ? { revisions } : {}),
|
|
117
|
+
finished: !busy(info.time) && !read.some(({ running }) => running) && parts.every((one) => isSettled(one.status)),
|
|
86
118
|
...(recalled ? { recalled } : {}),
|
|
87
119
|
...(lesson ? { lesson } : {}),
|
|
88
120
|
parts,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin";
|
|
2
2
|
import type { MemoryPorts } from "./ports.js";
|
|
3
3
|
import { type Records } from "./rpc.js";
|
|
4
|
+
/** What the memory's RPC runs on: one instance's ports. */
|
|
5
|
+
export type MemoryRpcPorts = Pick<MemoryPorts, "storage" | "projectID" | "memory" | "now">;
|
|
4
6
|
/**
|
|
5
|
-
* The records of the ports' project, newest first, as the memory view lists them; nothing, and
|
|
6
|
-
* option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
|
|
7
|
+
* The records of the ports' project, pinned first and then newest first, as the memory view lists them; nothing, and
|
|
8
|
+
* `memory` false, with the option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
|
|
7
9
|
*/
|
|
8
|
-
export declare function records(ports: Pick<
|
|
10
|
+
export declare function records(ports: Pick<MemoryRpcPorts, "storage" | "projectID" | "memory">): Promise<Records>;
|
|
9
11
|
/** Serves the memory's RPC at the plugin's location, after the tree's and the person's; disposed with the plugin. */
|
|
10
|
-
export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "register">, ports:
|
|
12
|
+
export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "register">, ports: MemoryRpcPorts): Promise<import("@opencode/plugin/promise/rpc").RpcRegistration<{
|
|
11
13
|
readonly id: "courier-memory";
|
|
12
14
|
readonly methods: {
|
|
13
15
|
readonly records: {
|
|
@@ -19,12 +21,12 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
19
21
|
readonly sessionID: string;
|
|
20
22
|
readonly split: string;
|
|
21
23
|
readonly title: string;
|
|
22
|
-
readonly movedAt: number;
|
|
23
24
|
readonly startedAt: number;
|
|
25
|
+
readonly movedAt: number;
|
|
24
26
|
readonly finished: boolean;
|
|
25
27
|
readonly parts: readonly {
|
|
26
28
|
readonly sessionID: string;
|
|
27
|
-
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "out" | "stopped" | "stalled";
|
|
29
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "out" | "stopped" | "gone" | "stalled";
|
|
28
30
|
readonly title: string;
|
|
29
31
|
readonly isolated: boolean;
|
|
30
32
|
readonly depth: number;
|
|
@@ -43,6 +45,14 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
43
45
|
readonly text: string;
|
|
44
46
|
readonly depth?: number | undefined;
|
|
45
47
|
} | undefined;
|
|
48
|
+
readonly reads?: {
|
|
49
|
+
readonly shown: number;
|
|
50
|
+
readonly read: number;
|
|
51
|
+
readonly at: number;
|
|
52
|
+
} | undefined;
|
|
53
|
+
readonly pinned?: {
|
|
54
|
+
readonly at: number;
|
|
55
|
+
} | undefined;
|
|
46
56
|
}[];
|
|
47
57
|
}, {
|
|
48
58
|
readonly memory: boolean;
|
|
@@ -51,12 +61,12 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
51
61
|
readonly sessionID: string;
|
|
52
62
|
readonly split: string;
|
|
53
63
|
readonly title: string;
|
|
54
|
-
readonly movedAt: number;
|
|
55
64
|
readonly startedAt: number;
|
|
65
|
+
readonly movedAt: number;
|
|
56
66
|
readonly finished: boolean;
|
|
57
67
|
readonly parts: readonly {
|
|
58
68
|
readonly sessionID: string;
|
|
59
|
-
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "out" | "stopped" | "stalled";
|
|
69
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "out" | "stopped" | "gone" | "stalled";
|
|
60
70
|
readonly title: string;
|
|
61
71
|
readonly isolated: boolean;
|
|
62
72
|
readonly depth: number;
|
|
@@ -75,6 +85,14 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
75
85
|
readonly text: string;
|
|
76
86
|
readonly depth?: number | undefined;
|
|
77
87
|
} | undefined;
|
|
88
|
+
readonly reads?: {
|
|
89
|
+
readonly shown: number;
|
|
90
|
+
readonly read: number;
|
|
91
|
+
readonly at: number;
|
|
92
|
+
} | undefined;
|
|
93
|
+
readonly pinned?: {
|
|
94
|
+
readonly at: number;
|
|
95
|
+
} | undefined;
|
|
78
96
|
}[];
|
|
79
97
|
}> & import("effect/Schema").Struct<{
|
|
80
98
|
readonly memory: import("effect/Schema").Boolean;
|
|
@@ -95,13 +113,21 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
95
113
|
readonly at: import("effect/Schema").Number;
|
|
96
114
|
readonly depth: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
97
115
|
}>>;
|
|
116
|
+
readonly reads: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
117
|
+
readonly shown: import("effect/Schema").Int;
|
|
118
|
+
readonly read: import("effect/Schema").Int;
|
|
119
|
+
readonly at: import("effect/Schema").Number;
|
|
120
|
+
}>>;
|
|
121
|
+
readonly pinned: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
122
|
+
readonly at: import("effect/Schema").Number;
|
|
123
|
+
}>>;
|
|
98
124
|
readonly parts: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
99
125
|
readonly sessionID: import("effect/Schema").String;
|
|
100
126
|
readonly title: import("effect/Schema").String;
|
|
101
127
|
readonly depth: import("effect/Schema").Int;
|
|
102
128
|
readonly isolated: import("effect/Schema").Boolean;
|
|
103
129
|
readonly group: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
104
|
-
readonly status: import("effect/Schema").Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"]>;
|
|
130
|
+
readonly status: import("effect/Schema").Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
105
131
|
readonly duration: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
|
|
106
132
|
readonly cost: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
|
|
107
133
|
readonly context: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
|
|
@@ -129,6 +155,25 @@ export declare function registerMemoryRpc(rpc: Pick<Plugin.Context["rpc"], "regi
|
|
|
129
155
|
readonly outcome: import("effect/Schema").Literals<readonly ["record", "lesson", "none"]>;
|
|
130
156
|
}>;
|
|
131
157
|
};
|
|
158
|
+
readonly pin: {
|
|
159
|
+
readonly input: import("effect/StandardSchema").StandardSchemaV1<{
|
|
160
|
+
readonly sessionID: string;
|
|
161
|
+
readonly pinned: boolean;
|
|
162
|
+
}, {
|
|
163
|
+
readonly sessionID: string;
|
|
164
|
+
readonly pinned: boolean;
|
|
165
|
+
}> & import("effect/Schema").Struct<{
|
|
166
|
+
readonly sessionID: import("effect/Schema").String;
|
|
167
|
+
readonly pinned: import("effect/Schema").Boolean;
|
|
168
|
+
}>;
|
|
169
|
+
readonly output: import("effect/StandardSchema").StandardSchemaV1<{
|
|
170
|
+
readonly outcome: "pinned" | "unpinned" | "none";
|
|
171
|
+
}, {
|
|
172
|
+
readonly outcome: "pinned" | "unpinned" | "none";
|
|
173
|
+
}> & import("effect/Schema").Struct<{
|
|
174
|
+
readonly outcome: import("effect/Schema").Literals<readonly ["pinned", "unpinned", "none"]>;
|
|
175
|
+
}>;
|
|
176
|
+
};
|
|
132
177
|
};
|
|
133
178
|
readonly events: {};
|
|
134
179
|
}>>;
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pinnedFirst } from "./domain.js";
|
|
2
2
|
import { forget } from "./forget.js";
|
|
3
|
+
import { pin } from "./pin.js";
|
|
3
4
|
import { memoryRpc } from "./rpc.js";
|
|
4
5
|
import { memoryStore } from "./store.js";
|
|
5
|
-
/** A stored record as the RPC gives it: without its project, which the listing names once. */
|
|
6
|
-
const toRecord = ({ projectID: _project, ...record }) => record;
|
|
6
|
+
/** A stored record as the RPC gives it: without its project, which the listing names once, nor the split line's revisions, which its schema lacks. */
|
|
7
|
+
const toRecord = ({ projectID: _project, revisions: _revisions, ...record }) => record;
|
|
7
8
|
/**
|
|
8
|
-
* The records of the ports' project, newest first, as the memory view lists them; nothing, and
|
|
9
|
-
* option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
|
|
9
|
+
* The records of the ports' project, pinned first and then newest first, as the memory view lists them; nothing, and
|
|
10
|
+
* `memory` false, with the option off. Read from the store alone: unlike courier_recall, a look from the terminal is noted on no record.
|
|
10
11
|
*/
|
|
11
12
|
export async function records(ports) {
|
|
12
13
|
const { projectID } = ports;
|
|
13
14
|
if (!ports.memory)
|
|
14
15
|
return { memory: false, projectID, records: [] };
|
|
15
|
-
return { memory: true, projectID, records:
|
|
16
|
+
return { memory: true, projectID, records: pinnedFirst(await memoryStore(ports.storage).ofProject(projectID)).map(toRecord) };
|
|
16
17
|
}
|
|
17
18
|
/** Serves the memory's RPC at the plugin's location, after the tree's and the person's; disposed with the plugin. */
|
|
18
19
|
export function registerMemoryRpc(rpc, ports) {
|
|
19
20
|
return rpc.register(memoryRpc, {
|
|
20
21
|
records: () => records(ports),
|
|
21
22
|
forget: async ({ sessionID, lesson }) => ({ outcome: await forget(ports, sessionID, { lesson: lesson === true }) }),
|
|
23
|
+
pin: async ({ sessionID, pinned }) => ({ outcome: await pin(ports, sessionID, pinned) }),
|
|
22
24
|
});
|
|
23
25
|
}
|
package/dist/memory/rpc.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
/** A part's statuses, as `memory/domain.ts`'s `PART_STATUSES` has them; a unit test keeps the two equal. */
|
|
3
|
-
export declare const PART_STATUSES: readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"];
|
|
3
|
+
export declare const PART_STATUSES: readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"];
|
|
4
4
|
/** One session started in a tree, as its record keeps it: durations in milliseconds, context use a share of the model's window. */
|
|
5
5
|
declare const Part: Schema.Struct<{
|
|
6
6
|
readonly sessionID: Schema.String;
|
|
@@ -9,7 +9,7 @@ declare const Part: Schema.Struct<{
|
|
|
9
9
|
readonly depth: Schema.Int;
|
|
10
10
|
readonly isolated: Schema.Boolean;
|
|
11
11
|
readonly group: Schema.optionalKey<Schema.String>;
|
|
12
|
-
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"]>;
|
|
12
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
13
13
|
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
14
14
|
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
15
15
|
readonly context: Schema.optionalKey<Schema.Number>;
|
|
@@ -38,6 +38,16 @@ declare const Record: Schema.Struct<{
|
|
|
38
38
|
readonly at: Schema.Number;
|
|
39
39
|
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
40
40
|
}>>;
|
|
41
|
+
/** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
|
|
42
|
+
readonly reads: Schema.optionalKey<Schema.Struct<{
|
|
43
|
+
readonly shown: Schema.Int;
|
|
44
|
+
readonly read: Schema.Int;
|
|
45
|
+
readonly at: Schema.Number;
|
|
46
|
+
}>>;
|
|
47
|
+
/** The person's pin, when; a pinned record is listed first and never swept. */
|
|
48
|
+
readonly pinned: Schema.optionalKey<Schema.Struct<{
|
|
49
|
+
readonly at: Schema.Number;
|
|
50
|
+
}>>;
|
|
41
51
|
readonly parts: Schema.$Array<Schema.Struct<{
|
|
42
52
|
readonly sessionID: Schema.String;
|
|
43
53
|
readonly title: Schema.String;
|
|
@@ -45,14 +55,14 @@ declare const Record: Schema.Struct<{
|
|
|
45
55
|
readonly depth: Schema.Int;
|
|
46
56
|
readonly isolated: Schema.Boolean;
|
|
47
57
|
readonly group: Schema.optionalKey<Schema.String>;
|
|
48
|
-
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"]>;
|
|
58
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
49
59
|
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
50
60
|
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
51
61
|
readonly context: Schema.optionalKey<Schema.Number>;
|
|
52
62
|
readonly refused: Schema.optionalKey<Schema.Int>;
|
|
53
63
|
}>>;
|
|
54
64
|
}>;
|
|
55
|
-
/** The records of the project of the location asked, newest first; none, and `memory` false, when the option is off there. */
|
|
65
|
+
/** The records of the project of the location asked, pinned first and then newest first; none, and `memory` false, when the option is off there. */
|
|
56
66
|
declare const Records: Schema.Struct<{
|
|
57
67
|
readonly memory: Schema.Boolean;
|
|
58
68
|
readonly projectID: Schema.String;
|
|
@@ -72,6 +82,16 @@ declare const Records: Schema.Struct<{
|
|
|
72
82
|
readonly at: Schema.Number;
|
|
73
83
|
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
74
84
|
}>>;
|
|
85
|
+
/** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
|
|
86
|
+
readonly reads: Schema.optionalKey<Schema.Struct<{
|
|
87
|
+
readonly shown: Schema.Int;
|
|
88
|
+
readonly read: Schema.Int;
|
|
89
|
+
readonly at: Schema.Number;
|
|
90
|
+
}>>;
|
|
91
|
+
/** The person's pin, when; a pinned record is listed first and never swept. */
|
|
92
|
+
readonly pinned: Schema.optionalKey<Schema.Struct<{
|
|
93
|
+
readonly at: Schema.Number;
|
|
94
|
+
}>>;
|
|
75
95
|
readonly parts: Schema.$Array<Schema.Struct<{
|
|
76
96
|
readonly sessionID: Schema.String;
|
|
77
97
|
readonly title: Schema.String;
|
|
@@ -79,7 +99,7 @@ declare const Records: Schema.Struct<{
|
|
|
79
99
|
readonly depth: Schema.Int;
|
|
80
100
|
readonly isolated: Schema.Boolean;
|
|
81
101
|
readonly group: Schema.optionalKey<Schema.String>;
|
|
82
|
-
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"]>;
|
|
102
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
83
103
|
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
84
104
|
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
85
105
|
readonly context: Schema.optionalKey<Schema.Number>;
|
|
@@ -91,11 +111,17 @@ declare const Records: Schema.Struct<{
|
|
|
91
111
|
declare const Forgotten: Schema.Struct<{
|
|
92
112
|
readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
|
|
93
113
|
}>;
|
|
114
|
+
/** What a pin did: pinned the record, unpinned it, or found no record to pin. */
|
|
115
|
+
declare const Pinned: Schema.Struct<{
|
|
116
|
+
readonly outcome: Schema.Literals<readonly ["pinned", "unpinned", "none"]>;
|
|
117
|
+
}>;
|
|
94
118
|
export type MemoryRecord = typeof Record.Type;
|
|
95
119
|
export type MemoryPart = typeof Part.Type;
|
|
96
120
|
export type MemoryLesson = typeof Lesson.Type;
|
|
121
|
+
export type MemoryReads = NonNullable<MemoryRecord["reads"]>;
|
|
97
122
|
export type Records = typeof Records.Type;
|
|
98
123
|
export type Forgotten = typeof Forgotten.Type;
|
|
124
|
+
export type Pinned = typeof Pinned.Type;
|
|
99
125
|
export declare const memoryRpc: {
|
|
100
126
|
readonly id: "courier-memory";
|
|
101
127
|
readonly methods: {
|
|
@@ -108,7 +134,7 @@ export declare const memoryRpc: {
|
|
|
108
134
|
readonly sessionID: string;
|
|
109
135
|
readonly title: string;
|
|
110
136
|
readonly parts: readonly {
|
|
111
|
-
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "stalled" | "out";
|
|
137
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "gone" | "stalled" | "out";
|
|
112
138
|
readonly sessionID: string;
|
|
113
139
|
readonly title: string;
|
|
114
140
|
readonly depth: number;
|
|
@@ -132,6 +158,14 @@ export declare const memoryRpc: {
|
|
|
132
158
|
readonly at: number;
|
|
133
159
|
readonly depth?: number | undefined;
|
|
134
160
|
} | undefined;
|
|
161
|
+
readonly reads?: {
|
|
162
|
+
readonly shown: number;
|
|
163
|
+
readonly read: number;
|
|
164
|
+
readonly at: number;
|
|
165
|
+
} | undefined;
|
|
166
|
+
readonly pinned?: {
|
|
167
|
+
readonly at: number;
|
|
168
|
+
} | undefined;
|
|
135
169
|
}[];
|
|
136
170
|
}, {
|
|
137
171
|
readonly memory: boolean;
|
|
@@ -140,7 +174,7 @@ export declare const memoryRpc: {
|
|
|
140
174
|
readonly sessionID: string;
|
|
141
175
|
readonly title: string;
|
|
142
176
|
readonly parts: readonly {
|
|
143
|
-
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "stalled" | "out";
|
|
177
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "gone" | "stalled" | "out";
|
|
144
178
|
readonly sessionID: string;
|
|
145
179
|
readonly title: string;
|
|
146
180
|
readonly depth: number;
|
|
@@ -164,6 +198,14 @@ export declare const memoryRpc: {
|
|
|
164
198
|
readonly at: number;
|
|
165
199
|
readonly depth?: number | undefined;
|
|
166
200
|
} | undefined;
|
|
201
|
+
readonly reads?: {
|
|
202
|
+
readonly shown: number;
|
|
203
|
+
readonly read: number;
|
|
204
|
+
readonly at: number;
|
|
205
|
+
} | undefined;
|
|
206
|
+
readonly pinned?: {
|
|
207
|
+
readonly at: number;
|
|
208
|
+
} | undefined;
|
|
167
209
|
}[];
|
|
168
210
|
}> & Schema.Struct<{
|
|
169
211
|
readonly memory: Schema.Boolean;
|
|
@@ -184,6 +226,16 @@ export declare const memoryRpc: {
|
|
|
184
226
|
readonly at: Schema.Number;
|
|
185
227
|
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
186
228
|
}>>;
|
|
229
|
+
/** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
|
|
230
|
+
readonly reads: Schema.optionalKey<Schema.Struct<{
|
|
231
|
+
readonly shown: Schema.Int;
|
|
232
|
+
readonly read: Schema.Int;
|
|
233
|
+
readonly at: Schema.Number;
|
|
234
|
+
}>>;
|
|
235
|
+
/** The person's pin, when; a pinned record is listed first and never swept. */
|
|
236
|
+
readonly pinned: Schema.optionalKey<Schema.Struct<{
|
|
237
|
+
readonly at: Schema.Number;
|
|
238
|
+
}>>;
|
|
187
239
|
readonly parts: Schema.$Array<Schema.Struct<{
|
|
188
240
|
readonly sessionID: Schema.String;
|
|
189
241
|
readonly title: Schema.String;
|
|
@@ -191,7 +243,7 @@ export declare const memoryRpc: {
|
|
|
191
243
|
readonly depth: Schema.Int;
|
|
192
244
|
readonly isolated: Schema.Boolean;
|
|
193
245
|
readonly group: Schema.optionalKey<Schema.String>;
|
|
194
|
-
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"]>;
|
|
246
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
195
247
|
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
196
248
|
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
197
249
|
readonly context: Schema.optionalKey<Schema.Number>;
|
|
@@ -220,6 +272,26 @@ export declare const memoryRpc: {
|
|
|
220
272
|
readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
|
|
221
273
|
}>;
|
|
222
274
|
};
|
|
275
|
+
/** Pins the record of the tree under `sessionID` in the project asked, or with `pinned` false unpins it. */
|
|
276
|
+
readonly pin: {
|
|
277
|
+
readonly input: import("effect/StandardSchema").StandardSchemaV1<{
|
|
278
|
+
readonly sessionID: string;
|
|
279
|
+
readonly pinned: boolean;
|
|
280
|
+
}, {
|
|
281
|
+
readonly sessionID: string;
|
|
282
|
+
readonly pinned: boolean;
|
|
283
|
+
}> & Schema.Struct<{
|
|
284
|
+
readonly sessionID: Schema.String;
|
|
285
|
+
readonly pinned: Schema.Boolean;
|
|
286
|
+
}>;
|
|
287
|
+
readonly output: import("effect/StandardSchema").StandardSchemaV1<{
|
|
288
|
+
readonly outcome: "pinned" | "none" | "unpinned";
|
|
289
|
+
}, {
|
|
290
|
+
readonly outcome: "pinned" | "none" | "unpinned";
|
|
291
|
+
}> & Schema.Struct<{
|
|
292
|
+
readonly outcome: Schema.Literals<readonly ["pinned", "unpinned", "none"]>;
|
|
293
|
+
}>;
|
|
294
|
+
};
|
|
223
295
|
};
|
|
224
296
|
readonly events: {};
|
|
225
297
|
};
|
package/dist/memory/rpc.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Rpc } from "@opencode/plugin/rpc";
|
|
|
6
6
|
import { Schema } from "effect";
|
|
7
7
|
const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
8
8
|
/** A part's statuses, as `memory/domain.ts`'s `PART_STATUSES` has them; a unit test keeps the two equal. */
|
|
9
|
-
export const PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "stalled", "out"];
|
|
9
|
+
export const PART_STATUSES = ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"];
|
|
10
10
|
/** One session started in a tree, as its record keeps it: durations in milliseconds, context use a share of the model's window. */
|
|
11
11
|
const Part = Schema.Struct({
|
|
12
12
|
sessionID: Schema.String,
|
|
@@ -33,9 +33,13 @@ const Record = Schema.Struct({
|
|
|
33
33
|
finished: Schema.Boolean,
|
|
34
34
|
recalled: Schema.optionalKey(Schema.Struct({ at: Schema.optionalKey(Schema.Number), calls: Count })),
|
|
35
35
|
lesson: Schema.optionalKey(Lesson),
|
|
36
|
+
/** How often other trees' courier_recall calls listed the record and gave it in full, and when one last did. */
|
|
37
|
+
reads: Schema.optionalKey(Schema.Struct({ shown: Count, read: Count, at: Schema.Number })),
|
|
38
|
+
/** The person's pin, when; a pinned record is listed first and never swept. */
|
|
39
|
+
pinned: Schema.optionalKey(Schema.Struct({ at: Schema.Number })),
|
|
36
40
|
parts: Schema.Array(Part),
|
|
37
41
|
});
|
|
38
|
-
/** The records of the project of the location asked, newest first; none, and `memory` false, when the option is off there. */
|
|
42
|
+
/** The records of the project of the location asked, pinned first and then newest first; none, and `memory` false, when the option is off there. */
|
|
39
43
|
const Records = Schema.Struct({
|
|
40
44
|
memory: Schema.Boolean,
|
|
41
45
|
projectID: Schema.String,
|
|
@@ -43,6 +47,8 @@ const Records = Schema.Struct({
|
|
|
43
47
|
});
|
|
44
48
|
/** What a forget did: removed the record, dropped only its lesson, or found nothing to forget. */
|
|
45
49
|
const Forgotten = Schema.Struct({ outcome: Schema.Literals(["record", "lesson", "none"]) });
|
|
50
|
+
/** What a pin did: pinned the record, unpinned it, or found no record to pin. */
|
|
51
|
+
const Pinned = Schema.Struct({ outcome: Schema.Literals(["pinned", "unpinned", "none"]) });
|
|
46
52
|
export const memoryRpc = Rpc.define({
|
|
47
53
|
id: "courier-memory",
|
|
48
54
|
methods: {
|
|
@@ -55,6 +61,11 @@ export const memoryRpc = Rpc.define({
|
|
|
55
61
|
input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
|
|
56
62
|
output: Schema.toStandardSchemaV1(Forgotten),
|
|
57
63
|
},
|
|
64
|
+
/** Pins the record of the tree under `sessionID` in the project asked, or with `pinned` false unpins it. */
|
|
65
|
+
pin: {
|
|
66
|
+
input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, pinned: Schema.Boolean })),
|
|
67
|
+
output: Schema.toStandardSchemaV1(Pinned),
|
|
68
|
+
},
|
|
58
69
|
},
|
|
59
70
|
events: {},
|
|
60
71
|
});
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -4,7 +4,11 @@ import type { MemoryStore } from "./ports.js";
|
|
|
4
4
|
export declare const recordKey: (projectID: string, sessionID: string) => string;
|
|
5
5
|
/** The key prefix of a project's records. */
|
|
6
6
|
export declare const projectPrefix: (projectID: string) => string;
|
|
7
|
-
/**
|
|
7
|
+
/** The person's pin on a record, `{ at }`; the key's presence pins. */
|
|
8
|
+
export declare const pinKey: (projectID: string, sessionID: string) => string;
|
|
9
|
+
/** The reads of a record by the tree under `by`, `{ shown, read, at }`, written only by that tree's sessions. */
|
|
10
|
+
export declare const readsKey: (projectID: string, sessionID: string, by: string) => string;
|
|
11
|
+
/** A record as stored, or undefined for a value that is not one; its reads and pin lie beside it (`beside`). */
|
|
8
12
|
export declare function readRecord(value: unknown): TreeRecord | undefined;
|
|
9
13
|
/** The `MemoryStore` over the plugin's storage. */
|
|
10
14
|
export declare function memoryStore(storage: Storage): MemoryStore;
|