opencode-courier 0.13.8 → 0.15.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 +7 -54
- package/dist/kernel/protocol.d.ts +3 -17
- package/dist/kernel/protocol.js +3 -17
- 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 +1 -9
- package/dist/memory/lessons.js +2 -11
- package/dist/memory/note.d.ts +2 -11
- package/dist/memory/note.js +5 -11
- 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 +1 -26
- package/dist/memory/text.js +8 -38
- package/dist/memory/tools.d.ts +0 -2
- package/dist/memory/tools.js +4 -6
- 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 +3 -4
- 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 +6 -8
- package/dist/skill.d.ts +1 -9
- package/dist/skill.js +40 -97
- 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 +17 -13
- package/dist/tree/domain.d.ts +0 -64
- package/dist/tree/domain.js +7 -45
- package/dist/tree/events.d.ts +5 -36
- package/dist/tree/events.js +23 -55
- 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 +6 -39
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.js +1 -3
- package/dist/tree/limits.d.ts +1 -4
- package/dist/tree/limits.js +9 -13
- 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 +5 -139
- package/dist/tree/report.d.ts +1 -10
- package/dist/tree/report.js +4 -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 +1 -7
- package/dist/tree/spawn.js +15 -56
- 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 +4 -46
- package/dist/tree/text.js +44 -83
- package/dist/tree/tools.d.ts +0 -6
- package/dist/tree/tools.js +15 -15
- 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 +24 -10
- 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/group.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { isNotFound, millis } from "../platform/json.js";
|
|
2
2
|
import { envelope } from "../kernel/protocol.js";
|
|
3
|
-
import { complete, standingOf } from "./domain.js";
|
|
3
|
+
import { complete, endedAt, standingOf, turnEnd } 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,16 +105,15 @@ 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
|
-
|
|
143
|
-
|
|
112
|
+
if (millis(time.idle) >= joined)
|
|
113
|
+
return true;
|
|
114
|
+
// a plain decline ends the turn on an aborted step, no idle (api-notes)
|
|
115
|
+
const end = turnEnd(await ports.session.context({ sessionID: parentID }).catch(() => []));
|
|
116
|
+
return end !== undefined && (endedAt(end, time) ?? Number.NEGATIVE_INFINITY) >= joined;
|
|
144
117
|
}
|
|
145
118
|
catch (error) {
|
|
146
119
|
if (isNotFound(error))
|
|
@@ -149,7 +122,6 @@ async function sealed(ports, parentID, members) {
|
|
|
149
122
|
}
|
|
150
123
|
}
|
|
151
124
|
async function release(ports, parentID, group) {
|
|
152
|
-
// Re-read: another server may have delivered and dropped it since the scan.
|
|
153
125
|
const members = (await groupStore(ports.storage).ofGroup(parentID, group)).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
154
126
|
if (!members.length || !complete(members))
|
|
155
127
|
return;
|
|
@@ -174,7 +146,6 @@ async function release(ports, parentID, group) {
|
|
|
174
146
|
});
|
|
175
147
|
}
|
|
176
148
|
catch (error) {
|
|
177
|
-
// A parent OpenCode no longer knows will never take it: dropped, like the children of a deleted session.
|
|
178
149
|
if (!isNotFound(error)) {
|
|
179
150
|
ports.log(`courier group ${group} of ${parentID} not delivered, held for another try: ${String(error)}`);
|
|
180
151
|
return;
|
|
@@ -183,21 +154,17 @@ async function release(ports, parentID, group) {
|
|
|
183
154
|
}
|
|
184
155
|
}
|
|
185
156
|
await drop(ports, parentID, group, members);
|
|
186
|
-
// Delivered: the members read as released in the parent's standing, which its next step shows.
|
|
187
157
|
await syncStanding(ports, parentID);
|
|
188
158
|
}
|
|
189
|
-
/** Drops a group's members once delivered, or unsent, saying why. */
|
|
190
159
|
async function drop(ports, parentID, group, members, unsent) {
|
|
191
160
|
if (unsent)
|
|
192
161
|
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
162
|
const groups = groupStore(ports.storage);
|
|
195
163
|
const dropped = await Promise.allSettled(members.map((member) => dropDelivered(groups, member)));
|
|
196
164
|
for (const [index, result] of dropped.entries())
|
|
197
165
|
if (result.status === "rejected")
|
|
198
166
|
ports.log(`courier group ${group} of ${parentID}: delivered, but ${members[index].sessionID} could not be dropped: ${String(result.reason)}`);
|
|
199
167
|
}
|
|
200
|
-
/** Drops a delivered member's key, unless it was written again since it was read: that report is still to deliver. */
|
|
201
168
|
async function dropDelivered(groups, delivered) {
|
|
202
169
|
const { parentID, group, sessionID, ...read } = delivered;
|
|
203
170
|
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,10 +1,7 @@
|
|
|
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;
|
|
9
5
|
release: () => void;
|
|
10
6
|
}>;
|
|
7
|
+
export declare function liveBelow(ports: Pick<LimitPorts, "session" | "storage">, sessionID: string): Promise<import("./domain.js").RosterEntry[]>;
|
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,18 +37,23 @@ 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;
|
|
56
45
|
if (mine >= maxChildren)
|
|
57
46
|
throw new Error(childrenRefusal(mine, maxChildren));
|
|
58
47
|
if (counted.length + pending.length >= maxTotal)
|
|
59
|
-
throw new Error(totalRefusal(counted.length + pending.length, maxTotal));
|
|
48
|
+
throw new Error(totalRefusal(counted.length + pending.length, maxTotal, mine));
|
|
60
49
|
const reservation = { parentID, top };
|
|
61
50
|
ports.gate.reserved.add(reservation);
|
|
62
51
|
return { depth: chain.length + 1, reservation };
|
|
63
52
|
}
|
|
53
|
+
export async function liveBelow(ports, sessionID) {
|
|
54
|
+
const entries = await childStore(ports.storage).all();
|
|
55
|
+
const parents = bySession(entries);
|
|
56
|
+
const below = [...parents.values()].filter((entry) => entry.sessionID !== sessionID && lineageBy(parents, entry.sessionID).some((above) => above.parentID === sessionID));
|
|
57
|
+
const counting = await Promise.all(below.map((entry) => live(ports, entry.sessionID)));
|
|
58
|
+
return below.filter((_, i) => counting[i]);
|
|
59
|
+
}
|
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 {};
|
package/dist/tree/mascot.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
/** Digby's frames take one panel frame in this many, about every 450 ms. */
|
|
2
1
|
export const MASCOT_EVERY = 5;
|
|
3
|
-
/** He blinks on every this-many-th of his frames where his eyes are open. */
|
|
4
2
|
export const BLINK_EVERY = 11;
|
|
5
|
-
/** Running, he is still and awake for this many of his frames, about 8 s, then digs for one cycle of six. */
|
|
6
3
|
export const DIG_EVERY = 18;
|
|
7
|
-
|
|
8
|
-
* The 8×4 glyph's frames by state, pixel rows top to bottom; a state with several cycles through them, one per mascot frame.
|
|
9
|
-
* No muted pixel touches the mound, here or in the 16×16, as `text.muted` is close to `background.raised.max`.
|
|
10
|
-
*/
|
|
4
|
+
// text.muted is close to background.raised.max: no muted pixel on the mound
|
|
11
5
|
export const GLYPH = {
|
|
12
6
|
idle: [["........", "........", "..gggg..", "gggggggg"]],
|
|
13
7
|
spawn: [
|
|
@@ -33,9 +27,7 @@ export const GLYPH = {
|
|
|
33
27
|
],
|
|
34
28
|
gone: [["........", ".mmmmmm.", ".mkmmkm.", "mmmmmmmm"]],
|
|
35
29
|
};
|
|
36
|
-
/** The states in which his eyes are open, so he blinks. */
|
|
37
30
|
const OPEN_EYES = new Set(["waiting", "report", "running"]);
|
|
38
|
-
/** The 8×4 glyph's pixels for `state` at mascot frame `frame`, its first frame with none: animations off. */
|
|
39
31
|
export function glyph(state, frame) {
|
|
40
32
|
const frames = GLYPH[state];
|
|
41
33
|
const pixels = frames[(frame ?? 0) % frames.length];
|
|
@@ -45,10 +37,8 @@ export function glyph(state, frame) {
|
|
|
45
37
|
return [...pixels.slice(0, 2), ".gggggg.", ...pixels.slice(3)];
|
|
46
38
|
return OPEN_EYES.has(state) && blinks(frame) ? pixels.map((row) => row.replaceAll("k", "b")) : pixels;
|
|
47
39
|
}
|
|
48
|
-
/** The mound's top row widens for two frames in four. */
|
|
49
40
|
const breathesOut = (frame) => Math.floor(frame / 2) % 2 === 1;
|
|
50
41
|
const blinks = (frame) => frame % BLINK_EVERY === BLINK_EVERY - 1;
|
|
51
|
-
/** Pixel rows, two to a line of cells, by the half-block rule; an odd last row has nothing under it. */
|
|
52
42
|
export function cells(pixels) {
|
|
53
43
|
const lines = [];
|
|
54
44
|
for (let row = 0; row < pixels.length; row += 2) {
|
|
@@ -68,14 +58,8 @@ function cell(top, bottom) {
|
|
|
68
58
|
return { char: "█", fg: top };
|
|
69
59
|
return bottom ? { char: "▀", fg: top, bg: bottom } : { char: "▀", fg: top };
|
|
70
60
|
}
|
|
71
|
-
/** The 16×16's size, in pixels. */
|
|
72
61
|
const SIZE = 16;
|
|
73
|
-
/** How far each of the head's rows leans right when he slumps. */
|
|
74
62
|
const SLUMP = [2, 2, 2, 1, 1, 0, 0];
|
|
75
|
-
/**
|
|
76
|
-
* 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,
|
|
77
|
-
* then the mound (its top row `top` wide, `shift` columns over) and the ground, both `mound`, drawn over it but for the hole.
|
|
78
|
-
*/
|
|
79
63
|
export function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0, mound = "g" } = {}) {
|
|
80
64
|
const grid = Array.from({ length: SIZE }, () => new Array(SIZE).fill("."));
|
|
81
65
|
const span = (row, width, pixel, skip = (_column) => false, by = 0) => {
|
|
@@ -104,7 +88,6 @@ export function digby(rise, { top = 6, eyes = "open", slump = false, shift = 0,
|
|
|
104
88
|
span(row, SIZE, mound);
|
|
105
89
|
return grid.map((row) => row.join(""));
|
|
106
90
|
}
|
|
107
|
-
/** `pixels` with `spots` drawn over them. */
|
|
108
91
|
export function paint(pixels, spots) {
|
|
109
92
|
const grid = pixels.map((row) => [...row]);
|
|
110
93
|
for (const [row, column, drawn] of spots)
|
|
@@ -114,42 +97,32 @@ export function paint(pixels, spots) {
|
|
|
114
97
|
});
|
|
115
98
|
return grid.map((row) => row.join(""));
|
|
116
99
|
}
|
|
117
|
-
/** A column of `length` pixels of `pixel` from row `row`. */
|
|
118
100
|
const post = (row, column, length, pixel) => Array.from({ length }, (_, at) => [row + at, column, pixel]);
|
|
119
|
-
/** The head's top row at `rise`. */
|
|
120
101
|
const headTop = (rise) => Math.round(10 - 8 * rise);
|
|
121
|
-
/** Up and awake at `rise`, his eyes `shift` columns over (0 straight ahead) when `open`. */
|
|
122
102
|
const awake = (rise, shift) => (open) => {
|
|
123
103
|
const row = headTop(rise) + 3;
|
|
124
104
|
return paint(digby(rise, { eyes: "open" }), [[row, 6, "b"], [row, 9, "b"], ...(open ? [[row, 6 + shift, "k"], [row, 9 + shift, "k"]] : [])]);
|
|
125
105
|
};
|
|
126
|
-
/** Where a clod flies on each side, frame by frame: out of the hole, up, over and down, a row clear of the mound's rim. */
|
|
127
106
|
const CLOD_LEFT = [[8, 3], [7, 2], [6, 1], [5, 0], [7, 0], [9, 0]];
|
|
128
107
|
const CLOD_RIGHT = [[8, 12], [7, 13], [6, 14], [5, 15], [7, 15], [9, 15]];
|
|
129
108
|
const CLODS = [[CLOD_LEFT, 0], [CLOD_LEFT, 3], [CLOD_RIGHT, 1], [CLOD_RIGHT, 4]];
|
|
130
|
-
/** Digging at frame `at`: up with his eyes open, the mound still, four clods on their arcs, one always in the air. */
|
|
131
109
|
const digging = (at) => (open) => paint(digby(0.6, { eyes: open ? "open" : "none" }), CLODS.map(([path, phase]) => {
|
|
132
110
|
const [row, column] = path[(at + phase) % path.length];
|
|
133
111
|
return [row, column, "m"];
|
|
134
112
|
}));
|
|
135
|
-
/** Running: still and awake, a glance left then right over twelve frames, then one dig cycle; the mound never moves. */
|
|
136
113
|
const running = [
|
|
137
114
|
...Array.from({ length: DIG_EVERY }, (_, at) => [0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 1, 1][at % 12]).map((shift) => awake(0.6, shift)),
|
|
138
115
|
...Array.from({ length: CLOD_LEFT.length }, (_, at) => digging(at)),
|
|
139
116
|
];
|
|
140
|
-
/** A child's head peeking out of a side mound at columns `column`–`column + 2`, `up` or half sunk. */
|
|
141
117
|
const peek = (column, up) => [
|
|
142
118
|
...(up ? [[8, column, ".b."], [9, column, "bkb"]] : [[9, column, ".b."]]),
|
|
143
119
|
[10, column, "gbg"],
|
|
144
120
|
[11, column, "ggg"],
|
|
145
121
|
];
|
|
146
122
|
const spawn = (up, dirt) => paint(digby(0.1), [...peek(0, up), ...peek(13, up), [12, 0, "gg"], [12, 14, "gg"], ...dirt]);
|
|
147
|
-
/** The sign on its post, which stops a pixel above the ground so the muted post does not sit on it. */
|
|
148
123
|
const sign = (shown) => [...(shown ? [[2, 13, "www"], [3, 13, "wkw"], [4, 13, "www"]] : []), ...post(5, 14, 7, "m")];
|
|
149
|
-
/** His paws, held a pixel above the mound's rim so the muted pixels do not touch it. */
|
|
150
124
|
const waiting = (shown) => (open) => paint(digby(1, { eyes: open ? "wide" : "none" }), [[8, 4, "mm"], [8, 10, "mm"], ...sign(shown)]);
|
|
151
125
|
const stalled = (dots) => () => paint(digby(0.7, { eyes: "shut", slump: true }), [[5, 0, dots]]);
|
|
152
|
-
/** The letter held up on his arm, which stops a pixel above the mound so it does not touch it, and the spark beside him. */
|
|
153
126
|
const report = (rise, spark) => (open) => paint(digby(rise, { eyes: open ? "open" : "none" }), [
|
|
154
127
|
[0, 12, "aaaa"],
|
|
155
128
|
[1, 12, "akka"],
|
|
@@ -157,7 +130,6 @@ const report = (rise, spark) => (open) => paint(digby(rise, { eyes: open ? "open
|
|
|
157
130
|
...post(3, 13, 8, "m"),
|
|
158
131
|
...spark.map((row, at) => [1 + at, 0, row]),
|
|
159
132
|
]);
|
|
160
|
-
/** 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. */
|
|
161
133
|
export const ART = {
|
|
162
134
|
spawn: [
|
|
163
135
|
() => spawn(true, [[3, 1, "m"], [5, 3, "m"], [2, 14, "m"], [5, 12, "m"]]),
|
|
@@ -169,9 +141,7 @@ export const ART = {
|
|
|
169
141
|
report: [report(1, [".s.", "sss", ".s."]), report(1.25, ["s.s", ".s.", "s.s"]), report(1.1, [".s.", "sss", ".s."])],
|
|
170
142
|
gone: [() => digby(-0.3, { mound: "m" })],
|
|
171
143
|
};
|
|
172
|
-
/** Where idle's `z` drifts up, by cell line, one step per mascot frame, then gone a frame. */
|
|
173
144
|
const Z_LINES = [3, 2, 1, undefined];
|
|
174
|
-
/** The 16×16 idle Digby at mascot frame `frame`, its first with none: the mound breathing, a `z` drifting up beside it. */
|
|
175
145
|
export function idleArt(frame) {
|
|
176
146
|
const lines = cells(digby(-0.3, { top: frame !== undefined && breathesOut(frame) ? 8 : 6 }));
|
|
177
147
|
const line = Z_LINES[(frame ?? 0) % Z_LINES.length];
|
|
@@ -179,7 +149,6 @@ export function idleArt(frame) {
|
|
|
179
149
|
return lines;
|
|
180
150
|
return lines.map((cellsOf, at) => (at === line ? cellsOf.map((one, column) => (column === 13 ? { char: "z", fg: "m" } : one)) : cellsOf));
|
|
181
151
|
}
|
|
182
|
-
/** The 16×16 Digby for `state` at mascot frame `frame`, its first frame with none: animations off. */
|
|
183
152
|
export function art(state, frame) {
|
|
184
153
|
if (state === "idle")
|
|
185
154
|
return idleArt(frame);
|
package/dist/tree/model.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { type ModelRef } from "./domain.js";
|
|
2
2
|
import type { CourierPorts } from "./ports.js";
|
|
3
|
-
/** What courier_spawn says of the model: `provider/model`, and the variant of whichever model is chosen. */
|
|
4
3
|
export interface ModelChoice {
|
|
5
4
|
readonly model?: string;
|
|
6
5
|
readonly variant?: string;
|
|
7
6
|
}
|
|
8
|
-
/**
|
|
9
|
-
* The model a child runs on: the `model` named, else the agent's, else the one its parent runs on, with the `variant`
|
|
10
|
-
* named; `parent` is the parent's own, for the roster to tell a child on another one.
|
|
11
|
-
*/
|
|
12
7
|
export declare function chooseModel(ports: CourierPorts, parentID: string, choice: ModelChoice, agent: string | undefined): Promise<{
|
|
13
8
|
model: undefined;
|
|
14
9
|
parent: ModelRef | undefined;
|
package/dist/tree/model.js
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { modelName, parseModelName } from "./domain.js";
|
|
2
2
|
import { modelFormatRefusal, unknownModelRefusal, unknownVariantRefusal, variantWithoutModelRefusal } from "./text.js";
|
|
3
|
-
/**
|
|
4
|
-
* The model a child runs on: the `model` named, else the agent's, else the one its parent runs on, with the `variant`
|
|
5
|
-
* named; `parent` is the parent's own, for the roster to tell a child on another one.
|
|
6
|
-
*/
|
|
7
3
|
export async function chooseModel(ports, parentID, choice, agent) {
|
|
8
|
-
// Some models send null, or an empty string, for an optional field they leave out.
|
|
9
4
|
const named = trimmed(choice.model);
|
|
10
5
|
const variant = trimmed(choice.variant);
|
|
11
6
|
const own = named === undefined ? undefined : parseModelName(named);
|
|
12
7
|
if (named !== undefined && !own)
|
|
13
8
|
throw new Error(modelFormatRefusal(named));
|
|
14
9
|
const parent = await parentModel(ports, parentID);
|
|
15
|
-
//
|
|
10
|
+
// with no model passed, OpenCode applies the agent's own (api-notes)
|
|
16
11
|
const inherited = own ? undefined : await inheritedModel(ports, parent, agent).catch(() => undefined);
|
|
17
12
|
const base = own ?? inherited;
|
|
18
13
|
if (!base) {
|
|
@@ -26,27 +21,19 @@ export async function chooseModel(ports, parentID, choice, agent) {
|
|
|
26
21
|
await check(ports, model, own !== undefined);
|
|
27
22
|
return { model, parent };
|
|
28
23
|
}
|
|
29
|
-
/**
|
|
30
|
-
* The model the parent runs on: its session's, else OpenCode's default. A failed lookup must not keep a child on no
|
|
31
|
-
* particular model from starting, so it reads as none, and the child then runs on OpenCode's default.
|
|
32
|
-
*/
|
|
33
24
|
async function parentModel(ports, parentID) {
|
|
34
25
|
const session = await ports.session.get({ sessionID: parentID }).catch(() => undefined);
|
|
35
26
|
if (!session)
|
|
36
27
|
return undefined;
|
|
37
28
|
return session.model ?? (await ports.defaultModel().catch(() => undefined));
|
|
38
29
|
}
|
|
39
|
-
/** The named agent's model, if it names one; else the parent's. */
|
|
40
30
|
async function inheritedModel(ports, parent, agent) {
|
|
41
31
|
if (!agent)
|
|
42
32
|
return parent;
|
|
43
33
|
const named = await ports.agent.get({ agentID: agent, location: { directory: ports.directory } });
|
|
44
34
|
return named.data.model ?? parent;
|
|
45
35
|
}
|
|
46
|
-
|
|
47
|
-
* Refuses a model OpenCode does not list, or a variant its model has not; `"default"` is every model's. A listing that
|
|
48
|
-
* cannot be read checks nothing: OpenCode refuses what it does not know at the child's first turn, which is reported.
|
|
49
|
-
*/
|
|
36
|
+
// OpenCode refuses an unknown model at the child's first turn (api-notes)
|
|
50
37
|
async function check(ports, model, named) {
|
|
51
38
|
let known;
|
|
52
39
|
try {
|
package/dist/tree/nudge.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import type { ChildStore, Waits, WatchPorts } from "./ports.js";
|
|
2
|
-
/** OpenCode's `experimental.idle` hook event: a session about to go idle, and the background work still running for it or below it. */
|
|
3
2
|
export interface Idle {
|
|
4
3
|
readonly sessionID: string;
|
|
5
4
|
readonly background: ReadonlyArray<{
|
|
6
5
|
readonly sessionID: string;
|
|
7
6
|
}>;
|
|
8
7
|
}
|
|
9
|
-
/** The ports the reminder is sent through: the watcher's. */
|
|
10
8
|
export type NudgePorts = Pick<WatchPorts, "storage" | "session" | "permissions" | "now" | "log">;
|
|
11
|
-
/**
|
|
12
|
-
* The report a spawned session owes since its last prompt, unless it waits or courier_stop stopped it since: what the
|
|
13
|
-
* silent-turn notice tells the parent of, and the reminder reminds the session of.
|
|
14
|
-
*/
|
|
15
9
|
export declare function owedUnwaited(ports: NudgePorts, children: ChildStore, outside: Waits, sessionID: string): Promise<{
|
|
16
10
|
resumesAt?: number | undefined;
|
|
17
11
|
prompt: number;
|
|
@@ -19,10 +13,5 @@ export declare function owedUnwaited(ports: NudgePorts, children: ChildStore, ou
|
|
|
19
13
|
progressed: boolean;
|
|
20
14
|
ended: boolean;
|
|
21
15
|
} | undefined>;
|
|
22
|
-
/**
|
|
23
|
-
* Reminds a spawned session about to go idle owing a report it does not wait on, once per prompt, so it reports in the
|
|
24
|
-
* same execution; one with background work of its own is woken again by it. Returns whether it was reminded.
|
|
25
|
-
*/
|
|
26
16
|
export declare function nudgeSilent(ports: NudgePorts, seen: Set<string>, outside: Waits, idle: Idle): Promise<boolean>;
|
|
27
|
-
/** `nudgeSilent` as the hook runs it: a hook that fails fails the execution, so a failure is logged, never thrown. */
|
|
28
17
|
export declare function onIdle(ports: NudgePorts, seen: Set<string>, outside: Waits, idle: Idle): Promise<boolean>;
|
package/dist/tree/nudge.js
CHANGED
|
@@ -4,24 +4,15 @@ import { hasCompleteGroup } from "./group.js";
|
|
|
4
4
|
import { awaited, reportOf, stoppedSincePrompt } from "./report.js";
|
|
5
5
|
import { childStore, groupStore } from "./store.js";
|
|
6
6
|
import { reportReminder } from "./text.js";
|
|
7
|
-
/**
|
|
8
|
-
* Whether a session prompted at `prompt` waits: on an untold report of a session it started, a complete group of
|
|
9
|
-
* its about to be delivered, or what `outside` says it waits on. Cheapest first.
|
|
10
|
-
*/
|
|
11
7
|
async function waits(ports, outside, sessionID, prompt) {
|
|
12
8
|
const children = childStore(ports.storage);
|
|
13
9
|
const started = await children.children(sessionID);
|
|
14
10
|
if ((await Promise.all(started.map((entry) => awaited(children, entry.sessionID)))).includes(true))
|
|
15
11
|
return true;
|
|
16
|
-
// A group with a member still out is waited on through that member, above; one whose every out member was told of is not.
|
|
17
12
|
if (started.some((entry) => entry.group) && (await hasCompleteGroup(groupStore(ports.storage), sessionID)))
|
|
18
13
|
return true;
|
|
19
14
|
return outside(ports, sessionID, prompt);
|
|
20
15
|
}
|
|
21
|
-
/**
|
|
22
|
-
* The report a spawned session owes since its last prompt, unless it waits or courier_stop stopped it since: what the
|
|
23
|
-
* silent-turn notice tells the parent of, and the reminder reminds the session of.
|
|
24
|
-
*/
|
|
25
16
|
export async function owedUnwaited(ports, children, outside, sessionID) {
|
|
26
17
|
const report = await reportOf(children, sessionID);
|
|
27
18
|
if (!report?.owes || (await waits(ports, outside, sessionID, report.prompt)))
|
|
@@ -30,10 +21,6 @@ export async function owedUnwaited(ports, children, outside, sessionID) {
|
|
|
30
21
|
return undefined;
|
|
31
22
|
return report;
|
|
32
23
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Reminds a spawned session about to go idle owing a report it does not wait on, once per prompt, so it reports in the
|
|
35
|
-
* same execution; one with background work of its own is woken again by it. Returns whether it was reminded.
|
|
36
|
-
*/
|
|
37
24
|
export async function nudgeSilent(ports, seen, outside, idle) {
|
|
38
25
|
const { sessionID } = idle;
|
|
39
26
|
if (idle.background.some((work) => work.sessionID === sessionID))
|
|
@@ -49,7 +36,7 @@ export async function nudgeSilent(ports, seen, outside, idle) {
|
|
|
49
36
|
if ((nudgedAt !== undefined && nudgedAt >= report.prompt) || !claim(seen, `nudge/${sessionID}/${report.prompt}`))
|
|
50
37
|
return false;
|
|
51
38
|
const at = Math.max(ports.now(), report.prompt);
|
|
52
|
-
//
|
|
39
|
+
// every instance's idle hook runs; delivery matches by inbox id (api-notes)
|
|
53
40
|
await children.nudged(sessionID, at);
|
|
54
41
|
const { id } = await ports.session.synthetic({
|
|
55
42
|
sessionID,
|
|
@@ -57,13 +44,13 @@ export async function nudgeSilent(ports, seen, outside, idle) {
|
|
|
57
44
|
description: "Reminded to report",
|
|
58
45
|
metadata: { source: "courier", reminder: "report", resume: false },
|
|
59
46
|
delivery: "steer",
|
|
60
|
-
//
|
|
47
|
+
// input the idle hook admits continues its execution (api-notes)
|
|
61
48
|
resume: false,
|
|
62
49
|
});
|
|
63
50
|
await children.nudged(sessionID, at, id);
|
|
64
51
|
return true;
|
|
65
52
|
}
|
|
66
|
-
|
|
53
|
+
// a throwing idle hook fails the execution (api-notes)
|
|
67
54
|
export async function onIdle(ports, seen, outside, idle) {
|
|
68
55
|
try {
|
|
69
56
|
return await nudgeSilent(ports, seen, outside, idle);
|
package/dist/tree/panel.d.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { CourierPorts, ModelWindow, Pending, ShownForm } from "./ports.js";
|
|
2
2
|
import type { Tree, Wait } from "./rpc.js";
|
|
3
3
|
type Messages = Awaited<ReturnType<CourierPorts["session"]["context"]>>;
|
|
4
|
-
/** The last tool call in a session's messages, and when it was made. */
|
|
5
4
|
export declare function lastTool(messages: Messages): {
|
|
6
5
|
name: string;
|
|
7
6
|
at: number;
|
|
8
7
|
} | undefined;
|
|
9
|
-
/** How full a session's context is, as OpenCode's session view reads it (packages/tui/src/util/session.ts, v2.1.0): the last usage since the last compaction over its model's window. */
|
|
10
8
|
export declare function contextOf(messages: Messages, windows: ReadonlyArray<ModelWindow>): number | undefined;
|
|
11
|
-
/** A request as the panel says what a paused session waits on: a permission request, or its first question. */
|
|
12
9
|
export declare const waitOf: (pending: Pending) => Wait;
|
|
13
|
-
/** A form a session shows, as the panel says what it waits on. */
|
|
14
10
|
export declare const formWait: (form: ShownForm) => Wait;
|
|
15
|
-
/**
|
|
16
|
-
* The tree panel's rows: the sessions under a session as courier_tree lists them, at most `maxTotal`, live ones first,
|
|
17
|
-
* then the newest, each as `row` reads it; `top` the session itself, `truncated` the ones left out, `stalled` its stall.
|
|
18
|
-
*/
|
|
19
11
|
export declare function panel(ports: CourierPorts, rootID: string): Promise<Tree>;
|
|
20
12
|
export {};
|