opencode-courier 0.13.7 → 0.14.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 +10 -11
- package/dist/index.d.ts +0 -8
- package/dist/index.js +6 -53
- package/dist/kernel/protocol.d.ts +0 -14
- package/dist/kernel/protocol.js +1 -15
- package/dist/kernel/tool.d.ts +0 -2
- package/dist/kernel/tool.js +0 -1
- package/dist/memory/domain.d.ts +6 -85
- package/dist/memory/domain.js +7 -63
- package/dist/memory/events.d.ts +0 -11
- package/dist/memory/events.js +1 -12
- package/dist/memory/forget.d.ts +0 -5
- package/dist/memory/forget.js +0 -4
- package/dist/memory/index.js +0 -2
- package/dist/memory/lessons.d.ts +0 -8
- package/dist/memory/lessons.js +0 -9
- package/dist/memory/note.d.ts +0 -9
- package/dist/memory/note.js +0 -5
- package/dist/memory/pin.d.ts +0 -5
- package/dist/memory/pin.js +0 -4
- package/dist/memory/ports.d.ts +2 -22
- package/dist/memory/recall.d.ts +0 -5
- package/dist/memory/recall.js +0 -12
- package/dist/memory/record.d.ts +0 -4
- package/dist/memory/record.js +8 -31
- package/dist/memory/rpc-handlers.d.ts +3 -6
- package/dist/memory/rpc-handlers.js +0 -6
- package/dist/memory/rpc.d.ts +6 -19
- package/dist/memory/rpc.js +1 -16
- package/dist/memory/store.d.ts +0 -5
- package/dist/memory/store.js +1 -16
- package/dist/memory/sweep.d.ts +0 -4
- package/dist/memory/sweep.js +0 -4
- package/dist/memory/text.d.ts +0 -25
- package/dist/memory/text.js +6 -36
- package/dist/memory/tools.d.ts +0 -2
- package/dist/memory/tools.js +0 -2
- package/dist/memory/tui.d.ts +1 -35
- package/dist/person/events.d.ts +0 -16
- package/dist/person/events.js +3 -30
- package/dist/person/form.d.ts +0 -9
- package/dist/person/form.js +0 -10
- package/dist/person/index.d.ts +0 -2
- package/dist/person/permission.d.ts +0 -34
- package/dist/person/permission.js +2 -28
- package/dist/person/ports.d.ts +0 -16
- package/dist/person/ports.js +0 -1
- package/dist/person/question/answer.d.ts +0 -28
- package/dist/person/question/answer.js +0 -49
- package/dist/person/question/index.js +0 -2
- package/dist/person/question/lifecycle.d.ts +0 -26
- package/dist/person/question/lifecycle.js +1 -32
- package/dist/person/question/pure.d.ts +0 -11
- package/dist/person/question/pure.js +0 -11
- package/dist/person/question/relay.d.ts +0 -1
- package/dist/person/question/relay.js +5 -67
- package/dist/person/question/runtime.d.ts +0 -14
- package/dist/person/question/runtime.js +1 -26
- package/dist/person/question/shared.d.ts +0 -17
- package/dist/person/question/shared.js +0 -20
- package/dist/person/question/types.d.ts +0 -44
- package/dist/person/rpc-handlers.d.ts +0 -7
- package/dist/person/rpc-handlers.js +0 -6
- package/dist/person/rpc.d.ts +0 -20
- package/dist/person/rpc.js +0 -16
- package/dist/person/text.d.ts +0 -45
- package/dist/person/text.js +2 -54
- package/dist/person/tools.d.ts +0 -3
- package/dist/person/tools.js +1 -3
- package/dist/person/tui.d.ts +0 -21
- package/dist/platform/bounded.d.ts +0 -8
- package/dist/platform/bounded.js +0 -8
- package/dist/platform/events.d.ts +0 -18
- package/dist/platform/events.js +3 -22
- package/dist/platform/hub.d.ts +0 -113
- package/dist/platform/hub.js +1 -76
- package/dist/platform/json.d.ts +0 -3
- package/dist/platform/json.js +1 -3
- package/dist/platform/storage.d.ts +0 -7
- package/dist/platform/storage.js +0 -6
- package/dist/platform/version.d.ts +0 -9
- package/dist/platform/version.js +0 -8
- package/dist/rpc.js +0 -1
- package/dist/scheduling/index.d.ts +0 -1
- package/dist/scheduling/index.js +0 -1
- package/dist/scheduling/later.d.ts +0 -11
- package/dist/scheduling/later.js +0 -14
- package/dist/scheduling/text.d.ts +0 -1
- package/dist/scheduling/text.js +0 -1
- package/dist/scheduling/tools.d.ts +0 -1
- package/dist/scheduling/tools.js +1 -5
- package/dist/skill.d.ts +1 -9
- package/dist/skill.js +97 -118
- package/dist/tree/cleanup.d.ts +0 -20
- package/dist/tree/cleanup.js +0 -26
- package/dist/tree/context.d.ts +0 -5
- package/dist/tree/context.js +0 -8
- package/dist/tree/domain.d.ts +0 -64
- package/dist/tree/domain.js +7 -45
- package/dist/tree/events.d.ts +0 -36
- package/dist/tree/events.js +4 -49
- package/dist/tree/forget.d.ts +0 -15
- package/dist/tree/forget.js +0 -18
- package/dist/tree/group.d.ts +0 -22
- package/dist/tree/group.js +1 -37
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.js +1 -3
- package/dist/tree/limits.d.ts +0 -4
- package/dist/tree/limits.js +1 -12
- package/dist/tree/mascot.d.ts +0 -25
- package/dist/tree/mascot.js +1 -32
- package/dist/tree/model.d.ts +0 -5
- package/dist/tree/model.js +2 -15
- package/dist/tree/nudge.d.ts +0 -11
- package/dist/tree/nudge.js +3 -16
- package/dist/tree/panel.d.ts +0 -8
- package/dist/tree/panel.js +3 -16
- package/dist/tree/ports.d.ts +2 -138
- package/dist/tree/report.d.ts +0 -10
- package/dist/tree/report.js +0 -11
- package/dist/tree/roster.d.ts +0 -21
- package/dist/tree/roster.js +0 -21
- package/dist/tree/rpc-handlers.d.ts +6 -1
- package/dist/tree/rpc-handlers.js +0 -5
- package/dist/tree/rpc.d.ts +9 -109
- package/dist/tree/rpc.js +2 -38
- package/dist/tree/running.d.ts +0 -6
- package/dist/tree/running.js +2 -10
- package/dist/tree/send.d.ts +0 -4
- package/dist/tree/send.js +4 -31
- package/dist/tree/spawn.d.ts +0 -7
- package/dist/tree/spawn.js +6 -47
- package/dist/tree/standing.d.ts +0 -8
- package/dist/tree/standing.js +1 -11
- package/dist/tree/status.d.ts +0 -8
- package/dist/tree/status.js +1 -9
- package/dist/tree/stop.d.ts +2 -9
- package/dist/tree/stop.js +58 -25
- package/dist/tree/store.d.ts +0 -8
- package/dist/tree/store.js +4 -38
- package/dist/tree/subtree.d.ts +8 -26
- package/dist/tree/subtree.js +37 -35
- package/dist/tree/summary.d.ts +0 -4
- package/dist/tree/summary.js +0 -8
- package/dist/tree/text.d.ts +1 -44
- package/dist/tree/text.js +6 -66
- package/dist/tree/tools.d.ts +0 -6
- package/dist/tree/tools.js +4 -8
- package/dist/tree/tui.d.ts +0 -81
- package/dist/tree/wake.d.ts +0 -30
- package/dist/tree/wake.js +5 -39
- package/dist/tui.js +22 -8
- package/package.json +8 -9
- package/dist/webhook/index.d.ts +0 -5
- package/dist/webhook/index.js +0 -3
- package/dist/webhook/receiver.d.ts +0 -80
- package/dist/webhook/receiver.js +0 -339
- package/dist/webhook/subscriptions.d.ts +0 -29
- package/dist/webhook/subscriptions.js +0 -52
- package/dist/webhook/text.d.ts +0 -23
- package/dist/webhook/text.js +0 -82
- package/dist/webhook/tools.d.ts +0 -19
- package/dist/webhook/tools.js +0 -46
package/dist/tree/domain.d.ts
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The tree's pure rules, on plain data: the limits, a session's lineage, whether it runs, and a group
|
|
3
|
-
* member's standing. Imports nothing of the plugin's but `kernel/` and `platform/json.ts`.
|
|
4
|
-
*/
|
|
5
1
|
import type { Plugin } from "@opencode/plugin";
|
|
6
2
|
import type { Status } from "../kernel/protocol.js";
|
|
7
3
|
type Context = Plugin.Context;
|
|
8
|
-
/** The limits on the tree of sessions courier_spawn builds, from the plugin's options. */
|
|
9
4
|
export interface Limits {
|
|
10
5
|
readonly maxDepth: number;
|
|
11
6
|
readonly maxChildren: number;
|
|
12
7
|
readonly maxTotal: number;
|
|
13
8
|
}
|
|
14
9
|
export declare const DEFAULT_LIMITS: Limits;
|
|
15
|
-
/** The limits in the plugin's options; one that is not a positive integer keeps its default and is named in `problems`. */
|
|
16
10
|
export declare function readLimits(options: unknown): {
|
|
17
11
|
limits: Limits;
|
|
18
12
|
problems: string[];
|
|
19
13
|
};
|
|
20
|
-
/** The `nativeChildren` option: whether a child started without isolate or `native` gets OpenCode's own `parentID`; off unless `true`. */
|
|
21
14
|
export declare function readNativeChildren(options: unknown): {
|
|
22
15
|
nativeChildren: boolean;
|
|
23
16
|
problems: string[];
|
|
24
17
|
};
|
|
25
|
-
/** A session's times, as `session.get` gives them. */
|
|
26
18
|
type Times = Awaited<ReturnType<Context["session"]["get"]>>["time"];
|
|
27
|
-
/** A session's times as `busy` reads them; `busy`, when its latest execution started, is stamped since OpenCode 2.2.2. */
|
|
28
19
|
export type BusyTimes = Pick<Times, "updated" | "idle" | "busy">;
|
|
29
|
-
/**
|
|
30
|
-
* Whether a session may run by its times: it has never finished a turn, or its latest execution started after the last one ended
|
|
31
|
-
* (`busy`; without it `updated`, which a rename, an archive or a waiting inbox item also stamp); only its transcript then tells.
|
|
32
|
-
*/
|
|
33
20
|
export declare const busy: (time: BusyTimes) => boolean;
|
|
34
|
-
/** A stored message as `turnEnd` reads it: an assistant message's `error` says how its step ended. */
|
|
35
21
|
type Stored = {
|
|
36
22
|
readonly type: string;
|
|
37
23
|
readonly error?: {
|
|
@@ -42,50 +28,26 @@ type Stored = {
|
|
|
42
28
|
readonly completed?: unknown;
|
|
43
29
|
};
|
|
44
30
|
};
|
|
45
|
-
/**
|
|
46
|
-
* The message a transcript's last turn ended on, unless one runs, past asides made since: an `idle` message, or a step
|
|
47
|
-
* interrupted with none after it, as a declined permission or dismissed question ends a turn, recording no `idle` (#326).
|
|
48
|
-
*/
|
|
49
31
|
export declare function turnEnd<M extends Stored>(messages: ReadonlyArray<M>): M | undefined;
|
|
50
32
|
export declare const endsIdle: (messages: ReadonlyArray<Stored>) => boolean;
|
|
51
|
-
/** When the turn `end` ended: the session's idle time for an `idle` message, else when its interrupted step ended. */
|
|
52
33
|
export declare function endedAt(end: Stored, time: BusyTimes): number | undefined;
|
|
53
|
-
/**
|
|
54
|
-
* Whether a session of a tree read is live, for a cut listing's order: it runs (its turn not failed or interrupted since its last
|
|
55
|
-
* prompt), waits on a request or form, or owes a report, but not, unlike for the limits, for a message waiting in its inbox.
|
|
56
|
-
*/
|
|
57
34
|
export declare const isLive: (running: boolean | undefined, waits: number, report: {
|
|
58
35
|
readonly owes: boolean;
|
|
59
36
|
readonly ended: boolean;
|
|
60
37
|
} | undefined) => boolean;
|
|
61
|
-
/** A session of a tree read, as `cut` takes it: where it is in the tree, when it was started, and whether it is live. */
|
|
62
38
|
export interface Cuttable {
|
|
63
39
|
readonly sessionID: string;
|
|
64
40
|
readonly parentID: string;
|
|
65
41
|
readonly createdAt: number;
|
|
66
42
|
readonly live: boolean;
|
|
67
43
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Which sessions a listing of at most `max` keeps, of a tree read in the order it is drawn: the live ones first, then
|
|
70
|
-
* the newest, each with the sessions above it so the tree still draws; `leftOut` counts the rest.
|
|
71
|
-
*/
|
|
72
44
|
export declare function cut<T extends Cuttable>(nodes: ReadonlyArray<T>, max: number): {
|
|
73
45
|
kept: T[];
|
|
74
46
|
leftOut: number;
|
|
75
47
|
};
|
|
76
|
-
/**
|
|
77
|
-
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
78
|
-
* except isolated children whose worktree is still there: those stay until courier_cleanup.
|
|
79
|
-
*/
|
|
80
48
|
export declare const RETENTION_MS: number;
|
|
81
|
-
/** Whether a directory still exists; tests pass a fake. */
|
|
82
49
|
export type Exists = (directory: string) => boolean;
|
|
83
|
-
/**
|
|
84
|
-
* Whether an entry is past retention at `now`: recorded more than `RETENTION_MS` before, unless it is an isolated
|
|
85
|
-
* child whose worktree exists, which is kept so it can still be cleaned up.
|
|
86
|
-
*/
|
|
87
50
|
export declare const isExpired: (entry: RosterEntry, now: number, exists: Exists) => boolean;
|
|
88
|
-
/** A session started with courier_spawn, recorded under the session that started it. */
|
|
89
51
|
export interface RosterEntry {
|
|
90
52
|
readonly sessionID: string;
|
|
91
53
|
readonly parentID: string;
|
|
@@ -93,49 +55,30 @@ export interface RosterEntry {
|
|
|
93
55
|
readonly directory: string;
|
|
94
56
|
readonly isolated: boolean;
|
|
95
57
|
readonly createdAt: number;
|
|
96
|
-
/** For an isolated child, the directory its worktree was made from; for the record, nothing acts on it. */
|
|
97
58
|
readonly source?: string;
|
|
98
|
-
/** For an isolated child, the project its worktree belongs to; courier_cleanup removes it from there. */
|
|
99
59
|
readonly project?: string;
|
|
100
|
-
/** For an isolated child, the commit its worktree was made from; its own work is what came after. */
|
|
101
60
|
readonly base?: string;
|
|
102
|
-
/** `"status"` when its brief asked for a report with a status; absent for a child an earlier release briefed. */
|
|
103
61
|
readonly reports?: "status";
|
|
104
|
-
/** The join group courier_spawn put it in, whose release holds its report; absent for a child in none. */
|
|
105
62
|
readonly group?: string;
|
|
106
|
-
/** Created with OpenCode's own `parentID` (courier_spawn's `native`, else the `nativeChildren` option), so OpenCode shows its asks in its parent's view. */
|
|
107
63
|
readonly native?: true;
|
|
108
|
-
/** The model it was started on, as `modelName` writes it, when that is not the model its parent was on. */
|
|
109
64
|
readonly model?: string;
|
|
110
65
|
}
|
|
111
|
-
/** How a child was started, as courier_children and the panel show it: isolated, native when it is, and its model when not its parent's. */
|
|
112
66
|
export declare function startedAs(entry: RosterEntry): {
|
|
113
67
|
model?: string | undefined;
|
|
114
68
|
native?: true | undefined;
|
|
115
69
|
isolated: boolean;
|
|
116
70
|
};
|
|
117
|
-
/** A model as a session names it: its provider, its id, and its variant, which `"default"` and none both leave to the provider. */
|
|
118
71
|
export interface ModelRef {
|
|
119
72
|
readonly providerID: string;
|
|
120
73
|
readonly id: string;
|
|
121
74
|
readonly variant?: string;
|
|
122
75
|
}
|
|
123
|
-
/** `provider/model` as `courier_spawn` takes it: one slash, nothing empty on either side. */
|
|
124
76
|
export declare function parseModelName(text: string): Pick<ModelRef, "providerID" | "id"> | undefined;
|
|
125
|
-
/** A model as the roster, the panel and the results name it: `provider/model`, with `#variant` for a variant that is not the default. */
|
|
126
77
|
export declare function modelName(model: ModelRef): string;
|
|
127
|
-
/** `lineage` over roster entries already read. */
|
|
128
78
|
export declare function lineageIn(entries: ReadonlyArray<RosterEntry>, sessionID: string): RosterEntry[];
|
|
129
|
-
/** Roster entries by their session, for `lineageBy`. */
|
|
130
79
|
export declare function bySession(entries: ReadonlyArray<RosterEntry>): Map<string, RosterEntry>;
|
|
131
|
-
/** `lineage` over roster entries already read and mapped by `bySession`. */
|
|
132
80
|
export declare function lineageBy(bySession: ReadonlyMap<string, RosterEntry>, sessionID: string): RosterEntry[];
|
|
133
|
-
/**
|
|
134
|
-
* The session whose view OpenCode shows the asks of a lineage's first session in: up its native parents, to the
|
|
135
|
-
* first session that is not a native child (an isolated one, or the top). The session itself when it is not native.
|
|
136
|
-
*/
|
|
137
81
|
export declare function nativeRootOf(chain: ReadonlyArray<RosterEntry>): string;
|
|
138
|
-
/** What a report lists besides its text; `checks` are the commands the session ran and what each gave. */
|
|
139
82
|
export interface Artifacts {
|
|
140
83
|
readonly branch?: string;
|
|
141
84
|
readonly commits?: ReadonlyArray<string>;
|
|
@@ -145,21 +88,18 @@ export interface Artifacts {
|
|
|
145
88
|
readonly result: string;
|
|
146
89
|
}>;
|
|
147
90
|
}
|
|
148
|
-
/** A report held with its group: its status, text and artifacts, as courier_send was given them, and when. */
|
|
149
91
|
export interface HeldReport {
|
|
150
92
|
readonly at: number;
|
|
151
93
|
readonly status: Status;
|
|
152
94
|
readonly message: string;
|
|
153
95
|
readonly artifacts?: Artifacts;
|
|
154
96
|
}
|
|
155
|
-
/** A member of a group that left it without a report, and why: its turn failed or was interrupted, it was deleted, or courier_cleanup dropped it. */
|
|
156
97
|
export interface LeftMember {
|
|
157
98
|
readonly sessionID: string;
|
|
158
99
|
readonly title: string;
|
|
159
100
|
readonly by: "failed" | "interrupted" | "deleted" | "dropped";
|
|
160
101
|
}
|
|
161
102
|
export declare const isGroupName: (value: unknown) => value is string;
|
|
162
|
-
/** A member of a group, as stored: its title for the release notice, and its report or how it left. */
|
|
163
103
|
export interface Member {
|
|
164
104
|
readonly title: string;
|
|
165
105
|
readonly joinedAt: number;
|
|
@@ -169,13 +109,9 @@ export interface Member {
|
|
|
169
109
|
readonly by: LeftMember["by"];
|
|
170
110
|
};
|
|
171
111
|
}
|
|
172
|
-
/** What is known of a member's standing: its report is held, still out, or it left, or it is no longer in an open group. */
|
|
173
112
|
export type Standing = "held" | "out" | LeftMember["by"] | "released";
|
|
174
|
-
/** A member's standing from its stored value, or `released` when there is none. */
|
|
175
113
|
export declare function standingOf(member: Member | undefined): Standing;
|
|
176
|
-
/** Whether every member of a group has a held report or has left: released once the parent's turn has ended. */
|
|
177
114
|
export declare const complete: (members: ReadonlyArray<Member>) => boolean;
|
|
178
|
-
/** How long the provider asked to wait after a failed turn, if it did: a positive number of milliseconds, at most `RETRY_MAX_MS`. */
|
|
179
115
|
export declare function retryDelay(error: {
|
|
180
116
|
readonly retryAfterMs?: unknown;
|
|
181
117
|
}): number | undefined;
|
package/dist/tree/domain.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { millis, obj } from "../platform/json.js";
|
|
2
2
|
export const DEFAULT_LIMITS = { maxDepth: 5, maxChildren: 5, maxTotal: 30 };
|
|
3
|
-
/** The limits in the plugin's options; one that is not a positive integer keeps its default and is named in `problems`. */
|
|
4
3
|
export function readLimits(options) {
|
|
5
4
|
const given = obj(options);
|
|
6
5
|
const problems = [];
|
|
@@ -16,43 +15,27 @@ export function readLimits(options) {
|
|
|
16
15
|
const limits = { maxDepth: read("maxDepth"), maxChildren: read("maxChildren"), maxTotal: read("maxTotal") };
|
|
17
16
|
return { limits, problems };
|
|
18
17
|
}
|
|
19
|
-
/** The `nativeChildren` option: whether a child started without isolate or `native` gets OpenCode's own `parentID`; off unless `true`. */
|
|
20
18
|
export function readNativeChildren(options) {
|
|
21
19
|
const value = obj(options).nativeChildren;
|
|
22
20
|
if (value === undefined || typeof value === "boolean")
|
|
23
21
|
return { nativeChildren: value === true, problems: [] };
|
|
24
22
|
return { nativeChildren: false, problems: [`nativeChildren must be true or false, not ${JSON.stringify(value)}; using false`] };
|
|
25
23
|
}
|
|
26
|
-
|
|
27
|
-
* Whether a session may run by its times: it has never finished a turn, or its latest execution started after the last one ended
|
|
28
|
-
* (`busy`; without it `updated`, which a rename, an archive or a waiting inbox item also stamp); only its transcript then tells.
|
|
29
|
-
*/
|
|
24
|
+
// time.busy exists since 2.2.2; time.updated is no sign of activity (api-notes)
|
|
30
25
|
export const busy = (time) => time.idle === undefined || millis(time.busy ?? time.updated) > millis(time.idle);
|
|
31
|
-
|
|
26
|
+
// a person's switches and shell commands append these, at rest or mid-turn
|
|
32
27
|
const ASIDES = new Set(["agent-switched", "model-switched", "location-switched", "shell"]);
|
|
33
|
-
|
|
34
|
-
* The message a transcript's last turn ended on, unless one runs, past asides made since: an `idle` message, or a step
|
|
35
|
-
* interrupted with none after it, as a declined permission or dismissed question ends a turn, recording no `idle` (#326).
|
|
36
|
-
*/
|
|
28
|
+
// a decline or dismissed question ends on an aborted step, no idle (api-notes)
|
|
37
29
|
export function turnEnd(messages) {
|
|
38
30
|
const last = messages.findLast((message) => !ASIDES.has(message.type));
|
|
39
31
|
return last?.type === "idle" || (last?.type === "assistant" && last.error?.type === "aborted") ? last : undefined;
|
|
40
32
|
}
|
|
41
33
|
export const endsIdle = (messages) => turnEnd(messages) !== undefined;
|
|
42
|
-
/** When the turn `end` ended: the session's idle time for an `idle` message, else when its interrupted step ended. */
|
|
43
34
|
export function endedAt(end, time) {
|
|
44
35
|
const at = end.type === "idle" ? millis(time.idle) : millis(end.time?.completed ?? end.time?.created);
|
|
45
36
|
return Number.isFinite(at) ? at : undefined;
|
|
46
37
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Whether a session of a tree read is live, for a cut listing's order: it runs (its turn not failed or interrupted since its last
|
|
49
|
-
* prompt), waits on a request or form, or owes a report, but not, unlike for the limits, for a message waiting in its inbox.
|
|
50
|
-
*/
|
|
51
38
|
export const isLive = (running, waits, report) => running !== undefined && ((running && !report?.ended) || waits > 0 || (report?.owes ?? false));
|
|
52
|
-
/**
|
|
53
|
-
* Which sessions a listing of at most `max` keeps, of a tree read in the order it is drawn: the live ones first, then
|
|
54
|
-
* the newest, each with the sessions above it so the tree still draws; `leftOut` counts the rest.
|
|
55
|
-
*/
|
|
56
39
|
export function cut(nodes, max) {
|
|
57
40
|
const byID = new Map(nodes.map((node) => [node.sessionID, node]));
|
|
58
41
|
const ranked = nodes
|
|
@@ -70,51 +53,34 @@ export function cut(nodes, max) {
|
|
|
70
53
|
}
|
|
71
54
|
return { kept: nodes.filter((node) => kept.has(node.sessionID)), leftOut: nodes.length - kept.size };
|
|
72
55
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
75
|
-
* except isolated children whose worktree is still there: those stay until courier_cleanup.
|
|
76
|
-
*/
|
|
77
56
|
export const RETENTION_MS = 14 * 24 * 60 * 60_000;
|
|
78
|
-
/**
|
|
79
|
-
* Whether an entry is past retention at `now`: recorded more than `RETENTION_MS` before, unless it is an isolated
|
|
80
|
-
* child whose worktree exists, which is kept so it can still be cleaned up.
|
|
81
|
-
*/
|
|
82
57
|
export const isExpired = (entry, now, exists) => now - entry.createdAt > RETENTION_MS && !(entry.isolated && exists(entry.directory));
|
|
83
|
-
/** How a child was started, as courier_children and the panel show it: isolated, native when it is, and its model when not its parent's. */
|
|
84
58
|
export function startedAs(entry) {
|
|
85
59
|
return { isolated: entry.isolated, ...(entry.native ? { native: true } : {}), ...(entry.model ? { model: entry.model } : {}) };
|
|
86
60
|
}
|
|
87
|
-
/** `provider/model` as `courier_spawn` takes it: one slash, nothing empty on either side. */
|
|
88
61
|
export function parseModelName(text) {
|
|
89
62
|
const slash = text.indexOf("/");
|
|
90
63
|
if (slash <= 0 || slash === text.length - 1)
|
|
91
64
|
return undefined;
|
|
92
65
|
return { providerID: text.slice(0, slash), id: text.slice(slash + 1) };
|
|
93
66
|
}
|
|
94
|
-
/** A model as the roster, the panel and the results name it: `provider/model`, with `#variant` for a variant that is not the default. */
|
|
95
67
|
export function modelName(model) {
|
|
96
68
|
const variant = model.variant && model.variant !== "default" ? `#${model.variant}` : "";
|
|
97
69
|
return `${model.providerID}/${model.id}${variant}`;
|
|
98
70
|
}
|
|
99
|
-
/** `lineage` over roster entries already read. */
|
|
100
71
|
export function lineageIn(entries, sessionID) {
|
|
101
72
|
return lineageBy(bySession(entries), sessionID);
|
|
102
73
|
}
|
|
103
|
-
/** Roster entries by their session, for `lineageBy`. */
|
|
104
74
|
export function bySession(entries) {
|
|
105
75
|
return new Map(entries.map((entry) => [entry.sessionID, entry]));
|
|
106
76
|
}
|
|
107
|
-
/** `lineage` over roster entries already read and mapped by `bySession`. */
|
|
108
77
|
export function lineageBy(bySession, sessionID) {
|
|
109
78
|
const chain = [];
|
|
110
79
|
for (let entry = bySession.get(sessionID); entry && !chain.includes(entry); entry = bySession.get(entry.parentID))
|
|
111
80
|
chain.push(entry);
|
|
112
81
|
return chain;
|
|
113
82
|
}
|
|
114
|
-
|
|
115
|
-
* The session whose view OpenCode shows the asks of a lineage's first session in: up its native parents, to the
|
|
116
|
-
* first session that is not a native child (an isolated one, or the top). The session itself when it is not native.
|
|
117
|
-
*/
|
|
83
|
+
// OpenCode shows a native child's asks in its root's view (api-notes)
|
|
118
84
|
export function nativeRootOf(chain) {
|
|
119
85
|
let root = chain[0].sessionID;
|
|
120
86
|
for (const entry of chain) {
|
|
@@ -124,10 +90,8 @@ export function nativeRootOf(chain) {
|
|
|
124
90
|
}
|
|
125
91
|
return root;
|
|
126
92
|
}
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
export const isGroupName = (value) => typeof value === "string" && NAME.test(value);
|
|
130
|
-
/** A member's standing from its stored value, or `released` when there is none. */
|
|
93
|
+
const GROUP_NAME = /^[\w.-]{1,60}$/;
|
|
94
|
+
export const isGroupName = (value) => typeof value === "string" && GROUP_NAME.test(value);
|
|
131
95
|
export function standingOf(member) {
|
|
132
96
|
if (!member)
|
|
133
97
|
return "released";
|
|
@@ -135,11 +99,9 @@ export function standingOf(member) {
|
|
|
135
99
|
return "held";
|
|
136
100
|
return member.left?.by ?? "out";
|
|
137
101
|
}
|
|
138
|
-
/** Whether every member of a group has a held report or has left: released once the parent's turn has ended. */
|
|
139
102
|
export const complete = (members) => members.every((member) => member.report || member.left);
|
|
140
|
-
|
|
103
|
+
// OpenCode clamps retry-after to 2^53 ms, past what a Date holds
|
|
141
104
|
const RETRY_MAX_MS = 30 * 24 * 3_600_000;
|
|
142
|
-
/** How long the provider asked to wait after a failed turn, if it did: a positive number of milliseconds, at most `RETRY_MAX_MS`. */
|
|
143
105
|
export function retryDelay(error) {
|
|
144
106
|
const wait = error.retryAfterMs;
|
|
145
107
|
return typeof wait === "number" && Number.isFinite(wait) && wait > 0 && wait <= RETRY_MAX_MS ? wait : undefined;
|
package/dist/tree/events.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { EventModule } from "../platform/events.js";
|
|
2
2
|
import type { ExecutionError, Waits, WatchPorts, WatchState } from "./ports.js";
|
|
3
|
-
/** The part of OpenCode's `session.execution.failed` event the notice is made from. */
|
|
4
3
|
export interface ExecutionFailed {
|
|
5
4
|
readonly id: string;
|
|
6
|
-
/** When OpenCode published it, in epoch milliseconds. */
|
|
7
5
|
readonly created?: number;
|
|
8
6
|
readonly data: {
|
|
9
7
|
readonly sessionID: string;
|
|
10
8
|
readonly error: ExecutionError;
|
|
11
9
|
};
|
|
12
10
|
}
|
|
13
|
-
/** OpenCode's events that concern one session's turns and inbox: `session.execution.succeeded` and the like. */
|
|
14
11
|
export interface SessionEvent {
|
|
15
12
|
readonly id: string;
|
|
16
13
|
readonly created?: number;
|
|
@@ -19,12 +16,7 @@ export interface SessionEvent {
|
|
|
19
16
|
readonly reason?: string;
|
|
20
17
|
};
|
|
21
18
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Tells the parent of a child whose turn failed, since a child that cannot reach its model cannot
|
|
24
|
-
* call courier_send. Returns the parents told.
|
|
25
|
-
*/
|
|
26
19
|
export declare function reportFailure(ports: WatchPorts, seen: Set<string>, event: ExecutionFailed): Promise<string[]>;
|
|
27
|
-
/** OpenCode's `session.inbox.enqueued`: the item's type, which its `session.inbox.delivered` does not carry. */
|
|
28
20
|
export interface InboxEnqueued {
|
|
29
21
|
readonly data: {
|
|
30
22
|
readonly sessionID: string;
|
|
@@ -34,43 +26,15 @@ export interface InboxEnqueued {
|
|
|
34
26
|
};
|
|
35
27
|
};
|
|
36
28
|
}
|
|
37
|
-
/** Remembers the type of an item enqueued for a spawned session, for `notePrompt`; other sessions cost one read. */
|
|
38
29
|
export declare function noteEnqueued(ports: WatchPorts, inbox: Map<string, string>, event: InboxEnqueued): Promise<boolean>;
|
|
39
|
-
/**
|
|
40
|
-
* Notes that a message reached a spawned session, which then owes its parent a report, unless `inbox` knows it for one of
|
|
41
|
-
* `UNOWED` (`spawn` notes the task itself); reads the reverse index alone, and claims nothing: the note is the same twice.
|
|
42
|
-
*/
|
|
43
30
|
export declare function notePrompt(ports: WatchPorts, event: SessionEvent & {
|
|
44
31
|
readonly data: {
|
|
45
32
|
readonly inboxID?: string;
|
|
46
33
|
};
|
|
47
34
|
}, inbox?: Map<string, string>): Promise<boolean>;
|
|
48
|
-
/**
|
|
49
|
-
* Notes that a spawned session's turn was stopped, which takes it out of its group without a report; a
|
|
50
|
-
* shutdown's is left to `reportDeclined`, as one stopped between steps resumes on the next start.
|
|
51
|
-
*/
|
|
52
35
|
export declare function noteInterrupted(ports: WatchPorts, event: SessionEvent): Promise<boolean>;
|
|
53
|
-
/**
|
|
54
|
-
* Forgets what a deleted session leaves (report state, groups) and, unless isolated or a parent on the roster, its
|
|
55
|
-
* entry. Returns the sessions forgotten.
|
|
56
|
-
*/
|
|
57
36
|
export declare function noteDeleted(ports: WatchPorts, event: SessionEvent): Promise<string[]>;
|
|
58
|
-
/** A `Waits` asking `suppliers` one after another, cheapest first: the first that waits answers. */
|
|
59
37
|
export declare const waitsOn: (...suppliers: Waits[]) => Waits;
|
|
60
|
-
/** A `Waits` asking `suppliers` at once, as cheap as each other: it waits when one does, and fails when one fails. */
|
|
61
|
-
export declare const together: (...suppliers: Waits[]) => Waits;
|
|
62
|
-
/**
|
|
63
|
-
* Tells the parent of a spawned session whose turn ended without reporting to it since its last
|
|
64
|
-
* prompt, unless it waits; finds the session by the reverse index alone. Returns the parents told.
|
|
65
|
-
*/
|
|
66
38
|
export declare function reportSilent(ports: WatchPorts, seen: Set<string>, event: SessionEvent, outside: Waits): Promise<string[]>;
|
|
67
|
-
/**
|
|
68
|
-
* A session's turn that OpenCode reports as a shutdown ended in a plain decline, if its transcript ends on an aborted step: its
|
|
69
|
-
* parent is told as for a turn that succeeded without a report. A real shutdown during a step reads the same, and is told too.
|
|
70
|
-
*/
|
|
71
39
|
export declare function reportDeclined(ports: WatchPorts, seen: Set<string>, event: SessionEvent, outside: Waits): Promise<string[]>;
|
|
72
|
-
/**
|
|
73
|
-
* Tells parents of a child's failed or silent turn, unless it waits on what `outside` says, and notes
|
|
74
|
-
* its prompts, interruptions and deletion; `after` a turn's end, seals the groups of its session.
|
|
75
|
-
*/
|
|
76
40
|
export declare function treeEvents(state: WatchState, outside: Waits): EventModule<WatchPorts>;
|
package/dist/tree/events.js
CHANGED
|
@@ -10,10 +10,6 @@ import { childStore, groupStore } from "./store.js";
|
|
|
10
10
|
import { lastReply } from "./status.js";
|
|
11
11
|
import { failureNotice, silentNotice } from "./text.js";
|
|
12
12
|
import { forgetWakes, settleWake, wakeEnded, wakeStarted, watchWake } from "./wake.js";
|
|
13
|
-
/**
|
|
14
|
-
* Tells the parent of a child whose turn failed, since a child that cannot reach its model cannot
|
|
15
|
-
* call courier_send. Returns the parents told.
|
|
16
|
-
*/
|
|
17
13
|
export async function reportFailure(ports, seen, event) {
|
|
18
14
|
if (!claim(seen, event.id))
|
|
19
15
|
return [];
|
|
@@ -22,14 +18,10 @@ export async function reportFailure(ports, seen, event) {
|
|
|
22
18
|
const entries = await children.entriesOf(sessionID);
|
|
23
19
|
const at = event.created ?? ports.now();
|
|
24
20
|
const wait = retryDelay(error);
|
|
25
|
-
// Not in the way of the notice: kept only so the limits stop counting the child, and the panel can say when its limit lifts.
|
|
26
21
|
if (entries.length)
|
|
27
22
|
await children.settled(sessionID, "failed", at, undefined, wait === undefined ? undefined : at + wait).catch((error) => ports.log(`courier watch: could not note the failed turn of ${sessionID}: ${String(error)}`));
|
|
28
23
|
const out = await Promise.all(entries.map((entry) => outIn(ports, entry)));
|
|
29
|
-
// Left before the notice, so the parent waits on the group meanwhile; nudged after it, so the group's
|
|
30
|
-
// message does not overtake the notice, short of an interval tick falling in between.
|
|
31
24
|
const completed = await Promise.all(out.map((member) => (member ? leave(ports, member, "failed", at) : Promise.resolve(false))));
|
|
32
|
-
// A session courier_stop stopped is not a failure to tell: its parent was told of the stop, or ordered it.
|
|
33
25
|
const quiet = entries.length > 0 && (await stoppedSincePrompt(children, sessionID).catch(() => false));
|
|
34
26
|
try {
|
|
35
27
|
if (quiet)
|
|
@@ -49,9 +41,8 @@ export async function reportFailure(ports, seen, event) {
|
|
|
49
41
|
}
|
|
50
42
|
return entries.map((entry) => entry.parentID);
|
|
51
43
|
}
|
|
52
|
-
|
|
44
|
+
// only inbox.enqueued carries the item's type (api-notes)
|
|
53
45
|
const UNOWED = new Set(["user", "compaction", "move"]);
|
|
54
|
-
/** Remembers the type of an item enqueued for a spawned session, for `notePrompt`; other sessions cost one read. */
|
|
55
46
|
export async function noteEnqueued(ports, inbox, event) {
|
|
56
47
|
const { sessionID, inboxID, item } = event.data;
|
|
57
48
|
if ((await childStore(ports.storage).indexedParent(sessionID)) === undefined)
|
|
@@ -59,30 +50,21 @@ export async function noteEnqueued(ports, inbox, event) {
|
|
|
59
50
|
setBounded(inbox, inboxID, item.type, SEEN_MAX);
|
|
60
51
|
return true;
|
|
61
52
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Notes that a message reached a spawned session, which then owes its parent a report, unless `inbox` knows it for one of
|
|
64
|
-
* `UNOWED` (`spawn` notes the task itself); reads the reverse index alone, and claims nothing: the note is the same twice.
|
|
65
|
-
*/
|
|
66
53
|
export async function notePrompt(ports, event, inbox) {
|
|
67
54
|
const { sessionID, inboxID } = event.data;
|
|
68
|
-
// Kept, not deleted: the hub's other subscription looks it up for the same delivery.
|
|
69
55
|
if (inboxID !== undefined && UNOWED.has(inbox?.get(inboxID) ?? ""))
|
|
70
56
|
return false;
|
|
71
57
|
const children = childStore(ports.storage);
|
|
72
58
|
const parentID = await children.indexedParent(sessionID);
|
|
73
59
|
if (parentID === undefined)
|
|
74
60
|
return false;
|
|
75
|
-
//
|
|
61
|
+
// the idle hook's reminder still publishes inbox.delivered (api-notes)
|
|
76
62
|
if (inboxID !== undefined && (await children.nudgeOf(sessionID)).inboxID === inboxID)
|
|
77
63
|
return false;
|
|
78
64
|
await children.prompted(sessionID, event.created ?? ports.now());
|
|
79
65
|
await syncStanding(ports, parentID);
|
|
80
66
|
return true;
|
|
81
67
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Notes that a spawned session's turn was stopped, which takes it out of its group without a report; a
|
|
84
|
-
* shutdown's is left to `reportDeclined`, as one stopped between steps resumes on the next start.
|
|
85
|
-
*/
|
|
86
68
|
export async function noteInterrupted(ports, event) {
|
|
87
69
|
if (event.data.reason === "shutdown")
|
|
88
70
|
return false;
|
|
@@ -99,10 +81,6 @@ export async function noteInterrupted(ports, event) {
|
|
|
99
81
|
await syncStanding(ports, entry.parentID);
|
|
100
82
|
return true;
|
|
101
83
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Forgets what a deleted session leaves (report state, groups) and, unless isolated or a parent on the roster, its
|
|
104
|
-
* entry. Returns the sessions forgotten.
|
|
105
|
-
*/
|
|
106
84
|
export async function noteDeleted(ports, event) {
|
|
107
85
|
const { sessionID } = event.data;
|
|
108
86
|
const children = childStore(ports.storage);
|
|
@@ -117,33 +95,23 @@ export async function noteDeleted(ports, event) {
|
|
|
117
95
|
await syncStanding(ports, parentID);
|
|
118
96
|
return forgotten;
|
|
119
97
|
}
|
|
120
|
-
/** A `Waits` asking `suppliers` one after another, cheapest first: the first that waits answers. */
|
|
121
98
|
export const waitsOn = (...suppliers) => async (ports, sessionID, since) => {
|
|
122
99
|
for (const supplier of suppliers)
|
|
123
100
|
if (await supplier(ports, sessionID, since))
|
|
124
101
|
return true;
|
|
125
102
|
return false;
|
|
126
103
|
};
|
|
127
|
-
/** A `Waits` asking `suppliers` at once, as cheap as each other: it waits when one does, and fails when one fails. */
|
|
128
|
-
export const together = (...suppliers) => async (ports, sessionID, since) => (await Promise.all(suppliers.map((supplier) => supplier(ports, sessionID, since)))).includes(true);
|
|
129
|
-
/**
|
|
130
|
-
* Tells the parent of a spawned session whose turn ended without reporting to it since its last
|
|
131
|
-
* prompt, unless it waits; finds the session by the reverse index alone. Returns the parents told.
|
|
132
|
-
*/
|
|
133
104
|
export async function reportSilent(ports, seen, event, outside) {
|
|
134
105
|
const { sessionID } = event.data;
|
|
135
106
|
const children = childStore(ports.storage);
|
|
136
107
|
const entry = await children.indexedEntry(sessionID);
|
|
137
|
-
// Claimed after the lookup, so other sessions' turns do not crowd `seen`, and before anything is told.
|
|
138
108
|
if (!entry || !claim(seen, event.id))
|
|
139
109
|
return [];
|
|
140
110
|
const report = await owedUnwaited(ports, children, outside, sessionID);
|
|
141
111
|
if (!report)
|
|
142
112
|
return [];
|
|
143
|
-
// The notice goes out without the reply rather than not at all.
|
|
144
113
|
const lastText = await ports.session.context({ sessionID }).then(lastReply, () => undefined);
|
|
145
114
|
const group = (await outIn(ports, entry))?.group;
|
|
146
|
-
// Before the notice, which may end the parent's turn at once: once told, it no longer waits for this one.
|
|
147
115
|
const toldAt = event.created ?? ports.now();
|
|
148
116
|
await children.told(sessionID, toldAt).catch((error) => ports.log(`courier watch: could not note that the parent of ${sessionID} was told: ${String(error)}`));
|
|
149
117
|
try {
|
|
@@ -156,17 +124,13 @@ export async function reportSilent(ports, seen, event, outside) {
|
|
|
156
124
|
});
|
|
157
125
|
}
|
|
158
126
|
catch (error) {
|
|
159
|
-
// Not told after all: the parent still waits for it.
|
|
160
127
|
await children.untold(sessionID, toldAt).catch(() => undefined);
|
|
161
128
|
throw error;
|
|
162
129
|
}
|
|
163
130
|
await syncStanding(ports, entry.parentID);
|
|
164
131
|
return [entry.parentID];
|
|
165
132
|
}
|
|
166
|
-
|
|
167
|
-
* A session's turn that OpenCode reports as a shutdown ended in a plain decline, if its transcript ends on an aborted step: its
|
|
168
|
-
* parent is told as for a turn that succeeded without a report. A real shutdown during a step reads the same, and is told too.
|
|
169
|
-
*/
|
|
133
|
+
// a plain decline ends as a shutdown, on an aborted step (api-notes)
|
|
170
134
|
export async function reportDeclined(ports, seen, event, outside) {
|
|
171
135
|
if (event.data.reason !== "shutdown" || (await childStore(ports.storage).indexedParent(event.data.sessionID)) === undefined)
|
|
172
136
|
return [];
|
|
@@ -175,7 +139,6 @@ export async function reportDeclined(ports, seen, event, outside) {
|
|
|
175
139
|
return reportSilent(ports, seen, event, outside);
|
|
176
140
|
}
|
|
177
141
|
const TURN_ENDS = new Set(["session.execution.succeeded", "session.execution.failed", "session.execution.interrupted"]);
|
|
178
|
-
/** What `route` and `after` dispatch on; the wake watch needs `inbox.enqueued` and `execution.started` both. */
|
|
179
142
|
const TREE_TYPES = [
|
|
180
143
|
"session.execution.started",
|
|
181
144
|
"session.execution.succeeded",
|
|
@@ -186,8 +149,8 @@ const TREE_TYPES = [
|
|
|
186
149
|
"session.inbox.cancelled",
|
|
187
150
|
"session.deleted",
|
|
188
151
|
];
|
|
189
|
-
/** A session's turn ended, sealing its groups: one that is complete has the scheduler deliver it now; not a shutdown's, which stamps no idle time. */
|
|
190
152
|
async function sealGroups(ports, event) {
|
|
153
|
+
// a shutdown stamps no time.idle (api-notes)
|
|
191
154
|
if (event.data.reason === "shutdown")
|
|
192
155
|
return;
|
|
193
156
|
try {
|
|
@@ -198,7 +161,6 @@ async function sealGroups(ports, event) {
|
|
|
198
161
|
ports.log(`courier watch: could not read the groups of ${event.data.sessionID}: ${String(error)}`);
|
|
199
162
|
}
|
|
200
163
|
}
|
|
201
|
-
/** The events of spawned sessions' turns and inboxes, and their deletion: the tree's. */
|
|
202
164
|
function routeTree(ports, state, outside, event) {
|
|
203
165
|
if (event.type === "session.execution.failed")
|
|
204
166
|
return reportFailure(ports, state.seen, event);
|
|
@@ -216,7 +178,6 @@ function routeTree(ports, state, outside, event) {
|
|
|
216
178
|
return noteDeleted(ports, event);
|
|
217
179
|
return undefined;
|
|
218
180
|
}
|
|
219
|
-
/** The events that move a courier message waiting in a session's inbox: enqueued, taken, cancelled, and the turns around it. */
|
|
220
181
|
function routeWake(ports, event) {
|
|
221
182
|
if (event.type === "session.inbox.enqueued")
|
|
222
183
|
return watchWake(ports, event);
|
|
@@ -230,7 +191,6 @@ function routeWake(ports, event) {
|
|
|
230
191
|
return forgetWakes(ports, event.data.sessionID);
|
|
231
192
|
return undefined;
|
|
232
193
|
}
|
|
233
|
-
/** The tree's handling of an event alongside the wake watch's, which touches other keys and never fails the tree's. */
|
|
234
194
|
async function route(ports, state, outside, event) {
|
|
235
195
|
const watched = routeWake(ports, event)?.catch((error) => ports.log(`courier watch: wake of ${event.type}: ${String(error)}`));
|
|
236
196
|
try {
|
|
@@ -240,15 +200,10 @@ async function route(ports, state, outside, event) {
|
|
|
240
200
|
await watched;
|
|
241
201
|
}
|
|
242
202
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Tells parents of a child's failed or silent turn, unless it waits on what `outside` says, and notes
|
|
245
|
-
* its prompts, interruptions and deletion; `after` a turn's end, seals the groups of its session.
|
|
246
|
-
*/
|
|
247
203
|
export function treeEvents(state, outside) {
|
|
248
204
|
return {
|
|
249
205
|
types: TREE_TYPES,
|
|
250
206
|
handle: (ports, event) => route(ports, state, outside, event),
|
|
251
|
-
// After: a silent end is judged while the group the nudge may deliver is still there to wait on.
|
|
252
207
|
after: (ports, event) => (TURN_ENDS.has(event.type) ? sealGroups(ports, event) : undefined),
|
|
253
208
|
};
|
|
254
209
|
}
|
package/dist/tree/forget.d.ts
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import type { CleanupPorts, GroupStore, Membership, ReportKeys, RosterEntry, RosterStorage, WatchPorts } from "./ports.js";
|
|
2
|
-
/** Drops a member from its group as its roster entry goes, unless its report is held: that still goes to the parent. True when dropped. */
|
|
3
2
|
export declare function dropMember(groups: GroupStore, parentID: string, group: string, sessionID: string): Promise<boolean>;
|
|
4
|
-
/** Drops every group of a parent, whose members' reports can no longer be delivered. */
|
|
5
3
|
export declare function dropGroups(groups: GroupStore, parentID: string): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Forgets a child: its roster entry, its reverse key, its report state and, given its group, its membership. Only
|
|
8
|
-
* the entry's removal can fail it: keys left behind lead nowhere, and the next load drops them.
|
|
9
|
-
*/
|
|
10
4
|
export declare function forgetChild(storage: RosterStorage, parentID: string, sessionID: string, group?: string): Promise<{
|
|
11
5
|
droppedFromGroup: boolean;
|
|
12
6
|
}>;
|
|
13
|
-
/**
|
|
14
|
-
* Drops a member still out from its group as `dropped`, so the group is released without it, then the roster entry
|
|
15
|
-
* and report state. A held report stays with its group. Returns the group it was dropped from.
|
|
16
|
-
*/
|
|
17
7
|
export declare function forgetDropped(ports: Pick<CleanupPorts, "storage" | "now" | "nudge">, entry: RosterEntry): Promise<string | undefined>;
|
|
18
|
-
/**
|
|
19
|
-
* Forgets what a session OpenCode deleted leaves: the report state of `forgotten`, itself and the sessions it started,
|
|
20
|
-
* and its groups; `member`, its place in its own group, it leaves at `created`, or now.
|
|
21
|
-
*/
|
|
22
8
|
export declare function forgetDeleted(ports: WatchPorts, sessionID: string, forgotten: ReadonlyArray<string>, member: Membership | undefined, created?: number): Promise<void>;
|
|
23
|
-
/** Removes report keys of sessions with no roster entry; a key that will not go is left for the next load. */
|
|
24
9
|
export declare function forgetReports(storage: RosterStorage, keys: ReportKeys, sessions: ReadonlyArray<string>): Promise<void>;
|
package/dist/tree/forget.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { leave, markLeft, membershipFor } from "./group.js";
|
|
2
2
|
import { childStore, groupStore } from "./store.js";
|
|
3
|
-
// Forgetting a child and what it leaves behind. Its roster entry and report state go together; its group is another
|
|
4
|
-
// aggregate, left best effort and alongside, so a report held there outlives the entry and still reaches the parent.
|
|
5
|
-
/** Drops a member from its group as its roster entry goes, unless its report is held: that still goes to the parent. True when dropped. */
|
|
6
3
|
export async function dropMember(groups, parentID, group, sessionID) {
|
|
7
4
|
const member = await groups.member(parentID, group, sessionID);
|
|
8
5
|
if (!member || member.report)
|
|
@@ -10,15 +7,10 @@ export async function dropMember(groups, parentID, group, sessionID) {
|
|
|
10
7
|
await groups.remove(parentID, group, sessionID);
|
|
11
8
|
return true;
|
|
12
9
|
}
|
|
13
|
-
/** Drops every group of a parent, whose members' reports can no longer be delivered. */
|
|
14
10
|
export async function dropGroups(groups, parentID) {
|
|
15
11
|
const members = await groups.ofParent(parentID);
|
|
16
12
|
await Promise.all(members.map((member) => groups.remove(parentID, member.group, member.sessionID)));
|
|
17
13
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Forgets a child: its roster entry, its reverse key, its report state and, given its group, its membership. Only
|
|
20
|
-
* the entry's removal can fail it: keys left behind lead nowhere, and the next load drops them.
|
|
21
|
-
*/
|
|
22
14
|
export async function forgetChild(storage, parentID, sessionID, group) {
|
|
23
15
|
const [, dropped] = await Promise.all([
|
|
24
16
|
childStore(storage).remove(parentID, sessionID),
|
|
@@ -26,10 +18,6 @@ export async function forgetChild(storage, parentID, sessionID, group) {
|
|
|
26
18
|
]);
|
|
27
19
|
return { droppedFromGroup: dropped };
|
|
28
20
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Drops a member still out from its group as `dropped`, so the group is released without it, then the roster entry
|
|
31
|
-
* and report state. A held report stays with its group. Returns the group it was dropped from.
|
|
32
|
-
*/
|
|
33
21
|
export async function forgetDropped(ports, entry) {
|
|
34
22
|
const groups = groupStore(ports.storage);
|
|
35
23
|
const member = await membershipFor(groups, entry);
|
|
@@ -38,13 +26,8 @@ export async function forgetDropped(ports, entry) {
|
|
|
38
26
|
await forgetChild(ports.storage, entry.parentID, entry.sessionID);
|
|
39
27
|
return member?.out ? member.group : undefined;
|
|
40
28
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Forgets what a session OpenCode deleted leaves: the report state of `forgotten`, itself and the sessions it started,
|
|
43
|
-
* and its groups; `member`, its place in its own group, it leaves at `created`, or now.
|
|
44
|
-
*/
|
|
45
29
|
export async function forgetDeleted(ports, sessionID, forgotten, member, created) {
|
|
46
30
|
const children = childStore(ports.storage);
|
|
47
|
-
// Its groups go whether or not it has children on the roster: a held report outlives the member's entry.
|
|
48
31
|
const [completed] = await Promise.all([
|
|
49
32
|
member ? leave(ports, member, "deleted", created ?? ports.now()) : Promise.resolve(false),
|
|
50
33
|
...forgotten.map((id) => children.forgetReport(id)),
|
|
@@ -53,7 +36,6 @@ export async function forgetDeleted(ports, sessionID, forgotten, member, created
|
|
|
53
36
|
if (completed)
|
|
54
37
|
ports.nudge();
|
|
55
38
|
}
|
|
56
|
-
/** Removes report keys of sessions with no roster entry; a key that will not go is left for the next load. */
|
|
57
39
|
export async function forgetReports(storage, keys, sessions) {
|
|
58
40
|
await childStore(storage).dropReported(sessions.flatMap((sessionID) => keys.get(sessionID) ?? []));
|
|
59
41
|
}
|