opencode-courier 0.3.0 → 0.4.1
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 +180 -274
- package/dist/index.js +47 -73
- 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} +27 -21
- package/dist/{relay.js → person/permission.js} +9 -5
- 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 +43 -8
- package/dist/person/question/index.d.ts +7 -0
- package/dist/{question → person/question}/index.js +3 -3
- package/dist/{question → person/question}/lifecycle.d.ts +1 -1
- package/dist/{question → person/question}/pure.d.ts +1 -2
- package/dist/{question → person/question}/pure.js +1 -1
- package/dist/{question → person/question}/relay.d.ts +1 -1
- package/dist/{question → person/question}/relay.js +14 -8
- 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/person/text.js +262 -0
- 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/{bounded.d.ts → platform/bounded.d.ts} +4 -0
- package/dist/{bounded.js → platform/bounded.js} +4 -0
- 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} +27 -71
- package/dist/{version.js → platform/version.js} +1 -1
- 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} +7 -3
- package/dist/{later.js → scheduling/later.js} +12 -4
- 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 +4 -0
- package/dist/skill.js +54 -1
- package/dist/{cleanup.d.ts → tree/cleanup.d.ts} +1 -27
- package/dist/{cleanup.js → tree/cleanup.js} +9 -16
- 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/{watch.d.ts → tree/events.d.ts} +10 -37
- 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/{limits.js → tree/limits.js} +6 -55
- 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/{stop.d.ts → tree/stop.d.ts} +1 -9
- package/dist/{stop.js → tree/stop.js} +9 -13
- package/dist/tree/store.d.ts +24 -0
- package/dist/tree/store.js +264 -0
- package/dist/{tree.d.ts → tree/subtree.d.ts} +5 -6
- package/dist/{tree.js → tree/subtree.js} +7 -5
- 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/{tools.js → tree/tools.js} +12 -142
- package/dist/tree/tui.d.ts +53 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +1342 -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} +17 -23
- package/dist/{webhook.js → webhook/receiver.js} +58 -50
- 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/courier.d.ts +0 -200
- package/dist/courier.js +0 -315
- package/dist/group.d.ts +0 -86
- package/dist/group.js +0 -247
- package/dist/hub.d.ts +0 -300
- package/dist/limits.d.ts +0 -65
- package/dist/notices.d.ts +0 -356
- package/dist/notices.js +0 -678
- package/dist/question/index.d.ts +0 -6
- package/dist/question/shared.js +0 -24
- package/dist/report.d.ts +0 -48
- package/dist/report.js +0 -88
- package/dist/roster.d.ts +0 -93
- package/dist/roster.js +0 -206
- package/dist/tools.d.ts +0 -94
- package/dist/watch.js +0 -474
- /package/dist/{question → person/question}/lifecycle.js +0 -0
- /package/dist/{question → person/question}/runtime.d.ts +0 -0
- /package/dist/{question → person/question}/runtime.js +0 -0
- /package/dist/{json.d.ts → platform/json.d.ts} +0 -0
- /package/dist/{json.js → platform/json.js} +0 -0
- /package/dist/{storage.d.ts → platform/storage.d.ts} +0 -0
- /package/dist/{storage.js → platform/storage.js} +0 -0
- /package/dist/{version.d.ts → platform/version.d.ts} +0 -0
package/dist/question/index.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { answerQuestion, 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, QUESTION_TOOL } from "./shared.js";
|
|
6
|
-
export type { QuestionPorts, QuestionTiming } from "../hub.js";
|
package/dist/question/shared.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { addBounded } from "../bounded.js";
|
|
2
|
-
import { hub } from "../hub.js";
|
|
3
|
-
/** OpenCode's own question tool, which the relay wraps. */
|
|
4
|
-
export const QUESTION_TOOL = "question";
|
|
5
|
-
export const PREFIX = "question/";
|
|
6
|
-
/** At most this many questions are kept in storage; the oldest go first. */
|
|
7
|
-
export const MAX_STORED = 100;
|
|
8
|
-
export const storedOf = ({ directory: _directory, call: _call, link: _link, relaying: _relaying, settling: _settling, ...asked }) => asked;
|
|
9
|
-
export const isPassing = (requestID) => shared.passing.has(requestID) || shared.answered.has(requestID);
|
|
10
|
-
// Shared by every plugin instance in the process, since a child and the session it asks can be in
|
|
11
|
-
// different locations.
|
|
12
|
-
export const shared = hub.questions;
|
|
13
|
-
/**
|
|
14
|
-
* The ports of an instance still loaded, if any: a member of this copy's hub, or else one that a
|
|
15
|
-
* copy of another hub version, or a release before the hub, keeps in `loaded`.
|
|
16
|
-
*/
|
|
17
|
-
export const anyLoaded = () => hub.members.values().next().value?.questions ?? shared.loaded.values().next().value;
|
|
18
|
-
export const keyOf = (requestID) => `${PREFIX}${requestID}`;
|
|
19
|
-
/** Whether a request id is a question's rather than a permission request's. */
|
|
20
|
-
export const isQuestion = (requestID) => requestID.startsWith("question_");
|
|
21
|
-
/** How many noticed and answered questions are remembered. */
|
|
22
|
-
const REMEMBERED = 1_000;
|
|
23
|
-
/** Adds to one of the shared bounded sets. */
|
|
24
|
-
export const claim = (set, value) => addBounded(set, value, REMEMBERED);
|
package/dist/report.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { type Storage } from "./storage.js";
|
|
2
|
-
/**
|
|
3
|
-
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
4
|
-
* except isolated children whose worktree is still there: those stay until courier_cleanup.
|
|
5
|
-
*/
|
|
6
|
-
export declare const RETENTION_MS: number;
|
|
7
|
-
/** A session started with courier_spawn, recorded under the session that started it. */
|
|
8
|
-
export interface RosterEntry {
|
|
9
|
-
readonly sessionID: string;
|
|
10
|
-
readonly parentID: string;
|
|
11
|
-
readonly title: string;
|
|
12
|
-
readonly directory: string;
|
|
13
|
-
readonly isolated: boolean;
|
|
14
|
-
readonly createdAt: number;
|
|
15
|
-
/** For an isolated child, the directory its worktree was made from; for the record, nothing acts on it. */
|
|
16
|
-
readonly source?: string;
|
|
17
|
-
/** For an isolated child, the project its worktree belongs to; courier_cleanup removes it from there. */
|
|
18
|
-
readonly project?: string;
|
|
19
|
-
/** For an isolated child, the commit its worktree was made from; its own work is what came after. */
|
|
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;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
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.
|
|
29
|
-
*/
|
|
30
|
-
export interface ReverseEntry {
|
|
31
|
-
readonly ancestors: ReadonlyArray<string>;
|
|
32
|
-
}
|
|
33
|
-
export type RosterStorage = Storage;
|
|
34
|
-
export declare function rosterKey(parentID: string, sessionID: string): string;
|
|
35
|
-
export declare function reverseKey(sessionID: string): string;
|
|
36
|
-
/**
|
|
37
|
-
* Records a child under its parent, then indexes it. The entry is what counts: a failed index
|
|
38
|
-
* write leaves it to be found by a scan, and to be indexed when the plugin is next loaded.
|
|
39
|
-
*/
|
|
40
|
-
export declare function record(storage: RosterStorage, entry: RosterEntry): Promise<void>;
|
|
41
|
-
/**
|
|
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.
|
|
44
|
-
*/
|
|
45
|
-
export declare function remove(storage: RosterStorage, parentID: string, sessionID: string, group?: string): Promise<{
|
|
46
|
-
droppedFromGroup: boolean;
|
|
47
|
-
}>;
|
|
48
|
-
/** Removes a child from its parent's roster; false when it was not there. */
|
|
49
|
-
export declare function forget(storage: RosterStorage, parentID: string, sessionID: string): Promise<boolean>;
|
|
50
|
-
/** A parent's children, oldest first. */
|
|
51
|
-
export declare function children(storage: RosterStorage, parentID: string): Promise<RosterEntry[]>;
|
|
52
|
-
/** Every parent's children. */
|
|
53
|
-
export declare function allEntries(storage: RosterStorage): Promise<RosterEntry[]>;
|
|
54
|
-
/** A session's roster entries: one for the parent that started it, none if courier_spawn did not. */
|
|
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>;
|
|
62
|
-
/**
|
|
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.
|
|
65
|
-
*/
|
|
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[]>;
|
|
69
|
-
/** `lineage` over roster entries already read. */
|
|
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[];
|
|
75
|
-
/**
|
|
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.
|
|
78
|
-
*/
|
|
79
|
-
export declare function answeringTop(storage: RosterStorage, sessionID: string, callerID: string, what: string): Promise<string>;
|
|
80
|
-
/** Whether a directory still exists; tests pass a fake. */
|
|
81
|
-
export type Exists = (directory: string) => boolean;
|
|
82
|
-
/** A parent's children, oldest first, after dropping the expired ones. */
|
|
83
|
-
export declare function current(storage: RosterStorage, parentID: string, now: number, exists?: Exists): Promise<RosterEntry[]>;
|
|
84
|
-
/**
|
|
85
|
-
* On loading: drops expired entries of every parent, so parents that never list their children
|
|
86
|
-
* don't keep them forever, then brings the reverse index in line with what is left (`backfill`).
|
|
87
|
-
*/
|
|
88
|
-
export declare function pruneExpired(storage: RosterStorage, now: number, exists?: Exists): Promise<void>;
|
|
89
|
-
/**
|
|
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.
|
|
92
|
-
*/
|
|
93
|
-
export declare function backfill(storage: RosterStorage, entries: ReadonlyArray<RosterEntry>): Promise<void>;
|
package/dist/roster.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { dropMember } from "./group.js";
|
|
3
|
-
import { forgetReport } from "./report.js";
|
|
4
|
-
import { scanAll, scanEntries } from "./storage.js";
|
|
5
|
-
/**
|
|
6
|
-
* Entries older than this are dropped when their parent's roster is read, and on plugin setup,
|
|
7
|
-
* except isolated children whose worktree is still there: those stay until courier_cleanup.
|
|
8
|
-
*/
|
|
9
|
-
export const RETENTION_MS = 14 * 24 * 60 * 60_000;
|
|
10
|
-
const PREFIX = "roster/";
|
|
11
|
-
/**
|
|
12
|
-
* The reverse index: a child's `ReverseEntry` under `roster-by-child/<sessionID>`, written with its
|
|
13
|
-
* roster entry; only an index, so `roster/` is scanned for entries lacking one, and confirms a hit.
|
|
14
|
-
*/
|
|
15
|
-
const BY_CHILD = "roster-by-child/";
|
|
16
|
-
export function rosterKey(parentID, sessionID) {
|
|
17
|
-
return `${PREFIX}${parentID}/${sessionID}`;
|
|
18
|
-
}
|
|
19
|
-
export function reverseKey(sessionID) {
|
|
20
|
-
return `${BY_CHILD}${sessionID}`;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Records a child under its parent, then indexes it. The entry is what counts: a failed index
|
|
24
|
-
* write leaves it to be found by a scan, and to be indexed when the plugin is next loaded.
|
|
25
|
-
*/
|
|
26
|
-
export async function record(storage, entry) {
|
|
27
|
-
await storage.set(rosterKey(entry.parentID, entry.sessionID), { ...entry });
|
|
28
|
-
await index(storage, entry).catch(() => undefined);
|
|
29
|
-
}
|
|
30
|
-
async function index(storage, entry) {
|
|
31
|
-
const above = await storage.get(reverseKey(entry.parentID));
|
|
32
|
-
const ancestors = isReverse(above) ? above.ancestors : (await lineage(storage, entry.parentID)).map((parent) => parent.parentID);
|
|
33
|
-
await storage.set(reverseKey(entry.sessionID), { ancestors: [entry.parentID, ...ancestors] });
|
|
34
|
-
}
|
|
35
|
-
function isReverse(value) {
|
|
36
|
-
const ancestors = value?.ancestors;
|
|
37
|
-
return Array.isArray(ancestors) && ancestors.length > 0 && ancestors.every((id) => typeof id === "string");
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Removes a child's roster entry, its reverse key, its report state and, given its group, its membership. Only
|
|
41
|
-
* the entry's removal can fail it: a reverse key left behind leads nowhere, and the next load drops it.
|
|
42
|
-
*/
|
|
43
|
-
export async function remove(storage, parentID, sessionID, group) {
|
|
44
|
-
const [, , , dropped] = await Promise.all([
|
|
45
|
-
storage.remove(rosterKey(parentID, sessionID)),
|
|
46
|
-
storage.remove(reverseKey(sessionID)).catch(() => undefined),
|
|
47
|
-
forgetReport(storage, sessionID).catch(() => undefined),
|
|
48
|
-
group ? dropMember(storage, parentID, group, sessionID).catch(() => false) : false,
|
|
49
|
-
]);
|
|
50
|
-
return { droppedFromGroup: dropped };
|
|
51
|
-
}
|
|
52
|
-
/** Removes a child from its parent's roster; false when it was not there. */
|
|
53
|
-
export async function forget(storage, parentID, sessionID) {
|
|
54
|
-
const entry = (await storage.get(rosterKey(parentID, sessionID)));
|
|
55
|
-
if (entry === undefined)
|
|
56
|
-
return false;
|
|
57
|
-
await remove(storage, parentID, sessionID, entry.group);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
/** A parent's children, oldest first. */
|
|
61
|
-
export async function children(storage, parentID) {
|
|
62
|
-
const entries = await scanAll(storage, `${PREFIX}${parentID}/`);
|
|
63
|
-
return entries.sort((a, b) => a.createdAt - b.createdAt);
|
|
64
|
-
}
|
|
65
|
-
/** Every parent's children. */
|
|
66
|
-
export async function allEntries(storage) {
|
|
67
|
-
return scanAll(storage, PREFIX);
|
|
68
|
-
}
|
|
69
|
-
/** A session's roster entries: one for the parent that started it, none if courier_spawn did not. */
|
|
70
|
-
export async function entriesOf(storage, sessionID) {
|
|
71
|
-
const parentID = await indexedParent(storage, sessionID);
|
|
72
|
-
if (parentID === undefined)
|
|
73
|
-
return (await scanAll(storage, PREFIX)).filter((entry) => entry.sessionID === sessionID);
|
|
74
|
-
const entry = await storage.get(rosterKey(parentID, sessionID));
|
|
75
|
-
return entry === undefined ? [] : [entry];
|
|
76
|
-
}
|
|
77
|
-
/** The session that started a session, by the reverse index alone, without scanning the roster. */
|
|
78
|
-
export async function indexedParent(storage, sessionID) {
|
|
79
|
-
const indexed = await storage.get(reverseKey(sessionID));
|
|
80
|
-
return isReverse(indexed) ? indexed.ancestors[0] : undefined;
|
|
81
|
-
}
|
|
82
|
-
/** A session's roster entry under a parent, if it is there. */
|
|
83
|
-
export async function entryUnder(storage, parentID, sessionID) {
|
|
84
|
-
return (await storage.get(rosterKey(parentID, sessionID)));
|
|
85
|
-
}
|
|
86
|
-
/** A session's roster entry, by the reverse index alone, without scanning the roster. */
|
|
87
|
-
export async function indexedEntry(storage, sessionID) {
|
|
88
|
-
const parentID = await indexedParent(storage, sessionID);
|
|
89
|
-
return parentID === undefined ? undefined : entryUnder(storage, parentID, sessionID);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* The roster entries from a session up to the top, its own first, ending with the entry whose parent
|
|
93
|
-
* courier_spawn did not start; empty when courier_spawn did not start the session.
|
|
94
|
-
*/
|
|
95
|
-
export async function lineage(storage, sessionID) {
|
|
96
|
-
const first = await storage.get(reverseKey(sessionID));
|
|
97
|
-
if (!isReverse(first))
|
|
98
|
-
return lineageIn(await allEntries(storage), sessionID);
|
|
99
|
-
return walkIndex(storage, sessionID, first);
|
|
100
|
-
}
|
|
101
|
-
/** `lineage` by the reverse index alone: empty, without scanning the roster, when it does not name the session. */
|
|
102
|
-
export async function indexedLineage(storage, sessionID) {
|
|
103
|
-
const first = await storage.get(reverseKey(sessionID));
|
|
104
|
-
return isReverse(first) ? walkIndex(storage, sessionID, first) : [];
|
|
105
|
-
}
|
|
106
|
-
async function walkIndex(storage, sessionID, first) {
|
|
107
|
-
let indexed = first;
|
|
108
|
-
const chain = [];
|
|
109
|
-
let below = sessionID;
|
|
110
|
-
// One read per level, all at once, and one more for the top's own reverse key, in case it was
|
|
111
|
-
// recorded after the key that names it; the chain ends where an entry is gone, as a scan's would.
|
|
112
|
-
for (;;) {
|
|
113
|
-
const { ancestors } = indexed;
|
|
114
|
-
const top = ancestors.at(-1);
|
|
115
|
-
const [entries, above] = await Promise.all([
|
|
116
|
-
Promise.all(ancestors.map((parentID, level) => storage.get(rosterKey(parentID, level ? ancestors[level - 1] : below)))),
|
|
117
|
-
storage.get(reverseKey(top)),
|
|
118
|
-
]);
|
|
119
|
-
for (const entry of entries) {
|
|
120
|
-
if (!entry || chain.some((known) => known.sessionID === entry.sessionID))
|
|
121
|
-
return chain;
|
|
122
|
-
chain.push(entry);
|
|
123
|
-
}
|
|
124
|
-
if (!isReverse(above))
|
|
125
|
-
return chain;
|
|
126
|
-
below = top;
|
|
127
|
-
indexed = above;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** `lineage` over roster entries already read. */
|
|
131
|
-
export function lineageIn(entries, sessionID) {
|
|
132
|
-
return lineageBy(bySession(entries), sessionID);
|
|
133
|
-
}
|
|
134
|
-
/** Roster entries by their session, for `lineageBy`. */
|
|
135
|
-
export function bySession(entries) {
|
|
136
|
-
return new Map(entries.map((entry) => [entry.sessionID, entry]));
|
|
137
|
-
}
|
|
138
|
-
/** `lineage` over roster entries already read and mapped by `bySession`. */
|
|
139
|
-
export function lineageBy(bySession, sessionID) {
|
|
140
|
-
const chain = [];
|
|
141
|
-
for (let entry = bySession.get(sessionID); entry && !chain.includes(entry); entry = bySession.get(entry.parentID))
|
|
142
|
-
chain.push(entry);
|
|
143
|
-
return chain;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The top of a spawned session's lineage, where its permission requests and questions go, since the
|
|
147
|
-
* person is there. Throws unless the session was spawned and `callerID` is that top; `what` names it.
|
|
148
|
-
*/
|
|
149
|
-
export async function answeringTop(storage, sessionID, callerID, what) {
|
|
150
|
-
const chain = await lineage(storage, sessionID);
|
|
151
|
-
if (!chain.length)
|
|
152
|
-
throw new Error(`${sessionID} was not started with courier_spawn, so courier_answer cannot answer for it.`);
|
|
153
|
-
const top = chain.at(-1).parentID;
|
|
154
|
-
if (top !== callerID)
|
|
155
|
-
throw new Error(`${sessionID}'s ${what} go to ${top}, the session at the top of the sessions started from it with courier_spawn; ${callerID} cannot answer them.`);
|
|
156
|
-
return top;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Removes the given entries that were recorded more than `RETENTION_MS` before `now`, and returns
|
|
160
|
-
* the rest. An isolated child is kept while its worktree exists, so it can still be cleaned up.
|
|
161
|
-
*/
|
|
162
|
-
async function dropExpired(storage, entries, now, exists) {
|
|
163
|
-
const expired = new Set(entries.filter((entry) => now - entry.createdAt > RETENTION_MS && !(entry.isolated && exists(entry.directory))));
|
|
164
|
-
await Promise.all([...expired].map((entry) => remove(storage, entry.parentID, entry.sessionID, entry.group)));
|
|
165
|
-
return entries.filter((entry) => !expired.has(entry));
|
|
166
|
-
}
|
|
167
|
-
/** A parent's children, oldest first, after dropping the expired ones. */
|
|
168
|
-
export async function current(storage, parentID, now, exists = existsSync) {
|
|
169
|
-
return dropExpired(storage, await children(storage, parentID), now, exists);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* On loading: drops expired entries of every parent, so parents that never list their children
|
|
173
|
-
* don't keep them forever, then brings the reverse index in line with what is left (`backfill`).
|
|
174
|
-
*/
|
|
175
|
-
export async function pruneExpired(storage, now, exists = existsSync) {
|
|
176
|
-
await backfill(storage, await dropExpired(storage, await scanAll(storage, PREFIX), now, exists));
|
|
177
|
-
}
|
|
178
|
-
/** Whether a reverse key's value leads to this entry. */
|
|
179
|
-
function indexes(value, entry) {
|
|
180
|
-
return isReverse(value) && value.ancestors[0] === entry.parentID;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Writes the reverse keys missing or stale for the given entries, or the whole roster, and drops those
|
|
184
|
-
* whose entry is gone, as an older copy's writes and removals leave them; a rerun changes nothing.
|
|
185
|
-
*/
|
|
186
|
-
export async function backfill(storage, entries) {
|
|
187
|
-
const indexed = new Map((await scanEntries(storage, BY_CHILD)).map(({ key, value }) => [key, value]));
|
|
188
|
-
const sessions = new Set(entries.map((entry) => entry.sessionID));
|
|
189
|
-
await Promise.all([
|
|
190
|
-
...entries
|
|
191
|
-
.filter((entry) => !indexes(indexed.get(entry.sessionID), entry))
|
|
192
|
-
.map((entry) => storage.set(reverseKey(entry.sessionID), { ancestors: lineageIn(entries, entry.sessionID).map((above) => above.parentID) })),
|
|
193
|
-
...[...indexed]
|
|
194
|
-
.filter(([sessionID]) => !sessions.has(sessionID))
|
|
195
|
-
.map(([sessionID, value]) => dropDangling(storage, sessionID, value)),
|
|
196
|
-
]);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Drops a reverse key whose entry was not in the roster read, unless that entry is there now:
|
|
200
|
-
* another instance may have recorded the child since, and it writes the entry before the key.
|
|
201
|
-
*/
|
|
202
|
-
async function dropDangling(storage, sessionID, value) {
|
|
203
|
-
if (isReverse(value) && (await storage.get(rosterKey(value.ancestors[0], sessionID))) !== undefined)
|
|
204
|
-
return;
|
|
205
|
-
await storage.remove(reverseKey(sessionID));
|
|
206
|
-
}
|
package/dist/tools.d.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode/plugin";
|
|
2
|
-
import { Schema } from "effect";
|
|
3
|
-
import { type CleanupPorts } from "./cleanup.js";
|
|
4
|
-
import { type StopPorts } from "./stop.js";
|
|
5
|
-
import { type CourierPorts } from "./courier.js";
|
|
6
|
-
import { type LaterPorts } from "./later.js";
|
|
7
|
-
import { type QuestionPorts } from "./question/index.js";
|
|
8
|
-
import { type AnswerPorts, type Waiting } from "./relay.js";
|
|
9
|
-
import { type WebhookPorts } from "./webhook.js";
|
|
10
|
-
/**
|
|
11
|
-
* The courier tools: their input schemas, descriptions, and what each returns. The descriptions are
|
|
12
|
-
* model-facing like the texts in `notices.ts`, and snapshotted with them in `test/notices.test.ts`.
|
|
13
|
-
*/
|
|
14
|
-
export declare const SpawnInput: Schema.Struct<{
|
|
15
|
-
readonly task: Schema.String;
|
|
16
|
-
readonly title: Schema.optional<Schema.String>;
|
|
17
|
-
readonly agent: Schema.optional<Schema.String>;
|
|
18
|
-
readonly isolate: Schema.optional<Schema.Boolean>;
|
|
19
|
-
readonly group: Schema.optional<Schema.String>;
|
|
20
|
-
}>;
|
|
21
|
-
export declare const SendInput: Schema.Struct<{
|
|
22
|
-
readonly sessionID: Schema.String;
|
|
23
|
-
readonly message: Schema.String;
|
|
24
|
-
readonly status: Schema.optional<Schema.Literals<readonly ["done", "partial", "blocked", "failed"]>>;
|
|
25
|
-
readonly artifacts: Schema.optional<Schema.Struct<{
|
|
26
|
-
readonly branch: Schema.optional<Schema.String>;
|
|
27
|
-
readonly commits: Schema.optional<Schema.$Array<Schema.String>>;
|
|
28
|
-
readonly files: Schema.optional<Schema.$Array<Schema.String>>;
|
|
29
|
-
readonly checks: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
30
|
-
readonly command: Schema.String;
|
|
31
|
-
readonly result: Schema.String;
|
|
32
|
-
}>>>;
|
|
33
|
-
}>>;
|
|
34
|
-
readonly queue: Schema.optional<Schema.Boolean>;
|
|
35
|
-
}>;
|
|
36
|
-
export declare const StatusInput: Schema.Struct<{
|
|
37
|
-
readonly sessionID: Schema.String;
|
|
38
|
-
}>;
|
|
39
|
-
export declare const ChildrenInput: Schema.Struct<{
|
|
40
|
-
readonly sessionID: Schema.optional<Schema.String>;
|
|
41
|
-
}>;
|
|
42
|
-
export declare const TreeInput: Schema.Struct<{
|
|
43
|
-
readonly sessionID: Schema.optional<Schema.String>;
|
|
44
|
-
}>;
|
|
45
|
-
export declare const StopInput: Schema.Struct<{
|
|
46
|
-
readonly sessionID: Schema.String;
|
|
47
|
-
readonly cleanup: Schema.optional<Schema.Boolean>;
|
|
48
|
-
}>;
|
|
49
|
-
export declare const CleanupInput: Schema.Struct<{
|
|
50
|
-
readonly sessionID: Schema.String;
|
|
51
|
-
readonly force: Schema.optional<Schema.Boolean>;
|
|
52
|
-
}>;
|
|
53
|
-
export declare const LaterInput: Schema.Struct<{
|
|
54
|
-
readonly message: Schema.String;
|
|
55
|
-
readonly delayMinutes: Schema.optional<Schema.Union<readonly [Schema.Number, Schema.String]>>;
|
|
56
|
-
readonly at: Schema.optional<Schema.String>;
|
|
57
|
-
readonly sessionID: Schema.optional<Schema.String>;
|
|
58
|
-
}>;
|
|
59
|
-
export declare const AnswerInput: Schema.Struct<{
|
|
60
|
-
readonly sessionID: Schema.String;
|
|
61
|
-
readonly requestID: Schema.String;
|
|
62
|
-
readonly reply: Schema.optional<Schema.Literals<readonly ["once", "always", "reject"]>>;
|
|
63
|
-
readonly message: Schema.optional<Schema.String>;
|
|
64
|
-
readonly answers: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.String, Schema.$Array<Schema.String>]>>>;
|
|
65
|
-
}>;
|
|
66
|
-
export declare const CancelInput: Schema.Struct<{
|
|
67
|
-
readonly id: Schema.String;
|
|
68
|
-
}>;
|
|
69
|
-
export declare const SubscribeInput: Schema.Struct<{
|
|
70
|
-
readonly topic: Schema.String;
|
|
71
|
-
readonly sessionID: Schema.optional<Schema.String>;
|
|
72
|
-
}>;
|
|
73
|
-
export declare const UnsubscribeInput: Schema.Struct<{
|
|
74
|
-
readonly topic: Schema.optional<Schema.String>;
|
|
75
|
-
readonly sessionID: Schema.optional<Schema.String>;
|
|
76
|
-
}>;
|
|
77
|
-
/** The editor OpenCode hands a tool transform. */
|
|
78
|
-
export type ToolEditor = Parameters<Parameters<Plugin.Context["tool"]["transform"]>[0]>[0];
|
|
79
|
-
/** What the tools run on: one plugin instance's ports. */
|
|
80
|
-
export interface ToolPorts {
|
|
81
|
-
readonly courier: CourierPorts;
|
|
82
|
-
readonly cleanup: CleanupPorts;
|
|
83
|
-
readonly stop: StopPorts;
|
|
84
|
-
readonly later: LaterPorts;
|
|
85
|
-
readonly answer: AnswerPorts;
|
|
86
|
-
readonly questions: QuestionPorts;
|
|
87
|
-
readonly hooks: WebhookPorts;
|
|
88
|
-
/** The permission requests sessions were told about and have not answered. */
|
|
89
|
-
readonly waiting: Waiting;
|
|
90
|
-
/** Whether a webhook receiver runs in this server. */
|
|
91
|
-
readonly receiving: () => Promise<boolean>;
|
|
92
|
-
}
|
|
93
|
-
/** Adds the courier tools, each a direct tool rather than a code-mode one. */
|
|
94
|
-
export declare function addTools(tools: ToolEditor, ports: ToolPorts): void;
|