opencode-courier 0.3.0 → 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 +175 -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 +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} +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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { type Registry, type Storage } from "./storage.js";
|
|
2
|
+
/**
|
|
3
|
+
* The plugin's process-wide state on `globalThis`, shared by every loaded copy; only this module puts
|
|
4
|
+
* it there. `HUB_VERSION`, the fixed claim keys and version skew: docs/maintainers/architecture.md, § Several copies in one process.
|
|
5
|
+
*/
|
|
6
|
+
export declare const HUB_VERSION = 2;
|
|
7
|
+
/** The key of the hub; a copy that finds another version there keeps its own under `${HUB_KEY}@<version>`. */
|
|
8
|
+
export declare const HUB_KEY = "opencode-courier.hub";
|
|
9
|
+
/** The ports a member's scheduler keeps the owner key through: what every member's `later` ports have. */
|
|
10
|
+
export interface TickPorts {
|
|
11
|
+
readonly storage: Pick<Storage, "get" | "set" | "remove">;
|
|
12
|
+
readonly now: () => number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The events handled, and the permission requests a session was told about or that were answered
|
|
16
|
+
* first: claim sets shared by every subscription and copy, so each event is handled once.
|
|
17
|
+
*/
|
|
18
|
+
export interface Watched {
|
|
19
|
+
readonly seen: Set<string>;
|
|
20
|
+
readonly waiting: Set<string>;
|
|
21
|
+
readonly answered: Set<string>;
|
|
22
|
+
}
|
|
23
|
+
/** The forms sessions were told about, kept apart from the permission requests. */
|
|
24
|
+
export interface FormsTold {
|
|
25
|
+
/** Forms still shown whose notice went out, or is going out: the notice's delivery. */
|
|
26
|
+
readonly told: Map<string, Promise<unknown>>;
|
|
27
|
+
/** Forms settled before anyone was told, so a notice whose roster lookup was overtaken is not sent. */
|
|
28
|
+
readonly settled: Set<string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A loaded plugin instance: its location's directory and permission domain, and the ports a job
|
|
32
|
+
* run once for the whole process would run it through.
|
|
33
|
+
*/
|
|
34
|
+
export interface Member {
|
|
35
|
+
readonly directory: string;
|
|
36
|
+
/**
|
|
37
|
+
* The location OpenCode set the instance up with, the very object the instance's Effect half is
|
|
38
|
+
* handed too, which finds its member by it (`portsAt`). A member of a copy before it has none.
|
|
39
|
+
*/
|
|
40
|
+
readonly location?: object;
|
|
41
|
+
/** The location's permission domain, kept in `locations`. */
|
|
42
|
+
readonly permission: unknown;
|
|
43
|
+
/** The ports the scheduler's tick runs through: the owner key's, and the tick jobs'. */
|
|
44
|
+
readonly later: TickPorts;
|
|
45
|
+
/** The ports the hub's subscriptions to OpenCode's events run through. */
|
|
46
|
+
readonly watch: unknown;
|
|
47
|
+
/** The question relay's ports. */
|
|
48
|
+
readonly questions: unknown;
|
|
49
|
+
readonly log: (message: string) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A tick under way: the member it runs through, when it started, by the wall clock every member's
|
|
53
|
+
* `now` reads (the next ticks compare it with another member's), and its end.
|
|
54
|
+
*/
|
|
55
|
+
export interface Tick {
|
|
56
|
+
readonly member: Member;
|
|
57
|
+
readonly since: number;
|
|
58
|
+
/** Settles once the tick has ended, the owner key released if its loop stopped meanwhile; never rejects. */
|
|
59
|
+
readonly done: Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The scheduler's loop, one per hub: it runs while the hub has members, and each tick runs the jobs
|
|
63
|
+
* registered with `open` through the ports of the member that joined first and is still loaded.
|
|
64
|
+
*/
|
|
65
|
+
export interface Scheduler {
|
|
66
|
+
/** The loop's interval, while it runs. */
|
|
67
|
+
timer?: unknown;
|
|
68
|
+
/**
|
|
69
|
+
* The tick under way, whichever copy started it: for a minute the key is renewed for it and due
|
|
70
|
+
* ticks skip; past it, one whose member left holds nothing up. Older copies have only `ticking`.
|
|
71
|
+
*/
|
|
72
|
+
running?: Tick;
|
|
73
|
+
/**
|
|
74
|
+
* The member the tick under way runs through, if any, set and cleared with `running`, for a copy
|
|
75
|
+
* that reads only this: a tick that falls due meanwhile is skipped, unless that member has left.
|
|
76
|
+
*/
|
|
77
|
+
ticking?: Member;
|
|
78
|
+
/** One tick, by the copy of the plugin that joined last, so a copy loaded after an update runs its own code. */
|
|
79
|
+
tick?: () => Promise<void>;
|
|
80
|
+
/** Whether a nudge came since the tick under way started, whichever copy's, so it ticks once more when done. */
|
|
81
|
+
nudged?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** How the hub starts and stops the scheduler's loop, and waits before re-reading the owner key; tests pass their own. */
|
|
84
|
+
export interface Timers {
|
|
85
|
+
readonly every: (run: () => void, ms: number) => unknown;
|
|
86
|
+
readonly stop: (timer: unknown) => void;
|
|
87
|
+
readonly wait: (ms: number) => Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/** How often the hub's scheduler ticks: what a job delivers, a `courier_later` message, can be this late. */
|
|
90
|
+
export declare const TICK_MS = 15000;
|
|
91
|
+
/**
|
|
92
|
+
* The storage key, shared by every server on one data directory, naming the server whose scheduler
|
|
93
|
+
* delivers `courier_later` messages: `{ server, at }`, its id and when it last wrote (epoch ms).
|
|
94
|
+
*/
|
|
95
|
+
export declare const OWNER_KEY = "scheduler/owner";
|
|
96
|
+
/** How long the owner key holds without being renewed; the owner renews it every tick, four times as often. */
|
|
97
|
+
export declare const OWNER_EXPIRY_MS: number;
|
|
98
|
+
/**
|
|
99
|
+
* How old the owner's own key may be for it to renew the key and deliver at once. An older one,
|
|
100
|
+
* which it may no longer hold by the time it writes, is taken again like a free one, with the wait.
|
|
101
|
+
*/
|
|
102
|
+
export declare const OWNER_RENEW_MS: number;
|
|
103
|
+
/** The wait between writing the owner key and reading it back, at least `OWNER_WAIT_MS` and up to twice that. */
|
|
104
|
+
export declare const OWNER_WAIT_MS = 500;
|
|
105
|
+
/**
|
|
106
|
+
* This server's id in the owner key: the same for every copy of the plugin in the process, and
|
|
107
|
+
* different in every other process, a restart of this one included.
|
|
108
|
+
*/
|
|
109
|
+
export declare const SERVER: string;
|
|
110
|
+
/**
|
|
111
|
+
* How long an unloading instance waits for the work it leaves behind: what it started itself, a tick
|
|
112
|
+
* or a notice under way through it, and on the last leave the owner key's release.
|
|
113
|
+
*/
|
|
114
|
+
export declare const LEAVE_MS = 2000;
|
|
115
|
+
/** How many subscriptions to OpenCode's events a hub keeps, each through another member: one, and a standby. */
|
|
116
|
+
export declare const WATCHERS = 2;
|
|
117
|
+
/** One of the hub's subscriptions to OpenCode's events: the member whose ports it runs through, and how it ends. */
|
|
118
|
+
export interface Watcher {
|
|
119
|
+
readonly member: Member;
|
|
120
|
+
readonly stop: AbortController;
|
|
121
|
+
/** Settles once the subscription has ended, after `stop`: after the event it is handling, not the stream's close. */
|
|
122
|
+
readonly done: Promise<void>;
|
|
123
|
+
}
|
|
124
|
+
export interface Hub {
|
|
125
|
+
readonly version: typeof HUB_VERSION;
|
|
126
|
+
/** The instances loaded now, in the order they joined. */
|
|
127
|
+
readonly members: Set<Member>;
|
|
128
|
+
readonly scheduler: Scheduler;
|
|
129
|
+
/**
|
|
130
|
+
* The hub's subscriptions to OpenCode's events, each through another member: two while two members
|
|
131
|
+
* are loaded, so one is always connected when the other's member leaves.
|
|
132
|
+
*/
|
|
133
|
+
readonly watchers: Watcher[];
|
|
134
|
+
/**
|
|
135
|
+
* Starts a subscription through a member, by the copy of the plugin that set it last; until one
|
|
136
|
+
* has, the hub follows no events.
|
|
137
|
+
*/
|
|
138
|
+
subscribe?: (member: Member) => Watcher;
|
|
139
|
+
/** The ids of the `courier_later` messages being delivered. */
|
|
140
|
+
readonly claimed: Set<string>;
|
|
141
|
+
/** The events handled, and the permission requests a session was told about or that were answered first. */
|
|
142
|
+
readonly watched: Watched;
|
|
143
|
+
/** The forms sessions were told about, and those settled first. */
|
|
144
|
+
readonly forms: FormsTold;
|
|
145
|
+
/**
|
|
146
|
+
* The permission domain of every loaded instance, by instance. OpenCode keeps a request where its
|
|
147
|
+
* session runs, so a request of an isolated child is answered through the instance loaded in its worktree.
|
|
148
|
+
*/
|
|
149
|
+
readonly locations: Map<object, unknown>;
|
|
150
|
+
/** The webhook receiver's state: the object every copy shares under the key `receiver`. */
|
|
151
|
+
readonly receivers: object;
|
|
152
|
+
/** The question relay's state: the object every copy shares under the key `questions`. */
|
|
153
|
+
readonly questions: object;
|
|
154
|
+
/** Whether the hub of another version found under `HUB_KEY` has been logged. */
|
|
155
|
+
skewLogged: boolean;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* A job each tick runs, in its turn, through the first loaded member, which may be another copy's:
|
|
159
|
+
* it uses only the ports every hub-v2 member has. `claimed` is the hub's claim set; a throw ends the tick.
|
|
160
|
+
*/
|
|
161
|
+
export type TickJob = (owner: Member, claimed: Set<string>) => Promise<void>;
|
|
162
|
+
/** A hub, as `open` finds or makes it, and how instances join it. */
|
|
163
|
+
export interface Opened {
|
|
164
|
+
readonly hub: Hub;
|
|
165
|
+
/**
|
|
166
|
+
* Adds a loaded instance to the hub and `locations`; returns its leave. The first join starts the
|
|
167
|
+
* scheduler and watchers, the last leave stops them; a leave waits `LEAVE_MS` for the member's work.
|
|
168
|
+
*/
|
|
169
|
+
readonly join: (member: Member) => () => Promise<void>;
|
|
170
|
+
/** Hands the hub work a member started, which its leave waits for; the member's own background work. */
|
|
171
|
+
readonly track: (member: Member, work: Promise<unknown>) => void;
|
|
172
|
+
/**
|
|
173
|
+
* Runs a tick now, or once the tick under way has ended, so what has just fallen due (a released
|
|
174
|
+
* group) is delivered at once rather than at the next tick; nothing while no instance is loaded.
|
|
175
|
+
*/
|
|
176
|
+
readonly nudge: () => void;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The value under `opencode-courier.<name>` every copy in the process shares, made by `create` the first
|
|
180
|
+
* time; another version, or a copy before the hub, may have made it, so its shape is kept for good.
|
|
181
|
+
*/
|
|
182
|
+
export declare function sharedState<T>(name: string, create: () => T, registry?: Registry): T;
|
|
183
|
+
/**
|
|
184
|
+
* This copy's hub in `registry`, and how its instances join it. Its ticks run `jobs` in order, kept in
|
|
185
|
+
* this copy's tick, which the hub runs once this copy has joined last, never in the hub itself.
|
|
186
|
+
*/
|
|
187
|
+
export declare function open(registry: Registry, jobs: readonly TickJob[], timers?: Timers, server?: string): Opened;
|
|
188
|
+
/** This copy's hub, in the process. */
|
|
189
|
+
export declare const hub: Hub;
|
|
190
|
+
/** Opens this copy's hub in the process, with its tick jobs; see `open`. */
|
|
191
|
+
export declare const openInProcess: (jobs: readonly TickJob[]) => Opened;
|
|
192
|
+
/** The member of this copy's hub set up with `location` that joined last, while it is loaded. */
|
|
193
|
+
export declare function memberAt(location: object): Member | undefined;
|
|
194
|
+
/** The permission domains of the loaded instances. */
|
|
195
|
+
export declare const permissions: () => MapIterator<unknown>;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { randomInt } from "node:crypto";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
|
-
import { deliverReleased } from "./group.js";
|
|
4
3
|
import { num, obj, str } from "./json.js";
|
|
5
|
-
import { deliverDue, TICK_MS } from "./later.js";
|
|
6
4
|
import { processWide } from "./storage.js";
|
|
7
5
|
/**
|
|
8
6
|
* The plugin's process-wide state on `globalThis`, shared by every loaded copy; only this module puts
|
|
9
|
-
* it there. `HUB_VERSION`, the fixed claim keys and version skew: docs/
|
|
7
|
+
* it there. `HUB_VERSION`, the fixed claim keys and version skew: docs/maintainers/architecture.md, § Several copies in one process.
|
|
10
8
|
*/
|
|
11
9
|
export const HUB_VERSION = 2;
|
|
12
10
|
/** The key of the hub; a copy that finds another version there keeps its own under `${HUB_KEY}@<version>`. */
|
|
@@ -16,6 +14,8 @@ const realTimers = {
|
|
|
16
14
|
stop: (timer) => clearInterval(timer),
|
|
17
15
|
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
18
16
|
};
|
|
17
|
+
/** How often the hub's scheduler ticks: what a job delivers, a `courier_later` message, can be this late. */
|
|
18
|
+
export const TICK_MS = 15_000;
|
|
19
19
|
/**
|
|
20
20
|
* The storage key, shared by every server on one data directory, naming the server whose scheduler
|
|
21
21
|
* delivers `courier_later` messages: `{ server, at }`, its id and when it last wrote (epoch ms).
|
|
@@ -50,27 +50,19 @@ export const LEAVE_MS = 2_000;
|
|
|
50
50
|
const holds = (at, now, ms) => Math.abs(now - at) < ms;
|
|
51
51
|
/** How many subscriptions to OpenCode's events a hub keeps, each through another member: one, and a standby. */
|
|
52
52
|
export const WATCHERS = 2;
|
|
53
|
+
/**
|
|
54
|
+
* The value under `opencode-courier.<name>` every copy in the process shares, made by `create` the first
|
|
55
|
+
* time; another version, or a copy before the hub, may have made it, so its shape is kept for good.
|
|
56
|
+
*/
|
|
57
|
+
export function sharedState(name, create, registry = globalThis) {
|
|
58
|
+
return processWide(`opencode-courier.${name}`, create, registry);
|
|
59
|
+
}
|
|
53
60
|
/**
|
|
54
61
|
* Finds or makes this copy's hub in `registry`: the one under `HUB_KEY`, or its own under a versioned
|
|
55
62
|
* key when another version holds that; its claim sets are the pre-hub keys', made when missing.
|
|
56
63
|
*/
|
|
57
|
-
|
|
58
|
-
const shared = (name, create) =>
|
|
59
|
-
const questionState = () => {
|
|
60
|
-
// Field by field, since a copy before the hub may have made it without the fields added later.
|
|
61
|
-
const state = shared("questions", () => ({}));
|
|
62
|
-
state.questions ??= new Map();
|
|
63
|
-
state.noticed ??= new Set();
|
|
64
|
-
state.shown ??= new Map();
|
|
65
|
-
state.passing ??= new Map();
|
|
66
|
-
state.answered ??= new Set();
|
|
67
|
-
state.loaded ??= new Set();
|
|
68
|
-
state.following ??= 0;
|
|
69
|
-
state.followed ??= false;
|
|
70
|
-
state.shutdowns ??= new Map();
|
|
71
|
-
state.closingWaiters ??= new Set();
|
|
72
|
-
return state;
|
|
73
|
-
};
|
|
64
|
+
function find(registry) {
|
|
65
|
+
const shared = (name, create) => sharedState(name, create, registry);
|
|
74
66
|
const create = () => ({
|
|
75
67
|
version: HUB_VERSION,
|
|
76
68
|
members: new Set(),
|
|
@@ -81,13 +73,21 @@ export function open(registry, timers = realTimers, server = SERVER) {
|
|
|
81
73
|
forms: shared("forms", () => ({ told: new Map(), settled: new Set() })),
|
|
82
74
|
locations: shared("locations", () => new Map()),
|
|
83
75
|
receivers: shared("receiver", () => ({})),
|
|
84
|
-
|
|
76
|
+
// Filled in by `person/question/`, field by field, since a copy before the hub may have made it without them.
|
|
77
|
+
questions: shared("questions", () => ({})),
|
|
85
78
|
skewLogged: false,
|
|
86
79
|
});
|
|
87
80
|
const found = registry[Symbol.for(HUB_KEY)];
|
|
88
81
|
// The version of the hub of another copy found under `HUB_KEY`, if one is there.
|
|
89
82
|
const skew = found != null && found.version !== HUB_VERSION ? { version: found.version } : undefined;
|
|
90
|
-
|
|
83
|
+
return { hub: processWide(skew ? `${HUB_KEY}@${HUB_VERSION}` : HUB_KEY, create, registry), skew };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* This copy's hub in `registry`, and how its instances join it. Its ticks run `jobs` in order, kept in
|
|
87
|
+
* this copy's tick, which the hub runs once this copy has joined last, never in the hub itself.
|
|
88
|
+
*/
|
|
89
|
+
export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
90
|
+
const { hub, skew } = find(registry);
|
|
91
91
|
/**
|
|
92
92
|
* Whether this server holds the owner key, taking or renewing it through `ports`. A free, expired
|
|
93
93
|
* or stalled key is written and read back after a random wait: the last of two writers holds it.
|
|
@@ -180,8 +180,8 @@ export function open(registry, timers = realTimers, server = SERVER) {
|
|
|
180
180
|
};
|
|
181
181
|
try {
|
|
182
182
|
if ((await own(owner.later)) && scheduler.timer !== undefined && hub.members.has(owner)) {
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
for (const job of jobs)
|
|
184
|
+
await job(owner, hub.claimed);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
catch (error) {
|
|
@@ -280,24 +280,13 @@ export function open(registry, timers = realTimers, server = SERVER) {
|
|
|
280
280
|
hub,
|
|
281
281
|
join,
|
|
282
282
|
track,
|
|
283
|
-
gate: shared("spawning", () => ({ reserved: new Set(), turn: Promise.resolve() })),
|
|
284
|
-
inbox: shared("inbox", () => new Map()),
|
|
285
283
|
nudge,
|
|
286
284
|
};
|
|
287
285
|
}
|
|
288
|
-
const opened = open(globalThis);
|
|
289
286
|
/** This copy's hub, in the process. */
|
|
290
|
-
export const hub =
|
|
291
|
-
/**
|
|
292
|
-
export const
|
|
293
|
-
/** Hands this copy's hub work a member started; see `Opened.track`. */
|
|
294
|
-
export const track = opened.track;
|
|
295
|
-
/** The spawns under way in the process; see `Opened.gate`. */
|
|
296
|
-
export const gate = opened.gate;
|
|
297
|
-
/** The types of the items enqueued for spawned sessions; see `Opened.inbox`. */
|
|
298
|
-
export const inbox = opened.inbox;
|
|
299
|
-
/** Has this copy's scheduler deliver what is due now; see `Opened.nudge`. */
|
|
300
|
-
export const nudge = opened.nudge;
|
|
287
|
+
export const hub = find(globalThis).hub;
|
|
288
|
+
/** Opens this copy's hub in the process, with its tick jobs; see `open`. */
|
|
289
|
+
export const openInProcess = (jobs) => open(globalThis, jobs);
|
|
301
290
|
/** The member of this copy's hub set up with `location` that joined last, while it is loaded. */
|
|
302
291
|
export function memberAt(location) {
|
|
303
292
|
let found;
|
|
@@ -306,38 +295,5 @@ export function memberAt(location) {
|
|
|
306
295
|
found = member;
|
|
307
296
|
return found;
|
|
308
297
|
}
|
|
309
|
-
/**
|
|
310
|
-
* The question ports of the member that just joined at `location`, while it is loaded: resolved once,
|
|
311
|
-
* so a later instance at the same location does not take its place while this one is still loaded.
|
|
312
|
-
*/
|
|
313
|
-
export function portsAt(location) {
|
|
314
|
-
const own = memberAt(location);
|
|
315
|
-
return () => (own && hub.members.has(own) ? own.questions : undefined);
|
|
316
|
-
}
|
|
317
298
|
/** The permission domains of the loaded instances. */
|
|
318
299
|
export const permissions = () => hub.locations.values();
|
|
319
|
-
/**
|
|
320
|
-
* For tests: forgets every remembered id, the spawns under way and the question relay's state, loaded
|
|
321
|
-
* instances and watcher count included. Members, locations and the receiver are left to unloading.
|
|
322
|
-
*/
|
|
323
|
-
export function resetHub() {
|
|
324
|
-
gate.reserved.clear();
|
|
325
|
-
inbox.clear();
|
|
326
|
-
hub.claimed.clear();
|
|
327
|
-
hub.watched.seen.clear();
|
|
328
|
-
hub.watched.waiting.clear();
|
|
329
|
-
hub.watched.answered.clear();
|
|
330
|
-
hub.forms.told.clear();
|
|
331
|
-
hub.forms.settled.clear();
|
|
332
|
-
const questions = hub.questions;
|
|
333
|
-
questions.questions.clear();
|
|
334
|
-
questions.noticed.clear();
|
|
335
|
-
questions.shown.clear();
|
|
336
|
-
questions.passing.clear();
|
|
337
|
-
questions.answered.clear();
|
|
338
|
-
questions.loaded.clear();
|
|
339
|
-
questions.following = 0;
|
|
340
|
-
questions.followed = false;
|
|
341
|
-
questions.shutdowns.clear();
|
|
342
|
-
questions.closingWaiters.clear();
|
|
343
|
-
}
|
|
@@ -5,7 +5,7 @@ const README_TABLE = "https://github.com/ivopogace/opencode-courier#supported-op
|
|
|
5
5
|
* `@opencode/plugin` devDependency version, which the peer repeats. Throws if either is missing.
|
|
6
6
|
*/
|
|
7
7
|
export function builtVersions() {
|
|
8
|
-
const pkg = JSON.parse(readFileSync(new URL("
|
|
8
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
9
9
|
const plugin = pkg.version;
|
|
10
10
|
const opencode = pkg.devDependencies?.["@opencode/plugin"];
|
|
11
11
|
if (typeof plugin !== "string")
|
package/dist/rpc.d.ts
ADDED
package/dist/rpc.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TickJob } from "../platform/hub.js";
|
|
2
|
+
export { cancel, cancelScheduled, deliverDue, isScheduled, schedule, scheduledFor } from "./later.js";
|
|
3
|
+
export type { LaterEntry, LaterInput, LaterPorts } from "./later.js";
|
|
4
|
+
export { addSchedulingTools } from "./tools.js";
|
|
5
|
+
/** The scheduler's tick job: due messages, through the first loaded member, whose `later` is every hub-v2 member's LaterPorts. */
|
|
6
|
+
export declare const dueJob: TickJob;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { deliverDue } from "./later.js";
|
|
2
|
+
export { cancel, cancelScheduled, deliverDue, isScheduled, schedule, scheduledFor } from "./later.js";
|
|
3
|
+
export { addSchedulingTools } from "./tools.js";
|
|
4
|
+
/** The scheduler's tick job: due messages, through the first loaded member, whose `later` is every hub-v2 member's LaterPorts. */
|
|
5
|
+
export const dueJob = (owner, claimed) => deliverDue(owner.later, claimed);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin";
|
|
2
|
-
import { type Storage } from "
|
|
2
|
+
import { type Storage } from "../platform/storage.js";
|
|
3
3
|
type Context = Plugin.Context;
|
|
4
|
-
/** How often the hub's scheduler looks for due messages; a delivery can be this late. */
|
|
5
|
-
export declare const TICK_MS = 15000;
|
|
6
4
|
export interface LaterEntry {
|
|
7
5
|
readonly id: string;
|
|
8
6
|
readonly sessionID: string;
|
|
@@ -32,6 +30,12 @@ export declare function schedule(ports: LaterPorts, from: string, input: LaterIn
|
|
|
32
30
|
export declare function cancel(ports: LaterPorts, id: string): Promise<boolean>;
|
|
33
31
|
/** The messages scheduled for a session that have not been delivered yet. */
|
|
34
32
|
export declare function scheduledFor(storage: Pick<Storage, "scan">, sessionID: string): Promise<LaterEntry[]>;
|
|
33
|
+
/** Cancels every message scheduled for a session; the ids of those cancelled. */
|
|
34
|
+
export declare function cancelScheduled(ports: LaterPorts, sessionID: string): Promise<string[]>;
|
|
35
|
+
/** Whether a session waits on a scheduled message of its own. */
|
|
36
|
+
export declare function isScheduled(ports: {
|
|
37
|
+
readonly storage: Pick<Storage, "scan">;
|
|
38
|
+
}, sessionID: string): Promise<boolean>;
|
|
35
39
|
/**
|
|
36
40
|
* Delivers every due message once: an id is claimed synchronously in the shared `claimed`, re-read,
|
|
37
41
|
* delivered and only then removed; a crash in between delivers it again after a restart, not never.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { envelope } from "
|
|
2
|
-
import { scanAll } from "
|
|
3
|
-
/** How often the hub's scheduler looks for due messages; a delivery can be this late. */
|
|
4
|
-
export const TICK_MS = 15_000;
|
|
1
|
+
import { envelope } from "../kernel/protocol.js";
|
|
2
|
+
import { scanAll } from "../platform/storage.js";
|
|
5
3
|
const PREFIX = "later/";
|
|
6
4
|
/** The latest time a Date can hold; a message due after it could never be shown or delivered. */
|
|
7
5
|
const LATEST = 8.64e15;
|
|
@@ -64,6 +62,16 @@ function pending(ports) {
|
|
|
64
62
|
export async function scheduledFor(storage, sessionID) {
|
|
65
63
|
return (await pending({ storage: storage })).filter((entry) => entry.sessionID === sessionID);
|
|
66
64
|
}
|
|
65
|
+
/** Cancels every message scheduled for a session; the ids of those cancelled. */
|
|
66
|
+
export async function cancelScheduled(ports, sessionID) {
|
|
67
|
+
const scheduled = await scheduledFor(ports.storage, sessionID);
|
|
68
|
+
const dropped = await Promise.all(scheduled.map(async (later) => ((await cancel(ports, later.id)) ? later.id : undefined)));
|
|
69
|
+
return dropped.filter((id) => id !== undefined);
|
|
70
|
+
}
|
|
71
|
+
/** Whether a session waits on a scheduled message of its own. */
|
|
72
|
+
export async function isScheduled(ports, sessionID) {
|
|
73
|
+
return (await scheduledFor(ports.storage, sessionID)).length > 0;
|
|
74
|
+
}
|
|
67
75
|
/**
|
|
68
76
|
* Delivers every due message once: an id is claimed synchronously in the shared `claimed`, re-read,
|
|
69
77
|
* delivered and only then removed; a crash in between delivers it again after a restart, not never.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** `own` when the message goes to the session that scheduled it. */
|
|
2
|
+
export declare function laterText(entry: {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly sessionID: string;
|
|
5
|
+
}, fireAt: string, own: boolean): string;
|
|
6
|
+
export declare const cancelText: (id: string, cancelled: boolean) => string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `own` when the message goes to the session that scheduled it. */
|
|
2
|
+
export function laterText(entry, fireAt, own) {
|
|
3
|
+
const next = own
|
|
4
|
+
? "It arrives when due, after your current turn if one is running, so do not wait for it: once nothing " +
|
|
5
|
+
"else is left to do now, end your turn by replying without calling more tools. If what it checks on " +
|
|
6
|
+
"reports first, cancel it then with courier_cancel."
|
|
7
|
+
: "Cancel it with courier_cancel if it is no longer needed.";
|
|
8
|
+
return `Scheduled ${entry.id} for ${fireAt}, to ${entry.sessionID}. ${next}`;
|
|
9
|
+
}
|
|
10
|
+
export const cancelText = (id, cancelled) => cancelled ? `Cancelled ${id}.` : `Nothing pending under ${id}; it may have been delivered.`;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { type ToolEditor } from "../kernel/tool.js";
|
|
3
|
+
import { type LaterPorts } from "./later.js";
|
|
4
|
+
export declare const LaterInput: Schema.Struct<{
|
|
5
|
+
readonly message: Schema.String;
|
|
6
|
+
readonly delayMinutes: Schema.optional<Schema.Union<readonly [Schema.Number, Schema.String]>>;
|
|
7
|
+
readonly at: Schema.optional<Schema.String>;
|
|
8
|
+
readonly sessionID: Schema.optional<Schema.String>;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const CancelInput: Schema.Struct<{
|
|
11
|
+
readonly id: Schema.String;
|
|
12
|
+
}>;
|
|
13
|
+
/** Adds `courier_later` and `courier_cancel`. */
|
|
14
|
+
export declare function addSchedulingTools(tools: ToolEditor, later: LaterPorts): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { rethrow } from "../kernel/tool.js";
|
|
3
|
+
import { cancel, schedule } from "./later.js";
|
|
4
|
+
import { cancelText, laterText } from "./text.js";
|
|
5
|
+
export const LaterInput = Schema.Struct({
|
|
6
|
+
message: Schema.String.annotate({ description: "The message to deliver." }),
|
|
7
|
+
delayMinutes: Schema.optional(
|
|
8
|
+
// Some models send the number as a string ("3", "3.0") whatever the schema says, and resend it
|
|
9
|
+
// unchanged after the error, so a string is accepted and schedule reads it as a number.
|
|
10
|
+
Schema.Union([Schema.Number, Schema.String]).annotate({
|
|
11
|
+
description: "Deliver this many minutes from now. Give this or at.",
|
|
12
|
+
})),
|
|
13
|
+
at: Schema.optional(Schema.String.annotate({ description: "Deliver at this ISO 8601 time. Give this or delayMinutes." })),
|
|
14
|
+
sessionID: Schema.optional(Schema.String.annotate({ description: "The session to deliver to; defaults to this one." })),
|
|
15
|
+
});
|
|
16
|
+
export const CancelInput = Schema.Struct({
|
|
17
|
+
id: Schema.String.annotate({ description: "The id courier_later returned." }),
|
|
18
|
+
});
|
|
19
|
+
/** Adds `courier_later` and `courier_cancel`. */
|
|
20
|
+
export function addSchedulingTools(tools, later) {
|
|
21
|
+
tools.add({
|
|
22
|
+
name: "courier_later",
|
|
23
|
+
options: { codemode: false },
|
|
24
|
+
description: "Schedule a message for a session (this one by default), delivered when due and waking it if idle. " +
|
|
25
|
+
"Use it as a safety net when you start sessions: schedule one check-in, end your turn by replying without " +
|
|
26
|
+
"calling more tools, and cancel it with courier_cancel if the child reports first. Survives server restarts; " +
|
|
27
|
+
"may arrive up to ~15 seconds late.",
|
|
28
|
+
input: LaterInput,
|
|
29
|
+
execute: async (input, context) => {
|
|
30
|
+
const entry = await schedule(later, context.sessionID, input).catch(rethrow("courier_later"));
|
|
31
|
+
const fireAt = new Date(entry.fireAt).toISOString();
|
|
32
|
+
return {
|
|
33
|
+
content: laterText(entry, fireAt, entry.sessionID === context.sessionID),
|
|
34
|
+
metadata: { id: entry.id, fireAt, sessionID: entry.sessionID },
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
tools.add({
|
|
39
|
+
name: "courier_cancel",
|
|
40
|
+
options: { codemode: false },
|
|
41
|
+
description: "Cancel a message scheduled with courier_later, e.g. because the child it was waiting for reported.",
|
|
42
|
+
input: CancelInput,
|
|
43
|
+
execute: async (input) => {
|
|
44
|
+
const cancelled = await cancel(later, input.id).catch(rethrow("courier_cancel"));
|
|
45
|
+
return { content: cancelText(input.id, cancelled), metadata: { id: input.id, cancelled } };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
package/dist/skill.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin";
|
|
2
|
+
/** Why a model loads the skill, listed with every session's available skills. */
|
|
3
|
+
export declare const SKILL_DESCRIPTION: string;
|
|
4
|
+
/** The skill's text; it names no limits, which the role part and the brief give for the session's own tree. */
|
|
5
|
+
export declare const SKILL_CONTENT: string;
|
|
2
6
|
/** The editor OpenCode hands a skill transform. */
|
|
3
7
|
export type SkillEditor = Parameters<Parameters<Plugin.Context["skill"]["transform"]>[0]>[0];
|
|
4
8
|
/** The courier-orchestrate skill as OpenCode lists it: its id, name and description, and the text the model loads. */
|
package/dist/skill.js
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SKILL_ID, SPLIT_LINE } from "./kernel/protocol.js";
|
|
2
|
+
// The courier-orchestrate skill: the playbook a session loads with its skill tool.
|
|
3
|
+
/** Why a model loads the skill, listed with every session's available skills. */
|
|
4
|
+
export const SKILL_DESCRIPTION = "Load at the start of any implementation task that may have independent parts, or when a session started you, to " +
|
|
5
|
+
"decide whether to split it across OpenCode sessions with courier_spawn and state the decision, and how: plan the " +
|
|
6
|
+
"parts, start them, end your turn, read the reports, verify the work, integrate it and report upward, and what to do " +
|
|
7
|
+
"when a part goes wrong.";
|
|
8
|
+
/** The skill's text; it names no limits, which the role part and the brief give for the session's own tree. */
|
|
9
|
+
export const SKILL_CONTENT = [
|
|
10
|
+
"How to split a task across OpenCode sessions with opencode-courier, and bring the results back up. Your brief, or the " +
|
|
11
|
+
"opencode-courier role line in your system prompt, names your depth in the session tree and its limits.",
|
|
12
|
+
"",
|
|
13
|
+
"1. Decide: split or do. Split when the task has 2 or more independent, substantial parts touching separate files or " +
|
|
14
|
+
"areas; do it yourself when it is small, sequential or tightly coupled. Never start exactly one session. " +
|
|
15
|
+
"An implementation task with one tightly coupled core usually still has independent parts: its e2e scenario, its " +
|
|
16
|
+
"documentation, sometimes a second test file, and a review of the core by a session with fresh context, which reads the " +
|
|
17
|
+
"committed diff against the task and the repository's rules and reports its findings (isolate: false, it edits nothing). " +
|
|
18
|
+
"Weigh each of them: do the core yourself first and commit it, so that the names and texts the other parts depend on are " +
|
|
19
|
+
"fixed, then, if 2 or more remain, start one session per remaining part, in one group. A part that is itself big and " +
|
|
20
|
+
"divisible is split again by the session that gets it; at the deepest level a session does its part itself. " +
|
|
21
|
+
`Say which in one line of your reply, before you start: ${SPLIT_LINE}, so that a reasoned decision to do it ` +
|
|
22
|
+
"yourself can be told from one never made.",
|
|
23
|
+
"2. Plan the parts so that no two edit the same files. Write each task so the session can do it without you: what to " +
|
|
24
|
+
"produce and where, how to check it, what to report. Say if it may split its part further.",
|
|
25
|
+
"3. Start them: one courier_spawn per part, all with the same group name, so you are woken once with every report; " +
|
|
26
|
+
"isolate: true for each part that edits files in parallel with another (commit your own work first if you are isolated " +
|
|
27
|
+
"yourself). For long work, courier_later a check-in for yourself, and courier_cancel it when the reports arrive.",
|
|
28
|
+
"4. End your turn: reply without calling more tools. Do not poll with courier_status, courier_children, courier_tree or " +
|
|
29
|
+
"sleep. The group's reports, a blocked report, a question, a permission request, a failed turn or a silent end each start " +
|
|
30
|
+
"a new turn for you.",
|
|
31
|
+
"5. Read each report by its status and its artifacts (branch, commits, files, checks). done: verify it. partial: what is " +
|
|
32
|
+
"left and why; have the session finish it with a courier_send, or do it yourself. blocked: make the decision it asks for " +
|
|
33
|
+
"and send it with courier_send; if only the person can make it, ask them with your question tool and pass the answer on. " +
|
|
34
|
+
"failed: read the error; message the session to try again, start a replacement, or carry on without it.",
|
|
35
|
+
"6. Verify first-hand: open the files, run the checks. Never hand the checking to another session: a review session's " +
|
|
36
|
+
"findings are a report to act on, and running the checks is still yours. Run checks through " +
|
|
37
|
+
"the project's scripts (bun run <script>, npm run <script>), not ./node_modules/.bin/<tool>, so that one permission " +
|
|
38
|
+
"rule covers them: your own permission prompts are relayed nowhere, and each one stalls the whole tree.",
|
|
39
|
+
"7. Integrate: merge each reported branch into your own checkout (git merge courier/<session>), resolve conflicts " +
|
|
40
|
+
"yourself, run the checks once more, then courier_cleanup the isolated sessions you are done with.",
|
|
41
|
+
"8. Report upward if a session started you: one courier_send to it, with one status for the whole (done only when every " +
|
|
42
|
+
"part is verified and integrated; partial, blocked or failed otherwise) and the artifacts. At the top, reply to the person.",
|
|
43
|
+
"",
|
|
44
|
+
"When a part goes wrong:",
|
|
45
|
+
"- A failed turn (failed=\"...\") or a silent end (ended=\"without-report\", quoting the session's last reply): message " +
|
|
46
|
+
"the session with courier_send to carry on, to report, or to try again; or start a replacement. Until it reports, it " +
|
|
47
|
+
"counts toward the limits.",
|
|
48
|
+
"- A refusal from courier_spawn naming maxDepth, maxChildren or maxTotal: do that part yourself, or end your turn and " +
|
|
49
|
+
"start it once a report has arrived. Do not retry it in a loop.",
|
|
50
|
+
"- Lost track, after a compaction or a restart: one courier_tree shows every session under you with its depth, its " +
|
|
51
|
+
"parent and whether it still owes a report; one look, not a loop.",
|
|
52
|
+
"- A subtree that went wrong or is no longer needed: courier_stop its top session; everything under it is stopped too, " +
|
|
53
|
+
"and nothing in it reports back. cleanup: true removes their worktrees.",
|
|
54
|
+
].join("\n");
|
|
2
55
|
/** A skill's path is where OpenCode would look for its files; this one has none, so a path of the plugin's own. */
|
|
3
56
|
const SKILL_PATH = `/opencode-courier/${SKILL_ID}.md`;
|
|
4
57
|
/** The courier-orchestrate skill as OpenCode lists it: its id, name and description, and the text the model loads. */
|
|
@@ -1,29 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { type CleanupResult } from "./notices.js";
|
|
3
|
-
import { type RosterStorage } from "./roster.js";
|
|
4
|
-
type Context = Plugin.Context;
|
|
5
|
-
/** What would be lost by removing a worktree. */
|
|
6
|
-
export interface WorktreeState {
|
|
7
|
-
/** Paths with uncommitted changes, untracked files included, as `git status --porcelain` lists them. */
|
|
8
|
-
readonly changes: readonly string[];
|
|
9
|
-
/**
|
|
10
|
-
* Commits reachable from the worktree's HEAD but from no branch, tag or remote-tracking ref, nor
|
|
11
|
-
* from the commit the worktree was made from, newest first.
|
|
12
|
-
*/
|
|
13
|
-
readonly commits: readonly string[];
|
|
14
|
-
}
|
|
15
|
-
export interface CleanupPorts {
|
|
16
|
-
readonly storage: RosterStorage;
|
|
17
|
-
readonly worktree: Pick<Context["worktree"], "remove">;
|
|
18
|
-
/** The project of the plugin's location, for roster entries recorded before they carried their project. */
|
|
19
|
-
readonly projectID: string;
|
|
20
|
-
/** The worktree's state, or undefined when its directory no longer exists; `base` is the commit it was made from. */
|
|
21
|
-
readonly inspect: (directory: string, base?: string) => Promise<WorktreeState | undefined>;
|
|
22
|
-
/** When a member still out is dropped from its group. */
|
|
23
|
-
readonly now: () => number;
|
|
24
|
-
/** Has the scheduler deliver what is due now: a group the forgotten child may have been the last member out of. */
|
|
25
|
-
readonly nudge: () => void;
|
|
26
|
-
}
|
|
1
|
+
import type { CleanupPorts, CleanupResult, WorktreeState } from "./ports.js";
|
|
27
2
|
export interface CleanupInput {
|
|
28
3
|
readonly sessionID: string;
|
|
29
4
|
readonly force?: boolean;
|
|
@@ -56,4 +31,3 @@ export declare function dirtyOf(directory: string): Promise<boolean | undefined>
|
|
|
56
31
|
* `base`, the commit the worktree was made from, are not its own work and are not listed.
|
|
57
32
|
*/
|
|
58
33
|
export declare function inspectWorktree(directory: string, base?: string): Promise<WorktreeState | undefined>;
|
|
59
|
-
export {};
|