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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
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", "gone", "stalled", "out"];
|
|
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
|
+
declare const Part: Schema.Struct<{
|
|
6
|
+
readonly sessionID: Schema.String;
|
|
7
|
+
readonly title: Schema.String;
|
|
8
|
+
/** 1 for a session the top started. */
|
|
9
|
+
readonly depth: Schema.Int;
|
|
10
|
+
readonly isolated: Schema.Boolean;
|
|
11
|
+
readonly group: Schema.optionalKey<Schema.String>;
|
|
12
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
13
|
+
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
14
|
+
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
15
|
+
readonly context: Schema.optionalKey<Schema.Number>;
|
|
16
|
+
readonly refused: Schema.optionalKey<Schema.Int>;
|
|
17
|
+
}>;
|
|
18
|
+
/** The one line the tree's orchestrator wrote with courier_note: when, and from which depth when not the top's own. */
|
|
19
|
+
declare const Lesson: Schema.Struct<{
|
|
20
|
+
readonly text: Schema.String;
|
|
21
|
+
readonly at: Schema.Number;
|
|
22
|
+
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
23
|
+
}>;
|
|
24
|
+
/** A tree's record as `courier_recall` reads it, with the top session it is kept under; times in epoch milliseconds. */
|
|
25
|
+
declare const Record: Schema.Struct<{
|
|
26
|
+
readonly sessionID: Schema.String;
|
|
27
|
+
readonly startedAt: Schema.Number;
|
|
28
|
+
readonly movedAt: Schema.Number;
|
|
29
|
+
readonly title: Schema.String;
|
|
30
|
+
readonly split: Schema.String;
|
|
31
|
+
readonly finished: Schema.Boolean;
|
|
32
|
+
readonly recalled: Schema.optionalKey<Schema.Struct<{
|
|
33
|
+
readonly at: Schema.optionalKey<Schema.Number>;
|
|
34
|
+
readonly calls: Schema.Int;
|
|
35
|
+
}>>;
|
|
36
|
+
readonly lesson: Schema.optionalKey<Schema.Struct<{
|
|
37
|
+
readonly text: Schema.String;
|
|
38
|
+
readonly at: Schema.Number;
|
|
39
|
+
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
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
|
+
}>>;
|
|
51
|
+
readonly parts: Schema.$Array<Schema.Struct<{
|
|
52
|
+
readonly sessionID: Schema.String;
|
|
53
|
+
readonly title: Schema.String;
|
|
54
|
+
/** 1 for a session the top started. */
|
|
55
|
+
readonly depth: Schema.Int;
|
|
56
|
+
readonly isolated: Schema.Boolean;
|
|
57
|
+
readonly group: Schema.optionalKey<Schema.String>;
|
|
58
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
59
|
+
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
60
|
+
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
61
|
+
readonly context: Schema.optionalKey<Schema.Number>;
|
|
62
|
+
readonly refused: Schema.optionalKey<Schema.Int>;
|
|
63
|
+
}>>;
|
|
64
|
+
}>;
|
|
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. */
|
|
66
|
+
declare const Records: Schema.Struct<{
|
|
67
|
+
readonly memory: Schema.Boolean;
|
|
68
|
+
readonly projectID: Schema.String;
|
|
69
|
+
readonly records: Schema.$Array<Schema.Struct<{
|
|
70
|
+
readonly sessionID: Schema.String;
|
|
71
|
+
readonly startedAt: Schema.Number;
|
|
72
|
+
readonly movedAt: Schema.Number;
|
|
73
|
+
readonly title: Schema.String;
|
|
74
|
+
readonly split: Schema.String;
|
|
75
|
+
readonly finished: Schema.Boolean;
|
|
76
|
+
readonly recalled: Schema.optionalKey<Schema.Struct<{
|
|
77
|
+
readonly at: Schema.optionalKey<Schema.Number>;
|
|
78
|
+
readonly calls: Schema.Int;
|
|
79
|
+
}>>;
|
|
80
|
+
readonly lesson: Schema.optionalKey<Schema.Struct<{
|
|
81
|
+
readonly text: Schema.String;
|
|
82
|
+
readonly at: Schema.Number;
|
|
83
|
+
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
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
|
+
}>>;
|
|
95
|
+
readonly parts: Schema.$Array<Schema.Struct<{
|
|
96
|
+
readonly sessionID: Schema.String;
|
|
97
|
+
readonly title: Schema.String;
|
|
98
|
+
/** 1 for a session the top started. */
|
|
99
|
+
readonly depth: Schema.Int;
|
|
100
|
+
readonly isolated: Schema.Boolean;
|
|
101
|
+
readonly group: Schema.optionalKey<Schema.String>;
|
|
102
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
103
|
+
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
104
|
+
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
105
|
+
readonly context: Schema.optionalKey<Schema.Number>;
|
|
106
|
+
readonly refused: Schema.optionalKey<Schema.Int>;
|
|
107
|
+
}>>;
|
|
108
|
+
}>>;
|
|
109
|
+
}>;
|
|
110
|
+
/** What a forget did: removed the record, dropped only its lesson, or found nothing to forget. */
|
|
111
|
+
declare const Forgotten: Schema.Struct<{
|
|
112
|
+
readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
|
|
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
|
+
}>;
|
|
118
|
+
export type MemoryRecord = typeof Record.Type;
|
|
119
|
+
export type MemoryPart = typeof Part.Type;
|
|
120
|
+
export type MemoryLesson = typeof Lesson.Type;
|
|
121
|
+
export type MemoryReads = NonNullable<MemoryRecord["reads"]>;
|
|
122
|
+
export type Records = typeof Records.Type;
|
|
123
|
+
export type Forgotten = typeof Forgotten.Type;
|
|
124
|
+
export type Pinned = typeof Pinned.Type;
|
|
125
|
+
export declare const memoryRpc: {
|
|
126
|
+
readonly id: "courier-memory";
|
|
127
|
+
readonly methods: {
|
|
128
|
+
readonly records: {
|
|
129
|
+
readonly input: import("effect/StandardSchema").StandardSchemaV1<{}, {}> & Schema.Struct<{}>;
|
|
130
|
+
readonly output: import("effect/StandardSchema").StandardSchemaV1<{
|
|
131
|
+
readonly memory: boolean;
|
|
132
|
+
readonly projectID: string;
|
|
133
|
+
readonly records: readonly {
|
|
134
|
+
readonly sessionID: string;
|
|
135
|
+
readonly title: string;
|
|
136
|
+
readonly parts: readonly {
|
|
137
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "gone" | "stalled" | "out";
|
|
138
|
+
readonly sessionID: string;
|
|
139
|
+
readonly title: string;
|
|
140
|
+
readonly depth: number;
|
|
141
|
+
readonly isolated: boolean;
|
|
142
|
+
readonly group?: string | undefined;
|
|
143
|
+
readonly duration?: number | undefined;
|
|
144
|
+
readonly cost?: number | undefined;
|
|
145
|
+
readonly context?: number | undefined;
|
|
146
|
+
readonly refused?: number | undefined;
|
|
147
|
+
}[];
|
|
148
|
+
readonly startedAt: number;
|
|
149
|
+
readonly movedAt: number;
|
|
150
|
+
readonly split: string;
|
|
151
|
+
readonly finished: boolean;
|
|
152
|
+
readonly recalled?: {
|
|
153
|
+
readonly calls: number;
|
|
154
|
+
readonly at?: number | undefined;
|
|
155
|
+
} | undefined;
|
|
156
|
+
readonly lesson?: {
|
|
157
|
+
readonly text: string;
|
|
158
|
+
readonly at: number;
|
|
159
|
+
readonly depth?: number | undefined;
|
|
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;
|
|
169
|
+
}[];
|
|
170
|
+
}, {
|
|
171
|
+
readonly memory: boolean;
|
|
172
|
+
readonly projectID: string;
|
|
173
|
+
readonly records: readonly {
|
|
174
|
+
readonly sessionID: string;
|
|
175
|
+
readonly title: string;
|
|
176
|
+
readonly parts: readonly {
|
|
177
|
+
readonly status: "done" | "partial" | "blocked" | "failed" | "interrupted" | "stopped" | "gone" | "stalled" | "out";
|
|
178
|
+
readonly sessionID: string;
|
|
179
|
+
readonly title: string;
|
|
180
|
+
readonly depth: number;
|
|
181
|
+
readonly isolated: boolean;
|
|
182
|
+
readonly group?: string | undefined;
|
|
183
|
+
readonly duration?: number | undefined;
|
|
184
|
+
readonly cost?: number | undefined;
|
|
185
|
+
readonly context?: number | undefined;
|
|
186
|
+
readonly refused?: number | undefined;
|
|
187
|
+
}[];
|
|
188
|
+
readonly startedAt: number;
|
|
189
|
+
readonly movedAt: number;
|
|
190
|
+
readonly split: string;
|
|
191
|
+
readonly finished: boolean;
|
|
192
|
+
readonly recalled?: {
|
|
193
|
+
readonly calls: number;
|
|
194
|
+
readonly at?: number | undefined;
|
|
195
|
+
} | undefined;
|
|
196
|
+
readonly lesson?: {
|
|
197
|
+
readonly text: string;
|
|
198
|
+
readonly at: number;
|
|
199
|
+
readonly depth?: number | undefined;
|
|
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;
|
|
209
|
+
}[];
|
|
210
|
+
}> & Schema.Struct<{
|
|
211
|
+
readonly memory: Schema.Boolean;
|
|
212
|
+
readonly projectID: Schema.String;
|
|
213
|
+
readonly records: Schema.$Array<Schema.Struct<{
|
|
214
|
+
readonly sessionID: Schema.String;
|
|
215
|
+
readonly startedAt: Schema.Number;
|
|
216
|
+
readonly movedAt: Schema.Number;
|
|
217
|
+
readonly title: Schema.String;
|
|
218
|
+
readonly split: Schema.String;
|
|
219
|
+
readonly finished: Schema.Boolean;
|
|
220
|
+
readonly recalled: Schema.optionalKey<Schema.Struct<{
|
|
221
|
+
readonly at: Schema.optionalKey<Schema.Number>;
|
|
222
|
+
readonly calls: Schema.Int;
|
|
223
|
+
}>>;
|
|
224
|
+
readonly lesson: Schema.optionalKey<Schema.Struct<{
|
|
225
|
+
readonly text: Schema.String;
|
|
226
|
+
readonly at: Schema.Number;
|
|
227
|
+
readonly depth: Schema.optionalKey<Schema.Int>;
|
|
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
|
+
}>>;
|
|
239
|
+
readonly parts: Schema.$Array<Schema.Struct<{
|
|
240
|
+
readonly sessionID: Schema.String;
|
|
241
|
+
readonly title: Schema.String;
|
|
242
|
+
/** 1 for a session the top started. */
|
|
243
|
+
readonly depth: Schema.Int;
|
|
244
|
+
readonly isolated: Schema.Boolean;
|
|
245
|
+
readonly group: Schema.optionalKey<Schema.String>;
|
|
246
|
+
readonly status: Schema.Literals<readonly ["done", "partial", "blocked", "failed", "interrupted", "stopped", "gone", "stalled", "out"]>;
|
|
247
|
+
readonly duration: Schema.optionalKey<Schema.Number>;
|
|
248
|
+
readonly cost: Schema.optionalKey<Schema.Number>;
|
|
249
|
+
readonly context: Schema.optionalKey<Schema.Number>;
|
|
250
|
+
readonly refused: Schema.optionalKey<Schema.Int>;
|
|
251
|
+
}>>;
|
|
252
|
+
}>>;
|
|
253
|
+
}>;
|
|
254
|
+
};
|
|
255
|
+
/** Forgets the record of the tree under `sessionID` in the project asked, or with `lesson` only its lesson, keeping the facts. */
|
|
256
|
+
readonly forget: {
|
|
257
|
+
readonly input: import("effect/StandardSchema").StandardSchemaV1<{
|
|
258
|
+
readonly sessionID: string;
|
|
259
|
+
readonly lesson?: boolean | undefined;
|
|
260
|
+
}, {
|
|
261
|
+
readonly sessionID: string;
|
|
262
|
+
readonly lesson?: boolean | undefined;
|
|
263
|
+
}> & Schema.Struct<{
|
|
264
|
+
readonly sessionID: Schema.String;
|
|
265
|
+
readonly lesson: Schema.optionalKey<Schema.Boolean>;
|
|
266
|
+
}>;
|
|
267
|
+
readonly output: import("effect/StandardSchema").StandardSchemaV1<{
|
|
268
|
+
readonly outcome: "lesson" | "record" | "none";
|
|
269
|
+
}, {
|
|
270
|
+
readonly outcome: "lesson" | "record" | "none";
|
|
271
|
+
}> & Schema.Struct<{
|
|
272
|
+
readonly outcome: Schema.Literals<readonly ["record", "lesson", "none"]>;
|
|
273
|
+
}>;
|
|
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
|
+
};
|
|
295
|
+
};
|
|
296
|
+
readonly events: {};
|
|
297
|
+
};
|
|
298
|
+
export type MemoryRpc = typeof memoryRpc;
|
|
299
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory's RPC, the contract its TUI part, the memory view, and its server half share. Standard Schema values, as
|
|
3
|
+
* the tree's and the person's; imports only `@opencode/plugin/rpc` and `effect`.
|
|
4
|
+
*/
|
|
5
|
+
import { Rpc } from "@opencode/plugin/rpc";
|
|
6
|
+
import { Schema } from "effect";
|
|
7
|
+
const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
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", "gone", "stalled", "out"];
|
|
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
|
+
const Part = Schema.Struct({
|
|
12
|
+
sessionID: Schema.String,
|
|
13
|
+
title: Schema.String,
|
|
14
|
+
/** 1 for a session the top started. */
|
|
15
|
+
depth: Count,
|
|
16
|
+
isolated: Schema.Boolean,
|
|
17
|
+
group: Schema.optionalKey(Schema.String),
|
|
18
|
+
status: Schema.Literals(PART_STATUSES),
|
|
19
|
+
duration: Schema.optionalKey(Schema.Number),
|
|
20
|
+
cost: Schema.optionalKey(Schema.Number),
|
|
21
|
+
context: Schema.optionalKey(Schema.Number),
|
|
22
|
+
refused: Schema.optionalKey(Count),
|
|
23
|
+
});
|
|
24
|
+
/** The one line the tree's orchestrator wrote with courier_note: when, and from which depth when not the top's own. */
|
|
25
|
+
const Lesson = Schema.Struct({ text: Schema.String, at: Schema.Number, depth: Schema.optionalKey(Count) });
|
|
26
|
+
/** A tree's record as `courier_recall` reads it, with the top session it is kept under; times in epoch milliseconds. */
|
|
27
|
+
const Record = Schema.Struct({
|
|
28
|
+
sessionID: Schema.String,
|
|
29
|
+
startedAt: Schema.Number,
|
|
30
|
+
movedAt: Schema.Number,
|
|
31
|
+
title: Schema.String,
|
|
32
|
+
split: Schema.String,
|
|
33
|
+
finished: Schema.Boolean,
|
|
34
|
+
recalled: Schema.optionalKey(Schema.Struct({ at: Schema.optionalKey(Schema.Number), calls: Count })),
|
|
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 })),
|
|
40
|
+
parts: Schema.Array(Part),
|
|
41
|
+
});
|
|
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. */
|
|
43
|
+
const Records = Schema.Struct({
|
|
44
|
+
memory: Schema.Boolean,
|
|
45
|
+
projectID: Schema.String,
|
|
46
|
+
records: Schema.Array(Record),
|
|
47
|
+
});
|
|
48
|
+
/** What a forget did: removed the record, dropped only its lesson, or found nothing to forget. */
|
|
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"]) });
|
|
52
|
+
export const memoryRpc = Rpc.define({
|
|
53
|
+
id: "courier-memory",
|
|
54
|
+
methods: {
|
|
55
|
+
records: {
|
|
56
|
+
input: Schema.toStandardSchemaV1(Schema.Struct({})),
|
|
57
|
+
output: Schema.toStandardSchemaV1(Records),
|
|
58
|
+
},
|
|
59
|
+
/** Forgets the record of the tree under `sessionID` in the project asked, or with `lesson` only its lesson, keeping the facts. */
|
|
60
|
+
forget: {
|
|
61
|
+
input: Schema.toStandardSchemaV1(Schema.Struct({ sessionID: Schema.String, lesson: Schema.optionalKey(Schema.Boolean) })),
|
|
62
|
+
output: Schema.toStandardSchemaV1(Forgotten),
|
|
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
|
+
},
|
|
69
|
+
},
|
|
70
|
+
events: {},
|
|
71
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Storage } from "../platform/storage.js";
|
|
2
|
+
import { type TreeRecord } from "./domain.js";
|
|
3
|
+
import type { MemoryStore } from "./ports.js";
|
|
4
|
+
export declare const recordKey: (projectID: string, sessionID: string) => string;
|
|
5
|
+
/** The key prefix of a project's records. */
|
|
6
|
+
export declare const projectPrefix: (projectID: string) => string;
|
|
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`). */
|
|
12
|
+
export declare function readRecord(value: unknown): TreeRecord | undefined;
|
|
13
|
+
/** The `MemoryStore` over the plugin's storage. */
|
|
14
|
+
export declare function memoryStore(storage: Storage): MemoryStore;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { finite, num, obj, str } from "../platform/json.js";
|
|
2
|
+
import { scanEntries } from "../platform/storage.js";
|
|
3
|
+
import { isPartStatus, lessonText, MAX_REVISIONS, NOT_STATED, noteRead, sumReads, } from "./domain.js";
|
|
4
|
+
// The memory's storage: the only file that knows its keys and layout. A record is read leniently, as one written by hand
|
|
5
|
+
// with `e2e/kv.ts set` is; beside it, under its key and each with its own writer, lie the person's pin and each reader tree's reads.
|
|
6
|
+
const PREFIX = "memory/";
|
|
7
|
+
export const recordKey = (projectID, sessionID) => `${PREFIX}${projectID}/${sessionID}`;
|
|
8
|
+
/** The key prefix of a project's records. */
|
|
9
|
+
export const projectPrefix = (projectID) => `${PREFIX}${projectID}/`;
|
|
10
|
+
/** The key prefix of what lies beside a record: its pin and its reads. */
|
|
11
|
+
const besidePrefix = (projectID, sessionID) => `${recordKey(projectID, sessionID)}/`;
|
|
12
|
+
/** The person's pin on a record, `{ at }`; the key's presence pins. */
|
|
13
|
+
export const pinKey = (projectID, sessionID) => `${besidePrefix(projectID, sessionID)}pin`;
|
|
14
|
+
/** The reads of a record by the tree under `by`, `{ shown, read, at }`, written only by that tree's sessions. */
|
|
15
|
+
export const readsKey = (projectID, sessionID, by) => `${besidePrefix(projectID, sessionID)}reads/${by}`;
|
|
16
|
+
function readPart(value) {
|
|
17
|
+
const given = obj(value);
|
|
18
|
+
const sessionID = str(given.sessionID);
|
|
19
|
+
if (sessionID === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const optional = (field) => {
|
|
22
|
+
const number = finite(given[field]);
|
|
23
|
+
return number === undefined ? {} : { [field]: number };
|
|
24
|
+
};
|
|
25
|
+
const group = str(given.group);
|
|
26
|
+
return {
|
|
27
|
+
sessionID,
|
|
28
|
+
title: str(given.title) ?? "",
|
|
29
|
+
depth: num(given.depth) ?? 1,
|
|
30
|
+
isolated: given.isolated === true,
|
|
31
|
+
...(group ? { group } : {}),
|
|
32
|
+
status: isPartStatus(given.status) ? given.status : "out",
|
|
33
|
+
...optional("duration"),
|
|
34
|
+
...optional("cost"),
|
|
35
|
+
...optional("context"),
|
|
36
|
+
...optional("refused"),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function readRecalled(value) {
|
|
40
|
+
const given = obj(value);
|
|
41
|
+
const calls = num(given.calls);
|
|
42
|
+
if (calls === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
const at = num(given.at);
|
|
45
|
+
return at === undefined ? { calls } : { at, calls };
|
|
46
|
+
}
|
|
47
|
+
/** A lesson as `{ text, at, depth }`, or as a bare string written by hand, dated `movedAt`; none for an empty text. */
|
|
48
|
+
function readLesson(value, movedAt) {
|
|
49
|
+
const given = typeof value === "string" ? { text: value } : obj(value);
|
|
50
|
+
const { text } = lessonText(str(given.text) ?? "");
|
|
51
|
+
if (!text)
|
|
52
|
+
return undefined;
|
|
53
|
+
const depth = num(given.depth);
|
|
54
|
+
return { text, at: num(given.at) ?? movedAt, ...(depth !== undefined && Number.isInteger(depth) && depth > 0 ? { depth } : {}) };
|
|
55
|
+
}
|
|
56
|
+
/** The revisions as `{ text, at, parts }`, the newest `MAX_REVISIONS`, leaving out an entry without a text, a time or a count of parts. */
|
|
57
|
+
function readRevisions(value) {
|
|
58
|
+
if (!Array.isArray(value))
|
|
59
|
+
return [];
|
|
60
|
+
const revisions = value.flatMap((entry) => {
|
|
61
|
+
const given = obj(entry);
|
|
62
|
+
const text = str(given.text)?.trim();
|
|
63
|
+
const at = num(given.at);
|
|
64
|
+
const parts = num(given.parts);
|
|
65
|
+
return text && at !== undefined && parts !== undefined && parts >= 0 ? [{ text, at, parts }] : [];
|
|
66
|
+
});
|
|
67
|
+
return revisions.slice(-MAX_REVISIONS);
|
|
68
|
+
}
|
|
69
|
+
/** A record as stored, or undefined for a value that is not one; its reads and pin lie beside it (`beside`). */
|
|
70
|
+
export function readRecord(value) {
|
|
71
|
+
const given = obj(value);
|
|
72
|
+
if (!Array.isArray(given.parts))
|
|
73
|
+
return undefined;
|
|
74
|
+
const recalled = readRecalled(given.recalled);
|
|
75
|
+
const movedAt = num(given.movedAt) ?? num(given.startedAt) ?? 0;
|
|
76
|
+
const lesson = readLesson(given.lesson, movedAt);
|
|
77
|
+
const revisions = readRevisions(given.revisions);
|
|
78
|
+
return {
|
|
79
|
+
startedAt: num(given.startedAt) ?? 0,
|
|
80
|
+
movedAt,
|
|
81
|
+
title: str(given.title) ?? "",
|
|
82
|
+
split: str(given.split) ?? NOT_STATED,
|
|
83
|
+
...(revisions.length ? { revisions } : {}),
|
|
84
|
+
finished: given.finished === true,
|
|
85
|
+
...(recalled ? { recalled } : {}),
|
|
86
|
+
...(lesson ? { lesson } : {}),
|
|
87
|
+
parts: given.parts.map(readPart).filter((part) => part !== undefined),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** One tree's reads as `{ shown, read, at }`; a count missing is 0, an entry without a time is none. */
|
|
91
|
+
function readReads(value) {
|
|
92
|
+
const given = obj(value);
|
|
93
|
+
const at = num(given.at);
|
|
94
|
+
if (at === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const count = (field) => Math.max(0, Math.floor(num(given[field]) ?? 0));
|
|
97
|
+
return { shown: count("shown"), read: count("read"), at };
|
|
98
|
+
}
|
|
99
|
+
/** The pin as `{ at }`: any value under the key pins, dated 0 when it says no time. */
|
|
100
|
+
const readPinned = (value) => ({ at: num(obj(value).at) ?? 0 });
|
|
101
|
+
/** A record with what lies beside it: its readers' reads as one, and its pin. */
|
|
102
|
+
function beside(record, { pinned, reads }) {
|
|
103
|
+
const { parts, ...facts } = record;
|
|
104
|
+
const summed = sumReads(reads);
|
|
105
|
+
return { ...facts, ...(summed ? { reads: summed } : {}), ...(pinned ? { pinned } : {}), parts };
|
|
106
|
+
}
|
|
107
|
+
/** The key's path under `memory/`, split: `[projectID, sessionID]` a record, then `pin`, or `reads` and the reader. */
|
|
108
|
+
const pathOf = (prefix, key) => `${prefix.slice(PREFIX.length)}${key}`.split("/");
|
|
109
|
+
/** Sorts a scan's entries by record: the record's value, when the scan held it, and what lies beside it. */
|
|
110
|
+
function group(prefix, entries) {
|
|
111
|
+
const groups = new Map();
|
|
112
|
+
const of = (projectID, sessionID) => {
|
|
113
|
+
const id = `${projectID}/${sessionID}`;
|
|
114
|
+
const found = groups.get(id) ?? { beside: { reads: [] } };
|
|
115
|
+
groups.set(id, found);
|
|
116
|
+
return found;
|
|
117
|
+
};
|
|
118
|
+
for (const { key, value } of entries) {
|
|
119
|
+
const [projectID, sessionID, kind, by] = pathOf(prefix, key);
|
|
120
|
+
if (!projectID || !sessionID)
|
|
121
|
+
continue;
|
|
122
|
+
if (kind === undefined)
|
|
123
|
+
of(projectID, sessionID).record = value;
|
|
124
|
+
else if (kind === "pin" && by === undefined)
|
|
125
|
+
of(projectID, sessionID).beside.pinned = readPinned(value);
|
|
126
|
+
else if (kind === "reads" && by) {
|
|
127
|
+
const reads = readReads(value);
|
|
128
|
+
if (reads)
|
|
129
|
+
of(projectID, sessionID).beside.reads.push(reads);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return [...groups].map(([id, found]) => {
|
|
133
|
+
const [projectID, sessionID] = id.split("/");
|
|
134
|
+
return { projectID, sessionID, ...found };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** The `MemoryStore` over the plugin's storage. */
|
|
138
|
+
export function memoryStore(storage) {
|
|
139
|
+
const scanned = async (prefix) => group(prefix, await scanEntries(storage, prefix));
|
|
140
|
+
const under = async (prefix) => (await scanned(prefix)).flatMap(({ projectID, sessionID, record: value, beside: others }) => {
|
|
141
|
+
const record = readRecord(value);
|
|
142
|
+
return record ? [{ ...beside(record, others), projectID, sessionID }] : [];
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
async get(projectID, sessionID) {
|
|
146
|
+
const [value, others] = await Promise.all([storage.get(recordKey(projectID, sessionID)), scanned(besidePrefix(projectID, sessionID))]);
|
|
147
|
+
const record = readRecord(value);
|
|
148
|
+
return record && beside(record, others[0]?.beside ?? { reads: [] });
|
|
149
|
+
},
|
|
150
|
+
// What lies beside the record is never written with it, so no refresh or note can write over a pin or a count.
|
|
151
|
+
async put(projectID, sessionID, { reads: _reads, pinned: _pinned, ...record }) {
|
|
152
|
+
await storage.set(recordKey(projectID, sessionID), { ...record, parts: record.parts.map((part) => ({ ...part })) });
|
|
153
|
+
},
|
|
154
|
+
async remove(projectID, sessionID) {
|
|
155
|
+
const others = await scanEntries(storage, besidePrefix(projectID, sessionID));
|
|
156
|
+
await Promise.all([storage.remove(recordKey(projectID, sessionID)), ...others.map(({ key }) => storage.remove(`${besidePrefix(projectID, sessionID)}${key}`))]);
|
|
157
|
+
},
|
|
158
|
+
async noteRead(projectID, sessionID, by, kind, at) {
|
|
159
|
+
const key = readsKey(projectID, sessionID, by);
|
|
160
|
+
const reads = noteRead(readReads(await storage.get(key)), kind, at);
|
|
161
|
+
await storage.set(key, { ...reads });
|
|
162
|
+
return reads;
|
|
163
|
+
},
|
|
164
|
+
async pin(projectID, sessionID, pinned) {
|
|
165
|
+
if (pinned)
|
|
166
|
+
await storage.set(pinKey(projectID, sessionID), { ...pinned });
|
|
167
|
+
else
|
|
168
|
+
await storage.remove(pinKey(projectID, sessionID));
|
|
169
|
+
},
|
|
170
|
+
ofProject: (projectID) => under(projectPrefix(projectID)),
|
|
171
|
+
all: () => under(PREFIX),
|
|
172
|
+
strays: async () => (await scanned(PREFIX)).filter(({ record }) => record === undefined).map(({ projectID, sessionID }) => ({ projectID, sessionID })),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Storage } from "../platform/storage.js";
|
|
2
|
+
import { type StoredRecord } from "./domain.js";
|
|
3
|
+
/**
|
|
4
|
+
* On loading: drops, per project, the records `keep` does not at `now`, the pinned never among them, and the reads or
|
|
5
|
+
* pin left beside a record removed meanwhile. Returns the records dropped.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sweepRecords(storage: Storage, now: number): Promise<StoredRecord[]>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { keep } from "./domain.js";
|
|
2
|
+
import { memoryStore } from "./store.js";
|
|
3
|
+
/**
|
|
4
|
+
* On loading: drops, per project, the records `keep` does not at `now`, the pinned never among them, and the reads or
|
|
5
|
+
* pin left beside a record removed meanwhile. Returns the records dropped.
|
|
6
|
+
*/
|
|
7
|
+
export async function sweepRecords(storage, now) {
|
|
8
|
+
const store = memoryStore(storage);
|
|
9
|
+
const byProject = new Map();
|
|
10
|
+
for (const record of await store.all())
|
|
11
|
+
byProject.set(record.projectID, [...(byProject.get(record.projectID) ?? []), record]);
|
|
12
|
+
const dropped = [...byProject.values()].flatMap((records) => keep(records, now).dropped);
|
|
13
|
+
const strays = await store.strays();
|
|
14
|
+
await Promise.all([...dropped, ...strays].map((record) => store.remove(record.projectID, record.sessionID)));
|
|
15
|
+
return dropped;
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Lesson, type StoredRecord } from "./domain.js";
|
|
2
|
+
/** The tool's name, which the skill and the role part name too. */
|
|
3
|
+
export declare const RECALL_TOOL = "courier_recall";
|
|
4
|
+
/** The lesson tool's name, which the skill's last step names. */
|
|
5
|
+
export declare const NOTE_TOOL = "courier_note";
|
|
6
|
+
/** A duration in milliseconds as a model reads it: seconds under half a minute, else whole minutes, else hours and minutes. */
|
|
7
|
+
export declare function duration(ms: number): string;
|
|
8
|
+
/** How long ago a record last moved: a duration under a day, else whole days, which a record kept for months is read in. */
|
|
9
|
+
export declare function age(ms: number): string;
|
|
10
|
+
/** A lesson as the listing and the note's result show it: dated, and from which depth when a session below the top wrote it. */
|
|
11
|
+
export declare function lessonLine(lesson: Lesson): string;
|
|
12
|
+
/**
|
|
13
|
+
* One record as the listing shows it: its top, when it started, its title, its split line and its latest revision as prose,
|
|
14
|
+
* its parts, how long it ran, how long ago it last moved, what it cost, and whether the person pinned it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordLine(record: StoredRecord, top: string, now: number): string;
|
|
17
|
+
/** What courier_recall says when the memory option is off. */
|
|
18
|
+
export declare const RECALL_OFF = "Memory is off: the plugin's memory option is false, so no record of a session tree is kept or listed in this project. Decide from the task alone.";
|
|
19
|
+
/**
|
|
20
|
+
* The listing: every record of the project, the pinned first and then newest first, the caller's own tree marked, each
|
|
21
|
+
* aged against `now`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function recallListText(records: ReadonlyArray<StoredRecord>, top: string, now: number): string;
|
|
24
|
+
/** One record in full, as JSON: durations in milliseconds, times in epoch milliseconds, context use as a share of the model's window. */
|
|
25
|
+
export declare function recallRecordText(record: StoredRecord): string;
|
|
26
|
+
export declare const recallMissingText: (sessionID: string) => string;
|
|
27
|
+
/** What courier_note says once the lesson is written: where, and what it replaced. */
|
|
28
|
+
export declare function noteText(top: string, lesson: Lesson, replaced: Lesson | undefined): string;
|
|
29
|
+
/** What courier_note says when the memory option is off. */
|
|
30
|
+
export declare const NOTE_OFF = "Not noted: the plugin's memory option is false, so no record of a session tree is kept in this project to write a lesson on.";
|
|
31
|
+
export declare const noteLeafRefusal: (depth: number, maxDepth: number) => string;
|
|
32
|
+
export declare const NOTE_EMPTY = "Not noted: text is empty. Write 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, or nothing.";
|
|
33
|
+
/** The refusal of a lesson longer than `LESSON_MAX` as one line: nothing is written, and the earlier lesson stays. */
|
|
34
|
+
export declare const noteLongRefusal: (length: number) => string;
|
|
35
|
+
export declare const noteUnrecordedRefusal: (top: string) => string;
|