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/wake.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { GroupPorts, Wake, WatchPorts } from "./ports.js";
|
|
2
|
-
/** How long a courier message may wait in an idle session's inbox before its wake counts as missed. */
|
|
3
2
|
export declare const WAKE_MS = 30000;
|
|
4
|
-
/** OpenCode's `session.inbox.enqueued`, with the item's payload, which carries the courier's metadata. */
|
|
5
3
|
export interface Enqueued {
|
|
6
4
|
readonly created?: number;
|
|
7
5
|
readonly data: {
|
|
@@ -20,68 +18,40 @@ export interface Enqueued {
|
|
|
20
18
|
};
|
|
21
19
|
};
|
|
22
20
|
}
|
|
23
|
-
/** OpenCode's `session.inbox.delivered` and `session.inbox.cancelled`. */
|
|
24
21
|
export interface InboxSettled {
|
|
25
22
|
readonly data: {
|
|
26
23
|
readonly sessionID: string;
|
|
27
24
|
readonly inboxID: string;
|
|
28
25
|
};
|
|
29
26
|
}
|
|
30
|
-
/** OpenCode's `session.execution.*` events, which frame a session's busy period. */
|
|
31
27
|
export interface Execution {
|
|
32
28
|
readonly created?: number;
|
|
33
29
|
readonly data: {
|
|
34
30
|
readonly sessionID: string;
|
|
35
31
|
};
|
|
36
32
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Watches a courier message enqueued for a session until it is delivered, noting whether the session runs.
|
|
39
|
-
* Returns whether it is watched, which one sent with `resume: false` is not.
|
|
40
|
-
*/
|
|
41
33
|
export declare function watchWake(ports: WatchPorts, event: Enqueued): Promise<boolean>;
|
|
42
|
-
/**
|
|
43
|
-
* Watches a message `courier_send` has just had enqueued, without waiting for its enqueued event, so the limits see a reported child woken
|
|
44
|
-
* for it at once; the event leaves this row be. Dropped again if the message is already stored, delivered before it was written.
|
|
45
|
-
*/
|
|
46
34
|
export declare function noteSent(ports: Pick<WatchPorts, "storage" | "session" | "now">, sent: {
|
|
47
35
|
readonly sessionID: string;
|
|
48
36
|
readonly inboxID: string;
|
|
49
37
|
readonly text: string;
|
|
50
38
|
readonly description?: string;
|
|
51
39
|
}): Promise<void>;
|
|
52
|
-
/** Stops watching a message once it is delivered or cancelled. */
|
|
53
40
|
export declare function settleWake(ports: Pick<WatchPorts, "storage">, event: InboxSettled): Promise<void>;
|
|
54
|
-
/** A turn started in a session: the messages waiting there are its to take. */
|
|
55
41
|
export declare function wakeStarted(ports: Pick<WatchPorts, "storage">, event: Execution): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* A session's turn ended: a message it took is no longer watched, as its delivery may have been missed by this
|
|
58
|
-
* subscription; one still waiting is watched as waiting in an idle session from now.
|
|
59
|
-
*/
|
|
60
42
|
export declare function wakeEnded(ports: WatchPorts, event: Execution & {
|
|
61
43
|
readonly data: {
|
|
62
44
|
readonly reason?: string;
|
|
63
45
|
};
|
|
64
46
|
}): Promise<void>;
|
|
65
|
-
/** Forgets the messages watched in a session's inbox: it was deleted, or stopped, which nothing is to wake again. */
|
|
66
47
|
export declare function forgetWakes(ports: Pick<WatchPorts, "storage">, sessionID: string): Promise<void>;
|
|
67
|
-
/** The ports the scheduler's tick wakes sessions through: what every hub-v2 member's `later` ports have, whose session is its `ctx.session`. */
|
|
68
48
|
export type WakePorts = GroupPorts & {
|
|
69
49
|
readonly session: Pick<WatchPorts["session"], "get" | "context">;
|
|
70
50
|
readonly now: () => number;
|
|
71
51
|
};
|
|
72
|
-
/**
|
|
73
|
-
* Sends once more, under its inbox id, each courier message an idle session has left waiting for `WAKE_MS`, flags stalled one still
|
|
74
|
-
* waiting `WAKE_MS` later, and marks running one a turn it shows will take; run by the scheduler's owner, `claimed` keeping a tick alongside off it.
|
|
75
|
-
*/
|
|
76
52
|
export declare function wakeMissed(ports: WakePorts, claimed: Set<string>): Promise<void>;
|
|
77
|
-
/** Whether a message has waited in an idle session past `WAKE_MS`, and since a wake again, if one was sent; a stalled one is done. */
|
|
78
53
|
export declare function missed(wake: Wake, now: number): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* Whether a courier message sent with a wake waits undelivered in the session's inbox while it is idle, waiting or, unless
|
|
81
|
-
* `stalled` is false, stalled; one a running turn is to take is not counted, as `runs` says that turn runs.
|
|
82
|
-
*/
|
|
83
54
|
export declare function waitsInInbox(ports: Pick<WatchPorts, "storage">, sessionID: string, { stalled }?: {
|
|
84
55
|
stalled?: boolean | undefined;
|
|
85
56
|
}): Promise<boolean>;
|
|
86
|
-
/** The sessions among `sessionIDs` flagged stalled, each with when its oldest stalled message was enqueued. */
|
|
87
57
|
export declare function stalledAmong(ports: Pick<WatchPorts, "storage">, sessionIDs: ReadonlyArray<string>): Promise<Map<string, number>>;
|
package/dist/tree/wake.js
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { millis } from "../platform/json.js";
|
|
2
2
|
import { busy, endsIdle } from "./domain.js";
|
|
3
3
|
import { wakeStore } from "./store.js";
|
|
4
|
-
/** How long a courier message may wait in an idle session's inbox before its wake counts as missed. */
|
|
5
4
|
export const WAKE_MS = 30_000;
|
|
6
|
-
/**
|
|
7
|
-
* Watches a courier message enqueued for a session until it is delivered, noting whether the session runs.
|
|
8
|
-
* Returns whether it is watched, which one sent with `resume: false` is not.
|
|
9
|
-
*/
|
|
10
5
|
export async function watchWake(ports, event) {
|
|
11
6
|
const { sessionID, inboxID, item } = event.data;
|
|
12
7
|
const payload = item.payload;
|
|
13
8
|
if (item.type !== "synthetic" || payload?.metadata?.source !== "courier" || typeof payload.text !== "string")
|
|
14
9
|
return false;
|
|
15
|
-
//
|
|
10
|
+
// resume: false starts no turn: the item waits for the next (api-notes)
|
|
16
11
|
if (payload.metadata.resume === false)
|
|
17
12
|
return false;
|
|
18
13
|
const at = event.created ?? ports.now();
|
|
19
|
-
// A row `noteSent` wrote already is kept: it may have moved on since, and is no later than this one.
|
|
20
14
|
if ((await wakeStore(ports.storage).of(sessionID)).some((wake) => wake.inboxID === inboxID))
|
|
21
15
|
return true;
|
|
22
16
|
await watch(ports, { sessionID, inboxID, at, text: payload.text, ...(typeof payload.description === "string" ? { description: payload.description } : {}) });
|
|
23
17
|
return true;
|
|
24
18
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Watches a message `courier_send` has just had enqueued, without waiting for its enqueued event, so the limits see a reported child woken
|
|
27
|
-
* for it at once; the event leaves this row be. Dropped again if the message is already stored, delivered before it was written.
|
|
28
|
-
*/
|
|
29
19
|
export async function noteSent(ports, sent) {
|
|
30
20
|
const store = wakeStore(ports.storage);
|
|
31
21
|
if ((await store.of(sent.sessionID)).some((wake) => wake.inboxID === sent.inboxID))
|
|
@@ -36,24 +26,17 @@ export async function noteSent(ports, sent) {
|
|
|
36
26
|
await store.remove(sent.sessionID, sent.inboxID);
|
|
37
27
|
}
|
|
38
28
|
async function watch(ports, wake) {
|
|
39
|
-
// Read after the enqueue: a turn it started has stored messages since, and an unknown session is taken to run.
|
|
40
29
|
const idle = await ports.session.context({ sessionID: wake.sessionID }).then(endsIdle, () => false);
|
|
41
30
|
await wakeStore(ports.storage).put({ ...wake, running: !idle, ...(idle ? { idleAt: wake.at } : {}) });
|
|
42
31
|
}
|
|
43
|
-
/** Stops watching a message once it is delivered or cancelled. */
|
|
44
32
|
export async function settleWake(ports, event) {
|
|
45
33
|
await wakeStore(ports.storage).remove(event.data.sessionID, event.data.inboxID);
|
|
46
34
|
}
|
|
47
|
-
/** A turn started in a session: the messages waiting there are its to take. */
|
|
48
35
|
export async function wakeStarted(ports, event) {
|
|
49
36
|
const store = wakeStore(ports.storage);
|
|
50
37
|
const waiting = await store.of(event.data.sessionID);
|
|
51
38
|
await Promise.all(waiting.filter((wake) => !wake.running).map(({ idleAt: _, ...wake }) => store.put({ ...wake, running: true })));
|
|
52
39
|
}
|
|
53
|
-
/**
|
|
54
|
-
* A session's turn ended: a message it took is no longer watched, as its delivery may have been missed by this
|
|
55
|
-
* subscription; one still waiting is watched as waiting in an idle session from now.
|
|
56
|
-
*/
|
|
57
40
|
export async function wakeEnded(ports, event) {
|
|
58
41
|
const { sessionID } = event.data;
|
|
59
42
|
const store = wakeStore(ports.storage);
|
|
@@ -62,22 +45,16 @@ export async function wakeEnded(ports, event) {
|
|
|
62
45
|
return;
|
|
63
46
|
const at = event.created ?? ports.now();
|
|
64
47
|
const messages = await ports.session.context({ sessionID }).catch(() => []);
|
|
65
|
-
//
|
|
66
|
-
// step reads as ended too, until the turn the next boot resumes is seen to start or `wakeAgain` finds it running.
|
|
48
|
+
// a plain decline reports a shutdown after its aborted step (api-notes)
|
|
67
49
|
if (event.data.reason === "shutdown" && !endsIdle(messages))
|
|
68
50
|
return;
|
|
69
51
|
const stored = new Set(messages.map((message) => message.id));
|
|
70
52
|
await Promise.all(waiting.map((wake) => (stored.has(wake.inboxID) ? store.remove(sessionID, wake.inboxID) : store.put({ ...wake, running: false, idleAt: at }))));
|
|
71
53
|
}
|
|
72
|
-
/** Forgets the messages watched in a session's inbox: it was deleted, or stopped, which nothing is to wake again. */
|
|
73
54
|
export async function forgetWakes(ports, sessionID) {
|
|
74
55
|
const store = wakeStore(ports.storage);
|
|
75
56
|
await Promise.all((await store.of(sessionID)).map((wake) => store.remove(sessionID, wake.inboxID)));
|
|
76
57
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Sends once more, under its inbox id, each courier message an idle session has left waiting for `WAKE_MS`, flags stalled one still
|
|
79
|
-
* waiting `WAKE_MS` later, and marks running one a turn it shows will take; run by the scheduler's owner, `claimed` keeping a tick alongside off it.
|
|
80
|
-
*/
|
|
81
58
|
export async function wakeMissed(ports, claimed) {
|
|
82
59
|
const store = wakeStore(ports.storage);
|
|
83
60
|
const now = ports.now();
|
|
@@ -87,9 +64,7 @@ export async function wakeMissed(ports, claimed) {
|
|
|
87
64
|
await Promise.all(due.map((wake) => wakeAgain(ports, store, wake, now).finally(() => claimed.delete(claimOf(wake)))));
|
|
88
65
|
}
|
|
89
66
|
const claimOf = (wake) => `wake ${wake.sessionID} ${wake.inboxID}`;
|
|
90
|
-
/** Since when a message has waited in an idle session: it was enqueued, seen waiting as the turn ended, or sent again, whichever is last. */
|
|
91
67
|
const waitingSince = (wake) => Math.max(wake.idleAt ?? wake.at, wake.rewoken ?? 0);
|
|
92
|
-
/** Whether a message has waited in an idle session past `WAKE_MS`, and since a wake again, if one was sent; a stalled one is done. */
|
|
93
68
|
export function missed(wake, now) {
|
|
94
69
|
if (wake.running || wake.stalled !== undefined)
|
|
95
70
|
return false;
|
|
@@ -97,11 +72,9 @@ export function missed(wake, now) {
|
|
|
97
72
|
}
|
|
98
73
|
async function wakeAgain(ports, store, wake, now) {
|
|
99
74
|
try {
|
|
100
|
-
// Re-read: the message may have been delivered, or the session woken, since the scan.
|
|
101
75
|
const current = (await store.of(wake.sessionID)).find((one) => one.inboxID === wake.inboxID);
|
|
102
76
|
if (!current || !missed(current, now))
|
|
103
77
|
return;
|
|
104
|
-
// A shutdown during a step reads as an end, and the turn the next boot resumes may start unseen: that turn takes the message.
|
|
105
78
|
if (await runsNow(ports, current)) {
|
|
106
79
|
const { idleAt: _, ...running } = current;
|
|
107
80
|
return await store.put({ ...running, running: true });
|
|
@@ -110,8 +83,9 @@ async function wakeAgain(ports, store, wake, now) {
|
|
|
110
83
|
ports.log(`courier wake: ${wake.sessionID} has not taken message ${wake.inboxID} since it was woken again; flagged stalled`);
|
|
111
84
|
return await store.put({ ...current, stalled: now });
|
|
112
85
|
}
|
|
113
|
-
//
|
|
86
|
+
// the woken turn may take the message before synthetic returns
|
|
114
87
|
await store.put({ ...current, rewoken: now });
|
|
88
|
+
// synthetic with a waiting item's id wakes again, adds no message (api-notes)
|
|
115
89
|
await ports.session.synthetic({
|
|
116
90
|
sessionID: wake.sessionID,
|
|
117
91
|
id: wake.inboxID,
|
|
@@ -129,15 +103,12 @@ async function wakeAgain(ports, store, wake, now) {
|
|
|
129
103
|
ports.log(`courier wake: could not wake ${wake.sessionID} again for ${wake.inboxID}: ${String(error)}`);
|
|
130
104
|
}
|
|
131
105
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Whether a session shows a turn running that takes the message: by `time.busy` (2.2.2), an execution started since the message was
|
|
134
|
-
* last left waiting and not ended; without it, by its times and its transcript read now. One that cannot be read does not, and is woken again.
|
|
135
|
-
*/
|
|
136
106
|
async function runsNow(ports, wake) {
|
|
137
107
|
const { sessionID } = wake;
|
|
138
108
|
const info = await ports.session.get({ sessionID }).catch(() => undefined);
|
|
139
109
|
if (!info)
|
|
140
110
|
return false;
|
|
111
|
+
// time.busy, since 2.2.2, records a start whose event was missed (api-notes)
|
|
141
112
|
if (info.time.busy !== undefined)
|
|
142
113
|
return busy(info.time) && millis(info.time.busy) >= waitingSince(wake);
|
|
143
114
|
if (!busy(info.time))
|
|
@@ -145,14 +116,9 @@ async function runsNow(ports, wake) {
|
|
|
145
116
|
const messages = await ports.session.context({ sessionID }).catch(() => []);
|
|
146
117
|
return messages.length > 0 && !endsIdle(messages);
|
|
147
118
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Whether a courier message sent with a wake waits undelivered in the session's inbox while it is idle, waiting or, unless
|
|
150
|
-
* `stalled` is false, stalled; one a running turn is to take is not counted, as `runs` says that turn runs.
|
|
151
|
-
*/
|
|
152
119
|
export async function waitsInInbox(ports, sessionID, { stalled = true } = {}) {
|
|
153
120
|
return (await wakeStore(ports.storage).of(sessionID)).some((wake) => !wake.running && (stalled || wake.stalled === undefined));
|
|
154
121
|
}
|
|
155
|
-
/** The sessions among `sessionIDs` flagged stalled, each with when its oldest stalled message was enqueued. */
|
|
156
122
|
export async function stalledAmong(ports, sessionIDs) {
|
|
157
123
|
const wanted = new Set(sessionIDs);
|
|
158
124
|
const stalled = new Map();
|
package/dist/tui.js
CHANGED
|
@@ -12,6 +12,7 @@ var Part = Schema.Struct({
|
|
|
12
12
|
title: Schema.String,
|
|
13
13
|
depth: Count,
|
|
14
14
|
isolated: Schema.Boolean,
|
|
15
|
+
subagent: Schema.optionalKey(Schema.Boolean),
|
|
15
16
|
group: Schema.optionalKey(Schema.String),
|
|
16
17
|
status: Schema.Literals(PART_STATUSES),
|
|
17
18
|
duration: Schema.optionalKey(Schema.Number),
|
|
@@ -61,8 +62,8 @@ var memoryRpc = Rpc.define({
|
|
|
61
62
|
|
|
62
63
|
// src/kernel/protocol.ts
|
|
63
64
|
var STATUSES = ["done", "partial", "blocked", "failed"];
|
|
64
|
-
var SPLIT_LINE = "split: no, because \u2026; considered: <the parts you weighed
|
|
65
|
-
var ONE_PART_LINE = "split: 1 part: <the task>, to keep my context";
|
|
65
|
+
var SPLIT_LINE = "`split: no, because \u2026; considered: <the parts you weighed>` or `split: <n> parts: \u2026` (one phrase per part)";
|
|
66
|
+
var ONE_PART_LINE = "`split: 1 part: <the task>, to keep my context`";
|
|
66
67
|
var ROOT_SPLIT_LINE = `${SPLIT_LINE} or ${ONE_PART_LINE}`;
|
|
67
68
|
|
|
68
69
|
// src/memory/domain.ts
|
|
@@ -110,14 +111,14 @@ var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
|
110
111
|
var times = (n) => n === 1 ? "once" : `${n} times`;
|
|
111
112
|
var money = (cost) => `$${cost.toFixed(2)}`;
|
|
112
113
|
var nameOf = (record) => clip(oneLine2(record.title) || record.sessionID, 60);
|
|
113
|
-
function partsCounted(
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
function partsCounted(all) {
|
|
115
|
+
const parts = all.filter((part) => !part.subagent);
|
|
116
|
+
const subagents = all.length - parts.length;
|
|
116
117
|
const statuses = PART_STATUSES.flatMap((status) => {
|
|
117
118
|
const n = parts.filter((part) => part.status === status).length;
|
|
118
119
|
return n ? [`${n} ${status}`] : [];
|
|
119
120
|
}).join(", ");
|
|
120
|
-
return `${count(parts.length, "part")}: ${statuses}
|
|
121
|
+
return [parts.length ? `${count(parts.length, "part")}: ${statuses}` : "no parts", ...subagents ? [count(subagents, "subagent")] : []].join(", ");
|
|
121
122
|
}
|
|
122
123
|
function costOf(record) {
|
|
123
124
|
return record.parts.some((part) => part.cost !== undefined) ? record.parts.reduce((sum, part) => sum + (part.cost ?? 0), 0) : undefined;
|
|
@@ -146,7 +147,7 @@ function recordDetails(record) {
|
|
|
146
147
|
return record.lesson ? [clip(`${lessonHeading(record.lesson)}: "${oneLine2(record.lesson.text)}"`, LESSON_LINE_MAX)] : [];
|
|
147
148
|
}
|
|
148
149
|
function partLine(part) {
|
|
149
|
-
return [part.status, `depth ${part.depth}`, part.isolated ? "isolated" : "", part.group ? `group ${part.group}` : "", part.duration === undefined ? "" : duration(part.duration), part.cost === undefined ? "" : money(part.cost), part.context === undefined ? "" : `ctx ${Math.round(part.context * 100)}%`, part.refused ? `${count(part.refused, "spawn")} refused` : ""].filter(Boolean).join(" \xB7 ");
|
|
150
|
+
return [part.status, `depth ${part.depth}`, part.isolated ? "isolated" : "", part.subagent ? "subagent" : "", part.group ? `group ${part.group}` : "", part.duration === undefined ? "" : duration(part.duration), part.cost === undefined ? "" : money(part.cost), part.context === undefined ? "" : `ctx ${Math.round(part.context * 100)}%`, part.refused ? `${count(part.refused, "spawn")} refused` : ""].filter(Boolean).join(" \xB7 ");
|
|
150
151
|
}
|
|
151
152
|
function wrap(text, width) {
|
|
152
153
|
const rows = [];
|
|
@@ -1470,6 +1471,7 @@ var Row = Schema3.Struct({
|
|
|
1470
1471
|
group: Schema3.optionalKey(Schema3.Struct({ name: Schema3.String, standing: Schema3.String })),
|
|
1471
1472
|
isolated: Schema3.Boolean,
|
|
1472
1473
|
native: Schema3.optionalKey(Schema3.Boolean),
|
|
1474
|
+
subagent: Schema3.optionalKey(Schema3.Boolean),
|
|
1473
1475
|
model: Schema3.optionalKey(Schema3.String),
|
|
1474
1476
|
created: Schema3.Number,
|
|
1475
1477
|
idle: Schema3.optionalKey(Schema3.Number),
|
|
@@ -1720,7 +1722,7 @@ function duration2(ms) {
|
|
|
1720
1722
|
return `${Math.floor(seconds / 3600)}h ${pad(Math.floor(seconds / 60) % 60)}m`;
|
|
1721
1723
|
}
|
|
1722
1724
|
function parts(rows, parentID) {
|
|
1723
|
-
const children = rows.filter((row) => row.parentID === parentID);
|
|
1725
|
+
const children = rows.filter((row) => row.parentID === parentID && !row.subagent);
|
|
1724
1726
|
return {
|
|
1725
1727
|
reported: children.filter((row) => row.report?.state === "reported").length,
|
|
1726
1728
|
started: children.length
|
|
@@ -1760,6 +1762,9 @@ function statusParts(row, now = Date.now()) {
|
|
|
1760
1762
|
}, {
|
|
1761
1763
|
text: row.native ? "native" : "",
|
|
1762
1764
|
kind: "plain"
|
|
1765
|
+
}, {
|
|
1766
|
+
text: row.subagent ? "subagent" : "",
|
|
1767
|
+
kind: "plain"
|
|
1763
1768
|
}, {
|
|
1764
1769
|
text: row.model ?? "",
|
|
1765
1770
|
kind: "plain"
|
|
@@ -2211,6 +2216,14 @@ function TreePanel(props) {
|
|
|
2211
2216
|
openRow(row);
|
|
2212
2217
|
};
|
|
2213
2218
|
const ownRow = (row, key) => {
|
|
2219
|
+
if (row.subagent) {
|
|
2220
|
+
ctx.ui.toast.show({
|
|
2221
|
+
title: "courier",
|
|
2222
|
+
message: `${key} acts on a session started with courier_spawn; "${oneLine3(row.title)}" is a subagent, stopped with the one above it.`,
|
|
2223
|
+
variant: "warning"
|
|
2224
|
+
});
|
|
2225
|
+
return true;
|
|
2226
|
+
}
|
|
2214
2227
|
if (row.sessionID !== view.root)
|
|
2215
2228
|
return false;
|
|
2216
2229
|
ctx.ui.toast.show({
|
|
@@ -2842,13 +2855,14 @@ function addTreePanel(ctx, verbs = {}) {
|
|
|
2842
2855
|
error
|
|
2843
2856
|
}));
|
|
2844
2857
|
const rows = read.rows;
|
|
2858
|
+
const spawned = rows.filter((row) => !row.subagent);
|
|
2845
2859
|
if (!target && "error" in read)
|
|
2846
2860
|
return ctx.ui.toast.show({
|
|
2847
2861
|
title: "courier stop",
|
|
2848
2862
|
message: reason(read.error),
|
|
2849
2863
|
variant: "error"
|
|
2850
2864
|
});
|
|
2851
|
-
if (!target && !
|
|
2865
|
+
if (!target && !spawned.length)
|
|
2852
2866
|
return ctx.ui.toast.show({
|
|
2853
2867
|
title: "courier stop",
|
|
2854
2868
|
message: "No session started with courier_spawn under this one.",
|
|
@@ -2856,7 +2870,7 @@ function addTreePanel(ctx, verbs = {}) {
|
|
|
2856
2870
|
});
|
|
2857
2871
|
const sessionID = target || await ctx.ui.dialog.select({
|
|
2858
2872
|
title: "Stop which session?",
|
|
2859
|
-
options:
|
|
2873
|
+
options: spawned.map((row) => ({
|
|
2860
2874
|
title: row.title,
|
|
2861
2875
|
value: row.sessionID,
|
|
2862
2876
|
description: `${row.sessionID} \xB7 ${statusLine(row)}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-courier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Multi-session orchestration for OpenCode V2: spawn child sessions, be woken by them. No polling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivo Pogace <71701666+ivopogace@users.noreply.github.com>",
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"background-agents",
|
|
46
46
|
"parallel",
|
|
47
47
|
"worktree",
|
|
48
|
-
"webhook",
|
|
49
48
|
"scheduler",
|
|
50
49
|
"ai-agents"
|
|
51
50
|
],
|
|
@@ -56,17 +55,17 @@
|
|
|
56
55
|
"test:e2e": "e2e/run.sh"
|
|
57
56
|
},
|
|
58
57
|
"peerDependencies": {
|
|
59
|
-
"@opencode/plugin": "2.2.
|
|
60
|
-
"effect": "4.0
|
|
58
|
+
"@opencode/plugin": "2.2.6",
|
|
59
|
+
"effect": "4.1.0"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
|
-
"@opencode/plugin": "2.2.
|
|
64
|
-
"@opencode/theme": "2.2.
|
|
65
|
-
"@opentui/core": "0.8.
|
|
66
|
-
"@opentui/solid": "0.8.
|
|
62
|
+
"@opencode/plugin": "2.2.6",
|
|
63
|
+
"@opencode/theme": "2.2.6",
|
|
64
|
+
"@opentui/core": "0.8.1",
|
|
65
|
+
"@opentui/solid": "0.8.1",
|
|
67
66
|
"@types/bun": "1.4.2",
|
|
68
67
|
"@types/node": "26.6.4",
|
|
69
|
-
"effect": "4.0
|
|
68
|
+
"effect": "4.1.0",
|
|
70
69
|
"solid-js": "1.9.17",
|
|
71
70
|
"typescript": "5.9.3"
|
|
72
71
|
}
|
package/dist/webhook/index.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { joinReceiver, listen, readConfig, receiving } from "./receiver.js";
|
|
2
|
-
export type { WebhookConfig } from "./receiver.js";
|
|
3
|
-
export { subscribedSince } from "./subscriptions.js";
|
|
4
|
-
export type { WebhookPorts } from "./subscriptions.js";
|
|
5
|
-
export { addWebhookTools } from "./tools.js";
|
package/dist/webhook/index.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { type IncomingMessage, type Server } from "node:http";
|
|
2
|
-
import { type WebhookPorts } from "./subscriptions.js";
|
|
3
|
-
/** Bodies above this are refused; GitHub's own deliveries are capped at 25 MB, real ones are far smaller. */
|
|
4
|
-
export declare const DEFAULT_MAX_BYTES: number;
|
|
5
|
-
/**
|
|
6
|
-
* The webhook receiver, one per process: every instance configured with a `webhook` option joins
|
|
7
|
-
* `instances`, the first one starts the server, and the last one to unload stops it.
|
|
8
|
-
*/
|
|
9
|
-
export interface Receiver {
|
|
10
|
-
readonly config: WebhookConfig;
|
|
11
|
-
readonly instances: Set<WebhookPorts>;
|
|
12
|
-
readonly server: Promise<Server | undefined>;
|
|
13
|
-
}
|
|
14
|
-
/** The current receiver, if any, and the previous one while it closes, which a new one waits for. */
|
|
15
|
-
export interface Receivers {
|
|
16
|
-
current?: Receiver;
|
|
17
|
-
closing?: Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
/** The receiver's settings, from the plugin's `webhook` option. */
|
|
20
|
-
export interface WebhookConfig {
|
|
21
|
-
readonly port: number;
|
|
22
|
-
readonly host: string;
|
|
23
|
-
readonly secret: string;
|
|
24
|
-
readonly maxBytes: number;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Checks an `X-Hub-Signature-256` header (`sha256=` + 64 hex digits) in constant time, returning the
|
|
28
|
-
* digest in lowercase hex, or undefined; replays are keyed by that digest, never by the header text.
|
|
29
|
-
*/
|
|
30
|
-
export declare function checkSignature(secret: string, body: Uint8Array, header: string | undefined, name?: string): string | undefined;
|
|
31
|
-
/** The `X-Hub-Signature-256` value for a body; give the topic name for `/hook/<name>`. */
|
|
32
|
-
export declare function sign(secret: string, body: string | Uint8Array, name?: string): string;
|
|
33
|
-
/** What an event means for subscribers: the topics it belongs to and a short summary. */
|
|
34
|
-
export interface Event {
|
|
35
|
-
readonly source: string;
|
|
36
|
-
readonly name: string;
|
|
37
|
-
readonly topics: readonly string[];
|
|
38
|
-
readonly summary: string;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Maps a GitHub delivery to its topics (`github:owner/repo`, plus `github:owner/repo#N` for PRs and
|
|
42
|
-
* issues) and a summary; undefined for pings, unfinished CI events and actions outside `ITEM_ACTIONS`.
|
|
43
|
-
*/
|
|
44
|
-
export declare function githubEvent(name: string, payload: unknown): Event | undefined;
|
|
45
|
-
/** A delivery to `/hook/<topic>`: a JSON body's `text`, `summary` or `message` field, else the body itself. */
|
|
46
|
-
export declare function genericEvent(topic: string, body: string): Event;
|
|
47
|
-
/**
|
|
48
|
-
* Delivers an event to every session subscribed to one of its topics, once per session. A session
|
|
49
|
-
* OpenCode no longer knows loses its subscriptions.
|
|
50
|
-
*/
|
|
51
|
-
export declare function dispatch(ports: WebhookPorts, event: Event, delivery?: string): Promise<{
|
|
52
|
-
delivered: number;
|
|
53
|
-
failed: number;
|
|
54
|
-
}>;
|
|
55
|
-
export interface Request {
|
|
56
|
-
readonly method: string;
|
|
57
|
-
readonly path: string;
|
|
58
|
-
readonly headers: Readonly<Record<string, string | string[] | undefined>>;
|
|
59
|
-
readonly body: Buffer;
|
|
60
|
-
}
|
|
61
|
-
export interface Response {
|
|
62
|
-
readonly status: number;
|
|
63
|
-
readonly body: string;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Handles one request: `POST /github` with GitHub's headers, or `POST /hook/<name>` signed over name
|
|
67
|
-
* and body; anything unsigned is refused before parsing, and a signature already accepted is ignored.
|
|
68
|
-
*/
|
|
69
|
-
export declare function receive(ports: WebhookPorts, secret: string, request: Request, seen?: Set<string>): Promise<Response>;
|
|
70
|
-
/** Starts the HTTP receiver; `ports()` picks a live plugin instance for each request. */
|
|
71
|
-
export declare function listen(config: WebhookConfig, ports: () => WebhookPorts | undefined): Promise<Server<typeof IncomingMessage, typeof import("node:http").ServerResponse>>;
|
|
72
|
-
/**
|
|
73
|
-
* Reads the `webhook` option, undefined when the receiver is not configured; the secret comes from
|
|
74
|
-
* `secretFile` or the env var `secretEnv` (default `COURIER_WEBHOOK_SECRET`), never from the option.
|
|
75
|
-
*/
|
|
76
|
-
export declare function readConfig(options: unknown, env?: Record<string, string | undefined>): WebhookConfig | undefined;
|
|
77
|
-
/** Joins this instance to the process's receiver, starting it if there is none; returns the leave. */
|
|
78
|
-
export declare function joinReceiver(config: WebhookConfig, ports: WebhookPorts): () => Promise<void>;
|
|
79
|
-
/** Whether a webhook receiver runs in this server. */
|
|
80
|
-
export declare const receiving: () => Promise<boolean>;
|