opencode-courier 0.2.3 → 0.4.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 +198 -193
- package/dist/index.d.ts +4 -8
- package/dist/index.js +66 -85
- package/dist/kernel/protocol.d.ts +20 -0
- package/dist/kernel/protocol.js +36 -0
- package/dist/kernel/tool.d.ts +5 -0
- package/dist/kernel/tool.js +5 -0
- package/dist/person/events.d.ts +23 -0
- package/dist/person/events.js +113 -0
- package/dist/person/form.d.ts +26 -0
- package/dist/person/form.js +64 -0
- package/dist/person/index.d.ts +17 -0
- package/dist/person/index.js +5 -0
- package/dist/{relay.d.ts → person/permission.d.ts} +29 -25
- package/dist/{relay.js → person/permission.js} +11 -9
- package/dist/person/ports.d.ts +58 -0
- package/dist/person/ports.js +2 -0
- package/dist/{question → person/question}/answer.d.ts +33 -4
- package/dist/{question → person/question}/answer.js +49 -17
- package/dist/person/question/index.d.ts +7 -0
- package/dist/{question → person/question}/index.js +3 -3
- package/dist/person/question/lifecycle.d.ts +43 -0
- package/dist/{question → person/question}/lifecycle.js +10 -19
- package/dist/{question → person/question}/pure.d.ts +4 -6
- package/dist/{question → person/question}/pure.js +3 -4
- package/dist/{question → person/question}/relay.d.ts +1 -1
- package/dist/{question → person/question}/relay.js +27 -35
- package/dist/{question → person/question}/runtime.d.ts +2 -3
- package/dist/{question → person/question}/runtime.js +2 -3
- package/dist/{question → person/question}/shared.d.ts +13 -3
- package/dist/person/question/shared.js +52 -0
- package/dist/person/question/types.d.ts +121 -0
- package/dist/person/question/types.js +1 -0
- package/dist/person/rpc-handlers.d.ts +160 -0
- package/dist/person/rpc-handlers.js +62 -0
- package/dist/person/rpc.d.ts +227 -0
- package/dist/person/rpc.js +70 -0
- package/dist/person/text.d.ts +68 -0
- package/dist/{notices.js → person/text.js} +53 -190
- package/dist/person/tools.d.ts +20 -0
- package/dist/person/tools.js +55 -0
- package/dist/person/tui.d.ts +46 -0
- package/dist/platform/bounded.d.ts +12 -0
- package/dist/{bounded.js → platform/bounded.js} +6 -4
- package/dist/platform/events.d.ts +34 -0
- package/dist/platform/events.js +121 -0
- package/dist/platform/hub.d.ts +195 -0
- package/dist/{hub.js → platform/hub.js} +78 -119
- package/dist/platform/json.d.ts +8 -0
- package/dist/platform/json.js +15 -0
- package/dist/platform/version.d.ts +16 -0
- package/dist/{version.js → platform/version.js} +5 -11
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.js +3 -0
- package/dist/scheduling/index.d.ts +6 -0
- package/dist/scheduling/index.js +5 -0
- package/dist/{later.d.ts → scheduling/later.d.ts} +13 -9
- package/dist/{later.js → scheduling/later.js} +18 -9
- package/dist/scheduling/text.d.ts +6 -0
- package/dist/scheduling/text.js +10 -0
- package/dist/scheduling/tools.d.ts +14 -0
- package/dist/scheduling/tools.js +48 -0
- package/dist/skill.d.ts +17 -0
- package/dist/skill.js +62 -0
- package/dist/{cleanup.d.ts → tree/cleanup.d.ts} +5 -26
- package/dist/{cleanup.js → tree/cleanup.js} +20 -15
- package/dist/tree/context.d.ts +15 -0
- package/dist/tree/context.js +34 -0
- package/dist/tree/domain.d.ts +101 -0
- package/dist/tree/domain.js +68 -0
- package/dist/tree/events.d.ts +71 -0
- package/dist/tree/events.js +204 -0
- package/dist/tree/forget.d.ts +24 -0
- package/dist/tree/forget.js +59 -0
- package/dist/tree/group.d.ts +82 -0
- package/dist/tree/group.js +203 -0
- package/dist/tree/index.d.ts +10 -0
- package/dist/tree/index.js +11 -0
- package/dist/tree/limits.d.ts +10 -0
- package/dist/tree/limits.js +60 -0
- package/dist/tree/panel.d.ts +13 -0
- package/dist/tree/panel.js +75 -0
- package/dist/tree/ports.d.ts +276 -0
- package/dist/tree/ports.js +1 -0
- package/dist/tree/report.d.ts +23 -0
- package/dist/tree/report.js +33 -0
- package/dist/tree/roster.d.ts +27 -0
- package/dist/tree/roster.js +62 -0
- package/dist/tree/rpc-handlers.d.ts +201 -0
- package/dist/tree/rpc-handlers.js +20 -0
- package/dist/tree/rpc.d.ts +344 -0
- package/dist/tree/rpc.js +83 -0
- package/dist/tree/send.d.ts +14 -0
- package/dist/tree/send.js +99 -0
- package/dist/tree/spawn.d.ts +21 -0
- package/dist/tree/spawn.js +173 -0
- package/dist/tree/status.d.ts +128 -0
- package/dist/tree/status.js +62 -0
- package/dist/tree/stop.d.ts +33 -0
- package/dist/tree/stop.js +116 -0
- package/dist/tree/store.d.ts +24 -0
- package/dist/tree/store.js +264 -0
- package/dist/tree/subtree.d.ts +101 -0
- package/dist/tree/subtree.js +60 -0
- package/dist/tree/summary.d.ts +7 -0
- package/dist/tree/summary.js +35 -0
- package/dist/tree/text.d.ts +77 -0
- package/dist/tree/text.js +287 -0
- package/dist/tree/tools.d.ts +54 -0
- package/dist/tree/tools.js +167 -0
- package/dist/tree/tui.d.ts +53 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +1219 -0
- package/dist/webhook/index.d.ts +5 -0
- package/dist/webhook/index.js +3 -0
- package/dist/{webhook.d.ts → webhook/receiver.d.ts} +25 -36
- package/dist/{webhook.js → webhook/receiver.js} +66 -67
- package/dist/webhook/subscriptions.d.ts +29 -0
- package/dist/webhook/subscriptions.js +52 -0
- package/dist/webhook/text.d.ts +23 -0
- package/dist/webhook/text.js +82 -0
- package/dist/webhook/tools.d.ts +19 -0
- package/dist/webhook/tools.js +46 -0
- package/package.json +18 -7
- package/dist/bounded.d.ts +0 -10
- package/dist/courier.d.ts +0 -95
- package/dist/courier.js +0 -131
- package/dist/hub.d.ts +0 -325
- package/dist/json.d.ts +0 -4
- package/dist/json.js +0 -4
- package/dist/notices.d.ts +0 -246
- package/dist/question/index.d.ts +0 -6
- package/dist/question/lifecycle.d.ts +0 -52
- package/dist/question/shared.js +0 -24
- package/dist/roster.d.ts +0 -81
- package/dist/roster.js +0 -175
- package/dist/tools.d.ts +0 -74
- package/dist/tools.js +0 -228
- package/dist/version.d.ts +0 -22
- package/dist/watch.d.ts +0 -61
- package/dist/watch.js +0 -289
- /package/dist/{storage.d.ts → platform/storage.d.ts} +0 -0
- /package/dist/{storage.js → platform/storage.js} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode/plugin";
|
|
2
|
+
import type { FormsTold } from "../platform/hub.js";
|
|
3
|
+
import type { RosterEntry, RosterStorage } from "../tree/index.js";
|
|
4
|
+
import type { Waiting } from "./permission.js";
|
|
5
|
+
export type { PermissionAnswered, PermissionRequest, Reply } from "./permission.js";
|
|
6
|
+
export type { Asked, Elsewhere, Outcome, Prompt, QuestionAnswered, Withdrawal } from "./question/index.js";
|
|
7
|
+
type Context = Plugin.Context;
|
|
8
|
+
/** What the person events need of a member's `watch` ports, which are a `WatchPorts` at runtime. */
|
|
9
|
+
export interface PersonPorts {
|
|
10
|
+
readonly storage: RosterStorage;
|
|
11
|
+
readonly session: Pick<Context["session"], "synthetic">;
|
|
12
|
+
readonly event: Pick<Context["event"], "subscribe">;
|
|
13
|
+
/**
|
|
14
|
+
* The permission domains of every loaded location, whose pending requests are relayed when the
|
|
15
|
+
* watcher (re)subscribes: one watcher serves them all, and an isolated child's are in its worktree's.
|
|
16
|
+
*/
|
|
17
|
+
readonly permissions: () => Iterable<Pick<Context["permission"], "list">>;
|
|
18
|
+
/** The clock a location shutdown is recorded by: the question relay's, which judges it. */
|
|
19
|
+
readonly now: () => number;
|
|
20
|
+
readonly log: (message: string) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What every plugin instance in the process shares of the person events: the handled event ids, shared
|
|
24
|
+
* with the tree's, and the requests and forms the top sessions were told about or that were answered first.
|
|
25
|
+
*/
|
|
26
|
+
export interface PersonState {
|
|
27
|
+
readonly seen: Set<string>;
|
|
28
|
+
readonly waiting: Waiting;
|
|
29
|
+
readonly answered: Set<string>;
|
|
30
|
+
readonly forms: FormsTold;
|
|
31
|
+
}
|
|
32
|
+
/** The session at the top of a lineage: where its permission requests go, since that is where the person is. */
|
|
33
|
+
export declare const topOf: (chain: RosterEntry[]) => string;
|
|
34
|
+
/** One field of an OpenCode form, as much of it as a notice shows. */
|
|
35
|
+
export interface FormField {
|
|
36
|
+
readonly key: string;
|
|
37
|
+
readonly type: string;
|
|
38
|
+
readonly title?: string;
|
|
39
|
+
readonly description?: string;
|
|
40
|
+
readonly options?: ReadonlyArray<{
|
|
41
|
+
readonly value: string;
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly description?: string;
|
|
44
|
+
}>;
|
|
45
|
+
readonly url?: string;
|
|
46
|
+
/** Not shown to the person. */
|
|
47
|
+
readonly hidden?: boolean;
|
|
48
|
+
/** Conditions on earlier answers; the field is shown only when they hold. */
|
|
49
|
+
readonly when?: ReadonlyArray<unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** An OpenCode form, as its `form.created` event carries it. */
|
|
52
|
+
export interface Form {
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly sessionID: string;
|
|
55
|
+
readonly title: string;
|
|
56
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
57
|
+
readonly fields: ReadonlyArray<FormField>;
|
|
58
|
+
}
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import type { QuestionPorts } from "
|
|
3
|
-
import { type
|
|
4
|
-
import { type RosterStorage } from "
|
|
2
|
+
import type { Answers, Asked, Outcome, Prompt, QuestionPorts, Withdrawal } from "./types.js";
|
|
3
|
+
import { type CutOff } from "../text.js";
|
|
4
|
+
import { type RosterStorage } from "../../tree/index.js";
|
|
5
5
|
import { type QuestionAnswerInput } from "./pure.js";
|
|
6
|
+
/** What answering a question did; `answered` is false when it no longer waited. */
|
|
7
|
+
export type QuestionAnswered = {
|
|
8
|
+
readonly sessionID: string;
|
|
9
|
+
readonly requestID: string;
|
|
10
|
+
readonly answered: false;
|
|
11
|
+
} | {
|
|
12
|
+
readonly sessionID: string;
|
|
13
|
+
readonly requestID: string;
|
|
14
|
+
readonly answered: true;
|
|
15
|
+
readonly by: "result" | "message";
|
|
16
|
+
readonly answers: Answers;
|
|
17
|
+
};
|
|
6
18
|
export declare function tell(ports: QuestionPorts, asked: Asked, text: string, attributes: Record<string, string>): Promise<void>;
|
|
7
19
|
/** Tells the top session that a question's call was cut off, once per question and process. */
|
|
8
20
|
export declare function tellCutOff(ports: QuestionPorts, asked: Asked, cutOff: CutOff): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* What a linked call of the top session is withdrawn with once an answer is passed on, `withdrawal`, and
|
|
23
|
+
* whether one was: `linked`, set once it is withdrawn.
|
|
24
|
+
*/
|
|
25
|
+
export interface Linked {
|
|
26
|
+
readonly withdrawal: Withdrawal;
|
|
27
|
+
linked: boolean;
|
|
28
|
+
}
|
|
9
29
|
/**
|
|
10
30
|
* Passes the top session's answer, or the person's dismissal, to a question: as the result of the
|
|
11
31
|
* call that waits on it, or as a message when that call was cut off. Undefined when it was settled.
|
|
12
32
|
*/
|
|
13
|
-
export declare const deliver: (ports: QuestionPorts, asked: Asked, outcome: Outcome) => Effect.Effect<"result" | "message" | undefined, unknown>;
|
|
33
|
+
export declare const deliver: (ports: QuestionPorts, asked: Asked, outcome: Outcome, given?: Linked) => Effect.Effect<"result" | "message" | undefined, unknown>;
|
|
14
34
|
/**
|
|
15
35
|
* Drops a stored question whose answer went out. If that fails, it is marked answered instead, so
|
|
16
36
|
* it is neither listed nor answered again, nor told about after a restart.
|
|
@@ -21,6 +41,15 @@ export declare function forget(ports: QuestionPorts, asked: Asked): Promise<void
|
|
|
21
41
|
* be the session at the top, as for permission requests. `answered` is false when it no longer waits.
|
|
22
42
|
*/
|
|
23
43
|
export declare function answerQuestion(ports: QuestionPorts, callerID: string, input: QuestionAnswerInput): Promise<QuestionAnswered>;
|
|
44
|
+
/**
|
|
45
|
+
* Answers a question for the person, from the TUI's inbox, as courier_answer does; the top session hears it once, as
|
|
46
|
+
* answered by the person: its linked call is withdrawn, or else, if it was told of it, it is told settled.
|
|
47
|
+
*/
|
|
48
|
+
export declare function answerForPerson(ports: QuestionPorts, input: QuestionAnswerInput): Promise<QuestionAnswered>;
|
|
49
|
+
/** Whether a session waits on a question of its own, open or cut off and not yet answered. */
|
|
50
|
+
export declare function asksQuestion(ports: {
|
|
51
|
+
readonly storage: RosterStorage;
|
|
52
|
+
}, sessionID: string): Promise<boolean>;
|
|
24
53
|
/** The questions a session waits on, for courier_status; `stopped` when its call was cut off. */
|
|
25
54
|
export declare function pendingQuestions(storage: RosterStorage, sessionID: string): Promise<{
|
|
26
55
|
type: "question";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Deferred, Effect, Semaphore } from "effect";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { envelope } from "../../kernel/protocol.js";
|
|
3
|
+
import { cutOffAnswer, questionNotice, questionSettledNotice } from "../text.js";
|
|
4
|
+
import { allEntries, answeringTop, lineageIn, RETENTION_MS } from "../../tree/index.js";
|
|
5
|
+
import { scanAll } from "../../platform/storage.js";
|
|
5
6
|
import { within, run } from "./runtime.js";
|
|
6
7
|
import { normalize } from "./pure.js";
|
|
7
8
|
import { claim, isPassing, keyOf, MAX_STORED, PREFIX, shared, storedOf } from "./shared.js";
|
|
@@ -54,12 +55,12 @@ const holding = (id, ms, effect) => Effect.acquireUseRelease(Effect.sync(() => {
|
|
|
54
55
|
* Passes the top session's answer, or the person's dismissal, to a question: as the result of the
|
|
55
56
|
* call that waits on it, or as a message when that call was cut off. Undefined when it was settled.
|
|
56
57
|
*/
|
|
57
|
-
export const deliver = (ports, asked, outcome) => Effect.suspend(() => {
|
|
58
|
+
export const deliver = (ports, asked, outcome, given) => Effect.suspend(() => {
|
|
59
|
+
const linked = given ?? { withdrawal: { by: "top", outcome }, linked: false };
|
|
58
60
|
const id = asked.requestID;
|
|
59
61
|
const wait = ports.timing.passingWaitMs;
|
|
60
|
-
// One answer at a time, and once, per question in this process
|
|
61
|
-
// atomically
|
|
62
|
-
// While one is under way, nobody is told that the question was cut off, nor links to it.
|
|
62
|
+
// One answer at a time, and once, per question in this process, since storage cannot be claimed
|
|
63
|
+
// atomically: a second waits, going only if the first did not; meanwhile no cut-off notice or link.
|
|
63
64
|
const since = ports.now();
|
|
64
65
|
const left = () => wait - (ports.now() - since);
|
|
65
66
|
const turn = holding(id, wait, Effect.gen(function* () {
|
|
@@ -71,11 +72,10 @@ export const deliver = (ports, asked, outcome) => Effect.suspend(() => {
|
|
|
71
72
|
}
|
|
72
73
|
if (shared.answered.has(id))
|
|
73
74
|
return undefined;
|
|
74
|
-
const passing = passOn(ports, asked, outcome);
|
|
75
|
+
const passing = passOn(ports, asked, outcome, linked);
|
|
75
76
|
shared.passing.set(id, passing);
|
|
76
|
-
// Held while it is passed on
|
|
77
|
-
//
|
|
78
|
-
// child is told twice.
|
|
77
|
+
// Held while it is passed on; one that hangs on a notice that never returns gives way after a
|
|
78
|
+
// while, so the question can be answered again, and the child may then be told twice.
|
|
79
79
|
yield* within(passing, wait).pipe(Effect.ensuring(Effect.sync(() => {
|
|
80
80
|
if (shared.passing.get(id) === passing)
|
|
81
81
|
shared.passing.delete(id);
|
|
@@ -84,7 +84,14 @@ export const deliver = (ports, asked, outcome) => Effect.suspend(() => {
|
|
|
84
84
|
}));
|
|
85
85
|
return Effect.flatMap(turn, (passing) => passing ? Effect.tryPromise({ try: () => passing, catch: (error) => error }) : Effect.succeed(undefined));
|
|
86
86
|
});
|
|
87
|
-
|
|
87
|
+
/** Withdraws the top session's call linked to a question, if any, now that it is settled. */
|
|
88
|
+
function withdraw(known, linked) {
|
|
89
|
+
if (!known.link)
|
|
90
|
+
return;
|
|
91
|
+
linked.linked = true;
|
|
92
|
+
known.link(linked.withdrawal);
|
|
93
|
+
}
|
|
94
|
+
async function passOn(ports, asked, outcome, linked) {
|
|
88
95
|
const id = asked.requestID;
|
|
89
96
|
let known = shared.questions.get(id);
|
|
90
97
|
// Taken only if the call ends with it: one already ending some other way, its turn being
|
|
@@ -93,12 +100,11 @@ async function passOn(ports, asked, outcome) {
|
|
|
93
100
|
claim(shared.answered, id);
|
|
94
101
|
shared.questions.delete(id);
|
|
95
102
|
// A call of the top session linked to it, asking the person, is withdrawn: it is settled.
|
|
96
|
-
known
|
|
103
|
+
withdraw(known, linked);
|
|
97
104
|
return "result";
|
|
98
105
|
}
|
|
99
|
-
// Its call
|
|
100
|
-
//
|
|
101
|
-
// question whose record could not be written is missing from.
|
|
106
|
+
// Its call just ended: whether it was cut off and stays or was settled and is dropped is known once
|
|
107
|
+
// settle is through, from the registry, not storage, which lacks a question whose write failed.
|
|
102
108
|
if (known) {
|
|
103
109
|
await known.settling;
|
|
104
110
|
if (shared.questions.get(id) !== known)
|
|
@@ -117,7 +123,7 @@ async function passOn(ports, asked, outcome) {
|
|
|
117
123
|
// linked call, if any, goes then too, not before the answer is out.
|
|
118
124
|
claim(shared.answered, id);
|
|
119
125
|
shared.questions.delete(id);
|
|
120
|
-
known
|
|
126
|
+
withdraw(known, linked);
|
|
121
127
|
await forget(ports, asked);
|
|
122
128
|
return "message";
|
|
123
129
|
}
|
|
@@ -158,6 +164,32 @@ export async function answerQuestion(ports, callerID, input) {
|
|
|
158
164
|
const by = await run(deliver(ports, asked, { answers }));
|
|
159
165
|
return by ? { sessionID, requestID, answered: true, by, answers } : { sessionID, requestID, answered: false };
|
|
160
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Answers a question for the person, from the TUI's inbox, as courier_answer does; the top session hears it once, as
|
|
169
|
+
* answered by the person: its linked call is withdrawn, or else, if it was told of it, it is told settled.
|
|
170
|
+
*/
|
|
171
|
+
export async function answerForPerson(ports, input) {
|
|
172
|
+
const { sessionID, requestID } = input;
|
|
173
|
+
const known = shared.questions.get(requestID);
|
|
174
|
+
const asked = known ?? (await ports.storage.get(keyOf(requestID)));
|
|
175
|
+
if (asked?.sessionID !== sessionID || asked.answered)
|
|
176
|
+
return { sessionID, requestID, answered: false };
|
|
177
|
+
const answers = normalize(asked, input.answers);
|
|
178
|
+
const settled = { by: "child", answers, via: "inbox" };
|
|
179
|
+
const linked = { withdrawal: settled, linked: false };
|
|
180
|
+
const by = await run(deliver(ports, asked, { answers }, linked));
|
|
181
|
+
if (!by)
|
|
182
|
+
return { sessionID, requestID, answered: false };
|
|
183
|
+
// A question whose notice did not go out is not settled with one either; a stored one was told when cut off.
|
|
184
|
+
const told = !known?.relaying || (await run(within(known.relaying, ports.timing.relayWaitMs))) === true;
|
|
185
|
+
if (!linked.linked && told)
|
|
186
|
+
await tell(ports, asked, questionSettledNotice(asked, settled), { answered: "elsewhere", request: requestID }).catch((error) => ports.log(`courier question: could not tell ${asked.top} that ${requestID} is settled: ${String(error)}`));
|
|
187
|
+
return { sessionID, requestID, answered: true, by, answers };
|
|
188
|
+
}
|
|
189
|
+
/** Whether a session waits on a question of its own, open or cut off and not yet answered. */
|
|
190
|
+
export async function asksQuestion(ports, sessionID) {
|
|
191
|
+
return (await pendingQuestions(ports.storage, sessionID)).length > 0;
|
|
192
|
+
}
|
|
161
193
|
/** The questions a session waits on, for courier_status; `stopped` when its call was cut off. */
|
|
162
194
|
export async function pendingQuestions(storage, sessionID) {
|
|
163
195
|
const found = new Map();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { answerForPerson, answerQuestion, asksQuestion, noticeCutOff, pendingQuestions } from "./answer.js";
|
|
2
|
+
export { eventsFollowed, eventsLeft, formShown, formsMayHaveBeenMissed, joinRelay, locationClosing, shutdownReportedAt } from "./lifecycle.js";
|
|
3
|
+
export { byCodeUnit, type QuestionAnswerInput } from "./pure.js";
|
|
4
|
+
export { relayQuestions } from "./relay.js";
|
|
5
|
+
export { isQuestion, MAX_STORED, portsAt, QUESTION_TOOL } from "./shared.js";
|
|
6
|
+
export type { Answers, Asked, Elsewhere, Outcome, Prompt, QuestionPorts, QuestionTiming, Withdrawal } from "./types.js";
|
|
7
|
+
export type { QuestionAnswered } from "./answer.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The question relay: what the rest of the plugin uses of it. The relay's state is kept in the hub
|
|
2
|
-
// (
|
|
3
|
-
export { answerQuestion, noticeCutOff, pendingQuestions } from "./answer.js";
|
|
2
|
+
// (`../../platform/hub.ts`), under a process-wide key of its own; `types.ts` types it.
|
|
3
|
+
export { answerForPerson, answerQuestion, asksQuestion, noticeCutOff, pendingQuestions } from "./answer.js";
|
|
4
4
|
export { eventsFollowed, eventsLeft, formShown, formsMayHaveBeenMissed, joinRelay, locationClosing, shutdownReportedAt } from "./lifecycle.js";
|
|
5
5
|
export { byCodeUnit } from "./pure.js";
|
|
6
6
|
export { relayQuestions } from "./relay.js";
|
|
7
|
-
export { isQuestion, MAX_STORED, QUESTION_TOOL } from "./shared.js";
|
|
7
|
+
export { isQuestion, MAX_STORED, portsAt, QUESTION_TOOL } from "./shared.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { QuestionPorts } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* For tests: when the location at `directory` was last reported shutting down, if at all; a shutdown
|
|
5
|
+
* reported without a location counts for every directory, and with none given, any location's counts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const shutdownReportedAt: (directory?: string) => number | undefined;
|
|
8
|
+
/**
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
export declare function locationClosing(at: number, directory?: string): void;
|
|
13
|
+
/**
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
export declare const closingSoon: (ports: QuestionPorts, loaded: () => boolean, directory: string | undefined) => Effect.Effect<boolean>;
|
|
18
|
+
/**
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
export declare function joinRelay(ports: QuestionPorts): () => void;
|
|
23
|
+
/** Called for OpenCode's `form.created`: a question call's form is shown, so its question can be relayed. */
|
|
24
|
+
export declare function formShown(event: {
|
|
25
|
+
readonly data: {
|
|
26
|
+
readonly form: {
|
|
27
|
+
readonly sessionID: string;
|
|
28
|
+
readonly metadata?: unknown;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}): void;
|
|
32
|
+
/**
|
|
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.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formsMayHaveBeenMissed(): void;
|
|
37
|
+
/**
|
|
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.
|
|
40
|
+
*/
|
|
41
|
+
export declare function eventsFollowed(): boolean;
|
|
42
|
+
/** Called when an instance's event stream ended or broke. */
|
|
43
|
+
export declare function eventsLeft(): void;
|
|
@@ -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;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Plugin as EffectPlugin } from "@opencode/plugin/effect";
|
|
2
2
|
import { Cause, Exit, type Effect } from "effect";
|
|
3
|
-
import type { Question } from "
|
|
4
|
-
import { type Answers, type Asked, type Elsewhere, type Prompt, type Withdrawal } from "../notices.js";
|
|
3
|
+
import type { Answers, Asked, Elsewhere, Prompt, Question, Withdrawal } from "./types.js";
|
|
5
4
|
type ToolEditor = Parameters<Parameters<EffectPlugin.Context["tool"]["transform"]>[0]>[0];
|
|
6
5
|
type HostTool = Parameters<Parameters<ToolEditor["update"]>[1]>[0];
|
|
7
6
|
export type Execute = HostTool["execute"];
|
|
@@ -28,11 +27,10 @@ export type CallExit = Exit.Exit<{
|
|
|
28
27
|
/** How a relayed call that the top session did not answer was settled in the asking session. */
|
|
29
28
|
export declare function settledIn(exit: CallExit): Elsewhere;
|
|
30
29
|
/**
|
|
31
|
-
* Orders strings by
|
|
32
|
-
*
|
|
33
|
-
* compare would not promise: it can rank two different labels as equal and leave them as listed.
|
|
30
|
+
* Orders strings by UTF-16 code units, the default sort's order, stated: it only has to come out the
|
|
31
|
+
* same on both sides of a comparison; a locale-aware compare may rank two different labels as equal.
|
|
34
32
|
*/
|
|
35
|
-
export declare function byCodeUnit(a: string, b: string):
|
|
33
|
+
export declare function byCodeUnit(a: string, b: string): 0 | 1 | -1;
|
|
36
34
|
export declare const choices: (questions: ReadonlyArray<Prompt>) => string;
|
|
37
35
|
export declare const wording: (questions: ReadonlyArray<Prompt>) => string;
|
|
38
36
|
/** The result of a linked call withdrawn because its question was settled without it. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Cause, Exit, Result } from "effect";
|
|
2
|
-
import { withdrawnText } from "../
|
|
2
|
+
import { withdrawnText } from "../text.js";
|
|
3
3
|
/** The questions of a call, without anything the tool does not define. */
|
|
4
4
|
export function promptsOf(input) {
|
|
5
5
|
const questions = input?.questions ?? [];
|
|
@@ -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)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin as EffectPlugin } from "@opencode/plugin/effect";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import type { QuestionPorts } from "
|
|
3
|
+
import type { QuestionPorts } from "./types.js";
|
|
4
4
|
/** Wraps OpenCode's question tool for the relay. A tool by that id that is missing is left alone. */
|
|
5
5
|
export declare function relayQuestions(host: Pick<EffectPlugin.Context, "tool" | "location">, ports: () => QuestionPorts | undefined): Effect.Effect<import("@opencode/plugin/effect/registration").Registration, never, import("effect/Scope").Scope>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Deferred, Effect, Exit, Fiber } from "effect";
|
|
2
|
-
import { answeredText, DISMISSED, passedNote, questionNotice, questionSettledNotice, unlinkedNote, } from "../
|
|
3
|
-
import { lineage } from "
|
|
2
|
+
import { answeredText, DISMISSED, nativeQuestionNotice, passedNote, questionNotice, questionSettledNotice, unlinkedNote, } from "../text.js";
|
|
3
|
+
import { lineage, nativeRootOf } from "../../tree/index.js";
|
|
4
4
|
import { deliver, forget, tell, tellCutOff } from "./answer.js";
|
|
5
5
|
import { closingSoon } from "./lifecycle.js";
|
|
6
6
|
import { choices, isDismissal, normalize, promptsOf, settledIn, withdrawnResult, wording, } from "./pure.js";
|
|
@@ -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(() => {
|
|
@@ -25,7 +24,10 @@ async function relay(ports, question) {
|
|
|
25
24
|
shared.questions.set(question.requestID, question);
|
|
26
25
|
try {
|
|
27
26
|
await ports.storage.set(keyOf(question.requestID), storedOf(question));
|
|
28
|
-
|
|
27
|
+
if (question.native)
|
|
28
|
+
await tell(ports, question, nativeQuestionNotice(question), { asks: "question", request: question.requestID, shown: "native" });
|
|
29
|
+
else
|
|
30
|
+
await tell(ports, question, questionNotice(question), { asks: "question", request: question.requestID });
|
|
29
31
|
return true;
|
|
30
32
|
}
|
|
31
33
|
catch (error) {
|
|
@@ -44,9 +46,8 @@ const settle = (ports, question, exit, loaded) => Effect.gen(function* () {
|
|
|
44
46
|
// A dismissal is held from now, alongside the wait below, so the grace is not spent waiting.
|
|
45
47
|
const dismissed = Exit.isFailure(exit) && isDismissal(exit.cause);
|
|
46
48
|
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.
|
|
49
|
+
// The top session is told first, for a while only, so what follows neither overtakes the notice
|
|
50
|
+
// nor waits forever on one that never returns; `told` is false when it was not told, or not yet.
|
|
50
51
|
const told = (yield* within(question.relaying, ports.timing.relayWaitMs)) === true;
|
|
51
52
|
// Answered by the top session.
|
|
52
53
|
if (Exit.isSuccess(exit) && exit.value.by === "top") {
|
|
@@ -58,15 +59,8 @@ const settle = (ports, question, exit, loaded) => Effect.gen(function* () {
|
|
|
58
59
|
if (shared.questions.get(question.requestID) !== question) {
|
|
59
60
|
return yield* Effect.promise(() => forget(ports, question));
|
|
60
61
|
}
|
|
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.
|
|
62
|
+
// Cut off: the turn was stopped, or the closing location withdrew its forms as if dismissed, so a
|
|
63
|
+
// dismissal is held for a following shutdown or unload; the question stays, answered by message.
|
|
70
64
|
const closing = unloaded || !loaded() || (held !== undefined && (yield* Fiber.join(held)));
|
|
71
65
|
if (Exit.isFailure(exit) && (closing || (!dismissed && Exit.hasInterrupts(exit))))
|
|
72
66
|
return closing ? tellLater(ports, question) : yield* Effect.promise(() => stopped(ports, question));
|
|
@@ -103,9 +97,8 @@ function startSettling(ports, question, exit, loaded) {
|
|
|
103
97
|
});
|
|
104
98
|
}
|
|
105
99
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* withdrawn. Other sessions' calls run unchanged.
|
|
100
|
+
* A spawned session's question call: once its form is shown the top session is told, and whichever
|
|
101
|
+
* answer comes first ends the call, the other side's question withdrawn. Other calls run unchanged.
|
|
109
102
|
*/
|
|
110
103
|
function asking(ports, directory, original) {
|
|
111
104
|
return (input, context) => Effect.gen(function* () {
|
|
@@ -120,14 +113,17 @@ function asking(ports, directory, original) {
|
|
|
120
113
|
const acceptance = () => (acceptancePromise ??= Effect.runPromise(Deferred.await(accepted)));
|
|
121
114
|
// Handed one outcome only: a second, while the first still ends the call, is not taken.
|
|
122
115
|
let handed = false;
|
|
116
|
+
const top = chain.at(-1).parentID;
|
|
123
117
|
const question = {
|
|
124
118
|
requestID: current.newID(),
|
|
125
119
|
sessionID: context.sessionID,
|
|
126
|
-
top
|
|
120
|
+
top,
|
|
127
121
|
title: chain[0].title,
|
|
128
122
|
...(chain.length > 1 ? { startedBy: chain[0].parentID } : {}),
|
|
129
123
|
questions: promptsOf(input),
|
|
130
124
|
askedAt: current.now(),
|
|
125
|
+
// OpenCode shows a native child's form in its native root's view: when that is the top's, the person answers there.
|
|
126
|
+
...(nativeRootOf(chain) === top ? { native: true } : {}),
|
|
131
127
|
directory,
|
|
132
128
|
call: (outcome) => {
|
|
133
129
|
if (handed)
|
|
@@ -179,40 +175,36 @@ function asking(ports, directory, original) {
|
|
|
179
175
|
};
|
|
180
176
|
});
|
|
181
177
|
}
|
|
178
|
+
/** Whether the top session's question call may be linked to a question: not a native child's while its own form is shown. */
|
|
179
|
+
const linkable = (question, sessionID) => question.top === sessionID && !question.link && !isPassing(question.requestID) && !(question.native && question.call);
|
|
182
180
|
/**
|
|
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.
|
|
181
|
+
* The waiting question a top session's call asks again, the oldest if several: the same question with
|
|
182
|
+
* the same choices, in any order. Choices alone would let an unrelated yes-or-no answer a child's.
|
|
186
183
|
*/
|
|
187
184
|
function linkFor(sessionID, questions) {
|
|
188
185
|
const asked = { choices: choices(questions), wording: wording(questions) };
|
|
189
186
|
return [...shared.questions.values()]
|
|
190
|
-
.filter((question) => question
|
|
191
|
-
!question.link &&
|
|
192
|
-
!isPassing(question.requestID) &&
|
|
187
|
+
.filter((question) => linkable(question, sessionID) &&
|
|
193
188
|
choices(question.questions) === asked.choices &&
|
|
194
189
|
wording(question.questions) === asked.wording)
|
|
195
190
|
.sort((a, b) => a.askedAt - b.askedAt)[0];
|
|
196
191
|
}
|
|
197
192
|
/**
|
|
198
|
-
* The result of a top session's question call that no waiting question was linked to: when one
|
|
199
|
-
*
|
|
200
|
-
* the answers on.
|
|
193
|
+
* The result of a top session's question call that no waiting question was linked to: when one waits
|
|
194
|
+
* with the same choices, reworded or asked earlier, it says how to pass the answers on.
|
|
201
195
|
*/
|
|
202
196
|
function unlinkedResult(result, sessionID, questions) {
|
|
203
197
|
if (typeof result.content !== "string")
|
|
204
198
|
return result;
|
|
205
199
|
const asked = choices(questions);
|
|
206
|
-
const similar = [...shared.questions.values()].filter((question) => question
|
|
200
|
+
const similar = [...shared.questions.values()].filter((question) => linkable(question, sessionID) && choices(question.questions) === asked);
|
|
207
201
|
if (!similar.length)
|
|
208
202
|
return result;
|
|
209
203
|
return { ...result, content: `${result.content}\n${unlinkedNote(similar)}` };
|
|
210
204
|
}
|
|
211
205
|
/**
|
|
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.
|
|
206
|
+
* After a linked call failed with its location still loaded: a dismissal by the person dismisses the
|
|
207
|
+
* child's question too, or is logged if answered meanwhile; a stop while cut off tells the top.
|
|
216
208
|
*/
|
|
217
209
|
const linkedCallFailed = (ports, linked, cause, loaded, directory) => Effect.gen(function* () {
|
|
218
210
|
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
|
/**
|
|
@@ -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)));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { Asked } from "
|
|
1
|
+
import type { Registry } from "../../platform/storage.js";
|
|
2
|
+
import type { Asked, Question, QuestionPorts, QuestionState } from "./types.js";
|
|
3
3
|
/** OpenCode's own question tool, which the relay wraps. */
|
|
4
4
|
export declare const QUESTION_TOOL = "question";
|
|
5
5
|
export declare const PREFIX = "question/";
|
|
@@ -11,12 +11,22 @@ export type Stored = Asked & {
|
|
|
11
11
|
};
|
|
12
12
|
export declare const storedOf: ({ directory: _directory, call: _call, link: _link, relaying: _relaying, settling: _settling, ...asked }: Question) => Asked;
|
|
13
13
|
export declare const isPassing: (requestID: string) => boolean;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The relay's state in `registry`, the hub's `questions`, with the fields it lacks made: field by
|
|
16
|
+
* field, since a copy before the hub may have made it without the fields added later.
|
|
17
|
+
*/
|
|
18
|
+
export declare function questionState(registry?: Registry): QuestionState;
|
|
19
|
+
export declare const shared: QuestionState;
|
|
15
20
|
/**
|
|
16
21
|
* The ports of an instance still loaded, if any: a member of this copy's hub, or else one that a
|
|
17
22
|
* copy of another hub version, or a release before the hub, keeps in `loaded`.
|
|
18
23
|
*/
|
|
19
24
|
export declare const anyLoaded: () => QuestionPorts | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The question ports of the member that just joined at `location`, while it is loaded: resolved once,
|
|
27
|
+
* so a later instance at the same location does not take its place while this one is still loaded.
|
|
28
|
+
*/
|
|
29
|
+
export declare function portsAt(location: object): () => QuestionPorts | undefined;
|
|
20
30
|
export declare const keyOf: (requestID: string) => string;
|
|
21
31
|
/** Whether a request id is a question's rather than a permission request's. */
|
|
22
32
|
export declare const isQuestion: (requestID: string) => boolean;
|