opencode-courier 0.13.8 → 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 +7 -9
- 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 +4 -12
- 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 +5 -65
- 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/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
|
}
|
package/dist/tree/group.d.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import { type HeldReport, type LeftMember, type RosterEntry } from "./domain.js";
|
|
2
2
|
import type { GroupPorts, GroupStore, Membership, WatchPorts } from "./ports.js";
|
|
3
|
-
/** Makes a spawned session a member of its parent's group, opening the group if it was not. */
|
|
4
3
|
export declare function joinGroup(groups: GroupStore, parentID: string, group: string, sessionID: string, title: string, at: number): Promise<void>;
|
|
5
|
-
/** Whether a parent has a complete group with a report in it, which the scheduler delivers once the parent's turn has ended. */
|
|
6
4
|
export declare function hasCompleteGroup(groups: GroupStore, parentID: string): Promise<boolean>;
|
|
7
|
-
/** Holds a member's report with its group, in place of an earlier one, or of how it had left. */
|
|
8
5
|
export declare function holdReport(groups: GroupStore, membership: Membership, report: HeldReport): Promise<void>;
|
|
9
|
-
/** How many members of a group have reported and how many have not left, and whether the group is complete. */
|
|
10
6
|
export declare function groupStanding(groups: GroupStore, parentID: string, group: string): Promise<{
|
|
11
7
|
reported: number;
|
|
12
8
|
members: number;
|
|
13
9
|
complete: boolean;
|
|
14
10
|
}>;
|
|
15
|
-
/** `groupStanding` of each open group of a parent, by name, from one read of its members. */
|
|
16
11
|
export declare function groupStandings(groups: Pick<GroupStore, "ofParent">, parentID: string): Promise<{
|
|
17
12
|
reported: number;
|
|
18
13
|
members: number;
|
|
19
14
|
complete: boolean;
|
|
20
15
|
name: string;
|
|
21
16
|
}[]>;
|
|
22
|
-
/** A session's membership of a parent's group, if it is a member; `out` says whether the group still waits for its report. */
|
|
23
17
|
export declare function membershipOf(groups: Pick<GroupStore, "member">, parentID: string, group: string, sessionID: string): Promise<{
|
|
24
18
|
parentID: string;
|
|
25
19
|
group: string;
|
|
@@ -33,7 +27,6 @@ export declare function membershipOf(groups: Pick<GroupStore, "member">, parentI
|
|
|
33
27
|
readonly by: LeftMember["by"];
|
|
34
28
|
};
|
|
35
29
|
} | undefined>;
|
|
36
|
-
/** A child's membership of the group its roster entry names; none without one. A failed read is the caller's to handle. */
|
|
37
30
|
export declare function membershipFor(groups: Pick<GroupStore, "member">, entry: RosterEntry): Promise<{
|
|
38
31
|
parentID: string;
|
|
39
32
|
group: string;
|
|
@@ -47,14 +40,8 @@ export declare function membershipFor(groups: Pick<GroupStore, "member">, entry:
|
|
|
47
40
|
readonly by: LeftMember["by"];
|
|
48
41
|
};
|
|
49
42
|
} | undefined>;
|
|
50
|
-
/**
|
|
51
|
-
* Notes that a member left its group without a report: its turn failed or was interrupted, it was deleted or dropped. Read
|
|
52
|
-
* again first: one whose report is held meanwhile keeps it, one that left keeps why. True when that completes the group.
|
|
53
|
-
*/
|
|
54
43
|
export declare function markLeft(groups: GroupStore, membership: Membership, by: LeftMember["by"], at: number): Promise<boolean>;
|
|
55
|
-
/** `markLeft` for a member not read yet; undefined when it is in no open group. */
|
|
56
44
|
export declare function leaveGroup(groups: GroupStore, parentID: string, group: string, sessionID: string, by: LeftMember["by"], at: number): Promise<boolean | undefined>;
|
|
57
|
-
/** A child's membership of an open group it is still out in: the group waits for it, and its end takes it out without a report. */
|
|
58
45
|
export declare function outIn(ports: Pick<WatchPorts, "storage" | "log">, entry: RosterEntry): Promise<{
|
|
59
46
|
parentID: string;
|
|
60
47
|
group: string;
|
|
@@ -68,15 +55,6 @@ export declare function outIn(ports: Pick<WatchPorts, "storage" | "log">, entry:
|
|
|
68
55
|
readonly by: LeftMember["by"];
|
|
69
56
|
};
|
|
70
57
|
} | undefined>;
|
|
71
|
-
/** Takes a member out of its group without a report; true when that completes the group, for the caller to nudge the scheduler. */
|
|
72
58
|
export declare function leave(ports: Pick<WatchPorts, "storage" | "log">, member: Membership, by: "failed" | "interrupted" | "deleted", at: number): Promise<boolean>;
|
|
73
|
-
/**
|
|
74
|
-
* Puts a member whose report is held back among those out: its latest word to the parent was a blocked report. Read
|
|
75
|
-
* again first: a group released meanwhile is not opened again, and a report held since is kept.
|
|
76
|
-
*/
|
|
77
59
|
export declare function unholdForBlocked(groups: GroupStore, membership: Membership): Promise<void>;
|
|
78
|
-
/**
|
|
79
|
-
* Delivers every released group once, from the scheduler's tick: claimed in the shared `claimed`, re-read,
|
|
80
|
-
* delivered and only then dropped, so a failed delivery is tried again at the next tick, and a crash in between delivers it again.
|
|
81
|
-
*/
|
|
82
60
|
export declare function deliverReleased(ports: GroupPorts, claimed: Set<string>): Promise<void>;
|
package/dist/tree/group.js
CHANGED
|
@@ -4,11 +4,9 @@ import { complete, standingOf } from "./domain.js";
|
|
|
4
4
|
import { syncStanding } from "./standing.js";
|
|
5
5
|
import { groupStore } from "./store.js";
|
|
6
6
|
import { groupNotice } from "./text.js";
|
|
7
|
-
/** Makes a spawned session a member of its parent's group, opening the group if it was not. */
|
|
8
7
|
export async function joinGroup(groups, parentID, group, sessionID, title, at) {
|
|
9
8
|
await groups.put(parentID, group, sessionID, { title, joinedAt: at });
|
|
10
9
|
}
|
|
11
|
-
/** The members of each group among `members`, by the claim key of the group. */
|
|
12
10
|
function byGroup(members) {
|
|
13
11
|
const groups = new Map();
|
|
14
12
|
for (const member of members) {
|
|
@@ -21,45 +19,34 @@ function byGroup(members) {
|
|
|
21
19
|
}
|
|
22
20
|
return groups;
|
|
23
21
|
}
|
|
24
|
-
/** Whether a parent has a complete group with a report in it, which the scheduler delivers once the parent's turn has ended. */
|
|
25
22
|
export async function hasCompleteGroup(groups, parentID) {
|
|
26
23
|
return [...byGroup(await groups.ofParent(parentID)).values()].some((members) => complete(members) && members.some((member) => member.report));
|
|
27
24
|
}
|
|
28
|
-
/** Holds a member's report with its group, in place of an earlier one, or of how it had left. */
|
|
29
25
|
export async function holdReport(groups, membership, report) {
|
|
30
26
|
const { parentID, group, sessionID, title, joinedAt } = membership;
|
|
31
27
|
await groups.put(parentID, group, sessionID, { title, joinedAt, report });
|
|
32
28
|
}
|
|
33
|
-
/** How many of these members of a group have reported and how many have not left, and whether the group is complete. */
|
|
34
29
|
const tally = (members) => ({
|
|
35
30
|
reported: members.filter((member) => member.report).length,
|
|
36
31
|
members: members.filter((member) => !member.left).length,
|
|
37
32
|
complete: complete(members),
|
|
38
33
|
});
|
|
39
|
-
/** How many members of a group have reported and how many have not left, and whether the group is complete. */
|
|
40
34
|
export async function groupStanding(groups, parentID, group) {
|
|
41
35
|
return tally(await groups.ofGroup(parentID, group));
|
|
42
36
|
}
|
|
43
|
-
/** `groupStanding` of each open group of a parent, by name, from one read of its members. */
|
|
44
37
|
export async function groupStandings(groups, parentID) {
|
|
45
38
|
const byName = new Map();
|
|
46
39
|
for (const member of await groups.ofParent(parentID))
|
|
47
40
|
byName.set(member.group, [...(byName.get(member.group) ?? []), member]);
|
|
48
41
|
return [...byName].map(([name, members]) => ({ name, ...tally(members) }));
|
|
49
42
|
}
|
|
50
|
-
/** A session's membership of a parent's group, if it is a member; `out` says whether the group still waits for its report. */
|
|
51
43
|
export async function membershipOf(groups, parentID, group, sessionID) {
|
|
52
44
|
const member = await groups.member(parentID, group, sessionID);
|
|
53
45
|
return member && { ...member, parentID, group, sessionID, out: standingOf(member) === "out" };
|
|
54
46
|
}
|
|
55
|
-
/** A child's membership of the group its roster entry names; none without one. A failed read is the caller's to handle. */
|
|
56
47
|
export async function membershipFor(groups, entry) {
|
|
57
48
|
return entry.group ? membershipOf(groups, entry.parentID, entry.group, entry.sessionID) : undefined;
|
|
58
49
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Notes that a member left its group without a report: its turn failed or was interrupted, it was deleted or dropped. Read
|
|
61
|
-
* again first: one whose report is held meanwhile keeps it, one that left keeps why. True when that completes the group.
|
|
62
|
-
*/
|
|
63
50
|
export async function markLeft(groups, membership, by, at) {
|
|
64
51
|
const { parentID, group, sessionID } = membership;
|
|
65
52
|
const current = await groups.member(parentID, group, sessionID);
|
|
@@ -68,12 +55,10 @@ export async function markLeft(groups, membership, by, at) {
|
|
|
68
55
|
await groups.put(parentID, group, sessionID, { title: current.title, joinedAt: current.joinedAt, left: { at, by } });
|
|
69
56
|
return (await groupStanding(groups, parentID, group)).complete;
|
|
70
57
|
}
|
|
71
|
-
/** `markLeft` for a member not read yet; undefined when it is in no open group. */
|
|
72
58
|
export async function leaveGroup(groups, parentID, group, sessionID, by, at) {
|
|
73
59
|
const membership = await membershipOf(groups, parentID, group, sessionID);
|
|
74
60
|
return membership && (await markLeft(groups, membership, by, at));
|
|
75
61
|
}
|
|
76
|
-
/** A child's membership of an open group it is still out in: the group waits for it, and its end takes it out without a report. */
|
|
77
62
|
export async function outIn(ports, entry) {
|
|
78
63
|
try {
|
|
79
64
|
const member = await membershipFor(groupStore(ports.storage), entry);
|
|
@@ -84,7 +69,6 @@ export async function outIn(ports, entry) {
|
|
|
84
69
|
return undefined;
|
|
85
70
|
}
|
|
86
71
|
}
|
|
87
|
-
/** Takes a member out of its group without a report; true when that completes the group, for the caller to nudge the scheduler. */
|
|
88
72
|
export async function leave(ports, member, by, at) {
|
|
89
73
|
try {
|
|
90
74
|
return await markLeft(groupStore(ports.storage), member, by, at);
|
|
@@ -94,10 +78,6 @@ export async function leave(ports, member, by, at) {
|
|
|
94
78
|
return false;
|
|
95
79
|
}
|
|
96
80
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Puts a member whose report is held back among those out: its latest word to the parent was a blocked report. Read
|
|
99
|
-
* again first: a group released meanwhile is not opened again, and a report held since is kept.
|
|
100
|
-
*/
|
|
101
81
|
export async function unholdForBlocked(groups, membership) {
|
|
102
82
|
const { parentID, group, sessionID, title, joinedAt } = membership;
|
|
103
83
|
if (!membership.report)
|
|
@@ -107,15 +87,9 @@ export async function unholdForBlocked(groups, membership) {
|
|
|
107
87
|
return;
|
|
108
88
|
await groups.put(parentID, group, sessionID, { title, joinedAt });
|
|
109
89
|
}
|
|
110
|
-
/** The claim key of a group's release, in the hub's shared set of deliveries under way. */
|
|
111
90
|
const claimKey = (parentID, group) => `group:${parentID}/${group}`;
|
|
112
|
-
/**
|
|
113
|
-
* Delivers every released group once, from the scheduler's tick: claimed in the shared `claimed`, re-read,
|
|
114
|
-
* delivered and only then dropped, so a failed delivery is tried again at the next tick, and a crash in between delivers it again.
|
|
115
|
-
*/
|
|
116
91
|
export async function deliverReleased(ports, claimed) {
|
|
117
92
|
const groups = [...byGroup(await groupStore(ports.storage).all())].filter(([key, members]) => complete(members) && !claimed.has(key));
|
|
118
|
-
// Claimed all at once, before any delivery, so a tick alongside claims none of these.
|
|
119
93
|
for (const [key] of groups)
|
|
120
94
|
claimed.add(key);
|
|
121
95
|
await Promise.all(groups.map(([key, members]) => releaseClaimed(ports, claimed, key, members[0])));
|
|
@@ -131,15 +105,11 @@ async function releaseClaimed(ports, claimed, key, { parentID, group }) {
|
|
|
131
105
|
claimed.delete(key);
|
|
132
106
|
}
|
|
133
107
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Whether the parent's turn that started a group's last member has ended, so no more join it; `gone` when OpenCode no longer knows the
|
|
136
|
-
* parent. Only a turn ending stamps `time.idle`: a shutdown, or a plain permission decline reported as one, holds it until the next ends.
|
|
137
|
-
*/
|
|
138
108
|
async function sealed(ports, parentID, members) {
|
|
139
109
|
const joined = Math.max(...members.map((member) => member.joinedAt));
|
|
140
110
|
try {
|
|
141
111
|
const { time } = await ports.session.get({ sessionID: parentID });
|
|
142
|
-
//
|
|
112
|
+
// only a turn's end stamps time.idle, not a shutdown (api-notes)
|
|
143
113
|
return millis(time.idle) >= joined;
|
|
144
114
|
}
|
|
145
115
|
catch (error) {
|
|
@@ -149,7 +119,6 @@ async function sealed(ports, parentID, members) {
|
|
|
149
119
|
}
|
|
150
120
|
}
|
|
151
121
|
async function release(ports, parentID, group) {
|
|
152
|
-
// Re-read: another server may have delivered and dropped it since the scan.
|
|
153
122
|
const members = (await groupStore(ports.storage).ofGroup(parentID, group)).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
154
123
|
if (!members.length || !complete(members))
|
|
155
124
|
return;
|
|
@@ -174,7 +143,6 @@ async function release(ports, parentID, group) {
|
|
|
174
143
|
});
|
|
175
144
|
}
|
|
176
145
|
catch (error) {
|
|
177
|
-
// A parent OpenCode no longer knows will never take it: dropped, like the children of a deleted session.
|
|
178
146
|
if (!isNotFound(error)) {
|
|
179
147
|
ports.log(`courier group ${group} of ${parentID} not delivered, held for another try: ${String(error)}`);
|
|
180
148
|
return;
|
|
@@ -183,21 +151,17 @@ async function release(ports, parentID, group) {
|
|
|
183
151
|
}
|
|
184
152
|
}
|
|
185
153
|
await drop(ports, parentID, group, members);
|
|
186
|
-
// Delivered: the members read as released in the parent's standing, which its next step shows.
|
|
187
154
|
await syncStanding(ports, parentID);
|
|
188
155
|
}
|
|
189
|
-
/** Drops a group's members once delivered, or unsent, saying why. */
|
|
190
156
|
async function drop(ports, parentID, group, members, unsent) {
|
|
191
157
|
if (unsent)
|
|
192
158
|
ports.log(`courier group ${group} of ${parentID} dropped: ${unsent}`);
|
|
193
|
-
// A member left behind here is delivered again at the next tick, as after a crash: logged, so it can be seen.
|
|
194
159
|
const groups = groupStore(ports.storage);
|
|
195
160
|
const dropped = await Promise.allSettled(members.map((member) => dropDelivered(groups, member)));
|
|
196
161
|
for (const [index, result] of dropped.entries())
|
|
197
162
|
if (result.status === "rejected")
|
|
198
163
|
ports.log(`courier group ${group} of ${parentID}: delivered, but ${members[index].sessionID} could not be dropped: ${String(result.reason)}`);
|
|
199
164
|
}
|
|
200
|
-
/** Drops a delivered member's key, unless it was written again since it was read: that report is still to deliver. */
|
|
201
165
|
async function dropDelivered(groups, delivered) {
|
|
202
166
|
const { parentID, group, sessionID, ...read } = delivered;
|
|
203
167
|
if (JSON.stringify(await groups.member(parentID, group, sessionID)) !== JSON.stringify(read))
|
package/dist/tree/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { dirtyOf, headOf, inspectWorktree } from "./cleanup.js";
|
|
2
2
|
export { shapeContext } from "./context.js";
|
|
3
3
|
export { endsIdle, lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
4
|
-
export {
|
|
4
|
+
export { treeEvents, waitsOn } from "./events.js";
|
|
5
5
|
export { deliverReleased } from "./group.js";
|
|
6
6
|
export { onIdle } from "./nudge.js";
|
|
7
7
|
export { contextOf } from "./panel.js";
|
|
8
|
-
export type { CleanupPorts, CourierPorts, Entries, Entry, Instructions, ModelWindow, Pending, PendingPrompt, Role, RosterEntry, RosterStorage, ShownForm, SpawnGate, Waits, WatchPorts, WatchState, } from "./ports.js";
|
|
8
|
+
export type { CleanupPorts, CourierPorts, Entries, Entry, Instructions, Lister, ModelWindow, Pending, PendingPrompt, Role, RosterEntry, RosterStorage, ShownForm, SpawnGate, Waits, WatchPorts, WatchState, } from "./ports.js";
|
|
9
9
|
export { allEntries, answeringTop, childrenOf, indexedLineage, lineage, pruneExpired, reportState, RETENTION_MS, type ReportState } from "./roster.js";
|
|
10
10
|
export { runs, turnIn, turnOf, type Turn } from "./running.js";
|
|
11
11
|
export { subtree, type Below } from "./subtree.js";
|
package/dist/tree/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
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`, the lineage and subtree queries and types `person/` reads, and what `memory/` reads of each session.
|
|
3
1
|
export { dirtyOf, headOf, inspectWorktree } from "./cleanup.js";
|
|
4
2
|
export { shapeContext } from "./context.js";
|
|
5
3
|
export { endsIdle, lineageIn, nativeRootOf, readLimits, readNativeChildren } from "./domain.js";
|
|
6
|
-
export {
|
|
4
|
+
export { treeEvents, waitsOn } from "./events.js";
|
|
7
5
|
export { deliverReleased } from "./group.js";
|
|
8
6
|
export { onIdle } from "./nudge.js";
|
|
9
7
|
export { contextOf } from "./panel.js";
|
package/dist/tree/limits.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type { LimitPorts, Reservation } from "./ports.js";
|
|
2
|
-
/**
|
|
3
|
-
* Checks the limits for a spawn from `parentID`, then holds its place in `gate` until `release`;
|
|
4
|
-
* throws a refusal naming the limit. Checks run one at a time, so spawns made together count each other.
|
|
5
|
-
*/
|
|
6
2
|
export declare function admit(ports: LimitPorts, parentID: string): Promise<{
|
|
7
3
|
depth: number;
|
|
8
4
|
reservation: Reservation;
|
package/dist/tree/limits.js
CHANGED
|
@@ -5,10 +5,6 @@ import { runs } from "./running.js";
|
|
|
5
5
|
import { childStore } from "./store.js";
|
|
6
6
|
import { childrenRefusal, depthRefusal, totalRefusal } from "./text.js";
|
|
7
7
|
import { waitsInInbox } from "./wake.js";
|
|
8
|
-
/**
|
|
9
|
-
* Whether a spawned session counts against the limits: it runs (its turn not failed or interrupted since its last prompt), owes
|
|
10
|
-
* its parent a report, or has a courier message waiting in its inbox that it is to be woken for. One OpenCode no longer knows does not.
|
|
11
|
-
*/
|
|
12
8
|
async function live(ports, sessionID) {
|
|
13
9
|
const [time, report] = await Promise.all([
|
|
14
10
|
ports.session.get({ sessionID }).then((info) => info.time, (error) => (isNotFound(error) ? null : undefined)),
|
|
@@ -16,15 +12,10 @@ async function live(ports, sessionID) {
|
|
|
16
12
|
]);
|
|
17
13
|
if (time === null)
|
|
18
14
|
return false;
|
|
19
|
-
// One whose first prompt failed never ran, and has no idle time to tell so.
|
|
20
15
|
const running = time !== undefined && !report?.ended && (await runs(ports.session, sessionID, time));
|
|
21
|
-
//
|
|
16
|
+
// a waiting inbox item is in no transcript until delivered (api-notes)
|
|
22
17
|
return running || (report?.owes ?? false) || (await waitsInInbox(ports, sessionID, { stalled: false }).catch(() => true));
|
|
23
18
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Checks the limits for a spawn from `parentID`, then holds its place in `gate` until `release`;
|
|
26
|
-
* throws a refusal naming the limit. Checks run one at a time, so spawns made together count each other.
|
|
27
|
-
*/
|
|
28
19
|
export async function admit(ports, parentID) {
|
|
29
20
|
const gate = ports.gate;
|
|
30
21
|
const checked = gate.turn.then(() => check(ports, parentID));
|
|
@@ -46,10 +37,8 @@ async function check(ports, parentID) {
|
|
|
46
37
|
const chains = new Map(entries.map((entry) => [entry.sessionID, lineageBy(parents, entry.sessionID)]));
|
|
47
38
|
const tree = entries.filter((entry) => chains.get(entry.sessionID).at(-1)?.parentID === top);
|
|
48
39
|
const counting = await Promise.all(tree.map((entry) => live(ports, entry.sessionID)));
|
|
49
|
-
// A session waiting on a live descendant is live too: the brief tells it to end its turn meanwhile.
|
|
50
40
|
const active = new Set(tree.filter((_, i) => counting[i]).flatMap((entry) => [entry.sessionID, ...chains.get(entry.sessionID).map((above) => above.parentID)]));
|
|
51
41
|
const counted = tree.filter((entry) => active.has(entry.sessionID));
|
|
52
|
-
// A spawn whose child is on the roster already is counted there, if it is live, not by its reservation.
|
|
53
42
|
const recorded = new Set(tree.map((entry) => entry.sessionID));
|
|
54
43
|
const pending = [...ports.gate.reserved].filter((held) => held.top === top && !(held.sessionID && recorded.has(held.sessionID)));
|
|
55
44
|
const mine = counted.filter((entry) => entry.parentID === parentID).length + pending.filter((held) => held.parentID === parentID).length;
|
package/dist/tree/mascot.d.ts
CHANGED
|
@@ -1,37 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A pixel of Digby, the tree panel's mole, as the theme colour it takes: `b` body, `k` background (eyes), `g` mound,
|
|
3
|
-
* `m` muted (paws, dirt, post, arm), `a` accent (snout, letter), `w`, `e` and `s` warning, error and success (sign, dots, spark).
|
|
4
|
-
*/
|
|
5
1
|
export type Token = "b" | "k" | "g" | "m" | "a" | "w" | "e" | "s";
|
|
6
|
-
/** What Digby shows: the tree's state, or for a while after one arrives, a spawn or a report. */
|
|
7
2
|
export type MascotState = "idle" | "spawn" | "running" | "waiting" | "stalled" | "report" | "gone";
|
|
8
|
-
/** One cell of a drawing: `▀` the top pixel's colour over the bottom's, `▄` only a bottom pixel, `█` both alike, or a character. */
|
|
9
3
|
export type Cell = {
|
|
10
4
|
readonly char: string;
|
|
11
5
|
readonly fg?: Token;
|
|
12
6
|
readonly bg?: Token;
|
|
13
7
|
};
|
|
14
|
-
/** Digby's frames take one panel frame in this many, about every 450 ms. */
|
|
15
8
|
export declare const MASCOT_EVERY = 5;
|
|
16
|
-
/** He blinks on every this-many-th of his frames where his eyes are open. */
|
|
17
9
|
export declare const BLINK_EVERY = 11;
|
|
18
|
-
/** Running, he is still and awake for this many of his frames, about 8 s, then digs for one cycle of six. */
|
|
19
10
|
export declare const DIG_EVERY = 18;
|
|
20
|
-
/**
|
|
21
|
-
* The 8×4 glyph's frames by state, pixel rows top to bottom; a state with several cycles through them, one per mascot frame.
|
|
22
|
-
* No muted pixel touches the mound, here or in the 16×16, as `text.muted` is close to `background.raised.max`.
|
|
23
|
-
*/
|
|
24
11
|
export declare const GLYPH: Readonly<Record<MascotState, ReadonlyArray<ReadonlyArray<string>>>>;
|
|
25
|
-
/** The 8×4 glyph's pixels for `state` at mascot frame `frame`, its first frame with none: animations off. */
|
|
26
12
|
export declare function glyph(state: MascotState, frame?: number): ReadonlyArray<string>;
|
|
27
|
-
/** Pixel rows, two to a line of cells, by the half-block rule; an odd last row has nothing under it. */
|
|
28
13
|
export declare function cells(pixels: ReadonlyArray<string>): ReadonlyArray<ReadonlyArray<Cell>>;
|
|
29
|
-
/** How his eyes are drawn: one pixel each, `wide` two tall, `shut` two wide, or `none` (blinking). */
|
|
30
14
|
export type Eyes = "open" | "wide" | "shut" | "none";
|
|
31
|
-
/**
|
|
32
|
-
* The 16×16 Digby, his head raised by `rise` (0 in the hole, 1 fully up; below 0 out of sight) over a body in the hole,
|
|
33
|
-
* then the mound (its top row `top` wide, `shift` columns over) and the ground, both `mound`, drawn over it but for the hole.
|
|
34
|
-
*/
|
|
35
15
|
export declare function digby(rise: number, { top, eyes, slump, shift, mound }?: {
|
|
36
16
|
top?: number;
|
|
37
17
|
eyes?: Eyes;
|
|
@@ -39,14 +19,9 @@ export declare function digby(rise: number, { top, eyes, slump, shift, mound }?:
|
|
|
39
19
|
shift?: number;
|
|
40
20
|
mound?: "g" | "m";
|
|
41
21
|
}): ReadonlyArray<string>;
|
|
42
|
-
/** A spot of `pixels` with its first at row `row`, column `column`; `.` leaves what is under it. */
|
|
43
22
|
type Spot = readonly [row: number, column: number, pixels: string];
|
|
44
|
-
/** `pixels` with `spots` drawn over them. */
|
|
45
23
|
export declare function paint(pixels: ReadonlyArray<string>, spots: ReadonlyArray<Spot>): ReadonlyArray<string>;
|
|
46
|
-
/** The 16×16's frames by state but idle, each its pixels with the eyes open or, for a blink, not; the first reads alone. */
|
|
47
24
|
export declare const ART: Readonly<Record<Exclude<MascotState, "idle">, ReadonlyArray<(open: boolean) => ReadonlyArray<string>>>>;
|
|
48
|
-
/** The 16×16 idle Digby at mascot frame `frame`, its first with none: the mound breathing, a `z` drifting up beside it. */
|
|
49
25
|
export declare function idleArt(frame?: number): ReadonlyArray<ReadonlyArray<Cell>>;
|
|
50
|
-
/** The 16×16 Digby for `state` at mascot frame `frame`, its first frame with none: animations off. */
|
|
51
26
|
export declare function art(state: MascotState, frame?: number): ReadonlyArray<ReadonlyArray<Cell>>;
|
|
52
27
|
export {};
|