opencode-courier 0.13.8 → 0.14.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 +7 -9
- package/dist/index.d.ts +0 -8
- package/dist/index.js +6 -53
- package/dist/kernel/protocol.d.ts +0 -14
- package/dist/kernel/protocol.js +1 -15
- package/dist/kernel/tool.d.ts +0 -2
- package/dist/kernel/tool.js +0 -1
- package/dist/memory/domain.d.ts +6 -85
- package/dist/memory/domain.js +7 -63
- package/dist/memory/events.d.ts +0 -11
- package/dist/memory/events.js +1 -12
- package/dist/memory/forget.d.ts +0 -5
- package/dist/memory/forget.js +0 -4
- package/dist/memory/index.js +0 -2
- package/dist/memory/lessons.d.ts +0 -8
- package/dist/memory/lessons.js +0 -9
- package/dist/memory/note.d.ts +0 -9
- package/dist/memory/note.js +0 -5
- package/dist/memory/pin.d.ts +0 -5
- package/dist/memory/pin.js +0 -4
- package/dist/memory/ports.d.ts +2 -22
- package/dist/memory/recall.d.ts +0 -5
- package/dist/memory/recall.js +0 -12
- package/dist/memory/record.d.ts +0 -4
- package/dist/memory/record.js +8 -31
- package/dist/memory/rpc-handlers.d.ts +3 -6
- package/dist/memory/rpc-handlers.js +0 -6
- package/dist/memory/rpc.d.ts +6 -19
- package/dist/memory/rpc.js +1 -16
- package/dist/memory/store.d.ts +0 -5
- package/dist/memory/store.js +1 -16
- package/dist/memory/sweep.d.ts +0 -4
- package/dist/memory/sweep.js +0 -4
- package/dist/memory/text.d.ts +0 -25
- package/dist/memory/text.js +6 -36
- package/dist/memory/tools.d.ts +0 -2
- package/dist/memory/tools.js +0 -2
- package/dist/memory/tui.d.ts +1 -35
- package/dist/person/events.d.ts +0 -16
- package/dist/person/events.js +3 -30
- package/dist/person/form.d.ts +0 -9
- package/dist/person/form.js +0 -10
- package/dist/person/index.d.ts +0 -2
- package/dist/person/permission.d.ts +0 -34
- package/dist/person/permission.js +2 -28
- package/dist/person/ports.d.ts +0 -16
- package/dist/person/ports.js +0 -1
- package/dist/person/question/answer.d.ts +0 -28
- package/dist/person/question/answer.js +0 -49
- package/dist/person/question/index.js +0 -2
- package/dist/person/question/lifecycle.d.ts +0 -26
- package/dist/person/question/lifecycle.js +1 -32
- package/dist/person/question/pure.d.ts +0 -11
- package/dist/person/question/pure.js +0 -11
- package/dist/person/question/relay.d.ts +0 -1
- package/dist/person/question/relay.js +5 -67
- package/dist/person/question/runtime.d.ts +0 -14
- package/dist/person/question/runtime.js +1 -26
- package/dist/person/question/shared.d.ts +0 -17
- package/dist/person/question/shared.js +0 -20
- package/dist/person/question/types.d.ts +0 -44
- package/dist/person/rpc-handlers.d.ts +0 -7
- package/dist/person/rpc-handlers.js +0 -6
- package/dist/person/rpc.d.ts +0 -20
- package/dist/person/rpc.js +0 -16
- package/dist/person/text.d.ts +0 -45
- package/dist/person/text.js +2 -54
- package/dist/person/tools.d.ts +0 -3
- package/dist/person/tools.js +1 -3
- package/dist/person/tui.d.ts +0 -21
- package/dist/platform/bounded.d.ts +0 -8
- package/dist/platform/bounded.js +0 -8
- package/dist/platform/events.d.ts +0 -18
- package/dist/platform/events.js +3 -22
- package/dist/platform/hub.d.ts +0 -113
- package/dist/platform/hub.js +1 -76
- package/dist/platform/json.d.ts +0 -3
- package/dist/platform/json.js +1 -3
- package/dist/platform/storage.d.ts +0 -7
- package/dist/platform/storage.js +0 -6
- package/dist/platform/version.d.ts +0 -9
- package/dist/platform/version.js +0 -8
- package/dist/rpc.js +0 -1
- package/dist/scheduling/index.d.ts +0 -1
- package/dist/scheduling/index.js +0 -1
- package/dist/scheduling/later.d.ts +0 -11
- package/dist/scheduling/later.js +0 -14
- package/dist/scheduling/text.d.ts +0 -1
- package/dist/scheduling/text.js +0 -1
- package/dist/scheduling/tools.d.ts +0 -1
- package/dist/scheduling/tools.js +1 -5
- package/dist/skill.d.ts +1 -9
- package/dist/skill.js +4 -12
- package/dist/tree/cleanup.d.ts +0 -20
- package/dist/tree/cleanup.js +0 -26
- package/dist/tree/context.d.ts +0 -5
- package/dist/tree/context.js +0 -8
- package/dist/tree/domain.d.ts +0 -64
- package/dist/tree/domain.js +7 -45
- package/dist/tree/events.d.ts +0 -36
- package/dist/tree/events.js +4 -49
- package/dist/tree/forget.d.ts +0 -15
- package/dist/tree/forget.js +0 -18
- package/dist/tree/group.d.ts +0 -22
- package/dist/tree/group.js +1 -37
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.js +1 -3
- package/dist/tree/limits.d.ts +0 -4
- package/dist/tree/limits.js +1 -12
- package/dist/tree/mascot.d.ts +0 -25
- package/dist/tree/mascot.js +1 -32
- package/dist/tree/model.d.ts +0 -5
- package/dist/tree/model.js +2 -15
- package/dist/tree/nudge.d.ts +0 -11
- package/dist/tree/nudge.js +3 -16
- package/dist/tree/panel.d.ts +0 -8
- package/dist/tree/panel.js +3 -16
- package/dist/tree/ports.d.ts +2 -138
- package/dist/tree/report.d.ts +0 -10
- package/dist/tree/report.js +0 -11
- package/dist/tree/roster.d.ts +0 -21
- package/dist/tree/roster.js +0 -21
- package/dist/tree/rpc-handlers.d.ts +6 -1
- package/dist/tree/rpc-handlers.js +0 -5
- package/dist/tree/rpc.d.ts +9 -109
- package/dist/tree/rpc.js +2 -38
- package/dist/tree/running.d.ts +0 -6
- package/dist/tree/running.js +2 -10
- package/dist/tree/send.d.ts +0 -4
- package/dist/tree/send.js +4 -31
- package/dist/tree/spawn.d.ts +0 -7
- package/dist/tree/spawn.js +6 -47
- package/dist/tree/standing.d.ts +0 -8
- package/dist/tree/standing.js +1 -11
- package/dist/tree/status.d.ts +0 -8
- package/dist/tree/status.js +1 -9
- package/dist/tree/stop.d.ts +2 -9
- package/dist/tree/stop.js +58 -25
- package/dist/tree/store.d.ts +0 -8
- package/dist/tree/store.js +4 -38
- package/dist/tree/subtree.d.ts +8 -26
- package/dist/tree/subtree.js +37 -35
- package/dist/tree/summary.d.ts +0 -4
- package/dist/tree/summary.js +0 -8
- package/dist/tree/text.d.ts +1 -44
- package/dist/tree/text.js +5 -65
- package/dist/tree/tools.d.ts +0 -6
- package/dist/tree/tools.js +4 -8
- package/dist/tree/tui.d.ts +0 -81
- package/dist/tree/wake.d.ts +0 -30
- package/dist/tree/wake.js +5 -39
- package/dist/tui.js +22 -8
- package/package.json +8 -9
- package/dist/webhook/index.d.ts +0 -5
- package/dist/webhook/index.js +0 -3
- package/dist/webhook/receiver.d.ts +0 -80
- package/dist/webhook/receiver.js +0 -339
- package/dist/webhook/subscriptions.d.ts +0 -29
- package/dist/webhook/subscriptions.js +0 -52
- package/dist/webhook/text.d.ts +0 -23
- package/dist/webhook/text.js +0 -82
- package/dist/webhook/tools.d.ts +0 -19
- package/dist/webhook/tools.js +0 -46
package/dist/platform/hub.d.ts
CHANGED
|
@@ -1,195 +1,82 @@
|
|
|
1
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
2
|
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
3
|
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
4
|
export interface TickPorts {
|
|
11
5
|
readonly storage: Pick<Storage, "get" | "set" | "remove">;
|
|
12
6
|
readonly now: () => number;
|
|
13
7
|
}
|
|
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
8
|
export interface Watched {
|
|
19
9
|
readonly seen: Set<string>;
|
|
20
10
|
readonly waiting: Set<string>;
|
|
21
11
|
readonly answered: Set<string>;
|
|
22
12
|
}
|
|
23
|
-
/** The forms sessions were told about, kept apart from the permission requests. */
|
|
24
13
|
export interface FormsTold {
|
|
25
|
-
/** Forms still shown whose notice went out, or is going out: the notice's delivery. */
|
|
26
14
|
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
15
|
readonly settled: Set<string>;
|
|
29
16
|
}
|
|
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
17
|
export interface Member {
|
|
35
18
|
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
19
|
readonly location?: object;
|
|
41
|
-
/** The location's permission domain, kept in `locations`. */
|
|
42
20
|
readonly permission: unknown;
|
|
43
|
-
/** The ports the scheduler's tick runs through: the owner key's, and the tick jobs'. */
|
|
44
21
|
readonly later: TickPorts;
|
|
45
|
-
/** The ports the hub's subscriptions to OpenCode's events run through. */
|
|
46
22
|
readonly watch: unknown;
|
|
47
|
-
/** The question relay's ports. */
|
|
48
23
|
readonly questions: unknown;
|
|
49
24
|
readonly log: (message: string) => void;
|
|
50
25
|
}
|
|
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
26
|
export interface Tick {
|
|
56
27
|
readonly member: Member;
|
|
57
28
|
readonly since: number;
|
|
58
|
-
/** Settles once the tick has ended, the owner key released if its loop stopped meanwhile; never rejects. */
|
|
59
29
|
readonly done: Promise<void>;
|
|
60
30
|
}
|
|
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
31
|
export interface Scheduler {
|
|
66
|
-
/** The loop's interval, while it runs. */
|
|
67
32
|
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
33
|
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
34
|
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
35
|
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
36
|
nudged?: boolean;
|
|
82
37
|
}
|
|
83
|
-
/** How the hub starts and stops the scheduler's loop, and waits before re-reading the owner key; tests pass their own. */
|
|
84
38
|
export interface Timers {
|
|
85
39
|
readonly every: (run: () => void, ms: number) => unknown;
|
|
86
40
|
readonly stop: (timer: unknown) => void;
|
|
87
41
|
readonly wait: (ms: number) => Promise<void>;
|
|
88
42
|
}
|
|
89
|
-
/** How often the hub's scheduler ticks: what a job delivers, a `courier_later` message, can be this late. */
|
|
90
43
|
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
44
|
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
45
|
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
46
|
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
47
|
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
48
|
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
49
|
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
50
|
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
51
|
export interface Watcher {
|
|
119
52
|
readonly member: Member;
|
|
120
53
|
readonly stop: AbortController;
|
|
121
|
-
/** Settles once the subscription has ended, after `stop`: after the event it is handling, not the stream's close. */
|
|
122
54
|
readonly done: Promise<void>;
|
|
123
55
|
}
|
|
124
56
|
export interface Hub {
|
|
125
57
|
readonly version: typeof HUB_VERSION;
|
|
126
|
-
/** The instances loaded now, in the order they joined. */
|
|
127
58
|
readonly members: Set<Member>;
|
|
128
59
|
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
60
|
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
61
|
subscribe?: (member: Member) => Watcher;
|
|
139
|
-
/** The ids of the `courier_later` messages being delivered. */
|
|
140
62
|
readonly claimed: Set<string>;
|
|
141
|
-
/** The events handled, and the permission requests a session was told about or that were answered first. */
|
|
142
63
|
readonly watched: Watched;
|
|
143
|
-
/** The forms sessions were told about, and those settled first. */
|
|
144
64
|
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
65
|
readonly locations: Map<object, unknown>;
|
|
150
|
-
/** The webhook receiver's state: the object every copy shares under the key `receiver`. */
|
|
151
66
|
readonly receivers: object;
|
|
152
|
-
/** The question relay's state: the object every copy shares under the key `questions`. */
|
|
153
67
|
readonly questions: object;
|
|
154
|
-
/** Whether the hub of another version found under `HUB_KEY` has been logged. */
|
|
155
68
|
skewLogged: boolean;
|
|
156
69
|
}
|
|
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
70
|
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
71
|
export interface Opened {
|
|
164
72
|
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
73
|
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
74
|
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
75
|
readonly nudge: () => void;
|
|
177
76
|
}
|
|
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
77
|
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
78
|
export declare function open(registry: Registry, jobs: readonly TickJob[], timers?: Timers, server?: string): Opened;
|
|
188
|
-
/** This copy's hub, in the process. */
|
|
189
79
|
export declare const hub: Hub;
|
|
190
|
-
/** Opens this copy's hub in the process, with its tick jobs; see `open`. */
|
|
191
80
|
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
81
|
export declare function memberAt(location: object): Member | undefined;
|
|
194
|
-
/** The permission domains of the loaded instances. */
|
|
195
82
|
export declare const permissions: () => MapIterator<unknown>;
|
package/dist/platform/hub.js
CHANGED
|
@@ -2,38 +2,18 @@ import { randomInt } from "node:crypto";
|
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { num, obj, str } from "./json.js";
|
|
4
4
|
import { processWide } from "./storage.js";
|
|
5
|
-
/**
|
|
6
|
-
* The plugin's process-wide state on `globalThis`, shared by every loaded copy; only this module puts
|
|
7
|
-
* it there. `HUB_VERSION`, the fixed claim keys and version skew: docs/maintainers/architecture.md, § Several copies in one process.
|
|
8
|
-
*/
|
|
9
5
|
export const HUB_VERSION = 2;
|
|
10
|
-
/** The key of the hub; a copy that finds another version there keeps its own under `${HUB_KEY}@<version>`. */
|
|
11
6
|
export const HUB_KEY = "opencode-courier.hub";
|
|
12
7
|
const realTimers = {
|
|
13
8
|
every: (run, ms) => setInterval(run, ms),
|
|
14
9
|
stop: (timer) => clearInterval(timer),
|
|
15
10
|
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
16
11
|
};
|
|
17
|
-
/** How often the hub's scheduler ticks: what a job delivers, a `courier_later` message, can be this late. */
|
|
18
12
|
export const TICK_MS = 15_000;
|
|
19
|
-
/**
|
|
20
|
-
* The storage key, shared by every server on one data directory, naming the server whose scheduler
|
|
21
|
-
* delivers `courier_later` messages: `{ server, at }`, its id and when it last wrote (epoch ms).
|
|
22
|
-
*/
|
|
23
13
|
export const OWNER_KEY = "scheduler/owner";
|
|
24
|
-
/** How long the owner key holds without being renewed; the owner renews it every tick, four times as often. */
|
|
25
14
|
export const OWNER_EXPIRY_MS = 4 * TICK_MS;
|
|
26
|
-
/**
|
|
27
|
-
* How old the owner's own key may be for it to renew the key and deliver at once. An older one,
|
|
28
|
-
* which it may no longer hold by the time it writes, is taken again like a free one, with the wait.
|
|
29
|
-
*/
|
|
30
15
|
export const OWNER_RENEW_MS = OWNER_EXPIRY_MS / 2;
|
|
31
|
-
/** The wait between writing the owner key and reading it back, at least `OWNER_WAIT_MS` and up to twice that. */
|
|
32
16
|
export const OWNER_WAIT_MS = 500;
|
|
33
|
-
/**
|
|
34
|
-
* This server's id in the owner key: the same for every copy of the plugin in the process, and
|
|
35
|
-
* different in every other process, a restart of this one included.
|
|
36
|
-
*/
|
|
37
17
|
export const SERVER = `${hostname()}:${process.pid}:${performance.timeOrigin}`;
|
|
38
18
|
async function readOwner(ports) {
|
|
39
19
|
const value = obj(await ports.storage.get(OWNER_KEY));
|
|
@@ -41,26 +21,13 @@ async function readOwner(ports) {
|
|
|
41
21
|
const at = num(value.at);
|
|
42
22
|
return server !== undefined && at !== undefined ? { server, at } : undefined;
|
|
43
23
|
}
|
|
44
|
-
|
|
45
|
-
* How long an unloading instance waits for the work it leaves behind: what it started itself, a tick
|
|
46
|
-
* or a notice under way through it, and on the last leave the owner key's release.
|
|
47
|
-
*/
|
|
24
|
+
// OpenCode waits up to 2 s for an unloading instance (architecture)
|
|
48
25
|
export const LEAVE_MS = 2_000;
|
|
49
|
-
/** Whether a key written at `at` still holds at `now`; one from the future, after the clock went back, holds as long. */
|
|
50
26
|
const holds = (at, now, ms) => Math.abs(now - at) < ms;
|
|
51
|
-
/** How many subscriptions to OpenCode's events a hub keeps, each through another member: one, and a standby. */
|
|
52
27
|
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
28
|
export function sharedState(name, create, registry = globalThis) {
|
|
58
29
|
return processWide(`opencode-courier.${name}`, create, registry);
|
|
59
30
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Finds or makes this copy's hub in `registry`: the one under `HUB_KEY`, or its own under a versioned
|
|
62
|
-
* key when another version holds that; its claim sets are the pre-hub keys', made when missing.
|
|
63
|
-
*/
|
|
64
31
|
function find(registry) {
|
|
65
32
|
const shared = (name, create) => sharedState(name, create, registry);
|
|
66
33
|
const create = () => ({
|
|
@@ -73,25 +40,15 @@ function find(registry) {
|
|
|
73
40
|
forms: shared("forms", () => ({ told: new Map(), settled: new Set() })),
|
|
74
41
|
locations: shared("locations", () => new Map()),
|
|
75
42
|
receivers: shared("receiver", () => ({})),
|
|
76
|
-
// Filled in by `person/question/`, field by field, since a copy before the hub may have made it without them.
|
|
77
43
|
questions: shared("questions", () => ({})),
|
|
78
44
|
skewLogged: false,
|
|
79
45
|
});
|
|
80
46
|
const found = registry[Symbol.for(HUB_KEY)];
|
|
81
|
-
// The version of the hub of another copy found under `HUB_KEY`, if one is there.
|
|
82
47
|
const skew = found != null && found.version !== HUB_VERSION ? { version: found.version } : undefined;
|
|
83
48
|
return { hub: processWide(skew ? `${HUB_KEY}@${HUB_VERSION}` : HUB_KEY, create, registry), skew };
|
|
84
49
|
}
|
|
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
50
|
export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
90
51
|
const { hub, skew } = find(registry);
|
|
91
|
-
/**
|
|
92
|
-
* Whether this server holds the owner key, taking or renewing it through `ports`. A free, expired
|
|
93
|
-
* or stalled key is written and read back after a random wait: the last of two writers holds it.
|
|
94
|
-
*/
|
|
95
52
|
const own = async (ports) => {
|
|
96
53
|
const held = await readOwner(ports);
|
|
97
54
|
const now = ports.now();
|
|
@@ -103,34 +60,23 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
103
60
|
await timers.wait(randomInt(OWNER_WAIT_MS, 2 * OWNER_WAIT_MS));
|
|
104
61
|
return (await readOwner(ports))?.server === server;
|
|
105
62
|
};
|
|
106
|
-
/** Whether `held` is this server's key, recent enough to renew without reading it back. */
|
|
107
63
|
const fresh = (held, now) => held?.server === server && holds(held.at, now, OWNER_RENEW_MS);
|
|
108
|
-
/** Renews the owner key if this server holds it fresh: what a tick does, for a tick still delivering. */
|
|
109
64
|
const renew = async (ports) => {
|
|
110
65
|
const now = ports.now();
|
|
111
66
|
if (fresh(await readOwner(ports), now))
|
|
112
67
|
await ports.storage.set(OWNER_KEY, { server, at: now });
|
|
113
68
|
};
|
|
114
|
-
/** Whether a hub of another version in the process has instances, so its loop, under the same server id, still holds the key. */
|
|
115
69
|
const othersRunning = () => Object.getOwnPropertySymbols(registry).some((key) => {
|
|
116
70
|
const other = registry[key];
|
|
117
71
|
const name = key.description ?? "";
|
|
118
72
|
return other !== hub && (name === HUB_KEY || name.startsWith(`${HUB_KEY}@`)) && Number(other?.members?.size) > 0;
|
|
119
73
|
});
|
|
120
|
-
/**
|
|
121
|
-
* Removes the owner key if this server holds it, so another takes over at its next tick: once the
|
|
122
|
-
* loop has stopped, no tick is under way and no other hub version here runs a loop under this id.
|
|
123
|
-
*/
|
|
124
74
|
const release = async (ports) => {
|
|
125
75
|
const scheduler = hub.scheduler;
|
|
126
76
|
const idle = () => scheduler.timer === undefined && !scheduler.running && !scheduler.ticking && !othersRunning();
|
|
127
77
|
if (idle() && (await readOwner(ports))?.server === server && idle())
|
|
128
78
|
await ports.storage.remove(OWNER_KEY);
|
|
129
79
|
};
|
|
130
|
-
/**
|
|
131
|
-
* Whether the tick under way still holds the next up: within a minute of its start it does; past
|
|
132
|
-
* that it is taken to hang, holding up only while its member is loaded. `recent` says which.
|
|
133
|
-
*/
|
|
134
80
|
const busy = () => {
|
|
135
81
|
const { running, ticking } = hub.scheduler;
|
|
136
82
|
if (!running)
|
|
@@ -140,7 +86,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
140
86
|
return { busy: true, recent: true };
|
|
141
87
|
return { busy: hub.members.has(running.member), recent: false };
|
|
142
88
|
};
|
|
143
|
-
/** `busy`, renewing the key for a recent tick under way, as each tick falling due meanwhile does. */
|
|
144
89
|
const underWay = () => {
|
|
145
90
|
const state = busy();
|
|
146
91
|
const first = hub.members.values().next().value;
|
|
@@ -148,8 +93,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
148
93
|
void renew(first.later).catch((error) => first.log(`courier_later scheduler: ${String(error)}`));
|
|
149
94
|
return state.busy;
|
|
150
95
|
};
|
|
151
|
-
// One tick, through the first loaded member, by the server holding the owner key. The claim on each
|
|
152
|
-
// delivery stays, as a second line of defence against a pre-hub copy running its own interval.
|
|
153
96
|
const tick = async () => {
|
|
154
97
|
const scheduler = hub.scheduler;
|
|
155
98
|
if (underWay())
|
|
@@ -170,8 +113,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
170
113
|
const run = { member: owner, since, done: new Promise((resolve) => (end = resolve)) };
|
|
171
114
|
scheduler.running = run;
|
|
172
115
|
scheduler.ticking = owner;
|
|
173
|
-
// `done` settles however the tick ends, and the key is given back, a log that throws included:
|
|
174
|
-
// leaves wait for it. A log that throws is not logged again; there is nowhere left to tell.
|
|
175
116
|
const tell = (message) => {
|
|
176
117
|
try {
|
|
177
118
|
owner.log(message);
|
|
@@ -188,21 +129,17 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
188
129
|
tell(`courier_later scheduler: ${String(error)}`);
|
|
189
130
|
}
|
|
190
131
|
finally {
|
|
191
|
-
// A tick past its minute may have been followed by another, whose record stays.
|
|
192
132
|
if (scheduler.running === run) {
|
|
193
133
|
scheduler.running = undefined;
|
|
194
134
|
scheduler.ticking = undefined;
|
|
195
135
|
}
|
|
196
136
|
}
|
|
197
|
-
// A loop stopped meanwhile, its members gone, gives back the key this tick may have written.
|
|
198
137
|
if (scheduler.timer === undefined)
|
|
199
138
|
await release(owner.later).catch((error) => tell(`courier_later scheduler: owner key not released: ${String(error)}`));
|
|
200
139
|
end();
|
|
201
140
|
if (scheduler.nudged && scheduler.timer !== undefined)
|
|
202
141
|
void scheduler.tick?.();
|
|
203
142
|
};
|
|
204
|
-
// Through the hub's tick, the copy loaded last's, like the loop's; this copy's only before one joined. A
|
|
205
|
-
// tick under way runs once more when it ends, so nothing is started here meanwhile.
|
|
206
143
|
const nudge = () => {
|
|
207
144
|
const scheduler = hub.scheduler;
|
|
208
145
|
if (scheduler.timer === undefined)
|
|
@@ -211,8 +148,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
211
148
|
if (!busy().busy)
|
|
212
149
|
void (scheduler.tick ?? tick)();
|
|
213
150
|
};
|
|
214
|
-
// Subscriptions through the earliest members without one, the longest loaded, until there are
|
|
215
|
-
// `WATCHERS`; never through `leaving`.
|
|
216
151
|
const watch = (leaving) => {
|
|
217
152
|
const watching = new Set(hub.watchers.map((watcher) => watcher.member));
|
|
218
153
|
for (const member of hub.members) {
|
|
@@ -222,7 +157,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
222
157
|
hub.watchers.push(hub.subscribe(member));
|
|
223
158
|
}
|
|
224
159
|
};
|
|
225
|
-
// The work each member of this copy started and handed over, until it settles.
|
|
226
160
|
const work = new WeakMap();
|
|
227
161
|
const track = (member, started) => {
|
|
228
162
|
const set = work.get(member) ?? new Set();
|
|
@@ -248,13 +182,9 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
248
182
|
return () => {
|
|
249
183
|
hub.members.delete(member);
|
|
250
184
|
hub.locations.delete(member);
|
|
251
|
-
// What this member leaves behind: the work it handed over, and a tick under way through it,
|
|
252
|
-
// which keeps using its storage and session until it ends.
|
|
253
185
|
const behind = [...(work.get(member) ?? [])];
|
|
254
186
|
if (scheduler.running?.member === member)
|
|
255
187
|
behind.push(scheduler.running.done);
|
|
256
|
-
// The other subscription, already connected, follows the events meanwhile; this one is replaced
|
|
257
|
-
// before it ends, after its current event, so the relay's watcher count never drops to none.
|
|
258
188
|
const index = hub.watchers.findIndex((watcher) => watcher.member === member);
|
|
259
189
|
if (index >= 0) {
|
|
260
190
|
const [leaving] = hub.watchers.splice(index, 1);
|
|
@@ -265,7 +195,6 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
265
195
|
if (hub.members.size === 0 && scheduler.timer !== undefined) {
|
|
266
196
|
timers.stop(scheduler.timer);
|
|
267
197
|
scheduler.timer = undefined;
|
|
268
|
-
// After the tick under way, whichever copy started it, which may be writing the key.
|
|
269
198
|
behind.push((async () => {
|
|
270
199
|
await scheduler.running?.done;
|
|
271
200
|
await release(member.later);
|
|
@@ -283,11 +212,8 @@ export function open(registry, jobs, timers = realTimers, server = SERVER) {
|
|
|
283
212
|
nudge,
|
|
284
213
|
};
|
|
285
214
|
}
|
|
286
|
-
/** This copy's hub, in the process. */
|
|
287
215
|
export const hub = find(globalThis).hub;
|
|
288
|
-
/** Opens this copy's hub in the process, with its tick jobs; see `open`. */
|
|
289
216
|
export const openInProcess = (jobs) => open(globalThis, jobs);
|
|
290
|
-
/** The member of this copy's hub set up with `location` that joined last, while it is loaded. */
|
|
291
217
|
export function memberAt(location) {
|
|
292
218
|
let found;
|
|
293
219
|
for (const member of hub.members)
|
|
@@ -295,5 +221,4 @@ export function memberAt(location) {
|
|
|
295
221
|
found = member;
|
|
296
222
|
return found;
|
|
297
223
|
}
|
|
298
|
-
/** The permission domains of the loaded instances. */
|
|
299
224
|
export const permissions = () => hub.locations.values();
|
package/dist/platform/json.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
/** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
|
|
2
1
|
export declare const str: (value: unknown) => string | undefined;
|
|
3
2
|
export declare const num: (value: unknown) => number | undefined;
|
|
4
3
|
export declare const finite: (value: unknown) => number | undefined;
|
|
5
4
|
export declare const obj: (value: unknown) => Record<string, any>;
|
|
6
|
-
/** Epoch milliseconds from a session time, a `DateTime` in OpenCode's API and a number in tests; NaN for none. */
|
|
7
5
|
export declare function millis(value: unknown): number;
|
|
8
|
-
/** Whether an error of OpenCode's says that what it names, such as a session, does not exist (`Session.NotFoundError`). */
|
|
9
6
|
export declare const isNotFound: (error: unknown) => boolean;
|
package/dist/platform/json.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
/** Readers for untyped JSON, such as a webhook payload or a plugin option: the value when it has the type, else nothing. */
|
|
2
1
|
export const str = (value) => (typeof value === "string" ? value : undefined);
|
|
3
2
|
export const num = (value) => (typeof value === "number" && Number.isInteger(value) ? value : undefined);
|
|
4
3
|
export const finite = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
|
|
5
4
|
export const obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6
|
-
|
|
5
|
+
// OpenCode's session times are DateTimes; tests pass numbers
|
|
7
6
|
export function millis(value) {
|
|
8
7
|
if (typeof value === "number")
|
|
9
8
|
return value;
|
|
@@ -12,5 +11,4 @@ export function millis(value) {
|
|
|
12
11
|
const epoch = obj(value).epochMilliseconds;
|
|
13
12
|
return typeof epoch === "number" ? epoch : Number.NaN;
|
|
14
13
|
}
|
|
15
|
-
/** Whether an error of OpenCode's says that what it names, such as a session, does not exist (`Session.NotFoundError`). */
|
|
16
14
|
export const isNotFound = (error) => str(obj(error)._tag)?.includes("NotFound") ?? false;
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin";
|
|
2
2
|
type Context = Plugin.Context;
|
|
3
3
|
export type Storage = Pick<Context["storage"], "get" | "set" | "remove" | "scan">;
|
|
4
|
-
/** Every value stored under `prefix`, following the scan's pages. */
|
|
5
4
|
export declare function scanAll<T>(storage: Pick<Storage, "scan">, prefix: string): Promise<T[]>;
|
|
6
|
-
/** Every key under `prefix`, without the prefix, with its value, following the scan's pages. */
|
|
7
5
|
export declare function scanEntries<T>(storage: Pick<Storage, "scan">, prefix: string): Promise<{
|
|
8
6
|
key: string;
|
|
9
7
|
value: T;
|
|
10
8
|
}[]>;
|
|
11
|
-
/** Where process-wide values are kept: `globalThis`, or a stand-in for it in tests. */
|
|
12
9
|
export type Registry = Record<symbol, unknown>;
|
|
13
|
-
/**
|
|
14
|
-
* The one value the whole process keeps under `Symbol.for(key)`, made by `create` the first time,
|
|
15
|
-
* so every instance of the plugin, and every copy of it loaded later, gets the same one.
|
|
16
|
-
*/
|
|
17
10
|
export declare function processWide<T>(key: string, create: () => T, registry?: Registry): T;
|
|
18
11
|
export {};
|
package/dist/platform/storage.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/** Every value stored under `prefix`, following the scan's pages. */
|
|
2
1
|
export async function scanAll(storage, prefix) {
|
|
3
2
|
return (await scanEntries(storage, prefix)).map((entry) => entry.value);
|
|
4
3
|
}
|
|
5
|
-
/** Every key under `prefix`, without the prefix, with its value, following the scan's pages. */
|
|
6
4
|
export async function scanEntries(storage, prefix) {
|
|
7
5
|
const entries = [];
|
|
8
6
|
let after;
|
|
@@ -14,10 +12,6 @@ export async function scanEntries(storage, prefix) {
|
|
|
14
12
|
} while (after);
|
|
15
13
|
return entries;
|
|
16
14
|
}
|
|
17
|
-
/**
|
|
18
|
-
* The one value the whole process keeps under `Symbol.for(key)`, made by `create` the first time,
|
|
19
|
-
* so every instance of the plugin, and every copy of it loaded later, gets the same one.
|
|
20
|
-
*/
|
|
21
15
|
export function processWide(key, create, registry = globalThis) {
|
|
22
16
|
const symbol = Symbol.for(key);
|
|
23
17
|
registry[symbol] ??= create();
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import type { App } from "@opencode/plugin/app";
|
|
2
|
-
/** The versions a build of the plugin carries: its own, and the OpenCode it was built and tested against. */
|
|
3
2
|
export interface BuiltVersions {
|
|
4
3
|
readonly plugin: string;
|
|
5
4
|
readonly opencode: string;
|
|
6
5
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Reads from the package's own package.json, next to `dist/` as to `src/`: `version` and the exact
|
|
9
|
-
* `@opencode/plugin` devDependency version, which the peer repeats. Throws if either is missing.
|
|
10
|
-
*/
|
|
11
6
|
export declare function builtVersions(): BuiltVersions;
|
|
12
|
-
/**
|
|
13
|
-
* The server-log line for an OpenCode other than the plugin was built against, undefined when it is;
|
|
14
|
-
* logged once per load, never in a tool result. A context without a version is still told the pin.
|
|
15
|
-
*/
|
|
16
7
|
export declare function versionNotice(built: BuiltVersions, app: Partial<Pick<App, "version" | "channel">> | undefined): string | undefined;
|
package/dist/platform/version.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
const VERSION_TABLE = "https://github.com/ivopogace/opencode-courier/blob/main/docs/reference.md#the-opencode-version";
|
|
3
|
-
/**
|
|
4
|
-
* Reads from the package's own package.json, next to `dist/` as to `src/`: `version` and the exact
|
|
5
|
-
* `@opencode/plugin` devDependency version, which the peer repeats. Throws if either is missing.
|
|
6
|
-
*/
|
|
7
3
|
export function builtVersions() {
|
|
8
4
|
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
9
5
|
const plugin = pkg.version;
|
|
@@ -14,10 +10,6 @@ export function builtVersions() {
|
|
|
14
10
|
throw new Error("package.json pins no @opencode/plugin under devDependencies");
|
|
15
11
|
return { plugin, opencode };
|
|
16
12
|
}
|
|
17
|
-
/**
|
|
18
|
-
* The server-log line for an OpenCode other than the plugin was built against, undefined when it is;
|
|
19
|
-
* logged once per load, never in a tool result. A context without a version is still told the pin.
|
|
20
|
-
*/
|
|
21
13
|
export function versionNotice(built, app) {
|
|
22
14
|
const version = typeof app?.version === "string" ? app.version : undefined;
|
|
23
15
|
if (version === built.opencode)
|
package/dist/rpc.js
CHANGED
|
@@ -2,5 +2,4 @@ import type { TickJob } from "../platform/hub.js";
|
|
|
2
2
|
export { cancel, cancelScheduled, deliverDue, isScheduled, schedule, scheduledFor } from "./later.js";
|
|
3
3
|
export type { LaterEntry, LaterInput, LaterPorts } from "./later.js";
|
|
4
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
5
|
export declare const dueJob: TickJob;
|
package/dist/scheduling/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { deliverDue } from "./later.js";
|
|
2
2
|
export { cancel, cancelScheduled, deliverDue, isScheduled, schedule, scheduledFor } from "./later.js";
|
|
3
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
4
|
export const dueJob = (owner, claimed) => deliverDue(owner.later, claimed);
|
|
@@ -11,7 +11,6 @@ export interface LaterEntry {
|
|
|
11
11
|
}
|
|
12
12
|
export interface LaterPorts {
|
|
13
13
|
readonly storage: Storage;
|
|
14
|
-
/** `get` for the release of a join group, which waits for its parent's turn to end; `get` and `context` for a missed wake (`ctx.session`). */
|
|
15
14
|
readonly session: Pick<Context["session"], "synthetic" | "get" | "context">;
|
|
16
15
|
readonly now: () => number;
|
|
17
16
|
readonly newID: () => string;
|
|
@@ -19,26 +18,16 @@ export interface LaterPorts {
|
|
|
19
18
|
}
|
|
20
19
|
export interface LaterInput {
|
|
21
20
|
readonly message: string;
|
|
22
|
-
/** A number, or a string holding one, which some models send instead. */
|
|
23
21
|
readonly delayMinutes?: number | string;
|
|
24
22
|
readonly at?: string;
|
|
25
23
|
readonly sessionID?: string;
|
|
26
24
|
}
|
|
27
|
-
/** Stores a message for later delivery; the scheduler in `deliverDue` sends it once it is due. */
|
|
28
25
|
export declare function schedule(ports: LaterPorts, from: string, input: LaterInput): Promise<LaterEntry>;
|
|
29
|
-
/** Drops a pending message; false when there was none, e.g. it was already delivered. */
|
|
30
26
|
export declare function cancel(ports: LaterPorts, id: string): Promise<boolean>;
|
|
31
|
-
/** The messages scheduled for a session that have not been delivered yet. */
|
|
32
27
|
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
28
|
export declare function cancelScheduled(ports: LaterPorts, sessionID: string): Promise<string[]>;
|
|
35
|
-
/** Whether a session waits on a scheduled message of its own. */
|
|
36
29
|
export declare function isScheduled(ports: {
|
|
37
30
|
readonly storage: Pick<Storage, "scan">;
|
|
38
31
|
}, sessionID: string): Promise<boolean>;
|
|
39
|
-
/**
|
|
40
|
-
* Delivers every due message once: an id is claimed synchronously in the shared `claimed`, re-read,
|
|
41
|
-
* delivered and only then removed; a crash in between delivers it again after a restart, not never.
|
|
42
|
-
*/
|
|
43
32
|
export declare function deliverDue(ports: LaterPorts, claimed: Set<string>): Promise<void>;
|
|
44
33
|
export {};
|
package/dist/scheduling/later.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { envelope } from "../kernel/protocol.js";
|
|
2
2
|
import { scanAll } from "../platform/storage.js";
|
|
3
3
|
const PREFIX = "later/";
|
|
4
|
-
/** The latest time a Date can hold; a message due after it could never be shown or delivered. */
|
|
5
4
|
const LATEST = 8.64e15;
|
|
6
|
-
/**
|
|
7
|
-
* A plain decimal number of minutes, the only string form of delayMinutes taken ("0x10" is not),
|
|
8
|
-
* once trimmed of the whitespace `\s` matches.
|
|
9
|
-
*/
|
|
10
5
|
const DECIMAL = /^(?:\d+(?:\.\d*)?|\.\d+)$/;
|
|
11
6
|
function minutesOf(given) {
|
|
12
7
|
if (typeof given !== "string")
|
|
@@ -34,7 +29,6 @@ function fireTime(now, input) {
|
|
|
34
29
|
throw new Error(`at is in the past: ${input.at}`);
|
|
35
30
|
return at;
|
|
36
31
|
}
|
|
37
|
-
/** Stores a message for later delivery; the scheduler in `deliverDue` sends it once it is due. */
|
|
38
32
|
export async function schedule(ports, from, input) {
|
|
39
33
|
const now = ports.now();
|
|
40
34
|
const entry = {
|
|
@@ -48,7 +42,6 @@ export async function schedule(ports, from, input) {
|
|
|
48
42
|
await ports.storage.set(PREFIX + entry.id, { ...entry });
|
|
49
43
|
return entry;
|
|
50
44
|
}
|
|
51
|
-
/** Drops a pending message; false when there was none, e.g. it was already delivered. */
|
|
52
45
|
export async function cancel(ports, id) {
|
|
53
46
|
if ((await ports.storage.get(PREFIX + id)) === undefined)
|
|
54
47
|
return false;
|
|
@@ -58,24 +51,17 @@ export async function cancel(ports, id) {
|
|
|
58
51
|
function pending(ports) {
|
|
59
52
|
return scanAll(ports.storage, PREFIX);
|
|
60
53
|
}
|
|
61
|
-
/** The messages scheduled for a session that have not been delivered yet. */
|
|
62
54
|
export async function scheduledFor(storage, sessionID) {
|
|
63
55
|
return (await pending({ storage: storage })).filter((entry) => entry.sessionID === sessionID);
|
|
64
56
|
}
|
|
65
|
-
/** Cancels every message scheduled for a session; the ids of those cancelled. */
|
|
66
57
|
export async function cancelScheduled(ports, sessionID) {
|
|
67
58
|
const scheduled = await scheduledFor(ports.storage, sessionID);
|
|
68
59
|
const dropped = await Promise.all(scheduled.map(async (later) => ((await cancel(ports, later.id)) ? later.id : undefined)));
|
|
69
60
|
return dropped.filter((id) => id !== undefined);
|
|
70
61
|
}
|
|
71
|
-
/** Whether a session waits on a scheduled message of its own. */
|
|
72
62
|
export async function isScheduled(ports, sessionID) {
|
|
73
63
|
return (await scheduledFor(ports.storage, sessionID)).length > 0;
|
|
74
64
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Delivers every due message once: an id is claimed synchronously in the shared `claimed`, re-read,
|
|
77
|
-
* delivered and only then removed; a crash in between delivers it again after a restart, not never.
|
|
78
|
-
*/
|
|
79
65
|
export async function deliverDue(ports, claimed) {
|
|
80
66
|
const now = ports.now();
|
|
81
67
|
for (const due of (await pending(ports)).filter((entry) => entry.fireAt <= now)) {
|
package/dist/scheduling/text.js
CHANGED