opencode-courier 0.7.3 → 0.8.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 +109 -135
- package/dist/index.js +23 -4
- package/dist/memory/domain.d.ts +119 -0
- package/dist/memory/domain.js +84 -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 +10 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/note.d.ts +37 -0
- package/dist/memory/note.js +29 -0
- package/dist/memory/ports.d.ts +44 -0
- package/dist/memory/ports.js +1 -0
- package/dist/memory/recall.d.ts +27 -0
- package/dist/memory/recall.js +48 -0
- package/dist/memory/record.d.ts +7 -0
- package/dist/memory/record.js +92 -0
- package/dist/memory/rpc-handlers.d.ts +134 -0
- package/dist/memory/rpc-handlers.js +23 -0
- package/dist/memory/rpc.d.ts +227 -0
- package/dist/memory/rpc.js +60 -0
- package/dist/memory/store.d.ts +10 -0
- package/dist/memory/store.js +90 -0
- package/dist/memory/sweep.d.ts +4 -0
- package/dist/memory/sweep.js +12 -0
- package/dist/memory/text.d.ts +27 -0
- package/dist/memory/text.js +95 -0
- package/dist/memory/tools.d.ts +16 -0
- package/dist/memory/tools.js +78 -0
- package/dist/memory/tui.d.ts +73 -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/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 +18 -18
- package/dist/tree/rpc.d.ts +18 -18
- 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 +544 -121
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { describeFailure } from "../kernel/protocol.js";
|
|
3
|
+
import { rethrow } from "../kernel/tool.js";
|
|
4
|
+
import { LESSON_MAX } from "./domain.js";
|
|
5
|
+
import { note } from "./note.js";
|
|
6
|
+
import { recall } from "./recall.js";
|
|
7
|
+
import { NOTE_EMPTY, NOTE_OFF, NOTE_TOOL, noteLeafRefusal, noteLongRefusal, noteText, noteUnrecordedRefusal, RECALL_OFF, RECALL_TOOL, recallListText, recallMissingText, recallRecordText } from "./text.js";
|
|
8
|
+
export const RecallInput = Schema.Struct({
|
|
9
|
+
sessionID: Schema.optional(Schema.String.annotate({ description: "The top session of a listed record, for that record in full; without it, one line per record of this project." })),
|
|
10
|
+
});
|
|
11
|
+
export const NoteInput = Schema.Struct({
|
|
12
|
+
text: Schema.String.annotate({
|
|
13
|
+
description: `The lesson, one line of at most ${LESSON_MAX} characters (longer is refused, not clipped): what the next orchestrator in this ` +
|
|
14
|
+
"project should know about running a tree like this one, how it was split, verified or briefed, or what the person decided, " +
|
|
15
|
+
"with the job it comes from and the reason.",
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
/** Adds `courier_recall` and `courier_note`, direct tools rather than code-mode ones. */
|
|
19
|
+
export function addMemoryTools(tools, ports) {
|
|
20
|
+
tools.add({
|
|
21
|
+
name: RECALL_TOOL,
|
|
22
|
+
options: { codemode: false },
|
|
23
|
+
description: "Look once at what earlier session trees in this project did, before you decide whether and how to split a task: " +
|
|
24
|
+
"one line per tree, newest first, with how its top split the task, how each part went (its status, how long it ran, " +
|
|
25
|
+
"what it cost, how full its context got) and whether a spawn was refused; with sessionID, one tree's record in full. " +
|
|
26
|
+
"Records hold no report bodies or files. Call it once at the start, not in a loop, and not to check on sessions you " +
|
|
27
|
+
"started: their reports wake you.",
|
|
28
|
+
input: RecallInput,
|
|
29
|
+
execute: async (input, context) => {
|
|
30
|
+
const result = await recall(ports.memory, ports.state, context.sessionID, input).catch(rethrow(RECALL_TOOL));
|
|
31
|
+
switch (result.kind) {
|
|
32
|
+
case "off":
|
|
33
|
+
return { content: RECALL_OFF, metadata: { memory: false } };
|
|
34
|
+
case "list":
|
|
35
|
+
return { content: recallListText(result.records, result.top), metadata: { projectID: result.projectID, top: result.top, records: result.records } };
|
|
36
|
+
case "record":
|
|
37
|
+
return { content: recallRecordText(result.record), metadata: { projectID: result.projectID, record: result.record } };
|
|
38
|
+
case "missing":
|
|
39
|
+
return { content: recallMissingText(result.sessionID), metadata: { projectID: result.projectID, sessionID: result.sessionID, found: false } };
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
tools.add({
|
|
44
|
+
name: NOTE_TOOL,
|
|
45
|
+
options: { codemode: false },
|
|
46
|
+
description: "Write the one lesson of your session tree for the next orchestrator in this project, once you have verified and " +
|
|
47
|
+
"integrated the work: what it should know about running a tree like this one that the record's facts do not say. How " +
|
|
48
|
+
"it was split (which parts collided, what was too small to hand off), how it was verified (what a part got wrong and " +
|
|
49
|
+
"what caught it), how the parts were briefed, or what the person decided. Name the job it comes from and the reason, " +
|
|
50
|
+
"so that a lesson for one kind of job can be told from one that holds everywhere. One line, at most " +
|
|
51
|
+
`${LESSON_MAX} characters; a longer one is refused, not clipped, so write it again shorter. Kept on your own tree's ` +
|
|
52
|
+
"record and listed by courier_recall after its facts, dated; a second call replaces the first. From the top session " +
|
|
53
|
+
"or one that started sessions, never a leaf. No code, no secrets, and not a rule that holds every time: that belongs " +
|
|
54
|
+
"in AGENTS.md or the skill, for the person to put there, so tell them instead.",
|
|
55
|
+
input: NoteInput,
|
|
56
|
+
execute: async (input, context) => {
|
|
57
|
+
const result = await note(ports.memory, context.sessionID, input).catch(rethrow(NOTE_TOOL));
|
|
58
|
+
// A refusal is the tool's failure, as courier_spawn's are, so the step ends on it and the model reads why.
|
|
59
|
+
const refuse = (text) => describeFailure(NOTE_TOOL, new Error(text));
|
|
60
|
+
switch (result.kind) {
|
|
61
|
+
case "noted": {
|
|
62
|
+
const { projectID, top, lesson, replaced } = result;
|
|
63
|
+
return { content: noteText(top, lesson, replaced), metadata: { projectID, top, lesson, ...(replaced ? { replaced } : {}) } };
|
|
64
|
+
}
|
|
65
|
+
case "off":
|
|
66
|
+
throw refuse(NOTE_OFF);
|
|
67
|
+
case "leaf":
|
|
68
|
+
throw refuse(noteLeafRefusal(result.depth, result.maxDepth));
|
|
69
|
+
case "empty":
|
|
70
|
+
throw refuse(NOTE_EMPTY);
|
|
71
|
+
case "long":
|
|
72
|
+
throw refuse(noteLongRefusal(result.length));
|
|
73
|
+
case "unrecorded":
|
|
74
|
+
throw refuse(noteUnrecordedRefusal(result.top));
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode/plugin/tui";
|
|
2
|
+
import { type Forgotten, type MemoryLesson, type MemoryPart, type MemoryRecord } from "./rpc.js";
|
|
3
|
+
type Context = Plugin.Context;
|
|
4
|
+
/** The command that opens the memory view: `/courier memory`, or the palette. */
|
|
5
|
+
export declare const MEMORY_COMMAND = "courier.memory";
|
|
6
|
+
/** The memory view's line of the `/courier` command's usage. */
|
|
7
|
+
export declare const MEMORY_USAGE = "/courier memory lists this project's tree records, to read or forget";
|
|
8
|
+
/** What the view says, in a toast, with the memory option off in the project asked. */
|
|
9
|
+
export declare const 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.";
|
|
10
|
+
/** What the view says, in a toast, when the project has no record: when none was written yet, or the last was forgotten. */
|
|
11
|
+
export declare const 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.";
|
|
12
|
+
/** The most characters of a lesson a list row's line under it shows; the record in full has it whole. */
|
|
13
|
+
export declare const LESSON_LINE_MAX = 160;
|
|
14
|
+
/** The most characters of a title a list row shows, so that "(this session)" after it fits the host's 61. */
|
|
15
|
+
export declare const ROW_TITLE_MAX = 46;
|
|
16
|
+
/** The group of the records moved on the current local day, over the others' local dates, as the host's Sessions dialog has it. */
|
|
17
|
+
export declare const TODAY = "Today";
|
|
18
|
+
/** The widest a row of the lesson in a record in full is; a longer lesson wraps over rows. */
|
|
19
|
+
export declare const LESSON_ROW = 90;
|
|
20
|
+
/** A record's name, as its rows and confirms show it: its title on one line, else its top session. */
|
|
21
|
+
export declare const nameOf: (record: MemoryRecord) => string;
|
|
22
|
+
/** A record's parts counted by status, in the order of `PART_STATUSES`: "3 parts: 2 done, 1 partial"; "no parts" with none. */
|
|
23
|
+
export declare function partsCounted(parts: ReadonlyArray<MemoryPart>): string;
|
|
24
|
+
/** The parts' costs summed, when any is known. */
|
|
25
|
+
export declare function costOf(record: MemoryRecord): number | undefined;
|
|
26
|
+
/** A list row's title: the tree's title, its top session's id without one; the open session's own tree says so. */
|
|
27
|
+
export declare function recordTitle(record: MemoryRecord, open?: string): string;
|
|
28
|
+
/** A list row's group: `Today` for a tree last moved on the local day of `now`, else that local date; the RPC's order, by the last move, keeps the groups newest first. */
|
|
29
|
+
export declare function recordCategory(record: MemoryRecord, now?: number): string;
|
|
30
|
+
/** A lesson as the view shows it: dated on the local day, from which depth when a session below the top wrote it, and its text. */
|
|
31
|
+
export declare function lessonHeading(lesson: MemoryLesson): string;
|
|
32
|
+
/** A list row's description: its parts by status, how long the tree ran, what its parts cost, and that it is not finished when it is not. */
|
|
33
|
+
export declare function recordDescription(record: MemoryRecord): string;
|
|
34
|
+
/** The lines under a list row: the lesson, dated and from which depth, clipped; none without a lesson. */
|
|
35
|
+
export declare function recordDetails(record: MemoryRecord): string[];
|
|
36
|
+
/** One part as the record in full describes it: its status, depth, isolation, group, duration, cost, context use and spawns refused. */
|
|
37
|
+
export declare function partLine(part: MemoryPart): string;
|
|
38
|
+
/** `text` in rows of at most `width` characters, broken at spaces where it can be. */
|
|
39
|
+
export declare function wrap(text: string, width: number): string[];
|
|
40
|
+
/** A row of a record in full: a fact of the tree, one of its parts, or a row of its lesson (0 its heading), which `x` forgets alone. */
|
|
41
|
+
export type Line = {
|
|
42
|
+
readonly kind: "fact";
|
|
43
|
+
readonly key: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "part";
|
|
46
|
+
readonly sessionID: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: "lesson";
|
|
49
|
+
readonly row: number;
|
|
50
|
+
};
|
|
51
|
+
/** Where the cursor goes in a record in full after its lesson is forgotten: the lesson's first row, or the row that replaced it. */
|
|
52
|
+
export declare const lessonLine: (record: MemoryRecord) => Line;
|
|
53
|
+
/** The records the list's cursor tries, in order, after one is forgotten, kept or left: it, unless forgotten, then those after it, then those before it, nearest first. */
|
|
54
|
+
export declare function nearby(sessionIDs: ReadonlyArray<string>, sessionID: string, forgotten: boolean): string[];
|
|
55
|
+
/** The rows of a record in full, in three categories: the tree's facts, its parts and its lesson. */
|
|
56
|
+
export declare function detailOptions(record: MemoryRecord): Array<{
|
|
57
|
+
title: string;
|
|
58
|
+
value: Line;
|
|
59
|
+
category: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
}>;
|
|
62
|
+
/** What the confirm asks before a forget: the record named, with the local day it last moved, and what goes and what stays. */
|
|
63
|
+
export declare function forgetMessage(record: MemoryRecord, lesson: boolean): string;
|
|
64
|
+
/** The toast after a forget, as the server reports it. */
|
|
65
|
+
export declare function forgottenLine(record: MemoryRecord, outcome: Forgotten["outcome"]): string;
|
|
66
|
+
/**
|
|
67
|
+
* Adds the memory view: the project's records (the open session's location, else the TUI's, else the server's default) under
|
|
68
|
+
* `Today` and the local days they last moved; `enter` shows one in full, `x` forgets one or its lesson after a confirm, `esc` goes back and closes.
|
|
69
|
+
*/
|
|
70
|
+
export declare function addMemoryView(ctx: Context): {
|
|
71
|
+
open: () => Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
export {};
|
package/dist/person/rpc.d.ts
CHANGED
|
@@ -106,8 +106,8 @@ export declare const personRpc: {
|
|
|
106
106
|
readonly sessions: readonly {
|
|
107
107
|
readonly sessionID: string;
|
|
108
108
|
readonly title: string;
|
|
109
|
-
readonly parentID: string;
|
|
110
109
|
readonly depth: number;
|
|
110
|
+
readonly parentID: string;
|
|
111
111
|
readonly group?: string | undefined;
|
|
112
112
|
}[];
|
|
113
113
|
readonly permissions: readonly {
|
|
@@ -140,8 +140,8 @@ export declare const personRpc: {
|
|
|
140
140
|
readonly sessions: readonly {
|
|
141
141
|
readonly sessionID: string;
|
|
142
142
|
readonly title: string;
|
|
143
|
-
readonly parentID: string;
|
|
144
143
|
readonly depth: number;
|
|
144
|
+
readonly parentID: string;
|
|
145
145
|
readonly group?: string | undefined;
|
|
146
146
|
}[];
|
|
147
147
|
readonly permissions: readonly {
|
|
@@ -227,11 +227,11 @@ export declare const personRpc: {
|
|
|
227
227
|
}>;
|
|
228
228
|
readonly output: import("effect/StandardSchema").StandardSchemaV1<{
|
|
229
229
|
readonly answered: boolean;
|
|
230
|
-
readonly by?: "
|
|
230
|
+
readonly by?: "message" | "result" | undefined;
|
|
231
231
|
readonly problem?: string | undefined;
|
|
232
232
|
}, {
|
|
233
233
|
readonly answered: boolean;
|
|
234
|
-
readonly by?: "
|
|
234
|
+
readonly by?: "message" | "result" | undefined;
|
|
235
235
|
readonly problem?: string | undefined;
|
|
236
236
|
}> & Schema.Struct<{
|
|
237
237
|
readonly answered: Schema.Boolean;
|
package/dist/platform/json.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
|
|
2
2
|
export declare const str: (value: unknown) => string | undefined;
|
|
3
3
|
export declare const num: (value: unknown) => number | undefined;
|
|
4
|
+
export declare const finite: (value: unknown) => number | undefined;
|
|
4
5
|
export declare const obj: (value: unknown) => Record<string, any>;
|
|
5
6
|
/** Epoch milliseconds from a session time, a `DateTime` in OpenCode's API and a number in tests; NaN for none. */
|
|
6
7
|
export declare function millis(value: unknown): number;
|
package/dist/platform/json.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
|
|
2
2
|
export const str = (value) => (typeof value === "string" ? value : undefined);
|
|
3
3
|
export const num = (value) => (typeof value === "number" && Number.isInteger(value) ? value : undefined);
|
|
4
|
+
export const finite = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
|
|
4
5
|
export const obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5
6
|
/** Epoch milliseconds from a session time, a `DateTime` in OpenCode's API and a number in tests; NaN for none. */
|
|
6
7
|
export function millis(value) {
|
package/dist/rpc.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { treeRpc, type Row, type Stopped, type Summary, type Tree, type TreeRpc, type Wait } from "./tree/rpc.js";
|
|
2
2
|
export { personRpc, type Answered, type Asks, type PersonRpc } from "./person/rpc.js";
|
|
3
|
+
export { memoryRpc, type Forgotten as MemoryForgotten, type MemoryLesson, type MemoryPart, type MemoryRecord, type MemoryRpc, type Records } from "./memory/rpc.js";
|
package/dist/rpc.js
CHANGED
package/dist/skill.js
CHANGED
|
@@ -10,8 +10,9 @@ export const SKILL_CONTENT = [
|
|
|
10
10
|
"How to split a task across OpenCode sessions with opencode-courier, and bring the results back up. Your brief, or the " +
|
|
11
11
|
"opencode-courier role line in your system prompt, names your depth in the session tree and its limits.",
|
|
12
12
|
"",
|
|
13
|
-
"1. Decide: split or do.
|
|
14
|
-
"
|
|
13
|
+
"1. Decide: split or do. First call courier_recall once: it lists how earlier trees in this project were split and how " +
|
|
14
|
+
"each part went, so weigh that; one call, before you decide, never in a loop. Split when the task has 2 or more " +
|
|
15
|
+
"independent, substantial parts touching separate files or areas; do it yourself when it is small, sequential or tightly coupled. Never start exactly one session for the whole " +
|
|
15
16
|
"task: it only adds a hop. What you read and write yourself stays in your context for every later turn, so keep the " +
|
|
16
17
|
"deciding, checking and integrating, and hand off the long reading and writing. An implementation task with one tightly " +
|
|
17
18
|
"coupled core usually still has independent parts: finding out what the change needs (a session with isolate: false " +
|
|
@@ -44,7 +45,13 @@ export const SKILL_CONTENT = [
|
|
|
44
45
|
"7. Integrate: merge each reported branch into your own checkout (git merge courier/<session>), resolve conflicts " +
|
|
45
46
|
"yourself, run the checks once more, then courier_cleanup the isolated sessions you are done with.",
|
|
46
47
|
"8. Report upward if a session started you: one courier_send to it, with one status for the whole (done only when every " +
|
|
47
|
-
"part is verified and integrated; partial, blocked or failed otherwise) and the artifacts. At the top,
|
|
48
|
+
"part is verified and integrated; partial, blocked or failed otherwise) and the artifacts. At the top, before you reply " +
|
|
49
|
+
"to the person: if there is something the next tree in this project should know about running a tree like this one (how " +
|
|
50
|
+
"it was split, what a part got wrong and what caught it, what a brief should have said, what the person decided), one " +
|
|
51
|
+
"courier_note with that one line; none when there is nothing. Name the job it comes from and the reason, so that a " +
|
|
52
|
+
"lesson for one kind of job is not taken for every job; the tool refuses a line over its limit rather than clipping " +
|
|
53
|
+
"it, so write it again shorter. A rule that holds every time goes to AGENTS.md or the skill, through the person, not " +
|
|
54
|
+
"into a note. Then reply to the person.",
|
|
48
55
|
"",
|
|
49
56
|
"When a part goes wrong:",
|
|
50
57
|
"- A failed turn (failed=\"...\") or a silent end (ended=\"without-report\", quoting the session's last reply): message " +
|
package/dist/tree/context.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface Shaped {
|
|
|
9
9
|
}>;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* The `context` hook: names a session's role in its tree in a system part, and hides courier_spawn
|
|
13
|
-
* from one at `maxDepth`. A session nobody spawned that has started none is pointed to the skill, if it could spawn.
|
|
12
|
+
* The `context` hook: names a session's role in its tree in a system part, and hides courier_spawn, and the tools
|
|
13
|
+
* named `hiddenFromLeaf`, from one at `maxDepth`. A session nobody spawned that has started none is pointed to the skill, if it could spawn.
|
|
14
14
|
*/
|
|
15
15
|
export declare function shapeContext(ports: ContextPorts, event: Shaped): Promise<void>;
|
package/dist/tree/context.js
CHANGED
|
@@ -4,8 +4,8 @@ import { ROLE_PREFIX, rolePart } from "./text.js";
|
|
|
4
4
|
/** How many sessions' depths an instance remembers. */
|
|
5
5
|
const ROLES_MAX = 1000;
|
|
6
6
|
/**
|
|
7
|
-
* The `context` hook: names a session's role in its tree in a system part, and hides courier_spawn
|
|
8
|
-
* from one at `maxDepth`. A session nobody spawned that has started none is pointed to the skill, if it could spawn.
|
|
7
|
+
* The `context` hook: names a session's role in its tree in a system part, and hides courier_spawn, and the tools
|
|
8
|
+
* named `hiddenFromLeaf`, from one at `maxDepth`. A session nobody spawned that has started none is pointed to the skill, if it could spawn.
|
|
9
9
|
*/
|
|
10
10
|
export async function shapeContext(ports, event) {
|
|
11
11
|
try {
|
|
@@ -13,7 +13,8 @@ export async function shapeContext(ports, event) {
|
|
|
13
13
|
if (depth === null && !event.tools.courier_spawn)
|
|
14
14
|
return;
|
|
15
15
|
if (depth !== null && depth >= ports.limits.maxDepth)
|
|
16
|
-
|
|
16
|
+
for (const tool of ["courier_spawn", ...(ports.hiddenFromLeaf ?? [])])
|
|
17
|
+
delete event.tools[tool];
|
|
17
18
|
if (!event.system.some((part) => part.text.startsWith(ROLE_PREFIX)))
|
|
18
19
|
event.system.push({ type: "text", text: rolePart(depth, ports.limits) });
|
|
19
20
|
}
|
package/dist/tree/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { dirtyOf, headOf, inspectWorktree } from "./cleanup.js";
|
|
2
2
|
export { shapeContext } from "./context.js";
|
|
3
|
-
export { lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
3
|
+
export { busy, lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
4
4
|
export { together, treeEvents, waitsOn } from "./events.js";
|
|
5
5
|
export { deliverReleased } from "./group.js";
|
|
6
|
-
export
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
6
|
+
export { contextOf } from "./panel.js";
|
|
7
|
+
export type { CleanupPorts, CourierPorts, ModelWindow, Pending, PendingPrompt, RosterEntry, RosterStorage, ShownForm, SpawnGate, Waits, WatchPorts, WatchState, } from "./ports.js";
|
|
8
|
+
export { allEntries, answeringTop, childrenOf, indexedLineage, lineage, pruneExpired, reportState, RETENTION_MS, type ReportState } from "./roster.js";
|
|
9
|
+
export { subtree, type Below } from "./subtree.js";
|
|
9
10
|
export { registerTreeRpc } from "./rpc-handlers.js";
|
|
10
11
|
export { addTreeTools } from "./tools.js";
|
|
11
|
-
export { wakeMissed } from "./wake.js";
|
|
12
|
+
export { stalledAmong, wakeMissed } from "./wake.js";
|
package/dist/tree/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// What the rest of the plugin uses of the session tree: its tools, RPC, event module, context hook and tick jobs
|
|
2
|
-
// for `src/index.ts`,
|
|
2
|
+
// for `src/index.ts`, the lineage and subtree queries and types `person/` reads, and what `memory/` reads of each session.
|
|
3
3
|
export { dirtyOf, headOf, inspectWorktree } from "./cleanup.js";
|
|
4
4
|
export { shapeContext } from "./context.js";
|
|
5
|
-
export { lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
5
|
+
export { busy, lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
6
6
|
export { together, treeEvents, waitsOn } from "./events.js";
|
|
7
7
|
export { deliverReleased } from "./group.js";
|
|
8
|
-
export {
|
|
8
|
+
export { contextOf } from "./panel.js";
|
|
9
|
+
export { allEntries, answeringTop, childrenOf, indexedLineage, lineage, pruneExpired, reportState, RETENTION_MS } from "./roster.js";
|
|
9
10
|
export { subtree } from "./subtree.js";
|
|
10
11
|
export { registerTreeRpc } from "./rpc-handlers.js";
|
|
11
12
|
export { addTreeTools } from "./tools.js";
|
|
12
|
-
export { wakeMissed } from "./wake.js";
|
|
13
|
+
export { stalledAmong, wakeMissed } from "./wake.js";
|
package/dist/tree/ports.d.ts
CHANGED
|
@@ -286,8 +286,8 @@ export interface ExecutionError {
|
|
|
286
286
|
}
|
|
287
287
|
export interface WatchPorts {
|
|
288
288
|
readonly storage: RosterStorage;
|
|
289
|
-
/** `context` reads the last reply of a session that ended its turn without reporting. */
|
|
290
|
-
readonly session: Pick<Context["session"], "synthetic" | "context">;
|
|
289
|
+
/** `context` reads the last reply of a session that ended its turn without reporting; `get` is what every member joins with too. */
|
|
290
|
+
readonly session: Pick<Context["session"], "synthetic" | "context" | "get">;
|
|
291
291
|
readonly event: Pick<Context["event"], "subscribe">;
|
|
292
292
|
/** The permission domains of every loaded location, which the `Waits` suppliers ask; an isolated child's are in its worktree's. */
|
|
293
293
|
readonly permissions: () => Iterable<Pick<Context["permission"], "list">>;
|
|
@@ -316,5 +316,7 @@ export interface ContextPorts {
|
|
|
316
316
|
readonly limits: Limits;
|
|
317
317
|
/** Each session's depth once looked up: null for one nobody spawned that has started none, until it starts one. */
|
|
318
318
|
readonly roles: Map<string, number | null>;
|
|
319
|
+
/** Tools of other modules hidden from a leaf alongside courier_spawn; `src/index.ts` names courier_recall and courier_note. */
|
|
320
|
+
readonly hiddenFromLeaf?: ReadonlyArray<string>;
|
|
319
321
|
readonly log: (message: string) => void;
|
|
320
322
|
}
|
package/dist/tree/roster.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Status } from "../kernel/protocol.js";
|
|
1
2
|
import type { RosterEntry, RosterStorage } from "./ports.js";
|
|
2
3
|
/**
|
|
3
4
|
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
@@ -11,6 +12,22 @@ export declare function allEntries(storage: RosterStorage): Promise<RosterEntry[
|
|
|
11
12
|
* courier_spawn did not start; empty when courier_spawn did not start the session.
|
|
12
13
|
*/
|
|
13
14
|
export declare function lineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
|
|
15
|
+
/** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
|
|
16
|
+
export declare function indexedLineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
|
|
17
|
+
/** The sessions a session started, oldest first, expired entries included. */
|
|
18
|
+
export declare function childrenOf(storage: RosterStorage, parentID: string): Promise<RosterEntry[]>;
|
|
19
|
+
/**
|
|
20
|
+
* What another module reads of a spawned session's report: `known` once a prompt was noted, whether it `owes` one, how
|
|
21
|
+
* its wait `ended` when not by a report, the `status` of its last report, and whether courier_stop `stopped` it since its last prompt.
|
|
22
|
+
*/
|
|
23
|
+
export interface ReportState {
|
|
24
|
+
readonly known: boolean;
|
|
25
|
+
readonly owes: boolean;
|
|
26
|
+
readonly ended?: "failed" | "interrupted";
|
|
27
|
+
readonly status?: Status;
|
|
28
|
+
readonly stopped: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function reportState(storage: RosterStorage, sessionID: string): Promise<ReportState>;
|
|
14
31
|
/**
|
|
15
32
|
* The top of a spawned session's lineage, where its permission requests and questions go, since the
|
|
16
33
|
* person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
|
package/dist/tree/roster.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { isStatus } from "../kernel/protocol.js";
|
|
2
3
|
import { forgetChild, forgetReports } from "./forget.js";
|
|
4
|
+
import { stoppedSincePrompt } from "./report.js";
|
|
3
5
|
import { childStore } from "./store.js";
|
|
4
6
|
/**
|
|
5
7
|
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
@@ -17,6 +19,28 @@ export async function allEntries(storage) {
|
|
|
17
19
|
export async function lineage(storage, sessionID) {
|
|
18
20
|
return childStore(storage).lineage(sessionID);
|
|
19
21
|
}
|
|
22
|
+
/** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
|
|
23
|
+
export async function indexedLineage(storage, sessionID) {
|
|
24
|
+
return childStore(storage).indexedLineage(sessionID);
|
|
25
|
+
}
|
|
26
|
+
/** The sessions a session started, oldest first, expired entries included. */
|
|
27
|
+
export async function childrenOf(storage, parentID) {
|
|
28
|
+
return childStore(storage).children(parentID);
|
|
29
|
+
}
|
|
30
|
+
export async function reportState(storage, sessionID) {
|
|
31
|
+
const children = childStore(storage);
|
|
32
|
+
const [{ prompt, settled }, stopped] = await Promise.all([children.notes(sessionID, ["prompt", "settled"]), stoppedSincePrompt(children, sessionID)]);
|
|
33
|
+
const known = prompt.at !== undefined;
|
|
34
|
+
const done = known && settled.at !== undefined && settled.at >= prompt.at;
|
|
35
|
+
const by = settled.by;
|
|
36
|
+
return {
|
|
37
|
+
known,
|
|
38
|
+
owes: known && !done,
|
|
39
|
+
...(done && (by === "failed" || by === "interrupted") ? { ended: by } : {}),
|
|
40
|
+
...(isStatus(settled.status) ? { status: settled.status } : {}),
|
|
41
|
+
stopped,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
20
44
|
/**
|
|
21
45
|
* The top of a spawned session's lineage, where its permission requests and questions go, since the
|
|
22
46
|
* person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
|
|
@@ -56,11 +56,11 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
|
|
|
56
56
|
readonly rows: readonly {
|
|
57
57
|
readonly sessionID: string;
|
|
58
58
|
readonly title: string;
|
|
59
|
-
readonly parentID: string;
|
|
60
59
|
readonly depth: number;
|
|
61
|
-
readonly created: number;
|
|
62
60
|
readonly isolated: boolean;
|
|
63
|
-
readonly
|
|
61
|
+
readonly parentID: string;
|
|
62
|
+
readonly created: number;
|
|
63
|
+
readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
|
|
64
64
|
readonly waits: readonly ({
|
|
65
65
|
readonly type: "permission";
|
|
66
66
|
readonly action: string;
|
|
@@ -72,25 +72,25 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
|
|
|
72
72
|
readonly type: "form";
|
|
73
73
|
readonly title: string;
|
|
74
74
|
})[];
|
|
75
|
+
readonly stopped?: boolean | undefined;
|
|
76
|
+
readonly stalled?: number | undefined;
|
|
75
77
|
readonly group?: {
|
|
76
78
|
readonly name: string;
|
|
77
79
|
readonly standing: string;
|
|
78
80
|
} | undefined;
|
|
79
|
-
readonly
|
|
81
|
+
readonly cost?: number | undefined;
|
|
80
82
|
readonly context?: number | undefined;
|
|
81
|
-
readonly
|
|
83
|
+
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
82
84
|
readonly report?: {
|
|
83
85
|
readonly progressed: boolean;
|
|
84
|
-
readonly state: "
|
|
86
|
+
readonly state: "ended" | "reported" | "owed";
|
|
85
87
|
readonly status?: string | undefined;
|
|
86
88
|
} | undefined;
|
|
87
|
-
readonly
|
|
88
|
-
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
89
|
+
readonly idle?: number | undefined;
|
|
89
90
|
readonly tool?: {
|
|
90
91
|
readonly name: string;
|
|
91
92
|
readonly at: number;
|
|
92
93
|
} | undefined;
|
|
93
|
-
readonly cost?: number | undefined;
|
|
94
94
|
}[];
|
|
95
95
|
readonly stalled?: number | undefined;
|
|
96
96
|
readonly truncated?: number | undefined;
|
|
@@ -98,11 +98,11 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
|
|
|
98
98
|
readonly rows: readonly {
|
|
99
99
|
readonly sessionID: string;
|
|
100
100
|
readonly title: string;
|
|
101
|
-
readonly parentID: string;
|
|
102
101
|
readonly depth: number;
|
|
103
|
-
readonly created: number;
|
|
104
102
|
readonly isolated: boolean;
|
|
105
|
-
readonly
|
|
103
|
+
readonly parentID: string;
|
|
104
|
+
readonly created: number;
|
|
105
|
+
readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
|
|
106
106
|
readonly waits: readonly ({
|
|
107
107
|
readonly type: "permission";
|
|
108
108
|
readonly action: string;
|
|
@@ -114,25 +114,25 @@ export declare function registerTreeRpc(rpc: Pick<Plugin.Context["rpc"], "regist
|
|
|
114
114
|
readonly type: "form";
|
|
115
115
|
readonly title: string;
|
|
116
116
|
})[];
|
|
117
|
+
readonly stopped?: boolean | undefined;
|
|
118
|
+
readonly stalled?: number | undefined;
|
|
117
119
|
readonly group?: {
|
|
118
120
|
readonly name: string;
|
|
119
121
|
readonly standing: string;
|
|
120
122
|
} | undefined;
|
|
121
|
-
readonly
|
|
123
|
+
readonly cost?: number | undefined;
|
|
122
124
|
readonly context?: number | undefined;
|
|
123
|
-
readonly
|
|
125
|
+
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
124
126
|
readonly report?: {
|
|
125
127
|
readonly progressed: boolean;
|
|
126
|
-
readonly state: "
|
|
128
|
+
readonly state: "ended" | "reported" | "owed";
|
|
127
129
|
readonly status?: string | undefined;
|
|
128
130
|
} | undefined;
|
|
129
|
-
readonly
|
|
130
|
-
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
131
|
+
readonly idle?: number | undefined;
|
|
131
132
|
readonly tool?: {
|
|
132
133
|
readonly name: string;
|
|
133
134
|
readonly at: number;
|
|
134
135
|
} | undefined;
|
|
135
|
-
readonly cost?: number | undefined;
|
|
136
136
|
}[];
|
|
137
137
|
readonly stalled?: number | undefined;
|
|
138
138
|
readonly truncated?: number | undefined;
|
package/dist/tree/rpc.d.ts
CHANGED
|
@@ -223,11 +223,11 @@ export declare const treeRpc: {
|
|
|
223
223
|
readonly rows: readonly {
|
|
224
224
|
readonly sessionID: string;
|
|
225
225
|
readonly title: string;
|
|
226
|
-
readonly parentID: string;
|
|
227
226
|
readonly depth: number;
|
|
228
|
-
readonly created: number;
|
|
229
227
|
readonly isolated: boolean;
|
|
230
|
-
readonly
|
|
228
|
+
readonly parentID: string;
|
|
229
|
+
readonly created: number;
|
|
230
|
+
readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
|
|
231
231
|
readonly waits: readonly ({
|
|
232
232
|
readonly type: "permission";
|
|
233
233
|
readonly action: string;
|
|
@@ -239,25 +239,25 @@ export declare const treeRpc: {
|
|
|
239
239
|
readonly type: "form";
|
|
240
240
|
readonly title: string;
|
|
241
241
|
})[];
|
|
242
|
+
readonly stopped?: boolean | undefined;
|
|
243
|
+
readonly stalled?: number | undefined;
|
|
242
244
|
readonly group?: {
|
|
243
245
|
readonly name: string;
|
|
244
246
|
readonly standing: string;
|
|
245
247
|
} | undefined;
|
|
246
|
-
readonly
|
|
248
|
+
readonly cost?: number | undefined;
|
|
247
249
|
readonly context?: number | undefined;
|
|
248
|
-
readonly
|
|
250
|
+
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
249
251
|
readonly report?: {
|
|
250
252
|
readonly progressed: boolean;
|
|
251
|
-
readonly state: "
|
|
253
|
+
readonly state: "ended" | "reported" | "owed";
|
|
252
254
|
readonly status?: string | undefined;
|
|
253
255
|
} | undefined;
|
|
254
|
-
readonly
|
|
255
|
-
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
256
|
+
readonly idle?: number | undefined;
|
|
256
257
|
readonly tool?: {
|
|
257
258
|
readonly name: string;
|
|
258
259
|
readonly at: number;
|
|
259
260
|
} | undefined;
|
|
260
|
-
readonly cost?: number | undefined;
|
|
261
261
|
}[];
|
|
262
262
|
readonly stalled?: number | undefined;
|
|
263
263
|
readonly truncated?: number | undefined;
|
|
@@ -265,11 +265,11 @@ export declare const treeRpc: {
|
|
|
265
265
|
readonly rows: readonly {
|
|
266
266
|
readonly sessionID: string;
|
|
267
267
|
readonly title: string;
|
|
268
|
-
readonly parentID: string;
|
|
269
268
|
readonly depth: number;
|
|
270
|
-
readonly created: number;
|
|
271
269
|
readonly isolated: boolean;
|
|
272
|
-
readonly
|
|
270
|
+
readonly parentID: string;
|
|
271
|
+
readonly created: number;
|
|
272
|
+
readonly state: "stalled" | "running" | "idle" | "gone" | "waiting";
|
|
273
273
|
readonly waits: readonly ({
|
|
274
274
|
readonly type: "permission";
|
|
275
275
|
readonly action: string;
|
|
@@ -281,25 +281,25 @@ export declare const treeRpc: {
|
|
|
281
281
|
readonly type: "form";
|
|
282
282
|
readonly title: string;
|
|
283
283
|
})[];
|
|
284
|
+
readonly stopped?: boolean | undefined;
|
|
285
|
+
readonly stalled?: number | undefined;
|
|
284
286
|
readonly group?: {
|
|
285
287
|
readonly name: string;
|
|
286
288
|
readonly standing: string;
|
|
287
289
|
} | undefined;
|
|
288
|
-
readonly
|
|
290
|
+
readonly cost?: number | undefined;
|
|
289
291
|
readonly context?: number | undefined;
|
|
290
|
-
readonly
|
|
292
|
+
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
291
293
|
readonly report?: {
|
|
292
294
|
readonly progressed: boolean;
|
|
293
|
-
readonly state: "
|
|
295
|
+
readonly state: "ended" | "reported" | "owed";
|
|
294
296
|
readonly status?: string | undefined;
|
|
295
297
|
} | undefined;
|
|
296
|
-
readonly
|
|
297
|
-
readonly outcome?: "failed" | "interrupted" | "succeeded" | undefined;
|
|
298
|
+
readonly idle?: number | undefined;
|
|
298
299
|
readonly tool?: {
|
|
299
300
|
readonly name: string;
|
|
300
301
|
readonly at: number;
|
|
301
302
|
} | undefined;
|
|
302
|
-
readonly cost?: number | undefined;
|
|
303
303
|
}[];
|
|
304
304
|
readonly stalled?: number | undefined;
|
|
305
305
|
readonly truncated?: number | undefined;
|