@haywoodmarx/agent-pager 0.1.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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/src/broker/address.d.ts +49 -0
- package/dist/src/broker/address.js +110 -0
- package/dist/src/broker/broker.d.ts +665 -0
- package/dist/src/broker/broker.js +2270 -0
- package/dist/src/broker/database.d.ts +81 -0
- package/dist/src/broker/database.js +69 -0
- package/dist/src/broker/operator-projection.d.ts +291 -0
- package/dist/src/broker/operator-projection.js +722 -0
- package/dist/src/broker/protocol.d.ts +237 -0
- package/dist/src/broker/protocol.js +311 -0
- package/dist/src/broker/schema.d.ts +94 -0
- package/dist/src/broker/schema.js +454 -0
- package/dist/src/broker/snapshot.d.ts +171 -0
- package/dist/src/broker/snapshot.js +78 -0
- package/dist/src/configuration-defaults.d.ts +64 -0
- package/dist/src/configuration-defaults.js +48 -0
- package/dist/src/configuration.d.ts +76 -0
- package/dist/src/configuration.js +156 -0
- package/dist/src/implementations/claude/environment.d.ts +8 -0
- package/dist/src/implementations/claude/environment.js +9 -0
- package/dist/src/implementations/claude/gateway-cli.d.ts +2 -0
- package/dist/src/implementations/claude/gateway-cli.js +136 -0
- package/dist/src/implementations/claude/gateway-identity.d.ts +134 -0
- package/dist/src/implementations/claude/gateway-identity.js +103 -0
- package/dist/src/implementations/claude/gateway.d.ts +148 -0
- package/dist/src/implementations/claude/gateway.js +236 -0
- package/dist/src/implementations/claude/instructions.d.ts +33 -0
- package/dist/src/implementations/claude/instructions.js +64 -0
- package/dist/src/implementations/claude/manifest.d.ts +33 -0
- package/dist/src/implementations/claude/manifest.js +108 -0
- package/dist/src/implementations/claude/plugin-directory.d.ts +80 -0
- package/dist/src/implementations/claude/plugin-directory.js +137 -0
- package/dist/src/implementations/claude/session-continuation.d.ts +25 -0
- package/dist/src/implementations/claude/session-continuation.js +125 -0
- package/dist/src/implementations/claude/session-identity.d.ts +25 -0
- package/dist/src/implementations/claude/session-identity.js +31 -0
- package/dist/src/implementations/claude/session-start-cli.d.ts +2 -0
- package/dist/src/implementations/claude/session-start-cli.js +16 -0
- package/dist/src/implementations/claude/socket-delivery.d.ts +52 -0
- package/dist/src/implementations/claude/socket-delivery.js +159 -0
- package/dist/src/implementations/model.d.ts +162 -0
- package/dist/src/implementations/model.js +1 -0
- package/dist/src/implementations/opencode/adapter.d.ts +71 -0
- package/dist/src/implementations/opencode/adapter.js +94 -0
- package/dist/src/implementations/opencode/environment.d.ts +8 -0
- package/dist/src/implementations/opencode/environment.js +9 -0
- package/dist/src/implementations/opencode/manifest.d.ts +30 -0
- package/dist/src/implementations/opencode/manifest.js +93 -0
- package/dist/src/implementations/opencode/plugin-api.d.ts +120 -0
- package/dist/src/implementations/opencode/plugin-api.js +1 -0
- package/dist/src/implementations/opencode/plugin.d.ts +38 -0
- package/dist/src/implementations/opencode/plugin.js +303 -0
- package/dist/src/implementations/opencode/self-close-tool.d.ts +9 -0
- package/dist/src/implementations/opencode/self-close-tool.js +31 -0
- package/dist/src/implementations/orca/terminal.d.ts +48 -0
- package/dist/src/implementations/orca/terminal.js +154 -0
- package/dist/src/implementations/pi/broker-client.d.ts +56 -0
- package/dist/src/implementations/pi/broker-client.js +156 -0
- package/dist/src/implementations/pi/environment.d.ts +8 -0
- package/dist/src/implementations/pi/environment.js +9 -0
- package/dist/src/implementations/pi/extension.d.ts +57 -0
- package/dist/src/implementations/pi/extension.js +253 -0
- package/dist/src/implementations/pi/host-slots.d.ts +18 -0
- package/dist/src/implementations/pi/host-slots.js +27 -0
- package/dist/src/implementations/pi/manifest.d.ts +11 -0
- package/dist/src/implementations/pi/manifest.js +61 -0
- package/dist/src/implementations/pi/self-close-tool.d.ts +7 -0
- package/dist/src/implementations/pi/self-close-tool.js +27 -0
- package/dist/src/implementations/registry.d.ts +47 -0
- package/dist/src/implementations/registry.js +157 -0
- package/dist/src/implementations/session-key.d.ts +13 -0
- package/dist/src/implementations/session-key.js +19 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.js +26 -0
- package/dist/src/launch/broker-lock.d.ts +257 -0
- package/dist/src/launch/broker-lock.js +644 -0
- package/dist/src/launch/cli.d.ts +37 -0
- package/dist/src/launch/cli.js +317 -0
- package/dist/src/launch/environment.d.ts +45 -0
- package/dist/src/launch/environment.js +48 -0
- package/dist/src/launch/launch.d.ts +235 -0
- package/dist/src/launch/launch.js +223 -0
- package/dist/src/launch/on-demand.d.ts +202 -0
- package/dist/src/launch/on-demand.js +435 -0
- package/dist/src/launch/runtime-composition.d.ts +88 -0
- package/dist/src/launch/runtime-composition.js +253 -0
- package/dist/src/launch/runtime.d.ts +79 -0
- package/dist/src/launch/runtime.js +196 -0
- package/dist/src/launch/service-system.d.ts +11 -0
- package/dist/src/launch/service-system.js +39 -0
- package/dist/src/launch/service.d.ts +173 -0
- package/dist/src/launch/service.js +492 -0
- package/dist/src/launch/skill.d.ts +57 -0
- package/dist/src/launch/skill.js +81 -0
- package/dist/src/launch/terminal.d.ts +26 -0
- package/dist/src/launch/terminal.js +43 -0
- package/dist/src/launch/workspace-branch.d.ts +10 -0
- package/dist/src/launch/workspace-branch.js +29 -0
- package/dist/src/messaging/delivery-client.d.ts +144 -0
- package/dist/src/messaging/delivery-client.js +261 -0
- package/dist/src/messaging/delivery.d.ts +75 -0
- package/dist/src/messaging/delivery.js +22 -0
- package/dist/src/messaging/inbox-socket.d.ts +98 -0
- package/dist/src/messaging/inbox-socket.js +278 -0
- package/dist/src/messaging/mcp.d.ts +53 -0
- package/dist/src/messaging/mcp.js +501 -0
- package/dist/src/messaging/notifications.d.ts +231 -0
- package/dist/src/messaging/notifications.js +489 -0
- package/dist/src/messaging/push-delivery.d.ts +77 -0
- package/dist/src/messaging/push-delivery.js +187 -0
- package/dist/src/messaging/tools.d.ts +11 -0
- package/dist/src/messaging/tools.js +102 -0
- package/dist/src/messaging/wake.d.ts +23 -0
- package/dist/src/messaging/wake.js +39 -0
- package/dist/src/operator/agent-projection.d.ts +102 -0
- package/dist/src/operator/agent-projection.js +249 -0
- package/dist/src/operator/commands.d.ts +50 -0
- package/dist/src/operator/commands.js +199 -0
- package/dist/src/operator/diagnostics.d.ts +21 -0
- package/dist/src/operator/diagnostics.js +22 -0
- package/dist/src/operator/protocol.d.ts +511 -0
- package/dist/src/operator/protocol.js +49 -0
- package/dist/src/operator/repository-system.d.ts +14 -0
- package/dist/src/operator/repository-system.js +150 -0
- package/dist/src/operator/repository.d.ts +79 -0
- package/dist/src/operator/repository.js +72 -0
- package/dist/src/operator/traffic-projection.d.ts +19 -0
- package/dist/src/operator/traffic-projection.js +229 -0
- package/dist/src/operator/transport.d.ts +87 -0
- package/dist/src/operator/transport.js +549 -0
- package/dist/src/operator/update-store.d.ts +53 -0
- package/dist/src/operator/update-store.js +162 -0
- package/dist/src/operator/updates.d.ts +94 -0
- package/dist/src/operator/updates.js +280 -0
- package/dist/src/operator/validation.d.ts +114 -0
- package/dist/src/operator/validation.js +916 -0
- package/dist/src/shared/guards.d.ts +59 -0
- package/dist/src/shared/guards.js +119 -0
- package/dist/src/shared/http.d.ts +30 -0
- package/dist/src/shared/http.js +50 -0
- package/dist/src/shared/row.d.ts +2 -0
- package/dist/src/shared/row.js +1 -0
- package/dist/src/shared/shell.d.ts +6 -0
- package/dist/src/shared/shell.js +8 -0
- package/dist/src/tui/actions.d.ts +166 -0
- package/dist/src/tui/actions.js +253 -0
- package/dist/src/tui/agent-tree.d.ts +125 -0
- package/dist/src/tui/agent-tree.js +663 -0
- package/dist/src/tui/app.d.ts +38 -0
- package/dist/src/tui/app.js +378 -0
- package/dist/src/tui/chrome.d.ts +86 -0
- package/dist/src/tui/chrome.js +85 -0
- package/dist/src/tui/client.d.ts +41 -0
- package/dist/src/tui/client.js +279 -0
- package/dist/src/tui/composition.d.ts +41 -0
- package/dist/src/tui/composition.js +146 -0
- package/dist/src/tui/message-views.d.ts +94 -0
- package/dist/src/tui/message-views.js +515 -0
- package/dist/src/tui/model.d.ts +414 -0
- package/dist/src/tui/model.js +758 -0
- package/dist/src/tui/presentation.d.ts +350 -0
- package/dist/src/tui/presentation.js +541 -0
- package/dist/src/tui/production.d.ts +79 -0
- package/dist/src/tui/production.js +253 -0
- package/dist/src/tui/run-terminal.d.ts +32 -0
- package/dist/src/tui/run-terminal.js +54 -0
- package/dist/src/tui/runtime.d.ts +113 -0
- package/dist/src/tui/runtime.js +189 -0
- package/dist/src/tui/theme.d.ts +49 -0
- package/dist/src/tui/theme.js +55 -0
- package/dist/src/tui/transport.d.ts +31 -0
- package/dist/src/tui/transport.js +255 -0
- package/dist/src/version.d.ts +11 -0
- package/dist/src/version.js +52 -0
- package/package.json +142 -0
- package/skills/pager/SKILL.md +112 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { access, readdir } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
+
import { Broker } from "../broker/broker.js";
|
|
14
|
+
import { discoverImplementationRegistry, implementationId, } from "../implementations/registry.js";
|
|
15
|
+
import { DirectTerminalProvider, ForegroundTerminalProvider, LaunchService, } from "./launch.js";
|
|
16
|
+
import { spawnForegroundProcess } from "./terminal.js";
|
|
17
|
+
import { systemWorkspaceBranch } from "./workspace-branch.js";
|
|
18
|
+
import { initializePagerStore } from "../configuration.js";
|
|
19
|
+
import { isPlainObject } from "../shared/guards.js";
|
|
20
|
+
export { readRuntimeEnvironment } from "./environment.js";
|
|
21
|
+
/**
|
|
22
|
+
* Construct the broker from resolved runtime configuration.
|
|
23
|
+
* @param environment - Resolved Bun runtime configuration.
|
|
24
|
+
* @returns - Handle that reads the current broker epoch without rotating it, so it does not detach live agents.
|
|
25
|
+
*/
|
|
26
|
+
export function createBroker(environment) {
|
|
27
|
+
initializePagerStore({
|
|
28
|
+
config: environment.config,
|
|
29
|
+
filePath: environment.configPath,
|
|
30
|
+
storeDirectory: environment.storeDirectory,
|
|
31
|
+
});
|
|
32
|
+
const config = environment.config;
|
|
33
|
+
return new Broker(environment.dbPath, {
|
|
34
|
+
now: Date.now,
|
|
35
|
+
workspaceBranch: systemWorkspaceBranch,
|
|
36
|
+
report: (message) => {
|
|
37
|
+
process.stderr.write(`${message}\n`);
|
|
38
|
+
},
|
|
39
|
+
epochOwner: false,
|
|
40
|
+
visibilityTimeoutMs: config.visibilityTimeoutMs,
|
|
41
|
+
redeliveryBaseDelayMs: config.redeliveryBaseDelayMs,
|
|
42
|
+
redeliveryGiveUpMs: config.redeliveryGiveUpMs,
|
|
43
|
+
reservationTtlMs: config.reservationTtlMs,
|
|
44
|
+
leaseTtlMs: config.leaseTtlMs,
|
|
45
|
+
perPairRateLimit: config.perPairRateLimit,
|
|
46
|
+
perPairRateWindowMs: config.perPairRateWindowMs,
|
|
47
|
+
maxInboxMessages: config.maxInboxMessages,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Construct the broker `BrokerService` owns for the life of its process.
|
|
52
|
+
* @param databasePath - Durable SQLite file the broker owns.
|
|
53
|
+
* @param options - Broker construction options, including the root's clock and report sink.
|
|
54
|
+
* @returns - Broker instance satisfying the service's declared port.
|
|
55
|
+
*/
|
|
56
|
+
export function openBroker(databasePath, options) {
|
|
57
|
+
return new Broker(databasePath, options);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discover every built-in implementation manifest without a shared product list.
|
|
61
|
+
* @returns - Registry of validated discovered implementations.
|
|
62
|
+
*/
|
|
63
|
+
export async function createImplementationRegistry() {
|
|
64
|
+
const root = fileURLToPath(new URL("../implementations", import.meta.url));
|
|
65
|
+
return discoverImplementationRegistry(root, {
|
|
66
|
+
listDirectories: async (directory) => {
|
|
67
|
+
const entries = (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory());
|
|
68
|
+
const implementations = [];
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const hasManifest = await Promise.any([
|
|
71
|
+
access(join(directory, entry.name, "manifest.js")).then(() => true),
|
|
72
|
+
access(join(directory, entry.name, "manifest.ts")).then(() => true),
|
|
73
|
+
]).catch(() => false);
|
|
74
|
+
if (hasManifest)
|
|
75
|
+
implementations.push(entry.name);
|
|
76
|
+
}
|
|
77
|
+
return implementations;
|
|
78
|
+
},
|
|
79
|
+
loadProvider: async (manifestPath) => {
|
|
80
|
+
const loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(manifestPath).href));
|
|
81
|
+
return isPlainObject(loaded) ? loaded.default : undefined;
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Construct launch orchestration from discovered implementation and terminal providers.
|
|
87
|
+
* @param broker - Durable broker used by launch orchestration.
|
|
88
|
+
* @param terminalProviderId - Open terminal-provider identifier to resolve.
|
|
89
|
+
* @param environment - Resolved Bun runtime configuration.
|
|
90
|
+
* @param implementations - Registry resolving agent implementations.
|
|
91
|
+
* @returns - Launch service offering only the requested terminal provider.
|
|
92
|
+
*/
|
|
93
|
+
export async function createLaunchService(broker, terminalProviderId, environment, implementations) {
|
|
94
|
+
const terminalProvider = await createTerminalProvider(terminalProviderId, environment.processEnv);
|
|
95
|
+
return new LaunchService(broker, implementations, [terminalProvider], environment.processEnv);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Compose an interactive launch service from conventionally discovered terminal providers.
|
|
99
|
+
* @param broker - Durable broker used by launch orchestration.
|
|
100
|
+
* @param environment - Resolved Bun runtime configuration.
|
|
101
|
+
* @param implementations - Registry resolving agent implementations.
|
|
102
|
+
* @param report - Receives each discovered terminal provider that was skipped, and why.
|
|
103
|
+
* @returns - Launch service offering only the selected provider, and that provider's name.
|
|
104
|
+
*/
|
|
105
|
+
export async function createInteractiveLaunchService(broker, environment, implementations, report) {
|
|
106
|
+
const discovered = await discoverTerminalProviders(environment.processEnv, report);
|
|
107
|
+
const foreground = new ForegroundTerminalProvider(spawnForegroundProcess);
|
|
108
|
+
const selected = await selectInteractiveTerminalProvider(discovered, foreground);
|
|
109
|
+
return {
|
|
110
|
+
service: new LaunchService(broker, implementations, [selected], environment.processEnv),
|
|
111
|
+
terminalProvider: selected.name,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Select the highest-priority available interactive provider or the inherited-terminal fallback.
|
|
116
|
+
* @param candidates - Conventionally discovered optional terminal providers.
|
|
117
|
+
* @param fallback - Foreground provider used when no optional provider is available.
|
|
118
|
+
* @returns - The available candidate with the highest priority, or the fallback when none is available.
|
|
119
|
+
*/
|
|
120
|
+
export async function selectInteractiveTerminalProvider(candidates, fallback) {
|
|
121
|
+
const available = [];
|
|
122
|
+
for (const provider of candidates) {
|
|
123
|
+
if (!provider.available || (await provider.available()))
|
|
124
|
+
available.push(provider);
|
|
125
|
+
}
|
|
126
|
+
available.sort((left, right) => (right.interactivePriority ?? 0) - (left.interactivePriority ?? 0));
|
|
127
|
+
return available[0] ?? fallback;
|
|
128
|
+
}
|
|
129
|
+
async function discoverTerminalProviders(environment, report) {
|
|
130
|
+
const root = fileURLToPath(new URL("../implementations", import.meta.url));
|
|
131
|
+
const modules = [];
|
|
132
|
+
for (const directory of await implementationDirectories(root)) {
|
|
133
|
+
const modulePath = join(root, directory, "terminal.js");
|
|
134
|
+
const present = await Promise.any([
|
|
135
|
+
access(modulePath).then(() => true),
|
|
136
|
+
access(join(root, directory, "terminal.ts")).then(() => true),
|
|
137
|
+
]).catch(() => false);
|
|
138
|
+
if (!present)
|
|
139
|
+
continue;
|
|
140
|
+
try {
|
|
141
|
+
modules.push({ modulePath, state: "loaded", namespace: await import(__rewriteRelativeImportExtension(pathToFileURL(modulePath).href)) });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
modules.push({ modulePath, state: "unloadable", error });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return adoptTerminalProviders(modules, environment, report);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build the provider each discovered terminal module offers, skipping any that cannot build one.
|
|
151
|
+
*
|
|
152
|
+
* An interactive launch always has the foreground fallback, so one broken module is named and left
|
|
153
|
+
* out rather than failing every launch.
|
|
154
|
+
* @param modules - Discovered `terminal.js` modules, in discovery order.
|
|
155
|
+
* @param environment - Process environment every provider is built from.
|
|
156
|
+
* @param report - Receives one line per skipped module, naming it and the reason.
|
|
157
|
+
* @returns - Providers from every module whose factory built one, in module order.
|
|
158
|
+
*/
|
|
159
|
+
export function adoptTerminalProviders(modules, environment, report) {
|
|
160
|
+
const skip = (reason) => {
|
|
161
|
+
report(`pager: ${reason}; skipped`);
|
|
162
|
+
};
|
|
163
|
+
const providers = [];
|
|
164
|
+
for (const discovered of modules) {
|
|
165
|
+
if (discovered.state === "unloadable") {
|
|
166
|
+
skip(`terminal provider module could not be loaded: ${discovered.modulePath}: ${errorText(discovered.error)}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const module = terminalProviderModule(discovered.namespace, discovered.modulePath);
|
|
170
|
+
if (!module) {
|
|
171
|
+
skip(`terminal provider module does not export a factory: ${discovered.modulePath}`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
providers.push(module.createTerminalProvider(environment));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
skip(errorText(error));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return providers;
|
|
182
|
+
}
|
|
183
|
+
function errorText(error) {
|
|
184
|
+
return error instanceof Error ? error.message : String(error);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Read a loaded `terminal.js` module as the port discovery expects, checking what its factory builds.
|
|
188
|
+
*
|
|
189
|
+
* The factory's parameter cannot be checked at runtime, so the provider it returns is, on every call.
|
|
190
|
+
* @param loaded - Module namespace a dynamic import produced.
|
|
191
|
+
* @param modulePath - Module path named in a rejection.
|
|
192
|
+
* @returns - The module's factory, or undefined when it exports none.
|
|
193
|
+
* @throws {Error} When the factory throws or builds something that is not a terminal provider, naming the module.
|
|
194
|
+
*/
|
|
195
|
+
export function terminalProviderModule(loaded, modulePath) {
|
|
196
|
+
if (!isPlainObject(loaded))
|
|
197
|
+
return undefined;
|
|
198
|
+
const factory = loaded.createTerminalProvider;
|
|
199
|
+
if (typeof factory !== "function")
|
|
200
|
+
return undefined;
|
|
201
|
+
return {
|
|
202
|
+
createTerminalProvider: (environment) => {
|
|
203
|
+
let provider;
|
|
204
|
+
try {
|
|
205
|
+
provider = Reflect.apply(factory, undefined, [environment]);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
throw new Error(`terminal provider module failed to build its provider: ${modulePath}: ${errorText(error)}`, {
|
|
209
|
+
cause: error,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (!isTerminalProvider(provider)) {
|
|
213
|
+
throw new Error(`terminal provider module built something that is not a terminal provider: ${modulePath}`);
|
|
214
|
+
}
|
|
215
|
+
return provider;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function isTerminalProvider(value) {
|
|
220
|
+
return (isPlainObject(value) &&
|
|
221
|
+
typeof value.name === "string" &&
|
|
222
|
+
value.name.length > 0 &&
|
|
223
|
+
typeof value.launch === "function" &&
|
|
224
|
+
(value.available === undefined || typeof value.available === "function") &&
|
|
225
|
+
(value.interactivePriority === undefined ||
|
|
226
|
+
(typeof value.interactivePriority === "number" && Number.isFinite(value.interactivePriority))));
|
|
227
|
+
}
|
|
228
|
+
async function implementationDirectories(root) {
|
|
229
|
+
return (await readdir(root, { withFileTypes: true }))
|
|
230
|
+
.filter((entry) => entry.isDirectory())
|
|
231
|
+
.map((entry) => entry.name)
|
|
232
|
+
.sort();
|
|
233
|
+
}
|
|
234
|
+
async function createTerminalProvider(id, environment) {
|
|
235
|
+
if (id === "direct") {
|
|
236
|
+
return new DirectTerminalProvider((command, args, options) => {
|
|
237
|
+
const child = spawn(command, args, options);
|
|
238
|
+
return {
|
|
239
|
+
pid: child.pid,
|
|
240
|
+
unref: () => {
|
|
241
|
+
child.unref();
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const validatedId = implementationId(id);
|
|
247
|
+
const root = fileURLToPath(new URL("../implementations", import.meta.url));
|
|
248
|
+
const modulePath = join(root, validatedId, "terminal.js");
|
|
249
|
+
const loaded = terminalProviderModule(await import(__rewriteRelativeImportExtension(pathToFileURL(modulePath).href)), modulePath);
|
|
250
|
+
if (!loaded)
|
|
251
|
+
throw new Error(`terminal provider module does not export a factory: ${modulePath}`);
|
|
252
|
+
return loaded.createTerminalProvider(environment);
|
|
253
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type RuntimeEnvironment } from "./environment.js";
|
|
2
|
+
/** Lowest Bun release whose SQLite, subprocess, and test behaviour this project is validated against. */
|
|
3
|
+
export declare const MINIMUM_BUN_VERSION = "1.4.2";
|
|
4
|
+
/** Names the environment variable that pins an explicit Bun executable for spawned services. */
|
|
5
|
+
export declare const BUN_PATH_ENVIRONMENT = "PAGER_BUN_PATH";
|
|
6
|
+
/** Names the Node runtime override variable; `resolveBunExecutable` rejects it whenever it holds a non-empty value. */
|
|
7
|
+
export declare const RETIRED_NODE_PATH_ENVIRONMENT = "PAGER_NODE_PATH";
|
|
8
|
+
/** Raised when no executable satisfying the Bun runtime policy can be selected. */
|
|
9
|
+
export declare class BunRuntimeError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* Construct the actionable runtime failure surfaced to operators.
|
|
12
|
+
* @param message - Explanation naming the executable and version that were rejected.
|
|
13
|
+
*/
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
/** Supplies the host-identity and executable-probe seams used when selecting the service runtime. */
|
|
17
|
+
export type RuntimeProbe = {
|
|
18
|
+
/** Reports the Bun version of the current process, or undefined when this process is not Bun. */
|
|
19
|
+
hostVersion: string | undefined;
|
|
20
|
+
/** Names the executable running the current process. */
|
|
21
|
+
execPath: string;
|
|
22
|
+
/** Lists directories searched for a standalone `bun`, highest precedence first. */
|
|
23
|
+
searchPath: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Report a candidate executable's own Bun version.
|
|
26
|
+
* @param executable - Absolute path or command probed as a standalone Bun runtime.
|
|
27
|
+
* @returns - Reported Bun version, or undefined when the executable is not a standalone Bun.
|
|
28
|
+
*/
|
|
29
|
+
probe(executable: string): string | undefined;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Report whether a Bun version meets the project floor.
|
|
33
|
+
*
|
|
34
|
+
* Only the numeric major, minor, and patch are compared, so a prerelease suffix does not lower a
|
|
35
|
+
* build below the floor.
|
|
36
|
+
* @param version - Version string reported by a Bun runtime.
|
|
37
|
+
* @returns - True when the version is at or above the supported floor.
|
|
38
|
+
*/
|
|
39
|
+
export declare function satisfiesBunVersionFloor(version: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Fail fast when the current process is not a supported Bun runtime.
|
|
42
|
+
* @param probe - Host-identity seam, overridden by isolated verification.
|
|
43
|
+
* @returns - The validated Bun version running this process.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertSupportedBunRuntime(probe?: Pick<RuntimeProbe, "hostVersion">): string;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the Bun executable that must own detached services, wrappers, and gateway children.
|
|
48
|
+
*
|
|
49
|
+
* An embedded Bun host such as an editor exposes its own application binary as the process
|
|
50
|
+
* executable, so the current executable is reused only when it is a reusable standalone `bun`.
|
|
51
|
+
* @param environment - Process environment supplying the explicit override.
|
|
52
|
+
* @param overrides - Host-identity and probe seams for isolated verification.
|
|
53
|
+
* @returns - Absolute path that runs a supported Bun runtime.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveBunExecutable(environment?: NodeJS.ProcessEnv, overrides?: Partial<RuntimeProbe>): string;
|
|
56
|
+
/**
|
|
57
|
+
* Prefer the `bun` a package manager keeps at a stable path over the versioned file it links to.
|
|
58
|
+
*
|
|
59
|
+
* A path written into a long-lived file must outlive an upgrade. Homebrew, for one, links
|
|
60
|
+
* `bin/bun` to a per-version directory that the next upgrade deletes.
|
|
61
|
+
* @param executable - Resolved Bun executable, typically already a real path.
|
|
62
|
+
* @param searchPath - Directories searched for a `bun` link, highest precedence first.
|
|
63
|
+
* @param realPath - Resolves symbolic links; overridden by isolated verification.
|
|
64
|
+
* @returns - First `bun` on the search path that resolves to the same file, or the executable itself.
|
|
65
|
+
*/
|
|
66
|
+
export declare function stableBunExecutable(executable: string, searchPath?: readonly string[], realPath?: (path: string) => string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve and pin the standalone Bun executable before an embedded host starts a service.
|
|
69
|
+
* @param processEnvironment - Environment inherited by the editor host.
|
|
70
|
+
* @param resolveRuntime - Validated standalone-runtime resolver.
|
|
71
|
+
* @returns - Runtime environment carrying an explicit absolute Bun selection.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readEmbeddedRuntimeEnvironment(processEnvironment: NodeJS.ProcessEnv, resolveRuntime: (environment: NodeJS.ProcessEnv) => string): RuntimeEnvironment;
|
|
74
|
+
/**
|
|
75
|
+
* Report the Bun version of another executable without trusting its name.
|
|
76
|
+
* @param executable - Absolute path or command to probe.
|
|
77
|
+
* @returns - Reported Bun version, or undefined when the executable is not a standalone Bun.
|
|
78
|
+
*/
|
|
79
|
+
export declare function probeBunVersion(executable: string): string | undefined;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, realpathSync } from "node:fs";
|
|
3
|
+
import { delimiter, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { readRuntimeEnvironment } from "./environment.js";
|
|
5
|
+
/** Bounds one probe of an external runtime executable. */
|
|
6
|
+
const RUNTIME_PROBE_TIMEOUT_MS = 10_000;
|
|
7
|
+
/** Lowest Bun release whose SQLite, subprocess, and test behaviour this project is validated against. */
|
|
8
|
+
export const MINIMUM_BUN_VERSION = "1.4.2";
|
|
9
|
+
/** Names the environment variable that pins an explicit Bun executable for spawned services. */
|
|
10
|
+
export const BUN_PATH_ENVIRONMENT = "PAGER_BUN_PATH";
|
|
11
|
+
/** Names the Node runtime override variable; `resolveBunExecutable` rejects it whenever it holds a non-empty value. */
|
|
12
|
+
export const RETIRED_NODE_PATH_ENVIRONMENT = "PAGER_NODE_PATH";
|
|
13
|
+
/** Raised when no executable satisfying the Bun runtime policy can be selected. */
|
|
14
|
+
export class BunRuntimeError extends Error {
|
|
15
|
+
/**
|
|
16
|
+
* Construct the actionable runtime failure surfaced to operators.
|
|
17
|
+
* @param message - Explanation naming the executable and version that were rejected.
|
|
18
|
+
*/
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "BunRuntimeError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Report whether a Bun version meets the project floor.
|
|
26
|
+
*
|
|
27
|
+
* Only the numeric major, minor, and patch are compared, so a prerelease suffix does not lower a
|
|
28
|
+
* build below the floor.
|
|
29
|
+
* @param version - Version string reported by a Bun runtime.
|
|
30
|
+
* @returns - True when the version is at or above the supported floor.
|
|
31
|
+
*/
|
|
32
|
+
export function satisfiesBunVersionFloor(version) {
|
|
33
|
+
const candidate = numericVersion(version);
|
|
34
|
+
const minimum = numericVersion(MINIMUM_BUN_VERSION);
|
|
35
|
+
if (!candidate || !minimum)
|
|
36
|
+
return false;
|
|
37
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
38
|
+
const difference = (candidate[index] ?? 0) - (minimum[index] ?? 0);
|
|
39
|
+
if (difference !== 0)
|
|
40
|
+
return difference > 0;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function numericVersion(version) {
|
|
45
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/u.exec(version);
|
|
46
|
+
if (!match)
|
|
47
|
+
return undefined;
|
|
48
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fail fast when the current process is not a supported Bun runtime.
|
|
52
|
+
* @param probe - Host-identity seam, overridden by isolated verification.
|
|
53
|
+
* @returns - The validated Bun version running this process.
|
|
54
|
+
*/
|
|
55
|
+
export function assertSupportedBunRuntime(probe = defaultProbe()) {
|
|
56
|
+
const version = probe.hostVersion;
|
|
57
|
+
if (version === undefined) {
|
|
58
|
+
throw new BunRuntimeError(`Pager requires Bun >= ${MINIMUM_BUN_VERSION}; this process is not running under Bun`);
|
|
59
|
+
}
|
|
60
|
+
if (!satisfiesBunVersionFloor(version)) {
|
|
61
|
+
throw new BunRuntimeError(`Pager requires Bun >= ${MINIMUM_BUN_VERSION}; this process runs Bun ${version}`);
|
|
62
|
+
}
|
|
63
|
+
return version;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the Bun executable that must own detached services, wrappers, and gateway children.
|
|
67
|
+
*
|
|
68
|
+
* An embedded Bun host such as an editor exposes its own application binary as the process
|
|
69
|
+
* executable, so the current executable is reused only when it is a reusable standalone `bun`.
|
|
70
|
+
* @param environment - Process environment supplying the explicit override.
|
|
71
|
+
* @param overrides - Host-identity and probe seams for isolated verification.
|
|
72
|
+
* @returns - Absolute path that runs a supported Bun runtime.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveBunExecutable(environment = process.env, overrides = {}) {
|
|
75
|
+
const probe = { ...defaultProbe(), ...overrides };
|
|
76
|
+
if (environment[RETIRED_NODE_PATH_ENVIRONMENT]) {
|
|
77
|
+
throw new BunRuntimeError(`${RETIRED_NODE_PATH_ENVIRONMENT} is no longer honoured; set ${BUN_PATH_ENVIRONMENT} to a Bun >= ${MINIMUM_BUN_VERSION} executable`);
|
|
78
|
+
}
|
|
79
|
+
const configured = environment[BUN_PATH_ENVIRONMENT];
|
|
80
|
+
if (configured) {
|
|
81
|
+
if (!isAbsolute(configured)) {
|
|
82
|
+
throw new BunRuntimeError(`${BUN_PATH_ENVIRONMENT} must name an absolute Bun executable path`);
|
|
83
|
+
}
|
|
84
|
+
return validated(configured, probe.probe(configured), "configured by " + BUN_PATH_ENVIRONMENT);
|
|
85
|
+
}
|
|
86
|
+
if (probe.hostVersion !== undefined &&
|
|
87
|
+
isAbsolute(probe.execPath) &&
|
|
88
|
+
isStandaloneBunPath(probe.execPath) &&
|
|
89
|
+
satisfiesBunVersionFloor(probe.hostVersion)) {
|
|
90
|
+
return probe.execPath;
|
|
91
|
+
}
|
|
92
|
+
const rejected = [];
|
|
93
|
+
for (const candidate of bunCandidatesOnPath(probe.searchPath)) {
|
|
94
|
+
const version = probe.probe(candidate);
|
|
95
|
+
if (version !== undefined && satisfiesBunVersionFloor(version))
|
|
96
|
+
return candidate;
|
|
97
|
+
rejected.push(version === undefined ? `${candidate} (not Bun)` : `${candidate} (Bun ${version})`);
|
|
98
|
+
}
|
|
99
|
+
const detail = rejected.length > 0 ? `; rejected ${rejected.join(", ")}` : "";
|
|
100
|
+
throw new BunRuntimeError(`Pager requires Bun >= ${MINIMUM_BUN_VERSION} on PATH or in ${BUN_PATH_ENVIRONMENT}${detail}`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Prefer the `bun` a package manager keeps at a stable path over the versioned file it links to.
|
|
104
|
+
*
|
|
105
|
+
* A path written into a long-lived file must outlive an upgrade. Homebrew, for one, links
|
|
106
|
+
* `bin/bun` to a per-version directory that the next upgrade deletes.
|
|
107
|
+
* @param executable - Resolved Bun executable, typically already a real path.
|
|
108
|
+
* @param searchPath - Directories searched for a `bun` link, highest precedence first.
|
|
109
|
+
* @param realPath - Resolves symbolic links; overridden by isolated verification.
|
|
110
|
+
* @returns - First `bun` on the search path that resolves to the same file, or the executable itself.
|
|
111
|
+
*/
|
|
112
|
+
export function stableBunExecutable(executable, searchPath = defaultProbe().searchPath, realPath = realpathSync) {
|
|
113
|
+
const target = tryRealPath(executable, realPath);
|
|
114
|
+
if (target === undefined)
|
|
115
|
+
return executable;
|
|
116
|
+
for (const candidate of bunCandidatesOnPath(searchPath)) {
|
|
117
|
+
if (tryRealPath(candidate, realPath) === target)
|
|
118
|
+
return candidate;
|
|
119
|
+
}
|
|
120
|
+
return executable;
|
|
121
|
+
}
|
|
122
|
+
function tryRealPath(path, realPath) {
|
|
123
|
+
try {
|
|
124
|
+
return realPath(path);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve and pin the standalone Bun executable before an embedded host starts a service.
|
|
132
|
+
* @param processEnvironment - Environment inherited by the editor host.
|
|
133
|
+
* @param resolveRuntime - Validated standalone-runtime resolver.
|
|
134
|
+
* @returns - Runtime environment carrying an explicit absolute Bun selection.
|
|
135
|
+
*/
|
|
136
|
+
export function readEmbeddedRuntimeEnvironment(processEnvironment, resolveRuntime) {
|
|
137
|
+
const environment = readRuntimeEnvironment(processEnvironment);
|
|
138
|
+
return {
|
|
139
|
+
...environment,
|
|
140
|
+
processEnv: { ...environment.processEnv, [BUN_PATH_ENVIRONMENT]: resolveRuntime(processEnvironment) },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Report the Bun version of another executable without trusting its name.
|
|
145
|
+
* @param executable - Absolute path or command to probe.
|
|
146
|
+
* @returns - Reported Bun version, or undefined when the executable is not a standalone Bun.
|
|
147
|
+
*/
|
|
148
|
+
export function probeBunVersion(executable) {
|
|
149
|
+
try {
|
|
150
|
+
const output = execFileSync(executable, ["-e", "process.stdout.write(String(Bun?.version ?? ''))"], {
|
|
151
|
+
encoding: "utf8",
|
|
152
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
153
|
+
timeout: RUNTIME_PROBE_TIMEOUT_MS,
|
|
154
|
+
}).trim();
|
|
155
|
+
return /^\d+\.\d+\.\d+/u.test(output) ? output : undefined;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function defaultProbe() {
|
|
162
|
+
return {
|
|
163
|
+
hostVersion: typeof globalThis.Bun === "object" ? Bun.version : undefined,
|
|
164
|
+
execPath: process.execPath,
|
|
165
|
+
searchPath: (process.env.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0),
|
|
166
|
+
probe: probeBunVersion,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function validated(executable, version, origin) {
|
|
170
|
+
if (version === undefined)
|
|
171
|
+
throw new BunRuntimeError(`${executable} (${origin}) is not a Bun executable`);
|
|
172
|
+
if (!satisfiesBunVersionFloor(version)) {
|
|
173
|
+
throw new BunRuntimeError(`${executable} (${origin}) runs Bun ${version}; Pager requires >= ${MINIMUM_BUN_VERSION}`);
|
|
174
|
+
}
|
|
175
|
+
return executable;
|
|
176
|
+
}
|
|
177
|
+
function isStandaloneBunPath(execPath) {
|
|
178
|
+
const name = execPath.slice(execPath.lastIndexOf("/") + 1).toLowerCase();
|
|
179
|
+
return name === "bun" || name === "bun.exe";
|
|
180
|
+
}
|
|
181
|
+
function bunCandidatesOnPath(searchPath) {
|
|
182
|
+
const candidates = [];
|
|
183
|
+
for (const directory of searchPath) {
|
|
184
|
+
for (const name of ["bun", "bun.exe"]) {
|
|
185
|
+
const candidate = resolve(directory, name);
|
|
186
|
+
try {
|
|
187
|
+
accessSync(candidate, constants.X_OK);
|
|
188
|
+
candidates.push(candidate);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return candidates;
|
|
196
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BrokerServiceFilesystemPort, BrokerServiceSchedulerPort } from "./service.js";
|
|
2
|
+
/**
|
|
3
|
+
* Schedule on the process timers without holding the process open.
|
|
4
|
+
* @returns - Scheduler whose timers never keep the event loop alive.
|
|
5
|
+
*/
|
|
6
|
+
export declare function systemBrokerServiceScheduler(): BrokerServiceSchedulerPort;
|
|
7
|
+
/**
|
|
8
|
+
* Operate on the real filesystem.
|
|
9
|
+
* @returns - Filesystem port over `node:fs`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function systemBrokerServiceFilesystem(): BrokerServiceFilesystemPort;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
/** Largest delay one process timer is armed with; a longer delay fires at this bound instead. */
|
|
3
|
+
const MAXIMUM_TIMER_DELAY_MS = 2_147_483_647;
|
|
4
|
+
/**
|
|
5
|
+
* Schedule on the process timers without holding the process open.
|
|
6
|
+
* @returns - Scheduler whose timers never keep the event loop alive.
|
|
7
|
+
*/
|
|
8
|
+
export function systemBrokerServiceScheduler() {
|
|
9
|
+
return {
|
|
10
|
+
once: (callback, delayMs) => {
|
|
11
|
+
const timer = setTimeout(callback, Math.min(delayMs, MAXIMUM_TIMER_DELAY_MS));
|
|
12
|
+
timer.unref();
|
|
13
|
+
return () => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
recurring: (callback, intervalMs) => {
|
|
18
|
+
const timer = setInterval(callback, intervalMs);
|
|
19
|
+
timer.unref();
|
|
20
|
+
return () => {
|
|
21
|
+
clearInterval(timer);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Operate on the real filesystem.
|
|
28
|
+
* @returns - Filesystem port over `node:fs`.
|
|
29
|
+
*/
|
|
30
|
+
export function systemBrokerServiceFilesystem() {
|
|
31
|
+
return {
|
|
32
|
+
exists: existsSync,
|
|
33
|
+
sizeOf: (path) => statSync(path).size,
|
|
34
|
+
remove: unlinkSync,
|
|
35
|
+
createDirectory: (path) => {
|
|
36
|
+
mkdirSync(path, { recursive: true });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|