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.
Files changed (143) hide show
  1. package/README.md +198 -193
  2. package/dist/index.d.ts +4 -8
  3. package/dist/index.js +66 -85
  4. package/dist/kernel/protocol.d.ts +20 -0
  5. package/dist/kernel/protocol.js +36 -0
  6. package/dist/kernel/tool.d.ts +5 -0
  7. package/dist/kernel/tool.js +5 -0
  8. package/dist/person/events.d.ts +23 -0
  9. package/dist/person/events.js +113 -0
  10. package/dist/person/form.d.ts +26 -0
  11. package/dist/person/form.js +64 -0
  12. package/dist/person/index.d.ts +17 -0
  13. package/dist/person/index.js +5 -0
  14. package/dist/{relay.d.ts → person/permission.d.ts} +29 -25
  15. package/dist/{relay.js → person/permission.js} +11 -9
  16. package/dist/person/ports.d.ts +58 -0
  17. package/dist/person/ports.js +2 -0
  18. package/dist/{question → person/question}/answer.d.ts +33 -4
  19. package/dist/{question → person/question}/answer.js +49 -17
  20. package/dist/person/question/index.d.ts +7 -0
  21. package/dist/{question → person/question}/index.js +3 -3
  22. package/dist/person/question/lifecycle.d.ts +43 -0
  23. package/dist/{question → person/question}/lifecycle.js +10 -19
  24. package/dist/{question → person/question}/pure.d.ts +4 -6
  25. package/dist/{question → person/question}/pure.js +3 -4
  26. package/dist/{question → person/question}/relay.d.ts +1 -1
  27. package/dist/{question → person/question}/relay.js +27 -35
  28. package/dist/{question → person/question}/runtime.d.ts +2 -3
  29. package/dist/{question → person/question}/runtime.js +2 -3
  30. package/dist/{question → person/question}/shared.d.ts +13 -3
  31. package/dist/person/question/shared.js +52 -0
  32. package/dist/person/question/types.d.ts +121 -0
  33. package/dist/person/question/types.js +1 -0
  34. package/dist/person/rpc-handlers.d.ts +160 -0
  35. package/dist/person/rpc-handlers.js +62 -0
  36. package/dist/person/rpc.d.ts +227 -0
  37. package/dist/person/rpc.js +70 -0
  38. package/dist/person/text.d.ts +68 -0
  39. package/dist/{notices.js → person/text.js} +53 -190
  40. package/dist/person/tools.d.ts +20 -0
  41. package/dist/person/tools.js +55 -0
  42. package/dist/person/tui.d.ts +46 -0
  43. package/dist/platform/bounded.d.ts +12 -0
  44. package/dist/{bounded.js → platform/bounded.js} +6 -4
  45. package/dist/platform/events.d.ts +34 -0
  46. package/dist/platform/events.js +121 -0
  47. package/dist/platform/hub.d.ts +195 -0
  48. package/dist/{hub.js → platform/hub.js} +78 -119
  49. package/dist/platform/json.d.ts +8 -0
  50. package/dist/platform/json.js +15 -0
  51. package/dist/platform/version.d.ts +16 -0
  52. package/dist/{version.js → platform/version.js} +5 -11
  53. package/dist/rpc.d.ts +2 -0
  54. package/dist/rpc.js +3 -0
  55. package/dist/scheduling/index.d.ts +6 -0
  56. package/dist/scheduling/index.js +5 -0
  57. package/dist/{later.d.ts → scheduling/later.d.ts} +13 -9
  58. package/dist/{later.js → scheduling/later.js} +18 -9
  59. package/dist/scheduling/text.d.ts +6 -0
  60. package/dist/scheduling/text.js +10 -0
  61. package/dist/scheduling/tools.d.ts +14 -0
  62. package/dist/scheduling/tools.js +48 -0
  63. package/dist/skill.d.ts +17 -0
  64. package/dist/skill.js +62 -0
  65. package/dist/{cleanup.d.ts → tree/cleanup.d.ts} +5 -26
  66. package/dist/{cleanup.js → tree/cleanup.js} +20 -15
  67. package/dist/tree/context.d.ts +15 -0
  68. package/dist/tree/context.js +34 -0
  69. package/dist/tree/domain.d.ts +101 -0
  70. package/dist/tree/domain.js +68 -0
  71. package/dist/tree/events.d.ts +71 -0
  72. package/dist/tree/events.js +204 -0
  73. package/dist/tree/forget.d.ts +24 -0
  74. package/dist/tree/forget.js +59 -0
  75. package/dist/tree/group.d.ts +82 -0
  76. package/dist/tree/group.js +203 -0
  77. package/dist/tree/index.d.ts +10 -0
  78. package/dist/tree/index.js +11 -0
  79. package/dist/tree/limits.d.ts +10 -0
  80. package/dist/tree/limits.js +60 -0
  81. package/dist/tree/panel.d.ts +13 -0
  82. package/dist/tree/panel.js +75 -0
  83. package/dist/tree/ports.d.ts +276 -0
  84. package/dist/tree/ports.js +1 -0
  85. package/dist/tree/report.d.ts +23 -0
  86. package/dist/tree/report.js +33 -0
  87. package/dist/tree/roster.d.ts +27 -0
  88. package/dist/tree/roster.js +62 -0
  89. package/dist/tree/rpc-handlers.d.ts +201 -0
  90. package/dist/tree/rpc-handlers.js +20 -0
  91. package/dist/tree/rpc.d.ts +344 -0
  92. package/dist/tree/rpc.js +83 -0
  93. package/dist/tree/send.d.ts +14 -0
  94. package/dist/tree/send.js +99 -0
  95. package/dist/tree/spawn.d.ts +21 -0
  96. package/dist/tree/spawn.js +173 -0
  97. package/dist/tree/status.d.ts +128 -0
  98. package/dist/tree/status.js +62 -0
  99. package/dist/tree/stop.d.ts +33 -0
  100. package/dist/tree/stop.js +116 -0
  101. package/dist/tree/store.d.ts +24 -0
  102. package/dist/tree/store.js +264 -0
  103. package/dist/tree/subtree.d.ts +101 -0
  104. package/dist/tree/subtree.js +60 -0
  105. package/dist/tree/summary.d.ts +7 -0
  106. package/dist/tree/summary.js +35 -0
  107. package/dist/tree/text.d.ts +77 -0
  108. package/dist/tree/text.js +287 -0
  109. package/dist/tree/tools.d.ts +54 -0
  110. package/dist/tree/tools.js +167 -0
  111. package/dist/tree/tui.d.ts +53 -0
  112. package/dist/tui.d.ts +3 -0
  113. package/dist/tui.js +1219 -0
  114. package/dist/webhook/index.d.ts +5 -0
  115. package/dist/webhook/index.js +3 -0
  116. package/dist/{webhook.d.ts → webhook/receiver.d.ts} +25 -36
  117. package/dist/{webhook.js → webhook/receiver.js} +66 -67
  118. package/dist/webhook/subscriptions.d.ts +29 -0
  119. package/dist/webhook/subscriptions.js +52 -0
  120. package/dist/webhook/text.d.ts +23 -0
  121. package/dist/webhook/text.js +82 -0
  122. package/dist/webhook/tools.d.ts +19 -0
  123. package/dist/webhook/tools.js +46 -0
  124. package/package.json +18 -7
  125. package/dist/bounded.d.ts +0 -10
  126. package/dist/courier.d.ts +0 -95
  127. package/dist/courier.js +0 -131
  128. package/dist/hub.d.ts +0 -325
  129. package/dist/json.d.ts +0 -4
  130. package/dist/json.js +0 -4
  131. package/dist/notices.d.ts +0 -246
  132. package/dist/question/index.d.ts +0 -6
  133. package/dist/question/lifecycle.d.ts +0 -52
  134. package/dist/question/shared.js +0 -24
  135. package/dist/roster.d.ts +0 -81
  136. package/dist/roster.js +0 -175
  137. package/dist/tools.d.ts +0 -74
  138. package/dist/tools.js +0 -228
  139. package/dist/version.d.ts +0 -22
  140. package/dist/watch.d.ts +0 -61
  141. package/dist/watch.js +0 -289
  142. /package/dist/{storage.d.ts → platform/storage.d.ts} +0 -0
  143. /package/dist/{storage.js → platform/storage.js} +0 -0
