opencode-courier 0.2.3 → 0.3.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 +124 -20
- package/dist/bounded.d.ts +2 -4
- package/dist/bounded.js +2 -4
- package/dist/cleanup.d.ts +8 -3
- package/dist/cleanup.js +24 -12
- package/dist/courier.d.ts +116 -11
- package/dist/courier.js +239 -55
- package/dist/group.d.ts +86 -0
- package/dist/group.js +247 -0
- package/dist/hub.d.ts +52 -77
- package/dist/hub.js +68 -65
- package/dist/index.d.ts +4 -8
- package/dist/index.js +27 -20
- package/dist/json.d.ts +4 -0
- package/dist/json.js +11 -0
- package/dist/later.d.ts +6 -6
- package/dist/later.js +6 -5
- package/dist/limits.d.ts +65 -0
- package/dist/limits.js +109 -0
- package/dist/notices.d.ts +125 -15
- package/dist/notices.js +304 -25
- package/dist/question/answer.js +6 -9
- package/dist/question/lifecycle.d.ts +10 -19
- package/dist/question/lifecycle.js +10 -19
- package/dist/question/pure.d.ts +3 -4
- package/dist/question/pure.js +2 -3
- package/dist/question/relay.js +13 -27
- package/dist/question/runtime.d.ts +2 -3
- package/dist/question/runtime.js +2 -3
- package/dist/relay.d.ts +2 -4
- package/dist/relay.js +2 -4
- package/dist/report.d.ts +48 -0
- package/dist/report.js +88 -0
- package/dist/roster.d.ts +29 -17
- package/dist/roster.js +57 -26
- package/dist/skill.d.ts +13 -0
- package/dist/skill.js +9 -0
- package/dist/stop.d.ts +41 -0
- package/dist/stop.js +120 -0
- package/dist/tools.d.ts +20 -0
- package/dist/tools.js +81 -12
- package/dist/tree.d.ts +102 -0
- package/dist/tree.js +58 -0
- package/dist/version.d.ts +4 -10
- package/dist/version.js +4 -10
- package/dist/watch.d.ts +55 -18
- package/dist/watch.js +219 -34
- package/dist/webhook.d.ts +10 -15
- package/dist/webhook.js +11 -20
- package/package.json +1 -1
|
@@ -6,25 +6,18 @@ import type { QuestionPorts } from "../hub.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const shutdownReportedAt: (directory?: string) => number | undefined;
|
|
8
8
|
/**
|
|
9
|
-
* Called for
|
|
10
|
-
*
|
|
11
|
-
* there, since OpenCode 2.0.22). The event's location is optional in the schema; without it,
|
|
12
|
-
* the shutdown counts for every location. `at` is when, by the watcher's clock (epoch milliseconds).
|
|
9
|
+
* Called for `location.shutdown`: the location at `directory` is closing, which withdraws its open
|
|
10
|
+
* forms as if dismissed; with no location it counts for all. `at` is the watcher's clock, epoch ms.
|
|
13
11
|
*/
|
|
14
12
|
export declare function locationClosing(at: number, directory?: string): void;
|
|
15
13
|
/**
|
|
16
|
-
* Whether a dismissal just seen
|
|
17
|
-
*
|
|
18
|
-
* instance unloads, within the grace from now, by the clock of `ports`. A shutdown reported without a location counts for every
|
|
19
|
-
* location, and with the directory unknown, any location's shutdown counts.
|
|
14
|
+
* Whether a dismissal just seen at `directory` was the location closing rather than the person: it
|
|
15
|
+
* shut down within the grace, before or after, or this instance unloads in it; unknown matches any.
|
|
20
16
|
*/
|
|
21
17
|
export declare const closingSoon: (ports: QuestionPorts, loaded: () => boolean, directory: string | undefined) => Effect.Effect<boolean>;
|
|
22
18
|
/**
|
|
23
|
-
* Adds
|
|
24
|
-
*
|
|
25
|
-
* shutdown recorded for it, so a dismissal there is not mistaken for that shutdown. One recorded for
|
|
26
|
-
* every location is left alone, since a dismissal held in another location may still need it; it
|
|
27
|
-
* ages out with the grace.
|
|
19
|
+
* Adds an instance's ports to `loaded`, which other copies tell cut-off questions through, until the
|
|
20
|
+
* returned function is called. A location loading again forgets the shutdown recorded for it alone.
|
|
28
21
|
*/
|
|
29
22
|
export declare function joinRelay(ports: QuestionPorts): () => void;
|
|
30
23
|
/** Called for OpenCode's `form.created`: a question call's form is shown, so its question can be relayed. */
|
|
@@ -37,15 +30,13 @@ export declare function formShown(event: {
|
|
|
37
30
|
};
|
|
38
31
|
}): void;
|
|
39
32
|
/**
|
|
40
|
-
* Called when an event stream may have missed forms
|
|
41
|
-
*
|
|
42
|
-
* lesser harm.
|
|
33
|
+
* Called when an event stream may have missed forms shown: every call still waiting for its form is
|
|
34
|
+
* taken as shown; one still waiting for its permission check is relayed early, the lesser harm.
|
|
43
35
|
*/
|
|
44
36
|
export declare function formsMayHaveBeenMissed(): void;
|
|
45
37
|
/**
|
|
46
|
-
* Called when an instance starts following OpenCode's events
|
|
47
|
-
* so
|
|
48
|
-
* true is returned, for the caller to release them again on its first event, once it is connected.
|
|
38
|
+
* Called when an instance starts following OpenCode's events: a shown form is missed only while none
|
|
39
|
+
* follows, so waiting calls are released; true tells the caller to release again on its first event.
|
|
49
40
|
*/
|
|
50
41
|
export declare function eventsFollowed(): boolean;
|
|
51
42
|
/** Called when an instance's event stream ended or broke. */
|
|
@@ -12,10 +12,8 @@ const shutdownAt = (directory) => directory === undefined
|
|
|
12
12
|
*/
|
|
13
13
|
export const shutdownReportedAt = (directory) => shutdownAt(directory) || undefined;
|
|
14
14
|
/**
|
|
15
|
-
* Called for
|
|
16
|
-
*
|
|
17
|
-
* there, since OpenCode 2.0.22). The event's location is optional in the schema; without it,
|
|
18
|
-
* the shutdown counts for every location. `at` is when, by the watcher's clock (epoch milliseconds).
|
|
15
|
+
* Called for `location.shutdown`: the location at `directory` is closing, which withdraws its open
|
|
16
|
+
* forms as if dismissed; with no location it counts for all. `at` is the watcher's clock, epoch ms.
|
|
19
17
|
*/
|
|
20
18
|
export function locationClosing(at, directory) {
|
|
21
19
|
shared.shutdowns.set(directory ?? ANYWHERE, at);
|
|
@@ -23,10 +21,8 @@ export function locationClosing(at, directory) {
|
|
|
23
21
|
wake();
|
|
24
22
|
}
|
|
25
23
|
/**
|
|
26
|
-
* Whether a dismissal just seen
|
|
27
|
-
*
|
|
28
|
-
* instance unloads, within the grace from now, by the clock of `ports`. A shutdown reported without a location counts for every
|
|
29
|
-
* location, and with the directory unknown, any location's shutdown counts.
|
|
24
|
+
* Whether a dismissal just seen at `directory` was the location closing rather than the person: it
|
|
25
|
+
* shut down within the grace, before or after, or this instance unloads in it; unknown matches any.
|
|
30
26
|
*/
|
|
31
27
|
export const closingSoon = (ports, loaded, directory) => Effect.suspend(() => {
|
|
32
28
|
const ms = ports.timing.dismissalGraceMs;
|
|
@@ -47,11 +43,8 @@ export const closingSoon = (ports, loaded, directory) => Effect.suspend(() => {
|
|
|
47
43
|
}), () => (Deferred.isDoneUnsafe(closed) ? Effect.succeed(true) : Effect.raceFirst(Deferred.await(closed), Effect.as(Effect.sleep(ms), false))), () => Effect.sync(() => shared.closingWaiters.delete(wake)));
|
|
48
44
|
});
|
|
49
45
|
/**
|
|
50
|
-
* Adds
|
|
51
|
-
*
|
|
52
|
-
* shutdown recorded for it, so a dismissal there is not mistaken for that shutdown. One recorded for
|
|
53
|
-
* every location is left alone, since a dismissal held in another location may still need it; it
|
|
54
|
-
* ages out with the grace.
|
|
46
|
+
* Adds an instance's ports to `loaded`, which other copies tell cut-off questions through, until the
|
|
47
|
+
* returned function is called. A location loading again forgets the shutdown recorded for it alone.
|
|
55
48
|
*/
|
|
56
49
|
export function joinRelay(ports) {
|
|
57
50
|
shared.loaded.add(ports);
|
|
@@ -72,9 +65,8 @@ export function formShown(event) {
|
|
|
72
65
|
shared.shown.delete(key);
|
|
73
66
|
}
|
|
74
67
|
/**
|
|
75
|
-
* Called when an event stream may have missed forms
|
|
76
|
-
*
|
|
77
|
-
* lesser harm.
|
|
68
|
+
* Called when an event stream may have missed forms shown: every call still waiting for its form is
|
|
69
|
+
* taken as shown; one still waiting for its permission check is relayed early, the lesser harm.
|
|
78
70
|
*/
|
|
79
71
|
export function formsMayHaveBeenMissed() {
|
|
80
72
|
for (const shown of shared.shown.values())
|
|
@@ -82,9 +74,8 @@ export function formsMayHaveBeenMissed() {
|
|
|
82
74
|
shared.shown.clear();
|
|
83
75
|
}
|
|
84
76
|
/**
|
|
85
|
-
* Called when an instance starts following OpenCode's events
|
|
86
|
-
* so
|
|
87
|
-
* true is returned, for the caller to release them again on its first event, once it is connected.
|
|
77
|
+
* Called when an instance starts following OpenCode's events: a shown form is missed only while none
|
|
78
|
+
* follows, so waiting calls are released; true tells the caller to release again on its first event.
|
|
88
79
|
*/
|
|
89
80
|
export function eventsFollowed() {
|
|
90
81
|
const missed = shared.following === 0 && shared.followed;
|
package/dist/question/pure.d.ts
CHANGED
|
@@ -28,11 +28,10 @@ export type CallExit = Exit.Exit<{
|
|
|
28
28
|
/** How a relayed call that the top session did not answer was settled in the asking session. */
|
|
29
29
|
export declare function settledIn(exit: CallExit): Elsewhere;
|
|
30
30
|
/**
|
|
31
|
-
* Orders strings by
|
|
32
|
-
*
|
|
33
|
-
* compare would not promise: it can rank two different labels as equal and leave them as listed.
|
|
31
|
+
* Orders strings by UTF-16 code units, the default sort's order, stated: it only has to come out the
|
|
32
|
+
* same on both sides of a comparison; a locale-aware compare may rank two different labels as equal.
|
|
34
33
|
*/
|
|
35
|
-
export declare function byCodeUnit(a: string, b: string):
|
|
34
|
+
export declare function byCodeUnit(a: string, b: string): 0 | 1 | -1;
|
|
36
35
|
export declare const choices: (questions: ReadonlyArray<Prompt>) => string;
|
|
37
36
|
export declare const wording: (questions: ReadonlyArray<Prompt>) => string;
|
|
38
37
|
/** The result of a linked call withdrawn because its question was settled without it. */
|
package/dist/question/pure.js
CHANGED
|
@@ -40,9 +40,8 @@ export function settledIn(exit) {
|
|
|
40
40
|
return { by: "failed", error: String(Cause.squash(exit.cause)) };
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Orders strings by
|
|
44
|
-
*
|
|
45
|
-
* compare would not promise: it can rank two different labels as equal and leave them as listed.
|
|
43
|
+
* Orders strings by UTF-16 code units, the default sort's order, stated: it only has to come out the
|
|
44
|
+
* same on both sides of a comparison; a locale-aware compare may rank two different labels as equal.
|
|
46
45
|
*/
|
|
47
46
|
export function byCodeUnit(a, b) {
|
|
48
47
|
if (a === b)
|
package/dist/question/relay.js
CHANGED
|
@@ -8,8 +8,7 @@ import { background, run, within } from "./runtime.js";
|
|
|
8
8
|
import { anyLoaded, isPassing, keyOf, QUESTION_TOOL, shared, storedOf } from "./shared.js";
|
|
9
9
|
/**
|
|
10
10
|
* Tells the top session about a question its closing location cut off, after the closing grace of
|
|
11
|
-
* `ports
|
|
12
|
-
* Without one, the next load tells it.
|
|
11
|
+
* `ports`, through any instance still loaded then; without one, the next load tells it.
|
|
13
12
|
*/
|
|
14
13
|
function tellLater(ports, question) {
|
|
15
14
|
const tellNow = Effect.suspend(() => {
|
|
@@ -44,9 +43,8 @@ const settle = (ports, question, exit, loaded) => Effect.gen(function* () {
|
|
|
44
43
|
// A dismissal is held from now, alongside the wait below, so the grace is not spent waiting.
|
|
45
44
|
const dismissed = Exit.isFailure(exit) && isDismissal(exit.cause);
|
|
46
45
|
const held = dismissed && !unloaded ? yield* Effect.forkChild(closingSoon(ports, loaded, question.directory), { startImmediately: true }) : undefined;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// `told` is false when the top session was not told, or not yet.
|
|
46
|
+
// The top session is told first, for a while only, so what follows neither overtakes the notice
|
|
47
|
+
// nor waits forever on one that never returns; `told` is false when it was not told, or not yet.
|
|
50
48
|
const told = (yield* within(question.relaying, ports.timing.relayWaitMs)) === true;
|
|
51
49
|
// Answered by the top session.
|
|
52
50
|
if (Exit.isSuccess(exit) && exit.value.by === "top") {
|
|
@@ -58,15 +56,8 @@ const settle = (ports, question, exit, loaded) => Effect.gen(function* () {
|
|
|
58
56
|
if (shared.questions.get(question.requestID) !== question) {
|
|
59
57
|
return yield* Effect.promise(() => forget(ports, question));
|
|
60
58
|
}
|
|
61
|
-
// Cut off: the turn was stopped, or
|
|
62
|
-
//
|
|
63
|
-
// plugin there (since 2.0.22) or after (the beta). So a dismissal is held for a moment, to see
|
|
64
|
-
// whether the location's shutdown or this instance's unload follows it. The question stays,
|
|
65
|
-
// answered by message from now on; a top session already asking the person passes their answer
|
|
66
|
-
// on that way. A closing location is not told now: the next load tells it, in this process or
|
|
67
|
-
// after a restart, or an instance still loaded does, a little later, when the process is still
|
|
68
|
-
// running then. The unload is looked at again here: it may have come during the wait above, after
|
|
69
|
-
// a held dismissal had already been taken for the person's.
|
|
59
|
+
// Cut off: the turn was stopped, or the closing location withdrew its forms as if dismissed, so a
|
|
60
|
+
// dismissal is held for a following shutdown or unload; the question stays, answered by message.
|
|
70
61
|
const closing = unloaded || !loaded() || (held !== undefined && (yield* Fiber.join(held)));
|
|
71
62
|
if (Exit.isFailure(exit) && (closing || (!dismissed && Exit.hasInterrupts(exit))))
|
|
72
63
|
return closing ? tellLater(ports, question) : yield* Effect.promise(() => stopped(ports, question));
|
|
@@ -103,9 +94,8 @@ function startSettling(ports, question, exit, loaded) {
|
|
|
103
94
|
});
|
|
104
95
|
}
|
|
105
96
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* withdrawn. Other sessions' calls run unchanged.
|
|
97
|
+
* A spawned session's question call: once its form is shown the top session is told, and whichever
|
|
98
|
+
* answer comes first ends the call, the other side's question withdrawn. Other calls run unchanged.
|
|
109
99
|
*/
|
|
110
100
|
function asking(ports, directory, original) {
|
|
111
101
|
return (input, context) => Effect.gen(function* () {
|
|
@@ -180,9 +170,8 @@ function asking(ports, directory, original) {
|
|
|
180
170
|
});
|
|
181
171
|
}
|
|
182
172
|
/**
|
|
183
|
-
* The waiting question a top session's call asks again, the oldest if several: the same
|
|
184
|
-
*
|
|
185
|
-
* yes-or-no question of the top session would answer a child's.
|
|
173
|
+
* The waiting question a top session's call asks again, the oldest if several: the same question with
|
|
174
|
+
* the same choices, in any order. Choices alone would let an unrelated yes-or-no answer a child's.
|
|
186
175
|
*/
|
|
187
176
|
function linkFor(sessionID, questions) {
|
|
188
177
|
const asked = { choices: choices(questions), wording: wording(questions) };
|
|
@@ -195,9 +184,8 @@ function linkFor(sessionID, questions) {
|
|
|
195
184
|
.sort((a, b) => a.askedAt - b.askedAt)[0];
|
|
196
185
|
}
|
|
197
186
|
/**
|
|
198
|
-
* The result of a top session's question call that no waiting question was linked to: when one
|
|
199
|
-
*
|
|
200
|
-
* the answers on.
|
|
187
|
+
* The result of a top session's question call that no waiting question was linked to: when one waits
|
|
188
|
+
* with the same choices, reworded or asked earlier, it says how to pass the answers on.
|
|
201
189
|
*/
|
|
202
190
|
function unlinkedResult(result, sessionID, questions) {
|
|
203
191
|
if (typeof result.content !== "string")
|
|
@@ -209,10 +197,8 @@ function unlinkedResult(result, sessionID, questions) {
|
|
|
209
197
|
return { ...result, content: `${result.content}\n${unlinkedNote(similar)}` };
|
|
210
198
|
}
|
|
211
199
|
/**
|
|
212
|
-
* After a linked call failed
|
|
213
|
-
*
|
|
214
|
-
* when the question was answered meanwhile; a call stopped while the asking call was cut off as
|
|
215
|
-
* well tells the top session, which nobody has told yet.
|
|
200
|
+
* After a linked call failed with its location still loaded: a dismissal by the person dismisses the
|
|
201
|
+
* child's question too, or is logged if answered meanwhile; a stop while cut off tells the top.
|
|
216
202
|
*/
|
|
217
203
|
const linkedCallFailed = (ports, linked, cause, loaded, directory) => Effect.gen(function* () {
|
|
218
204
|
if (isDismissal(cause)) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Clock, Effect } from "effect";
|
|
2
2
|
/**
|
|
3
|
-
* Runs
|
|
4
|
-
*
|
|
5
|
-
* is interrupted. Interrupting what waits on the promise does not stop the fiber.
|
|
3
|
+
* Runs a relay effect in its own fiber by the relay's clock (`ports.now`): the promise settles as the
|
|
4
|
+
* effect ends, rejecting on failure or interruption; interrupting the waiter does not stop the fiber.
|
|
6
5
|
*/
|
|
7
6
|
export declare function run<A, E>(effect: Effect.Effect<A, E>): Promise<A>;
|
|
8
7
|
/**
|
package/dist/question/runtime.js
CHANGED
|
@@ -38,9 +38,8 @@ let scope = Scope.makeUnsafe();
|
|
|
38
38
|
*/
|
|
39
39
|
const start = (effect) => Effect.runFork(Effect.provideService(effect, Clock.Clock, clock)).pipe(Fiber.runIn(scope));
|
|
40
40
|
/**
|
|
41
|
-
* Runs
|
|
42
|
-
*
|
|
43
|
-
* is interrupted. Interrupting what waits on the promise does not stop the fiber.
|
|
41
|
+
* Runs a relay effect in its own fiber by the relay's clock (`ports.now`): the promise settles as the
|
|
42
|
+
* effect ends, rejecting on failure or interruption; interrupting the waiter does not stop the fiber.
|
|
44
43
|
*/
|
|
45
44
|
export function run(effect) {
|
|
46
45
|
return Effect.runPromise(Fiber.join(start(effect)));
|
package/dist/relay.d.ts
CHANGED
|
@@ -45,10 +45,8 @@ export declare function listEverywhere<D extends Pick<Permissions, "list">>(perm
|
|
|
45
45
|
requests: import("@opencode/client").PermissionRequest[];
|
|
46
46
|
})[]>;
|
|
47
47
|
/**
|
|
48
|
-
* Answers a permission request of a session started, directly or through others, from the
|
|
49
|
-
*
|
|
50
|
-
* started with courier_spawn cannot approve what its own children ask. `answered` is false when
|
|
51
|
-
* no location loaded in this server holds the request, which may then wait in another server.
|
|
48
|
+
* Answers a permission request of a session started, directly or through others, from the caller,
|
|
49
|
+
* which must be the top session; `answered` is false when no location loaded here holds the request.
|
|
52
50
|
*/
|
|
53
51
|
export declare function answer(ports: AnswerPorts, waiting: Waiting, callerID: string, input: AnswerInput): Promise<PermissionAnswered>;
|
|
54
52
|
/** The requests a session waits on, in whichever location holds them, for courier_status. */
|
package/dist/relay.js
CHANGED
|
@@ -19,10 +19,8 @@ async function locate(ports, sessionID, requestID) {
|
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Answers a permission request of a session started, directly or through others, from the
|
|
23
|
-
*
|
|
24
|
-
* started with courier_spawn cannot approve what its own children ask. `answered` is false when
|
|
25
|
-
* no location loaded in this server holds the request, which may then wait in another server.
|
|
22
|
+
* Answers a permission request of a session started, directly or through others, from the caller,
|
|
23
|
+
* which must be the top session; `answered` is false when no location loaded here holds the request.
|
|
26
24
|
*/
|
|
27
25
|
export async function answer(ports, waiting, callerID, input) {
|
|
28
26
|
const reply = input.reply;
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Status } from "./notices.js";
|
|
2
|
+
import type { Storage } from "./storage.js";
|
|
3
|
+
export declare const promptKey: (sessionID: string) => string;
|
|
4
|
+
export declare const settledKey: (sessionID: string) => string;
|
|
5
|
+
export declare const progressKey: (sessionID: string) => string;
|
|
6
|
+
export declare const toldKey: (sessionID: string) => string;
|
|
7
|
+
export declare const stoppedKey: (sessionID: string) => string;
|
|
8
|
+
/** How a session's wait for a report ended: it reported to its parent, or its turn failed or was interrupted. */
|
|
9
|
+
export type Settled = "report" | "failed" | "interrupted";
|
|
10
|
+
/**
|
|
11
|
+
* Notes that a prompt or message reached a spawned session at `at`, in epoch milliseconds, unless a
|
|
12
|
+
* later one is noted: both of the hub's subscriptions note each event, one of them maybe behind.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prompted(storage: Pick<Storage, "get" | "set">, sessionID: string, at: number): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Notes that a spawned session reported to its parent, with `status` when its report carried one, or
|
|
17
|
+
* its turn failed or was interrupted, at `at`, unless a later end is noted.
|
|
18
|
+
*/
|
|
19
|
+
export declare function settled(storage: Pick<Storage, "get" | "set">, sessionID: string, by: Settled, at: number, status?: Status): Promise<void>;
|
|
20
|
+
/** Notes that a spawned session messaged its parent without a status at `at`, which is progress, not its report. */
|
|
21
|
+
export declare function progressed(storage: Pick<Storage, "get" | "set">, sessionID: string, at: number): Promise<void>;
|
|
22
|
+
/** Notes that a spawned session's parent was told, at `at`, that a turn of it ended without a report, unless a later telling is noted. */
|
|
23
|
+
export declare function told(storage: Pick<Storage, "get" | "set">, sessionID: string, at: number): Promise<void>;
|
|
24
|
+
/** Takes back the note `told` made at `at`, whose notice did not go out; a later one stays, and one that cannot be read goes. */
|
|
25
|
+
export declare function untold(storage: Pick<Storage, "get" | "remove">, sessionID: string, at: number): Promise<void>;
|
|
26
|
+
/** Notes that courier_stop stopped a spawned session at `at`, so the failure and silent-end notices stay quiet for it. */
|
|
27
|
+
export declare function stopped(storage: Pick<Storage, "set">, sessionID: string, at: number): Promise<void>;
|
|
28
|
+
/** Whether courier_stop stopped a session after its last prompt, or with none noted, at all. */
|
|
29
|
+
export declare function stoppedSincePrompt(storage: Pick<Storage, "get">, sessionID: string): Promise<boolean>;
|
|
30
|
+
/** The status of a spawned session's last report to its parent, if it has reported with one. */
|
|
31
|
+
export declare function lastStatus(storage: Pick<Storage, "get">, sessionID: string): Promise<Status | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* What is known of a spawned session's report: when a prompt last reached it, and whether since then it owes one, has
|
|
34
|
+
* messaged its parent without a status, or failed or was interrupted. Undefined for one spawned before this was kept.
|
|
35
|
+
*/
|
|
36
|
+
export declare function reportOf(storage: Pick<Storage, "get">, sessionID: string): Promise<{
|
|
37
|
+
prompt: number;
|
|
38
|
+
owes: boolean;
|
|
39
|
+
progressed: boolean;
|
|
40
|
+
ended: boolean;
|
|
41
|
+
} | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Whether its parent still waits on a spawned session's report: it owes one, and the parent has not
|
|
44
|
+
* been told since its last prompt that it ended a turn without one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function awaited(storage: Pick<Storage, "get">, sessionID: string): Promise<boolean>;
|
|
47
|
+
/** Drops what is kept about a session, with its roster entry. */
|
|
48
|
+
export declare function forgetReport(storage: Pick<Storage, "remove">, sessionID: string): Promise<void>;
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { obj } from "./json.js";
|
|
2
|
+
import { isStatus } from "./notices.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a spawned session has reported since its last prompt, under `report/<sessionID>/`: `prompt`, `settled` (with
|
|
5
|
+
* the report's status), `progress` (a message to its parent without a status) and `told`; docs/reference.md has each.
|
|
6
|
+
*/
|
|
7
|
+
const PREFIX = "report/";
|
|
8
|
+
export const promptKey = (sessionID) => `${PREFIX}${sessionID}/prompt`;
|
|
9
|
+
export const settledKey = (sessionID) => `${PREFIX}${sessionID}/settled`;
|
|
10
|
+
export const progressKey = (sessionID) => `${PREFIX}${sessionID}/progress`;
|
|
11
|
+
export const toldKey = (sessionID) => `${PREFIX}${sessionID}/told`;
|
|
12
|
+
export const stoppedKey = (sessionID) => `${PREFIX}${sessionID}/stopped`;
|
|
13
|
+
const timeOf = (value) => {
|
|
14
|
+
const at = obj(value).at;
|
|
15
|
+
return typeof at === "number" && Number.isFinite(at) ? at : undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Notes that a prompt or message reached a spawned session at `at`, in epoch milliseconds, unless a
|
|
19
|
+
* later one is noted: both of the hub's subscriptions note each event, one of them maybe behind.
|
|
20
|
+
*/
|
|
21
|
+
export async function prompted(storage, sessionID, at) {
|
|
22
|
+
if ((timeOf(await storage.get(promptKey(sessionID))) ?? -Infinity) < at)
|
|
23
|
+
await storage.set(promptKey(sessionID), { at });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Notes that a spawned session reported to its parent, with `status` when its report carried one, or
|
|
27
|
+
* its turn failed or was interrupted, at `at`, unless a later end is noted.
|
|
28
|
+
*/
|
|
29
|
+
export async function settled(storage, sessionID, by, at, status) {
|
|
30
|
+
if ((timeOf(await storage.get(settledKey(sessionID))) ?? -Infinity) <= at)
|
|
31
|
+
await storage.set(settledKey(sessionID), { at, by, ...(status ? { status } : {}) });
|
|
32
|
+
}
|
|
33
|
+
/** Notes that a spawned session messaged its parent without a status at `at`, which is progress, not its report. */
|
|
34
|
+
export async function progressed(storage, sessionID, at) {
|
|
35
|
+
if ((timeOf(await storage.get(progressKey(sessionID))) ?? -Infinity) <= at)
|
|
36
|
+
await storage.set(progressKey(sessionID), { at });
|
|
37
|
+
}
|
|
38
|
+
/** Notes that a spawned session's parent was told, at `at`, that a turn of it ended without a report, unless a later telling is noted. */
|
|
39
|
+
export async function told(storage, sessionID, at) {
|
|
40
|
+
if ((timeOf(await storage.get(toldKey(sessionID))) ?? -Infinity) <= at)
|
|
41
|
+
await storage.set(toldKey(sessionID), { at });
|
|
42
|
+
}
|
|
43
|
+
/** Takes back the note `told` made at `at`, whose notice did not go out; a later one stays, and one that cannot be read goes. */
|
|
44
|
+
export async function untold(storage, sessionID, at) {
|
|
45
|
+
const stored = await storage.get(toldKey(sessionID)).then(timeOf, () => at);
|
|
46
|
+
if (stored === at)
|
|
47
|
+
await storage.remove(toldKey(sessionID));
|
|
48
|
+
}
|
|
49
|
+
/** Whether `at` is known and no earlier than the prompt at `prompt`. */
|
|
50
|
+
const since = (at, prompt) => at !== undefined && at >= prompt;
|
|
51
|
+
/** Notes that courier_stop stopped a spawned session at `at`, so the failure and silent-end notices stay quiet for it. */
|
|
52
|
+
export async function stopped(storage, sessionID, at) {
|
|
53
|
+
await storage.set(stoppedKey(sessionID), { at });
|
|
54
|
+
}
|
|
55
|
+
/** Whether courier_stop stopped a session after its last prompt, or with none noted, at all. */
|
|
56
|
+
export async function stoppedSincePrompt(storage, sessionID) {
|
|
57
|
+
const [stoppedAt, prompt] = await Promise.all([stoppedKey, promptKey].map((key) => storage.get(key(sessionID)).then(timeOf)));
|
|
58
|
+
return stoppedAt !== undefined && (prompt === undefined || stoppedAt >= prompt);
|
|
59
|
+
}
|
|
60
|
+
/** The status of a spawned session's last report to its parent, if it has reported with one. */
|
|
61
|
+
export async function lastStatus(storage, sessionID) {
|
|
62
|
+
const status = obj(await storage.get(settledKey(sessionID))).status;
|
|
63
|
+
return isStatus(status) ? status : undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What is known of a spawned session's report: when a prompt last reached it, and whether since then it owes one, has
|
|
67
|
+
* messaged its parent without a status, or failed or was interrupted. Undefined for one spawned before this was kept.
|
|
68
|
+
*/
|
|
69
|
+
export async function reportOf(storage, sessionID) {
|
|
70
|
+
const [prompt, settledValue, progress] = await Promise.all([promptKey, settledKey, progressKey].map((key) => storage.get(key(sessionID))));
|
|
71
|
+
const promptAt = timeOf(prompt);
|
|
72
|
+
if (promptAt === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const done = since(timeOf(settledValue), promptAt);
|
|
75
|
+
return { prompt: promptAt, owes: !done, progressed: since(timeOf(progress), promptAt), ended: done && obj(settledValue).by !== "report" };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether its parent still waits on a spawned session's report: it owes one, and the parent has not
|
|
79
|
+
* been told since its last prompt that it ended a turn without one.
|
|
80
|
+
*/
|
|
81
|
+
export async function awaited(storage, sessionID) {
|
|
82
|
+
const [prompt, settledAt, toldAt] = await Promise.all([promptKey, settledKey, toldKey].map((key) => storage.get(key(sessionID)).then(timeOf)));
|
|
83
|
+
return prompt !== undefined && !since(settledAt, prompt) && !since(toldAt, prompt);
|
|
84
|
+
}
|
|
85
|
+
/** Drops what is kept about a session, with its roster entry. */
|
|
86
|
+
export async function forgetReport(storage, sessionID) {
|
|
87
|
+
await Promise.all([promptKey, settledKey, progressKey, toldKey, stoppedKey].map((key) => storage.remove(key(sessionID))));
|
|
88
|
+
}
|
package/dist/roster.d.ts
CHANGED
|
@@ -18,11 +18,14 @@ export interface RosterEntry {
|
|
|
18
18
|
readonly project?: string;
|
|
19
19
|
/** For an isolated child, the commit its worktree was made from; its own work is what came after. */
|
|
20
20
|
readonly base?: string;
|
|
21
|
+
/** `"status"` when its brief asked for a report with a status; absent for a child an earlier release briefed. */
|
|
22
|
+
readonly reports?: "status";
|
|
23
|
+
/** The join group courier_spawn put it in, whose release holds its report; absent for a child in none. */
|
|
24
|
+
readonly group?: string;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
|
-
* What the reverse index holds for a child: the sessions above it, its parent first and the
|
|
24
|
-
*
|
|
25
|
-
* of the others is the parent of the one before it.
|
|
27
|
+
* What the reverse index holds for a child: the sessions above it, its parent first and the top
|
|
28
|
+
* session, which courier_spawn did not start, last; each is the parent of the one before it.
|
|
26
29
|
*/
|
|
27
30
|
export interface ReverseEntry {
|
|
28
31
|
readonly ancestors: ReadonlyArray<string>;
|
|
@@ -36,10 +39,12 @@ export declare function reverseKey(sessionID: string): string;
|
|
|
36
39
|
*/
|
|
37
40
|
export declare function record(storage: RosterStorage, entry: RosterEntry): Promise<void>;
|
|
38
41
|
/**
|
|
39
|
-
* Removes a child's roster entry
|
|
40
|
-
* reverse key left behind leads nowhere, and the next load drops it.
|
|
42
|
+
* Removes a child's roster entry, its reverse key, its report state and, given its group, its membership. Only
|
|
43
|
+
* the entry's removal can fail it: a reverse key left behind leads nowhere, and the next load drops it.
|
|
41
44
|
*/
|
|
42
|
-
export declare function remove(storage: RosterStorage, parentID: string, sessionID: string): Promise<
|
|
45
|
+
export declare function remove(storage: RosterStorage, parentID: string, sessionID: string, group?: string): Promise<{
|
|
46
|
+
droppedFromGroup: boolean;
|
|
47
|
+
}>;
|
|
43
48
|
/** Removes a child from its parent's roster; false when it was not there. */
|
|
44
49
|
export declare function forget(storage: RosterStorage, parentID: string, sessionID: string): Promise<boolean>;
|
|
45
50
|
/** A parent's children, oldest first. */
|
|
@@ -48,19 +53,28 @@ export declare function children(storage: RosterStorage, parentID: string): Prom
|
|
|
48
53
|
export declare function allEntries(storage: RosterStorage): Promise<RosterEntry[]>;
|
|
49
54
|
/** A session's roster entries: one for the parent that started it, none if courier_spawn did not. */
|
|
50
55
|
export declare function entriesOf(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
|
|
56
|
+
/** The session that started a session, by the reverse index alone, without scanning the roster. */
|
|
57
|
+
export declare function indexedParent(storage: RosterStorage, sessionID: string): Promise<string | undefined>;
|
|
58
|
+
/** A session's roster entry under a parent, if it is there. */
|
|
59
|
+
export declare function entryUnder(storage: RosterStorage, parentID: string, sessionID: string): Promise<RosterEntry | undefined>;
|
|
60
|
+
/** A session's roster entry, by the reverse index alone, without scanning the roster. */
|
|
61
|
+
export declare function indexedEntry(storage: RosterStorage, sessionID: string): Promise<RosterEntry | undefined>;
|
|
51
62
|
/**
|
|
52
|
-
* The roster entries from a session up to the top, its own first
|
|
53
|
-
*
|
|
54
|
-
* did not start the session.
|
|
63
|
+
* The roster entries from a session up to the top, its own first, ending with the entry whose parent
|
|
64
|
+
* courier_spawn did not start; empty when courier_spawn did not start the session.
|
|
55
65
|
*/
|
|
56
66
|
export declare function lineage(storage: RosterStorage, sessionID: string): Promise<RosterEntry[]>;
|
|
67
|
+
/** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
|
|
68
|
+
export declare function indexedLineage(storage: RosterStorage, sessionID: string): Promise<never[] | RosterEntry[]>;
|
|
57
69
|
/** `lineage` over roster entries already read. */
|
|
58
70
|
export declare function lineageIn(entries: ReadonlyArray<RosterEntry>, sessionID: string): RosterEntry[];
|
|
71
|
+
/** Roster entries by their session, for `lineageBy`. */
|
|
72
|
+
export declare function bySession(entries: ReadonlyArray<RosterEntry>): Map<string, RosterEntry>;
|
|
73
|
+
/** `lineage` over roster entries already read and mapped by `bySession`. */
|
|
74
|
+
export declare function lineageBy(bySession: ReadonlyMap<string, RosterEntry>, sessionID: string): RosterEntry[];
|
|
59
75
|
/**
|
|
60
|
-
* The
|
|
61
|
-
*
|
|
62
|
-
* session was not started with courier_spawn or `callerID` is another session. `what` names
|
|
63
|
-
* what is being answered.
|
|
76
|
+
* The top of a spawned session's lineage, where its permission requests and questions go, since the
|
|
77
|
+
* person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
|
|
64
78
|
*/
|
|
65
79
|
export declare function answeringTop(storage: RosterStorage, sessionID: string, callerID: string, what: string): Promise<string>;
|
|
66
80
|
/** Whether a directory still exists; tests pass a fake. */
|
|
@@ -73,9 +87,7 @@ export declare function current(storage: RosterStorage, parentID: string, now: n
|
|
|
73
87
|
*/
|
|
74
88
|
export declare function pruneExpired(storage: RosterStorage, now: number, exists?: Exists): Promise<void>;
|
|
75
89
|
/**
|
|
76
|
-
* Writes the reverse keys missing for the given entries, or
|
|
77
|
-
*
|
|
78
|
-
* entry an older copy removed. Keys that lead to their entry are left as they are, so running it
|
|
79
|
-
* again changes nothing.
|
|
90
|
+
* Writes the reverse keys missing or stale for the given entries, or the whole roster, and drops those
|
|
91
|
+
* whose entry is gone, as an older copy's writes and removals leave them; a rerun changes nothing.
|
|
80
92
|
*/
|
|
81
93
|
export declare function backfill(storage: RosterStorage, entries: ReadonlyArray<RosterEntry>): Promise<void>;
|