package/dist/courier.d.ts DELETED
@@ -1,95 +0,0 @@
1
- import type { Plugin } from "@opencode/plugin";
2
- import { type Prompt } from "./notices.js";
3
- import { type RosterStorage } from "./roster.js";
4
- type Context = Plugin.Context;
5
- /** The slice of the plugin context the courier tools use; tests pass a fake. */
6
- export interface CourierPorts {
7
- readonly session: Pick<Context["session"], "create" | "prompt" | "synthetic" | "get" | "context">;
8
- readonly agent: Pick<Context["agent"], "get">;
9
- readonly worktree: Pick<Context["worktree"], "create" | "remove">;
10
- /** The project of the plugin's location; an isolated child's worktree is made in it, and removed from it. */
11
- readonly projectID: string;
12
- /** The commit a directory's checkout is on, or undefined; recorded as an isolated child's base. */
13
- readonly head: (directory: string) => Promise<string | undefined>;
14
- readonly storage: RosterStorage;
15
- readonly directory: string;
16
- readonly now: () => number;
17
- /** The permission requests and questions a session waits on, wherever they are pending. */
18
- readonly pending: (sessionID: string) => Promise<ReadonlyArray<Pending>>;
19
- }
20
- /** A request a session waits on until someone answers it: a permission request, or a question it asked. */
21
- export type Pending = {
22
- readonly type: "permission";
23
- readonly requestID: string;
24
- readonly action: string;
25
- readonly resources: ReadonlyArray<string>;
26
- readonly save?: ReadonlyArray<string>;
27
- } | {
28
- readonly type: "question";
29
- readonly requestID: string;
30
- readonly questions: ReadonlyArray<Prompt>;
31
- /** Its question call was cut off; the answer reaches it as a message. */
32
- readonly stopped?: true;
33
- };
34
- export interface SpawnInput {
35
- readonly task: string;
36
- readonly title?: string;
37
- readonly agent?: string;
38
- readonly isolate?: boolean;
39
- }
40
- export interface SendInput {
41
- readonly sessionID: string;
42
- readonly message: string;
43
- readonly queue?: boolean;
44
- }
45
- export interface StatusInput {
46
- readonly sessionID: string;
47
- }
48
- export interface ChildrenInput {
49
- readonly sessionID?: string;
50
- }
51
- /** Creates a child session, hands it the task and returns at once; the child reports back with courier_send. */
52
- export declare function spawn(ports: CourierPorts, parentID: string, input: SpawnInput): Promise<{
53
- rosterError?: string | undefined;
54
- sessionID: string;
55
- directory: string;
56
- }>;
57
- /** Drops a message into another session's inbox; OpenCode wakes that session if it is idle. */
58
- export declare function send(ports: CourierPorts, from: string, input: SendInput): Promise<{
59
- messageID: string;
60
- }>;
61
- /** A one-off look at a session, for check-ins; not meant to be called in a loop. */
62
- export declare function status(ports: CourierPorts, input: StatusInput): Promise<Partial<{
63
- sessionID: string;
64
- title: string | undefined;
65
- parentID: string | undefined;
66
- outcome: "failed" | "succeeded" | "interrupted" | undefined;
67
- updated: number;
68
- idle: number | undefined;
69
- lastText: string | undefined;
70
- pending: readonly Pending[] | undefined;
71
- }>>;
72
- /** The sessions a parent started, each with what courier_status reports, or the error it gave. */
73
- export declare function listChildren(ports: CourierPorts, parentID: string): Promise<({
74
- directory: string;
75
- isolated: boolean;
76
- created: number;
77
- sessionID?: string | undefined;
78
- title?: string | undefined;
79
- parentID?: string | undefined;
80
- outcome?: "failed" | "succeeded" | "interrupted" | undefined;
81
- updated?: number | undefined;
82
- idle?: number | undefined;
83
- lastText?: string | undefined;
84
- pending?: readonly Pending[] | undefined;
85
- } | {
86
- error: string;
87
- directory: string;
88
- isolated: boolean;
89
- created: number;
90
- sessionID: string;
91
- title: string;
92
- })[]>;
93
- /** A readable message for a failed courier call; OpenCode's own errors can carry an empty message. */
94
- export declare function describeFailure(tool: string, error: unknown): Error;
95
- export {};
package/dist/courier.js DELETED
@@ -1,131 +0,0 @@
1
- import { childBrief, envelope } from "./notices.js";
2
- import { current, record } from "./roster.js";
3
- function titleOf(task) {
4
- const line = task.trim().split("\n")[0] ?? "";
5
- return line.length > 60 ? `${line.slice(0, 57)}...` : line;
6
- }
7
- /**
8
- * The model a child runs on: its parent's, so it does not fall back to OpenCode's default, which
9
- * the parent may have been moved off for a reason. An agent that names its own model keeps it.
10
- */
11
- async function inheritedModel(ports, parentID, agent) {
12
- const parent = await ports.session.get({ sessionID: parentID });
13
- if (!parent.model || !agent)
14
- return parent.model;
15
- const named = await ports.agent.get({ agentID: agent, location: { directory: ports.directory } });
16
- return named.data.model ? undefined : parent.model;
17
- }
18
- /** Removes a worktree made for a child that was never created; one that cannot be removed stays. */
19
- async function dropWorktree(ports, directory) {
20
- try {
21
- await ports.worktree.remove({ projectID: ports.projectID, directory, force: false });
22
- }
23
- catch {
24
- // The spawn's own failure is the one to report.
25
- }
26
- }
27
- /** Creates a child session, hands it the task and returns at once; the child reports back with courier_send. */
28
- export async function spawn(ports, parentID, input) {
29
- // A failed lookup must not keep the child from starting; it then runs on OpenCode's default.
30
- const model = await inheritedModel(ports, parentID, input.agent).catch(() => undefined);
31
- const directory = input.isolate
32
- ? (await ports.worktree.create({ projectID: ports.projectID })).directory
33
- : undefined;
34
- const base = directory ? await ports.head(directory) : undefined;
35
- const title = input.title ?? titleOf(input.task);
36
- const child = await ports.session
37
- .create({
38
- title,
39
- ...(input.agent ? { agent: input.agent } : {}),
40
- ...(model ? { model } : {}),
41
- ...(directory ? { location: { directory } } : {}),
42
- metadata: { courier: { parentID } },
43
- })
44
- .catch(async (error) => {
45
- // No session will ever use the fresh worktree, and nothing records it, so it goes now.
46
- if (directory)
47
- await dropWorktree(ports, directory);
48
- throw error;
49
- });
50
- // Recorded before the prompt, so a child that exists is on the roster even if prompting fails. A
51
- // failed write must not keep the child from its task, so it is reported instead of thrown.
52
- const rosterError = await record(ports.storage, {
53
- sessionID: child.id,
54
- parentID,
55
- title,
56
- directory: directory ?? child.location.directory,
57
- isolated: directory !== undefined,
58
- createdAt: ports.now(),
59
- ...(directory ? { source: ports.directory, project: ports.projectID } : {}),
60
- ...(base ? { base } : {}),
61
- }).then(() => undefined, (error) => describeFailure("roster", error).message);
62
- await ports.session.prompt({ sessionID: child.id, text: childBrief(parentID, input.task) });
63
- return {
64
- sessionID: child.id,
65
- directory: directory ?? child.location.directory,
66
- ...(rosterError ? { rosterError } : {}),
67
- };
68
- }
69
- /** Drops a message into another session's inbox; OpenCode wakes that session if it is idle. */
70
- export async function send(ports, from, input) {
71
- const delivered = await ports.session.synthetic({
72
- sessionID: input.sessionID,
73
- text: envelope(from, input.message),
74
- description: `Message from ${from}`,
75
- metadata: { source: "courier", from },
76
- delivery: input.queue ? "queue" : "steer",
77
- });
78
- return { messageID: delivered.id };
79
- }
80
- /** A one-off look at a session, for check-ins; not meant to be called in a loop. */
81
- export async function status(ports, input) {
82
- const [info, messages, pending] = await Promise.all([
83
- ports.session.get({ sessionID: input.sessionID }),
84
- ports.session.context({ sessionID: input.sessionID }),
85
- ports.pending(input.sessionID),
86
- ]);
87
- const last = messages.findLast((message) => message.type === "assistant");
88
- const lastText = last?.type === "assistant"
89
- ? last.content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("")
90
- : undefined;
91
- return withoutUndefined({
92
- sessionID: info.id,
93
- title: info.title,
94
- parentID: info.parentID,
95
- outcome: info.outcome,
96
- updated: info.time.updated,
97
- idle: info.time.idle,
98
- lastText,
99
- pending: pending.length ? pending : undefined,
100
- });
101
- }
102
- /** The sessions a parent started, each with what courier_status reports, or the error it gave. */
103
- export async function listChildren(ports, parentID) {
104
- const entries = await current(ports.storage, parentID, ports.now());
105
- return Promise.all(entries.map(async (entry) => {
106
- const roster = { directory: entry.directory, isolated: entry.isolated, created: entry.createdAt };
107
- try {
108
- return { ...(await status(ports, { sessionID: entry.sessionID })), ...roster };
109
- }
110
- catch (error) {
111
- return {
112
- sessionID: entry.sessionID,
113
- title: entry.title,
114
- ...roster,
115
- error: describeFailure("courier_status", error).message,
116
- };
117
- }
118
- }));
119
- }
120
- /** OpenCode leaves a tool call hanging when its metadata holds `undefined`, so results drop those keys. */
121
- function withoutUndefined(value) {
122
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
123
- }
124
- /** A readable message for a failed courier call; OpenCode's own errors can carry an empty message. */
125
- export function describeFailure(tool, error) {
126
- const tagged = error;
127
- const message = (typeof tagged?.message === "string" && tagged.message) ||
128
- [tagged?._tag, tagged?.sessionID].filter((item) => typeof item === "string").join(" ") ||
129
- String(error);
130
- return new Error(`${tool} failed: ${message}`);
131
- }
package/dist/hub.d.ts DELETED
@@ -1,325 +0,0 @@
1
- import type { Plugin } from "@opencode/plugin";
2
- import type { Server } from "node:http";
3
- import { type LaterPorts } from "./later.js";
4
- import type { Asked, Outcome, Withdrawal } from "./notices.js";
5
- import type { Permissions, Waiting } from "./relay.js";
6
- import type { RosterStorage } from "./roster.js";
7
- import { type Registry } from "./storage.js";
8
- import type { WebhookConfig, WebhookPorts } from "./webhook.js";
9
- type Context = Plugin.Context;
10
- /**
11
- * The plugin's process-wide state, in one place. OpenCode sets the plugin up once per location
12
- * (project or worktree), all in one process, and after an update loads the new copy of the package
13
- * next to the old one until the old one unloads; so the state every instance shares lives on
14
- * `globalThis`, outside the module graph, and this is the only module that puts it there.
15
- *
16
- * The hub is versioned by `HUB_VERSION`, not by the package's version: copies whose hub has the
17
- * same shape share one hub, however far apart their releases, and only a change to the hub's shape
18
- * (the ports a `Member` carries included) or to the meaning of one of its fields bumps it. An
19
- * optional field that a copy without it gets along without, such as `Member.location` or
20
- * `Scheduler.running`, is added without a bump while no release has the hub (the latest is 0.2.2).
21
- *
22
- * Version skew: a copy that finds a hub of another version under `opencode-courier.hub` logs that
23
- * once and runs its own hub under `opencode-courier.hub@<version>`. Whatever its version, a hub
24
- * takes the claim sets and the other shared objects of the copies before the hub from their own
25
- * keys (`opencode-courier.claimed`, `.watched`, `.forms`, `.questions`, `.receiver`, `.locations`),
26
- * making each when it is missing, so every copy in the process, with a hub or without, claims an
27
- * event or a delivery in the same set, and an old and a new copy never both act on it. Those keys'
28
- * shapes and meanings are therefore fixed: a hub version that needs other ones makes new keys, and
29
- * keeps claiming in the old ones too while a copy that uses them may still be loaded. `members` is
30
- * the one thing each version keeps to itself, so a job a hub runs once for its members runs once
31
- * per hub version, and still claims what it acts on in the shared sets, which keep it from acting
32
- * twice.
33
- */
34
- export declare const HUB_VERSION = 1;
35
- /** The key of the hub; a copy that finds another version there keeps its own under `${HUB_KEY}@<version>`. */
36
- export declare const HUB_KEY = "opencode-courier.hub";
37
- /**
38
- * A question this process knows: one whose call waits (`call`), or one whose call was cut off,
39
- * which is answered by message. `link` withdraws the top session's question call that passes its
40
- * answer on, once the question is settled without that call.
41
- */
42
- export interface Question extends Asked {
43
- /** The directory of the location the call runs in, whose shutdown withdraws its form. */
44
- directory?: string;
45
- /** Hands the waiting call the top session's outcome; true once the call has ended with it. */
46
- call?: (outcome: Outcome) => Promise<boolean>;
47
- link?: (how: Withdrawal) => void;
48
- /** Storing the question and telling the top session, while that is under way. */
49
- relaying?: Promise<boolean>;
50
- /** Working out what became of it once its call ended without the top session's answer. */
51
- settling?: Promise<void>;
52
- }
53
- /**
54
- * The relay's timings, carried by its ports: how long a question cut off by a closing location
55
- * waits before an instance still loaded tells its top session, how long an answer waits for another
56
- * one to it that is still being passed on, how long what follows a call's end waits for the notice
57
- * of the question to go out, and how long a dismissal is held to see whether the location is
58
- * shutting down.
59
- */
60
- export interface QuestionTiming {
61
- readonly closingGraceMs: number;
62
- readonly passingWaitMs: number;
63
- readonly relayWaitMs: number;
64
- readonly dismissalGraceMs: number;
65
- }
66
- export interface QuestionPorts {
67
- readonly storage: RosterStorage;
68
- readonly session: Pick<Context["session"], "synthetic">;
69
- /** The directory of the instance's location. */
70
- readonly directory: string;
71
- /** The clock, in epoch milliseconds, as OpenCode's location shutdowns are recorded. */
72
- readonly now: () => number;
73
- readonly timing: QuestionTiming;
74
- readonly newID: () => string;
75
- readonly log: (message: string) => void;
76
- }
77
- export interface WatchPorts {
78
- readonly storage: RosterStorage;
79
- readonly session: Pick<Context["session"], "synthetic">;
80
- readonly event: Pick<Context["event"], "subscribe">;
81
- /**
82
- * The permission domains of every loaded location, whose pending requests are relayed when the
83
- * watcher (re)subscribes: one watcher serves them all, and an isolated child's are in its worktree's.
84
- */
85
- readonly permissions: () => Iterable<Pick<Context["permission"], "list">>;
86
- /** The clock a location shutdown is recorded by: the question relay's, which judges it. */
87
- readonly now: () => number;
88
- readonly log: (message: string) => void;
89
- }
90
- /**
91
- * What every plugin instance in the process shares. A hub follows OpenCode's events through two of
92
- * its members, one a standby, and a copy of another hub version, or one from before the hub, follows
93
- * them too, all sent the same events; so an event id is claimed synchronously and handled once.
94
- * `waiting` holds the permission requests a session was told about and has not answered;
95
- * `answered`, requests answered before anyone was told, so a notice whose roster lookup was
96
- * overtaken by the answer is not sent. `forms` does the same for the forms of spawned sessions.
97
- */
98
- export interface WatchState {
99
- readonly seen: Set<string>;
100
- readonly waiting: Waiting;
101
- readonly answered: Set<string>;
102
- readonly forms: FormsTold;
103
- }
104
- /** The forms sessions were told about, kept apart from the permission requests. */
105
- export interface FormsTold {
106
- /** Forms still shown whose notice went out, or is going out: the notice's delivery. */
107
- readonly told: Map<string, Promise<unknown>>;
108
- /** Forms settled before anyone was told, so a notice whose roster lookup was overtaken is not sent. */
109
- readonly settled: Set<string>;
110
- }
111
- /**
112
- * The webhook receiver, one per process: every instance configured with a `webhook` option joins
113
- * `instances`, the first one starts the server, and the last one to unload stops it.
114
- */
115
- export interface Receiver {
116
- readonly config: WebhookConfig;
117
- readonly instances: Set<WebhookPorts>;
118
- readonly server: Promise<Server | undefined>;
119
- }
120
- /** The current receiver, if any, and the previous one while it closes, which a new one waits for. */
121
- export interface Receivers {
122
- current?: Receiver;
123
- closing?: Promise<void>;
124
- }
125
- /**
126
- * The question relay's state, shared by every instance, since a child and the session it asks can
127
- * be in different locations: the questions told to a top session and not yet settled, the cut-off
128
- * ones whose top session has been told so, and the question calls waiting for their form to be shown.
129
- */
130
- export interface QuestionState {
131
- readonly questions: Map<string, Question>;
132
- readonly noticed: Set<string>;
133
- readonly shown: Map<string, () => void>;
134
- /** The questions whose answer is being passed on, each until that is done, and those whose answer went out. */
135
- readonly passing: Map<string, Promise<unknown>>;
136
- readonly answered: Set<string>;
137
- /**
138
- * The ports of the loaded instances, of every copy of the plugin. A release before the hub picks
139
- * one here to tell a cut-off question through, so every copy keeps it filled; this copy reads its
140
- * hub's members, and this only for an instance of another copy, when it has none.
141
- */
142
- readonly loaded: Set<QuestionPorts>;
143
- /** How many instances follow OpenCode's events now, and whether any has since the process started. */
144
- following: number;
145
- followed: boolean;
146
- /**
147
- * When OpenCode last reported each location shutting down (`location.shutdown`), by directory
148
- * in epoch milliseconds by the clock of the watcher that saw it; under `""` when the event named no location.
149
- */
150
- readonly shutdowns: Map<string, number>;
151
- /** Woken when a location shuts down or an instance unloads: dismissals held to see whether one follows. */
152
- readonly closingWaiters: Set<() => void>;
153
- }
154
- /**
155
- * A loaded plugin instance: its location's directory and permission domain, and the ports a job
156
- * run once for the whole process would run it through.
157
- */
158
- export interface Member {
159
- readonly directory: string;
160
- /**
161
- * The location OpenCode set the instance up with, the very object the instance's Effect half is
162
- * handed too, which finds its member by it (`portsAt`). A member of a copy before it has none.
163
- */
164
- readonly location?: object;
165
- readonly permission: Permissions;
166
- readonly later: LaterPorts;
167
- readonly watch: WatchPorts;
168
- readonly questions: QuestionPorts;
169
- readonly log: (message: string) => void;
170
- }
171
- /**
172
- * A tick under way: the member it runs through, when it started, by the wall clock every member's
173
- * `now` reads (the next ticks compare it with another member's), and its end.
174
- */
175
- export interface Tick {
176
- readonly member: Member;
177
- readonly since: number;
178
- /** Settles once the tick has ended, the owner key released if its loop stopped meanwhile; never rejects. */
179
- readonly done: Promise<void>;
180
- }
181
- /**
182
- * The scheduler's loop, one per hub: it runs while the hub has members, and each tick delivers the
183
- * due `courier_later` messages through the ports of the member that joined first and is still loaded.
184
- */
185
- export interface Scheduler {
186
- /** The loop's interval, while it runs. */
187
- timer?: unknown;
188
- /**
189
- * The tick under way, if any, whichever copy of the plugin started it and whether or not its
190
- * member is still loaded. A tick that falls due meanwhile renews the owner key for it, for up to a
191
- * minute from its start, and is skipped; past that minute one whose member has left holds up
192
- * nothing, and the next tick starts. Optional, since a copy of the same hub version made before it
193
- * existed keeps only `ticking`.
194
- */
195
- running?: Tick;
196
- /**
197
- * The member the tick under way runs through, if any, set and cleared with `running`, for a copy
198
- * that reads only this: a tick that falls due meanwhile is skipped, unless that member has left.
199
- */
200
- ticking?: Member;
201
- /** One tick, by the copy of the plugin that joined last, so a copy loaded after an update runs its own code. */
202
- tick?: () => Promise<void>;
203
- }
204
- /** How the hub starts and stops the scheduler's loop, and waits before re-reading the owner key; tests pass their own. */
205
- export interface Timers {
206
- readonly every: (run: () => void, ms: number) => unknown;
207
- readonly stop: (timer: unknown) => void;
208
- readonly wait: (ms: number) => Promise<void>;
209
- }
210
- /**
211
- * The key in the plugin's storage, shared by every OpenCode server on one data directory, naming
212
- * the server whose scheduler delivers the `courier_later` messages: `{ server, at }`, its id and
213
- * when it last wrote the key, in epoch milliseconds. Outside `later/`, which the schedulers scan
214
- * for messages, those of copies before the key included.
215
- */
216
- export declare const OWNER_KEY = "scheduler/owner";
217
- /** How long the owner key holds without being renewed; the owner renews it every tick, four times as often. */
218
- export declare const OWNER_EXPIRY_MS: number;
219
- /**
220
- * How old the owner's own key may be for it to renew the key and deliver at once. An older one,
221
- * which it may no longer hold by the time it writes, is taken again like a free one, with the wait.
222
- */
223
- export declare const OWNER_RENEW_MS: number;
224
- /** The wait between writing the owner key and reading it back, at least `OWNER_WAIT_MS` and up to twice that. */
225
- export declare const OWNER_WAIT_MS = 500;
226
- /**
227
- * This server's id in the owner key: the same for every copy of the plugin in the process, and
228
- * different in every other process, a restart of this one included.
229
- */
230
- export declare const SERVER: string;
231
- /**
232
- * How long an unloading instance waits, before it lets go, for the work it leaves behind: what it
233
- * started itself, a tick or an event's notice still under way through it, and on the last leave the
234
- * owner key's release.
235
- */
236
- export declare const LEAVE_MS = 2000;
237
- /** How many subscriptions to OpenCode's events a hub keeps, each through another member: one, and a standby. */
238
- export declare const WATCHERS = 2;
239
- /** One of the hub's subscriptions to OpenCode's events: the member whose ports it runs through, and how it ends. */
240
- export interface Watcher {
241
- readonly member: Member;
242
- readonly stop: AbortController;
243
- /** Settles once the subscription has ended, after `stop`: after the event it is handling, not the stream's close. */
244
- readonly done: Promise<void>;
245
- }
246
- export interface Hub {
247
- readonly version: typeof HUB_VERSION;
248
- /** The instances loaded now, in the order they joined. */
249
- readonly members: Set<Member>;
250
- readonly scheduler: Scheduler;
251
- /**
252
- * The hub's subscriptions to OpenCode's events, each through another member: two while two members
253
- * are loaded, so one is always connected when the other's member leaves.
254
- */
255
- readonly watchers: Watcher[];
256
- /**
257
- * Starts a subscription through a member, by the copy of the plugin that set it last; until one
258
- * has, the hub follows no events.
259
- */
260
- subscribe?: (member: Member) => Watcher;
261
- /** The ids of the `courier_later` messages being delivered. */
262
- readonly claimed: Set<string>;
263
- /** The events handled, and the permission requests a session was told about or that were answered first. */
264
- readonly watched: Omit<WatchState, "forms">;
265
- /** The forms sessions were told about, and those settled first. */
266
- readonly forms: FormsTold;
267
- /**
268
- * The permission domain of every loaded instance, by instance. OpenCode keeps a request where its
269
- * session runs, so a request of an isolated child is answered through the instance loaded in its worktree.
270
- */
271
- readonly locations: Map<object, Permissions>;
272
- readonly receivers: Receivers;
273
- readonly questions: QuestionState;
274
- /** Whether the hub of another version found under `HUB_KEY` has been logged. */
275
- skewLogged: boolean;
276
- }
277
- /** A hub, as `open` finds or makes it, and how instances join it. */
278
- export interface Opened {
279
- readonly hub: Hub;
280
- /**
281
- * Adds a loaded instance to the hub and its permission domain to `locations`; returns its leave,
282
- * which takes both out again. The first instance to join logs a hub of another version found
283
- * under `HUB_KEY`. An instance joining a hub without members starts the scheduler's loop, with a
284
- * tick at once; the last one to leave stops it. An instance joining while the hub has fewer than
285
- * `WATCHERS` subscriptions to OpenCode's events starts one through itself; when one leaves that
286
- * has one, another member without one starts its replacement before it ends. The last leave
287
- * settles once the scheduler's owner key is released, after the tick under way, whichever copy
288
- * started it. Every leave first waits for the work its member leaves behind: what was handed to
289
- * `track`, a tick under way through it, and the subscription it ran, which ends after the event
290
- * it is handling; all of it, the release included, for up to `LEAVE_MS`. The question relay's
291
- * own work is not waited for: it outlives the instance on purpose.
292
- */
293
- readonly join: (member: Member) => () => Promise<void>;
294
- /** Hands the hub work a member started, which its leave waits for; the member's own background work. */
295
- readonly track: (member: Member, work: Promise<unknown>) => void;
296
- }
297
- /**
298
- * Finds or makes this copy's hub in `registry`: the one under `HUB_KEY`, or its own under a
299
- * versioned key when a hub of another version holds that. Either way, its claim sets and shared
300
- * objects are the ones under the keys of the copies before the hub, made when missing.
301
- */
302
- export declare function open(registry: Registry, timers?: Timers, server?: string): Opened;
303
- /** This copy's hub, in the process. */
304
- export declare const hub: Hub;
305
- /** Joins this copy's hub; see `Opened.join`. */
306
- export declare const join: (member: Member) => () => Promise<void>;
307
- /** Hands this copy's hub work a member started; see `Opened.track`. */
308
- export declare const track: (member: Member, work: Promise<unknown>) => void;
309
- /** The member of this copy's hub set up with `location` that joined last, while it is loaded. */
310
- export declare function memberAt(location: object): Member | undefined;
311
- /**
312
- * The question ports of the member that has just joined at `location`, the newest there, for as long
313
- * as it stays loaded: resolved once, so an instance joining later at the same location, while this
314
- * one is still loaded, does not take its place.
315
- */
316
- export declare function portsAt(location: object): () => QuestionPorts | undefined;
317
- /** The permission domains of the loaded instances. */
318
- export declare const permissions: () => MapIterator<Permissions>;
319
- /**
320
- * For tests: forgets every remembered id and the question relay's state, the relay's loaded
321
- * instances and its count of watchers included. The hub's members, the locations and the webhook
322
- * receiver are left alone; leaving and unloading take them out.
323
- */
324
- export declare function resetHub(): void;
325
- export {};
package/dist/json.d.ts DELETED
@@ -1,4 +0,0 @@
1
- /** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
2
- export declare const str: (value: unknown) => string | undefined;
3
- export declare const num: (value: unknown) => number | undefined;
4
- export declare const obj: (value: unknown) => Record<string, any>;
package/dist/json.js DELETED
@@ -1,4 +0,0 @@
1
- /** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
2
- export const str = (value) => (typeof value === "string" ? value : undefined);
3
- export const num = (value) => (typeof value === "number" && Number.isInteger(value) ? value : undefined);
4
- export const obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};