@scopebond/agent 0.4.6 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -6
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +4 -4
- package/dist/agent.js.map +1 -1
- package/dist/allowance-sender.d.ts +8 -0
- package/dist/allowance-sender.d.ts.map +1 -0
- package/dist/allowance-sender.js +80 -0
- package/dist/allowance-sender.js.map +1 -0
- package/dist/autostart.d.ts +13 -0
- package/dist/autostart.d.ts.map +1 -1
- package/dist/autostart.js +60 -10
- package/dist/autostart.js.map +1 -1
- package/dist/cli-main.d.ts +3 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +218 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.js +4 -185
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +7 -1
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +64 -12
- package/dist/ipc.js.map +1 -1
- package/dist/log-file.d.ts +6 -0
- package/dist/log-file.d.ts.map +1 -0
- package/dist/log-file.js +40 -0
- package/dist/log-file.js.map +1 -0
- package/dist/native-update.d.ts +51 -0
- package/dist/native-update.d.ts.map +1 -0
- package/dist/native-update.js +120 -0
- package/dist/native-update.js.map +1 -0
- package/dist/prompt.d.ts +11 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +57 -23
- package/dist/prompt.js.map +1 -1
- package/dist/reconnect.d.ts +19 -0
- package/dist/reconnect.d.ts.map +1 -0
- package/dist/reconnect.js +60 -0
- package/dist/reconnect.js.map +1 -0
- package/dist/self.d.ts +5 -0
- package/dist/self.d.ts.map +1 -0
- package/dist/self.js +15 -0
- package/dist/self.js.map +1 -0
- package/dist/service.d.ts +50 -4
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +292 -18
- package/dist/service.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +4 -9
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts +16 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +47 -0
- package/dist/summary.js.map +1 -0
- package/dist/tray-model.d.ts +91 -0
- package/dist/tray-model.d.ts.map +1 -0
- package/dist/tray-model.js +159 -0
- package/dist/tray-model.js.map +1 -0
- package/dist/tray.d.ts.map +1 -1
- package/dist/tray.js +103 -29
- package/dist/tray.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +7 -4
- package/dist/update.js.map +1 -1
- package/package.json +4 -4
package/dist/service.js
CHANGED
|
@@ -3,25 +3,55 @@
|
|
|
3
3
|
// second one finds the first answering and exits. Every six hours it asks the workspace which
|
|
4
4
|
// versions to run, and once a day it runs the end-to-end self-check.
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { hostname } from "node:os";
|
|
7
8
|
import { join } from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import { hookVersion, loadConnection, userHarnessFile, writeHarnessConfig } from "@scopebond/hook";
|
|
9
|
+
import { actOnBlocked, blockedQuestion, hookVersion, isSingleExecutable, isManaged, loadConnection, localActivity, readBlocked, readMeta, ruleReport, runStoreUpkeep, userHarnessFile, writeHarnessConfig } from "@scopebond/hook";
|
|
10
10
|
import { computerStatus, expectedHarnesses, missingHookEntries, runCycle } from "./agent.js";
|
|
11
|
-
import {
|
|
11
|
+
import { agentCliPath } from "./self.js";
|
|
12
|
+
import { fetchVerifiedInstaller, installAfterExit, installKind } from "./native-update.js";
|
|
13
|
+
import { AGENT_LOG_ENV, launcherIsCurrent, launcherPath, refreshLauncher, startCommands } from "./autostart.js";
|
|
12
14
|
import { callAgent, startControl } from "./ipc.js";
|
|
13
15
|
import { runSelfCheck } from "./selfcheck.js";
|
|
14
16
|
import { flushReasons, queueReason } from "./override-reasons.js";
|
|
15
17
|
import { parseQuestion, serialized, systemPrompter } from "./prompt.js";
|
|
16
18
|
import { healthOf } from "./health.js";
|
|
17
19
|
import { notifyChange, startTray } from "./tray.js";
|
|
20
|
+
import { sendAllowancesAndRequests } from "./allowance-sender.js";
|
|
21
|
+
import { checkResult, trayModel } from "./tray-model.js";
|
|
22
|
+
import { fetchComputerSummary, openInBrowser, sameOrigin } from "./summary.js";
|
|
23
|
+
import { startReconnect } from "./reconnect.js";
|
|
18
24
|
import { agentVersion, compareVersions, fetchClientVersion, installAgent, maintainHookEntries, maintainedHookCommand } from "./update.js";
|
|
19
25
|
export const AGENT_VERSION = `agent/${agentVersion()}`;
|
|
20
26
|
const INTERVAL_MS = 60_000;
|
|
21
27
|
const MAX_BACKOFF_MS = 15 * 60_000;
|
|
22
28
|
const UPDATE_EVERY_MS = 6 * 60 * 60 * 1000;
|
|
23
29
|
const SELF_CHECK_EVERY_MS = 24 * 60 * 60 * 1000;
|
|
30
|
+
const SUMMARY_EVERY_MS = 5 * 60 * 1000;
|
|
24
31
|
export const AFTER_PID_ENV = "SCOPEBOND_AGENT_AFTER_PID";
|
|
32
|
+
/** The person's tray settings (D143: notifications about problems by default, none about blocks). */
|
|
33
|
+
export const TRAY_SETTINGS_FILE = "agent-settings.json";
|
|
34
|
+
export function readTraySettings(dir) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(readFileSync(join(dir, TRAY_SETTINGS_FILE), "utf8"));
|
|
37
|
+
return { notifications: parsed.notifications === "all" || parsed.notifications === "off" ? parsed.notifications : "problems" };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { notifications: "problems" };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function writeTraySettings(dir, patch) {
|
|
44
|
+
const next = readTraySettings(dir);
|
|
45
|
+
const value = patch?.notifications;
|
|
46
|
+
if (value === "all" || value === "problems" || value === "off")
|
|
47
|
+
next.notifications = value;
|
|
48
|
+
writeFileSync(join(dir, TRAY_SETTINGS_FILE), JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
/** Set for a replacement started directly because the launcher is an older one: it rewrites the launcher once the old agent is gone. */
|
|
52
|
+
export const REFRESH_LAUNCHER_ENV = "SCOPEBOND_AGENT_REFRESH_LAUNCHER";
|
|
53
|
+
/** The exit code that asks a service manager to start the agent again (systemd's Restart=on-failure), used where a detached replacement would not survive. */
|
|
54
|
+
export const RESTART_EXIT_CODE = 75;
|
|
25
55
|
/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is
|
|
26
56
|
* logged and returned, and only settings of agents this computer has are touched. */
|
|
27
57
|
export function repairHookEntries(harnesses = expectedHarnesses()) {
|
|
@@ -32,17 +62,76 @@ export function repairHookEntries(harnesses = expectedHarnesses()) {
|
|
|
32
62
|
}
|
|
33
63
|
return repaired;
|
|
34
64
|
}
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
65
|
+
/** Decide how to start the updated agent. Through the autostart launcher when there is one, so it finds Node the same way
|
|
66
|
+
* sign-in does and is restarted after a crash. On Windows an older launcher cannot do it: it redirects the agent's output
|
|
67
|
+
* into agent.log, this agent's children inherit that handle, and the new launcher's own redirect then fails, which cmd
|
|
68
|
+
* takes for a clean exit. So under an older launcher the updated agent starts directly and rewrites the launcher once
|
|
69
|
+
* this one is gone. Under systemd a detached child is stopped with the service, so the service manager restarts it. */
|
|
70
|
+
export function handoverPlan(o) {
|
|
71
|
+
if (o.platform === "linux" && o.env.INVOCATION_ID)
|
|
72
|
+
return { kind: "service-restart" };
|
|
73
|
+
const launcher = launcherPath(o.dir, o.platform);
|
|
74
|
+
const env = { ...o.env, [AFTER_PID_ENV]: String(o.pid), [AGENT_LOG_ENV]: join(o.dir, "agent.log") };
|
|
75
|
+
if (o.launcherText !== null && launcherIsCurrent(o.launcherText, o.platform)) {
|
|
76
|
+
if (o.platform === "win32") {
|
|
77
|
+
const [command, args] = startCommands(launcher, o.platform)[1];
|
|
78
|
+
return { kind: "spawn", command, args, env, verbatim: true };
|
|
79
|
+
}
|
|
80
|
+
return { kind: "spawn", command: "/bin/sh", args: [launcher], env, verbatim: false };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
kind: "spawn", command: o.execPath, args: o.singleExecutable ? ["run"] : ["--disable-warning=ExperimentalWarning", o.cli, "run"], verbatim: false,
|
|
84
|
+
env: o.launcherText !== null ? { ...env, [REFRESH_LAUNCHER_ENV]: "1" } : env,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Start the updated agent, which waits for this process to exit. Returns false when a service manager restarts it instead
|
|
88
|
+
* (this process then exits with RESTART_EXIT_CODE). */
|
|
89
|
+
export function spawnReplacement(dir, cli = agentCliPath()) {
|
|
90
|
+
let launcherText = null;
|
|
91
|
+
try {
|
|
92
|
+
launcherText = readFileSync(launcherPath(dir), "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch { /* no autostart launcher */ }
|
|
95
|
+
const plan = handoverPlan({ dir, cli, pid: process.pid, execPath: process.execPath, platform: process.platform, env: process.env, launcherText, singleExecutable: isSingleExecutable() });
|
|
96
|
+
if (plan.kind === "service-restart")
|
|
97
|
+
return false;
|
|
98
|
+
const child = spawn(plan.command, plan.args, { env: plan.env, detached: true, stdio: "ignore", windowsHide: true, windowsVerbatimArguments: plan.verbatim });
|
|
99
|
+
child.on("error", () => { });
|
|
45
100
|
child.unref();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** The first thing a starting agent does: wait for the agent it replaces to exit, then, when asked, rewrite the older launcher
|
|
104
|
+
* that agent ran under (by now nothing runs it). The handover variables are dropped, so a later restart waits for nothing. */
|
|
105
|
+
export async function takeOver(dir, cli, waitMs = 30_000) {
|
|
106
|
+
const previous = Number(process.env[AFTER_PID_ENV]);
|
|
107
|
+
const refresh = process.env[REFRESH_LAUNCHER_ENV] === "1";
|
|
108
|
+
delete process.env[AFTER_PID_ENV];
|
|
109
|
+
delete process.env[REFRESH_LAUNCHER_ENV];
|
|
110
|
+
if (Number.isInteger(previous) && previous > 0)
|
|
111
|
+
await waitForExit(previous, waitMs);
|
|
112
|
+
if (!refresh)
|
|
113
|
+
return;
|
|
114
|
+
// The old launcher reads its last lines after its agent exits; give it a moment to finish before its file changes.
|
|
115
|
+
await new Promise((r) => setTimeout(r, 2_000));
|
|
116
|
+
try {
|
|
117
|
+
if (refreshLauncher(dir, cli))
|
|
118
|
+
console.log(`${new Date().toISOString()} rewrote the autostart launcher for this version`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.log(`${new Date().toISOString()} could not rewrite the autostart launcher: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export async function waitForExit(pid, timeoutMs) {
|
|
125
|
+
const until = Date.now() + timeoutMs;
|
|
126
|
+
while (Date.now() < until) {
|
|
127
|
+
try {
|
|
128
|
+
process.kill(pid, 0);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
134
|
+
}
|
|
46
135
|
}
|
|
47
136
|
export const AGENT_LOCK = "agent.lock";
|
|
48
137
|
/** How long a lock whose agent never answered on its local channel still counts as an agent starting up. */
|
|
@@ -111,6 +200,23 @@ export async function startService(options) {
|
|
|
111
200
|
let running = null;
|
|
112
201
|
let stopped = false;
|
|
113
202
|
let lastSelfCheckAt = 0;
|
|
203
|
+
// SB388: time asleep never counts as records waiting. A gap between cycles longer than the schedule allows means the
|
|
204
|
+
// computer slept (or the agent was stopped); waiting is counted again from the next cycle.
|
|
205
|
+
let awakeSince = Date.now();
|
|
206
|
+
let lastCycleAt = 0;
|
|
207
|
+
// A long step the person should see as "working" (an update), or null.
|
|
208
|
+
let working = null;
|
|
209
|
+
// SB391: the workspace's own summary for the tray (names, links, open reviews), refreshed every five minutes; null when the
|
|
210
|
+
// workspace does not answer it (a self-hosted gateway), and the tray simply leaves those rows out.
|
|
211
|
+
// SB392: a sign-in started from the tray, while it waits for approval.
|
|
212
|
+
let reconnecting = null;
|
|
213
|
+
let summary = null;
|
|
214
|
+
let summaryAt = 0;
|
|
215
|
+
const refreshSummary = async () => {
|
|
216
|
+
summaryAt = Date.now();
|
|
217
|
+
const connection = loadConnection(options.dir);
|
|
218
|
+
summary = connection ? await fetchComputerSummary(connection, options.fetchImpl) : null;
|
|
219
|
+
};
|
|
114
220
|
const prompt = serialized(options.prompter ?? systemPrompter);
|
|
115
221
|
let shownLevel = null;
|
|
116
222
|
const health = () => healthOf(computerStatus(options.dir), lastMaintenance?.selfCheck ?? null);
|
|
@@ -125,10 +231,22 @@ export async function startService(options) {
|
|
|
125
231
|
log(`sent ${sent} override reason(s) to the workspace`);
|
|
126
232
|
}
|
|
127
233
|
catch { /* they wait for the next cycle */ }
|
|
234
|
+
// D144: allowances a person made here, and their requests to an admin.
|
|
235
|
+
try {
|
|
236
|
+
const sent = await sendAllowancesAndRequests(options.dir, connection, options.fetchImpl);
|
|
237
|
+
if (sent.allowances || sent.requests)
|
|
238
|
+
log(`sent ${sent.allowances} allowance(s) and ${sent.requests} request(s) to the workspace`);
|
|
239
|
+
}
|
|
240
|
+
catch { /* they wait for the next cycle */ }
|
|
128
241
|
};
|
|
129
242
|
const cycle = async () => {
|
|
130
243
|
if (running)
|
|
131
244
|
return running;
|
|
245
|
+
const started = Date.now();
|
|
246
|
+
const allowedGap = failures ? MAX_BACKOFF_MS + 5 * 60_000 : Math.max(3 * interval, 5 * 60_000);
|
|
247
|
+
if (lastCycleAt && started - lastCycleAt > allowedGap)
|
|
248
|
+
awakeSince = started;
|
|
249
|
+
lastCycleAt = started;
|
|
132
250
|
running = runCycle({ dir: options.dir, fetchImpl: options.fetchImpl }).then(async (result) => {
|
|
133
251
|
await sendReasons();
|
|
134
252
|
try {
|
|
@@ -141,6 +259,18 @@ export async function startService(options) {
|
|
|
141
259
|
log(`delivered ${result.delivered} record(s); ${result.pending} waiting`);
|
|
142
260
|
if (result.deliveryError)
|
|
143
261
|
log(`delivery problem: ${result.deliveryError}`);
|
|
262
|
+
// SB390: asked from the workspace's computer page. Send again now (the cycle sent before its rules check), or run the
|
|
263
|
+
// self-check; each once.
|
|
264
|
+
if (result.requested === "flush") {
|
|
265
|
+
log("the workspace asked this computer to send now");
|
|
266
|
+
setTimeout(() => { void cycle(); }, 0);
|
|
267
|
+
}
|
|
268
|
+
if (result.requested === "self_check") {
|
|
269
|
+
log("the workspace asked this computer to check now");
|
|
270
|
+
setTimeout(() => { void maintain(true); }, 0);
|
|
271
|
+
}
|
|
272
|
+
if (result.connected && Date.now() - summaryAt > SUMMARY_EVERY_MS)
|
|
273
|
+
void refreshSummary();
|
|
144
274
|
return result;
|
|
145
275
|
}).finally(() => { running = null; });
|
|
146
276
|
return running;
|
|
@@ -161,7 +291,30 @@ export async function startService(options) {
|
|
|
161
291
|
result.hookEntries = maintainHookEntries(harnesses, hookTarget, target?.policy !== "hold");
|
|
162
292
|
for (const change of result.hookEntries)
|
|
163
293
|
log(`${change.reason}: ${change.file}`);
|
|
164
|
-
|
|
294
|
+
const kind = installKind();
|
|
295
|
+
if (target?.policy === "recommended" && target.agent && compareVersions(target.agent, current) > 0 && kind === "per-machine") {
|
|
296
|
+
// Installed for every user: the organisation that deployed it updates it.
|
|
297
|
+
log(`the workspace recommends the Scopebond Agent ${target.agent}; this install is updated by your organization`);
|
|
298
|
+
}
|
|
299
|
+
else if (target?.policy === "recommended" && target.agent && compareVersions(target.agent, current) > 0 && kind === "per-user") {
|
|
300
|
+
log(`updating the Scopebond Agent ${current} -> ${target.agent} (signed installer)`);
|
|
301
|
+
const verified = await fetchVerifiedInstaller(target.agent, { dir: options.dir, fetchImpl: options.fetchImpl });
|
|
302
|
+
if (verified.ok) {
|
|
303
|
+
result.updatedTo = target.agent;
|
|
304
|
+
lastMaintenance = result;
|
|
305
|
+
if (options.onUpdated)
|
|
306
|
+
options.onUpdated(target.agent);
|
|
307
|
+
else {
|
|
308
|
+
// The installer replaces this file, so it runs after this agent has exited, and starts the agent again.
|
|
309
|
+
installAfterExit(verified.path, process.pid, existsSync(launcherPath(options.dir)) ? launcherPath(options.dir) : null);
|
|
310
|
+
setTimeout(() => { setTimeout(() => process.exit(0), 5_000).unref(); void stop().finally(() => process.exit(0)); }, 500);
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
result.error = `update to ${target.agent} could not be verified: ${verified.reason}; nothing was installed`;
|
|
315
|
+
log(result.error);
|
|
316
|
+
}
|
|
317
|
+
else if (target?.policy === "recommended" && target.agent && compareVersions(target.agent, current) > 0) {
|
|
165
318
|
log(`updating the Scopebond Agent ${current} -> ${target.agent}`);
|
|
166
319
|
const installed = await installAgent(target.agent);
|
|
167
320
|
if (installed.ok) {
|
|
@@ -170,16 +323,28 @@ export async function startService(options) {
|
|
|
170
323
|
if (options.onUpdated)
|
|
171
324
|
options.onUpdated(target.agent);
|
|
172
325
|
else {
|
|
173
|
-
spawnReplacement(options.dir);
|
|
326
|
+
const code = spawnReplacement(options.dir) ? 0 : RESTART_EXIT_CODE;
|
|
174
327
|
// Hand over: stop, then exit. A stop that never finishes (a tray or window child that will not close) must not
|
|
175
328
|
// keep the old agent alive with the replacement waiting on it, so the exit has a hard deadline.
|
|
176
|
-
setTimeout(() => { setTimeout(() => process.exit(
|
|
329
|
+
setTimeout(() => { setTimeout(() => process.exit(code), 5_000).unref(); void stop().finally(() => process.exit(code)); }, 500);
|
|
177
330
|
}
|
|
178
331
|
return result;
|
|
179
332
|
}
|
|
180
333
|
result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;
|
|
181
334
|
log(result.error);
|
|
182
335
|
}
|
|
336
|
+
// The local store: older rows rewritten, acknowledged receipts past the retention window removed, space returned.
|
|
337
|
+
// The agent may rewrite the whole file once (the hook never does that during a tool call).
|
|
338
|
+
try {
|
|
339
|
+
result.store = runStoreUpkeep(options.dir, { budgetMs: 30_000, allowFullVacuum: true });
|
|
340
|
+
const kept = result.store;
|
|
341
|
+
if (kept && (kept.migrated || kept.receiptsRemoved || kept.stateRemoved || kept.pagesFreed)) {
|
|
342
|
+
log(`local store: ${kept.migrated} row(s) rewritten, ${kept.receiptsRemoved} receipt(s) past retention removed, ${kept.stateRemoved} finished check record(s) removed, ${kept.pagesFreed} page(s) returned`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
log(`local store upkeep failed: ${error.message}`);
|
|
347
|
+
}
|
|
183
348
|
if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {
|
|
184
349
|
result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });
|
|
185
350
|
lastSelfCheckAt = Date.now();
|
|
@@ -198,7 +363,103 @@ export async function startService(options) {
|
|
|
198
363
|
const delay = failures ? Math.min(MAX_BACKOFF_MS, interval * 2 ** Math.min(failures, 6)) : interval;
|
|
199
364
|
timer = setTimeout(() => { void cycle().finally(schedule); }, delay);
|
|
200
365
|
};
|
|
366
|
+
// D144: a block under a rule a person may allow or ask about can be acted on afterwards, from the tray.
|
|
367
|
+
const actionable = (blocks, now) => {
|
|
368
|
+
const known = new Map(readBlocked(options.dir).map((b) => [b.id, b]));
|
|
369
|
+
return blocks.map((b) => {
|
|
370
|
+
const item = b.action_id ? known.get(b.action_id) : undefined;
|
|
371
|
+
return { ...b, rule: b.rule ?? item?.rule ?? null, can_act: Boolean(item && b.action_id && blockedQuestion(options.dir, b.action_id, now)), acted: item?.acted ?? null };
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
const trayState = () => {
|
|
375
|
+
const now = Date.now();
|
|
376
|
+
const status = computerStatus(options.dir);
|
|
377
|
+
const meta = readMeta(options.dir);
|
|
378
|
+
const report = ruleReport(options.dir);
|
|
379
|
+
const modes = report ? Object.values(report).map(([mode]) => mode) : [];
|
|
380
|
+
const activity = localActivity(options.dir);
|
|
381
|
+
return trayModel({
|
|
382
|
+
status, health: health(), now, awakeSince, working,
|
|
383
|
+
rules: report ? { checked_at: meta.checked_at ? Date.parse(meta.checked_at) : null, managed: isManaged(options.dir), block: modes.filter((m) => m === "enforce").length, monitor: modes.filter((m) => m === "monitor").length } : null,
|
|
384
|
+
today: activity?.today ?? null,
|
|
385
|
+
recentBlocks: actionable(activity?.recent_blocks ?? [], now),
|
|
386
|
+
version: { agent: agentVersion(), hook: hookVersion(), policy: lastMaintenance?.policy ?? "unknown", recommendedAgent: meta.recommended?.agent ?? null, recommendedHook: meta.recommended?.hook ?? null },
|
|
387
|
+
workspace: summary ? { name: summary.workspace_name, environment: summary.environment_name, computer_url: summary.computer_url } : null,
|
|
388
|
+
openReviews: summary?.open_reviews ?? 0,
|
|
389
|
+
computerName: hostname(),
|
|
390
|
+
canReconnect: true,
|
|
391
|
+
});
|
|
392
|
+
};
|
|
201
393
|
const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {
|
|
394
|
+
// SB387: what the tray draws, and the person's tray settings.
|
|
395
|
+
"GET /tray": () => ({ tray: trayState(), settings: readTraySettings(options.dir) }),
|
|
396
|
+
"POST /settings": (body) => ({ settings: writeTraySettings(options.dir, body) }),
|
|
397
|
+
// SB387: "Check now" always says what it found.
|
|
398
|
+
"POST /check": async () => {
|
|
399
|
+
const result = await maintain(true);
|
|
400
|
+
return { text: checkResult(result.selfCheck, result.error), tray: trayState() };
|
|
401
|
+
},
|
|
402
|
+
// SB389: install the version the workspace recommends now, instead of at the next six-hourly check.
|
|
403
|
+
"POST /update": async () => {
|
|
404
|
+
working = "Updating Scopebond…";
|
|
405
|
+
try {
|
|
406
|
+
const result = await maintain(true);
|
|
407
|
+
return { updated_to: result.updatedTo, error: result.error };
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
working = null;
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
// SB389: the newest blocks on this computer, summarised the way `log` prints them (never raw arguments).
|
|
414
|
+
// SB391: open this computer's page in the workspace, only on the workspace this computer is connected to.
|
|
415
|
+
"POST /open-workspace": () => {
|
|
416
|
+
const connection = loadConnection(options.dir);
|
|
417
|
+
const url = summary?.computer_url;
|
|
418
|
+
if (!connection || !url || !sameOrigin(url, connection.url))
|
|
419
|
+
return { opened: false };
|
|
420
|
+
openInBrowser(url);
|
|
421
|
+
return { opened: true };
|
|
422
|
+
},
|
|
423
|
+
// SB392: sign in again for the connected workspace; the tray shows the code, the approval page opens in the browser.
|
|
424
|
+
"POST /reconnect": async () => {
|
|
425
|
+
if (reconnecting)
|
|
426
|
+
return reconnecting;
|
|
427
|
+
working = "Signing in again…";
|
|
428
|
+
const result = await startReconnect(options.dir, (ok) => {
|
|
429
|
+
reconnecting = null;
|
|
430
|
+
working = null;
|
|
431
|
+
log(ok ? "signed in again" : "the sign-in did not finish");
|
|
432
|
+
if (ok)
|
|
433
|
+
void cycle();
|
|
434
|
+
});
|
|
435
|
+
if ("error" in result) {
|
|
436
|
+
working = null;
|
|
437
|
+
return { error: result.error };
|
|
438
|
+
}
|
|
439
|
+
reconnecting = result.started;
|
|
440
|
+
log(`signing in again: waiting for approval of code ${result.started.user_code}`);
|
|
441
|
+
return result.started;
|
|
442
|
+
},
|
|
443
|
+
"GET /recent-blocks": () => ({ recent_blocks: actionable(localActivity(options.dir, { limit: 10 })?.recent_blocks ?? [], Date.now()) }),
|
|
444
|
+
// D144: allow an earlier block, or ask an admin, from the tray. The caller names the block; only the Scopebond window
|
|
445
|
+
// answers (the same window the hook uses), so whoever calls this channel cannot allow anything by itself. Scopebond never
|
|
446
|
+
// runs the action: the person or the coding agent runs it again.
|
|
447
|
+
"POST /blocked": async (body) => {
|
|
448
|
+
const id = typeof body?.action_id === "string" ? String(body.action_id) : "";
|
|
449
|
+
const q = id ? blockedQuestion(options.dir, id) : null;
|
|
450
|
+
if (!q)
|
|
451
|
+
return { outcome: "gone", text: "This block can no longer be allowed or sent to an admin from here." };
|
|
452
|
+
log(`asking about an earlier block under "${q.title}"`);
|
|
453
|
+
const answer = await prompt({ action_id: id, rule: q.item.rule, title: q.title, summary: q.item.summary, reason_min: q.reason_min,
|
|
454
|
+
lasts: "the next time it runs", timeout_ms: 55_000, mode: q.mode, offers: q.offers });
|
|
455
|
+
if (answer.decision === "unavailable")
|
|
456
|
+
return { outcome: "unavailable", text: "The Scopebond window could not be shown." };
|
|
457
|
+
const done = actOnBlocked(options.dir, id, answer);
|
|
458
|
+
if (done.outcome === "allowed" || done.outcome === "proposed" || done.outcome === "asked")
|
|
459
|
+
setTimeout(() => { void sendReasons(); }, 1_000);
|
|
460
|
+
log(`earlier block under "${q.title}": ${done.outcome}`);
|
|
461
|
+
return { outcome: done.outcome, text: blockedText(done.outcome) };
|
|
462
|
+
},
|
|
202
463
|
"GET /status": () => ({ ...computerStatus(options.dir), health: health(), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last, last_maintenance: lastMaintenance } }),
|
|
203
464
|
"POST /flush": async () => ({ cycle: await cycle() }),
|
|
204
465
|
"POST /repair": () => {
|
|
@@ -224,8 +485,11 @@ export async function startService(options) {
|
|
|
224
485
|
log(`override ${answer.decision === "allow" ? "given" : answer.decision === "deny" ? "not given" : "not available"} for "${question.title}"`);
|
|
225
486
|
if (answer.decision === "allow" && answer.reason) {
|
|
226
487
|
queueReason(options.dir, question.action_id, answer.reason);
|
|
227
|
-
void sendReasons();
|
|
488
|
+
setTimeout(() => { void sendReasons(); }, 1_000);
|
|
228
489
|
}
|
|
490
|
+
// The hook writes the request or allowance after this answer; send it shortly after.
|
|
491
|
+
if (answer.decision === "ask" || answer.lasts === "always" || answer.lasts === "15m")
|
|
492
|
+
setTimeout(() => { void sendReasons(); }, 2_000);
|
|
229
493
|
return answer;
|
|
230
494
|
},
|
|
231
495
|
});
|
|
@@ -257,4 +521,14 @@ export async function startService(options) {
|
|
|
257
521
|
}
|
|
258
522
|
return { port: control.endpoint.port, cycleNow: cycle, maintainNow: () => maintain(true), stop };
|
|
259
523
|
}
|
|
524
|
+
/** What the tray says after a person acted on an earlier block. */
|
|
525
|
+
export function blockedText(outcome) {
|
|
526
|
+
switch (outcome) {
|
|
527
|
+
case "allowed": return "Allowed. Run it again, or let the coding agent retry.";
|
|
528
|
+
case "proposed": return "Allowed for 15 minutes. Your workspace's admins decide whether it stands; run it again meanwhile.";
|
|
529
|
+
case "asked": return "Asked your workspace's admins. The Scopebond icon shows their answer; then run it again.";
|
|
530
|
+
case "declined": return "Nothing was allowed.";
|
|
531
|
+
case "gone": return "This block can no longer be allowed or sent to an admin from here.";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
260
534
|
//# sourceMappingURL=service.js.map
|
package/dist/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,+FAA+F;AAC/F,8FAA8F;AAC9F,qEAAqE;AAErE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7G,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,iBAAiB,CAAC;AACjH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAoB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1I,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,YAAY,EAAE,EAAE,CAAC;AACvD,MAAM,WAAW,GAAG,MAAM,CAAC;AAC3B,MAAM,cAAc,GAAG,EAAE,GAAG,MAAM,CAAC;AACnC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC3C,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAgCzD;sFACsF;AACtF,MAAM,UAAU,iBAAiB,CAAC,YAAuB,iBAAiB,EAAE;IAC1E,MAAM,QAAQ,GAA8C,EAAE,CAAC;IAC/D,KAAK,MAAM,OAAO,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,kBAAkB,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;QACnG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;uFACuF;AACvF,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC;QAC1C,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAU,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAU;QAChH,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAU,CAAC;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEvC,4GAA4G;AAC5G,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C;;;;;mEAKmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACnC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;IACrC,IAAI,CAAC;QAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IACpG,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,IAAI,CAAC;QAAC,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAAC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;IACrH,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC3D,MAAM,KAAK,GAAG,GAAG,GAAG,KAAK,GAAG,sBAAsB,CAAC;IACnD,IAAI,MAAM,KAAK,OAAO,CAAC,GAAG,IAAI,KAAK,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,iDAAiD;QAC7G,IAAI,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,4CAA4C,CAAC,CAAC;IACtG,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QAAC,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;AACpJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAuB;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IAClG,IAAI,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAChJ,qGAAqG;IACrG,mGAAmG;IACnG,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC;IACnD,IAAI,IAAI,GAAuB,IAAI,CAAC;IACpC,IAAI,eAAe,GAA6B,IAAI,CAAC;IACrD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,GAAyC,IAAI,CAAC;IACvD,IAAI,gBAAgB,GAA0C,IAAI,CAAC;IACnE,IAAI,OAAO,GAAgC,IAAI,CAAC;IAChD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;IAC9D,IAAI,UAAU,GAAuB,IAAI,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,eAAe,EAAE,SAAS,IAAI,IAAI,CAAC,CAAC;IAC/F,MAAM,YAAY,GAAG,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtG,MAAM,WAAW,GAAG,KAAK,IAAI,EAAE;QAC7B,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,IAAI,CAAC;YAAC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YAAC,IAAI,IAAI;gBAAE,GAAG,CAAC,QAAQ,IAAI,sCAAsC,CAAC,CAAC;QAAC,CAAC;QACvJ,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;IAC9C,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,KAAK,IAA0B,EAAE;QAC7C,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC5B,OAAO,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC3F,MAAM,WAAW,EAAE,CAAC;YACpB,IAAI,CAAC;gBAAC,YAAY,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;YAC7D,IAAI,GAAG,MAAM,CAAC;YACd,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,MAAM,CAAC,SAAS;gBAAE,GAAG,CAAC,aAAa,MAAM,CAAC,SAAS,eAAe,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;YAChG,IAAI,MAAM,CAAC,aAAa;gBAAE,GAAG,CAAC,qBAAqB,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;YAC3E,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,EAAE,cAAc,GAAG,KAAK,EAA8B,EAAE;QAC5E,MAAM,MAAM,GAAsB,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACxI,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3F,IAAI,MAAM;gBAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC1C,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;YAC/B,uHAAuH;YACvH,uHAAuH;YACvH,qDAAqD;YACrD,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,KAAK,aAAa,IAAI,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACpJ,MAAM,CAAC,WAAW,GAAG,mBAAmB,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;YAC3F,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,WAAW;gBAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACjF,IAAI,MAAM,EAAE,MAAM,KAAK,aAAa,IAAI,MAAM,CAAC,KAAK,IAAI,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnG,GAAG,CAAC,gCAAgC,OAAO,OAAO,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAClE,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnD,IAAI,SAAS,CAAC,EAAE,EAAE,CAAC;oBACjB,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;oBAChC,eAAe,GAAG,MAAM,CAAC;oBACzB,IAAI,OAAO,CAAC,SAAS;wBAAE,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;yBAClD,CAAC;wBACJ,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;wBAC9B,+GAA+G;wBAC/G,gGAAgG;wBAChG,UAAU,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;oBAC3H,CAAC;oBACD,OAAO,MAAM,CAAC;gBAChB,CAAC;gBACD,MAAM,CAAC,KAAK,GAAG,aAAa,MAAM,CAAC,KAAK,YAAY,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnF,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YACD,IAAI,UAAU,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,IAAI,mBAAmB,CAAC,EAAE,CAAC;gBAC1F,MAAM,CAAC,SAAS,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;gBACrH,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC7B,GAAG,CAAC,cAAc,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,EAAE,CAAC,CAAC;YACzH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAAC,MAAM,CAAC,KAAK,GAAI,KAAe,CAAC,OAAO,CAAC;QAAC,CAAC;QAC5D,eAAe,GAAG,MAAM,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,IAAI,OAAO;YAAE,OAAO;QACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpG,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,KAAK,KAAK,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACvE,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,aAAa,SAAS,WAAW,EAAE,EAAE,EAAE;QACxF,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,EAAE,CAAC;QACrL,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,EAAE,EAAE,CAAC;QACrD,cAAc,EAAE,GAAG,EAAE;YACnB,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;YACrC,KAAK,MAAM,CAAC,IAAI,QAAQ;gBAAE,GAAG,CAAC,wCAAwC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAChF,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,CAAC;QACD,gBAAgB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,iGAAiG;QACjG,YAAY,EAAE,GAAG,EAAE;YACjB,GAAG,CAAC,4CAA4C,CAAC,CAAC;YAClD,UAAU,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,qGAAqG;QACrG,gBAAgB,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC/B,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,QAAQ;gBAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;YAClD,GAAG,CAAC,4BAA4B,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACtC,GAAG,CAAC,YAAY,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,SAAS,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC;YAC9I,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBAAC,KAAK,WAAW,EAAE,CAAC;YAAC,CAAC;YACtI,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClH,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,CAAC;YAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;QAClD,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,gBAAgB;YAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,OAAO,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC;IACF,GAAG,CAAC,mBAAmB,YAAY,EAAE,gBAAgB,OAAO,CAAC,GAAG,0BAA0B,OAAO,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;IACpH,MAAM,KAAK,EAAE,CAAC;IACd,QAAQ,EAAE,CAAC;IACX,IAAI,OAAO,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;QAClC,gFAAgF;QAChF,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO;YAAE,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3E,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO;YAAE,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;QAC1F,gBAAgB,CAAC,KAAK,EAAE,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;AACnG,CAAC","sourcesContent":["// The resident loop: one cycle now, then one every interval, sooner after a change is asked for,\n// backing off while the workspace is unreachable. Exactly one agent serves a Scopebond home: a\n// second one finds the first answering and exits. Every six hours it asks the workspace which\n// versions to run, and once a day it runs the end-to-end self-check.\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { hookVersion, loadConnection, userHarnessFile, writeHarnessConfig, type Harness } from \"@scopebond/hook\";\nimport { computerStatus, expectedHarnesses, missingHookEntries, runCycle, type CycleResult } from \"./agent.js\";\nimport { launcherPath } from \"./autostart.js\";\nimport { callAgent, startControl } from \"./ipc.js\";\nimport { runSelfCheck } from \"./selfcheck.js\";\nimport { flushReasons, queueReason } from \"./override-reasons.js\";\nimport { parseQuestion, serialized, systemPrompter, type Prompter } from \"./prompt.js\";\nimport { healthOf, type HealthLevel } from \"./health.js\";\nimport { notifyChange, startTray } from \"./tray.js\";\nimport { agentVersion, compareVersions, fetchClientVersion, installAgent, maintainHookEntries, maintainedHookCommand } from \"./update.js\";\n\nexport const AGENT_VERSION = `agent/${agentVersion()}`;\nconst INTERVAL_MS = 60_000;\nconst MAX_BACKOFF_MS = 15 * 60_000;\nconst UPDATE_EVERY_MS = 6 * 60 * 60 * 1000;\nconst SELF_CHECK_EVERY_MS = 24 * 60 * 60 * 1000;\nexport const AFTER_PID_ENV = \"SCOPEBOND_AGENT_AFTER_PID\";\n\nexport interface ServiceOptions {\n dir: string; intervalMs?: number; fetchImpl?: typeof fetch; log?: (line: string) => void;\n /** Off in tests: the update check and self-check run only when asked. */\n maintenance?: boolean;\n /** Called after a successful update instead of restarting (tests). */\n onUpdated?: (version: string) => void;\n /** Shows the Scopebond window for an override (tests replace it). */\n prompter?: Prompter;\n /** The Windows tray icon (default on Windows; SCOPEBOND_AGENT_TRAY=off turns it off). */\n tray?: boolean;\n /** Called once the agent has stopped because `stop` (or `autostart off`) asked it to; the CLI exits. */\n onStopped?: () => void;\n}\n\nexport interface Service {\n stop(): Promise<void>;\n cycleNow(): Promise<CycleResult>;\n maintainNow(): Promise<MaintenanceResult>;\n port: number;\n}\n\nexport interface MaintenanceResult {\n at: number;\n policy: \"recommended\" | \"hold\" | \"unknown\";\n updatedTo: string | null;\n hookEntries: Array<{ harness: Harness; file: string; reason: string }>;\n selfCheck: Awaited<ReturnType<typeof runSelfCheck>>;\n error: string | null;\n}\n\n/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is\n * logged and returned, and only settings of agents this computer has are touched. */\nexport function repairHookEntries(harnesses: Harness[] = expectedHarnesses()): Array<{ harness: Harness; file: string }> {\n const repaired: Array<{ harness: Harness; file: string }> = [];\n for (const harness of missingHookEntries(harnesses)) {\n const file = writeHarnessConfig(userHarnessFile(harness), harness, maintainedHookCommand(harness));\n repaired.push({ harness, file });\n }\n return repaired;\n}\n\n/** Start a fresh agent (the updated one) that waits for this process to exit, through the\n * autostart launcher when there is one so it finds Node the same way sign-in does. */\nexport function spawnReplacement(dir: string): void {\n const launcher = launcherPath(dir);\n const cli = realpathSync(fileURLToPath(new URL(\"./cli.js\", import.meta.url)));\n const env = { ...process.env, [AFTER_PID_ENV]: String(process.pid) };\n const [command, args] = existsSync(launcher)\n ? process.platform === \"win32\" ? [\"cmd.exe\", [\"/d\", \"/c\", launcher]] as const : [\"/bin/sh\", [launcher]] as const\n : [process.execPath, [cli, \"run\"]] as const;\n const child = spawn(command, [...args], { env, detached: true, stdio: \"ignore\", windowsHide: true });\n child.unref();\n}\n\nexport const AGENT_LOCK = \"agent.lock\";\n\n/** How long a lock whose agent never answered on its local channel still counts as an agent starting up. */\nexport const AGENT_LOCK_STARTING_MS = 60_000;\n\n/** Take the one-agent-per-home lock: created exclusively, or taken over when the agent named in it is\n * gone. The caller has already found no agent answering on the local channel, so a lock older than\n * AGENT_LOCK_STARTING_MS is left over even when its process id is alive: Windows reuses process ids\n * soon after a restart, and a sign-in after a reboot must not find \"already running\". A newer lock\n * is an agent still starting (or one whose id is not written yet): this start gives way.\n * Returns the lock file, or null while another agent holds it. */\nexport function acquireAgentLock(dir: string, now = Date.now()): string | null {\n const file = join(dir, AGENT_LOCK);\n mkdirSync(dir, { recursive: true });\n const mine = `${process.pid} ${now}`;\n try { writeFileSync(file, mine, { flag: \"wx\" }); return file; } catch { /* held, or left behind */ }\n let text = \"\";\n let written = now;\n try { text = readFileSync(file, \"utf8\").trim(); written = statSync(file).mtimeMs; } catch { /* removed meanwhile */ }\n const [pidText, atText] = text.split(/\\s+/);\n const holder = Number(pidText);\n const at = Number(atText);\n const since = Number.isFinite(at) && at > 0 ? at : written;\n const fresh = now - since < AGENT_LOCK_STARTING_MS;\n if (holder !== process.pid && fresh) {\n if (!(Number.isInteger(holder) && holder > 0)) return null; // being written by an agent starting this moment\n try { process.kill(holder, 0); return null; } catch { /* that agent is gone: take the lock over */ }\n }\n writeFileSync(file, mine);\n return file;\n}\n\nexport function releaseAgentLock(file: string): void {\n try { if (readFileSync(file, \"utf8\").trim().split(/\\s+/)[0] === String(process.pid)) rmSync(file, { force: true }); } catch { /* already gone */ }\n}\n\nexport async function startService(options: ServiceOptions): Promise<Service> {\n const log = options.log ?? ((line: string) => console.log(`${new Date().toISOString()} ${line}`));\n if (await callAgent(options.dir, \"GET\", \"/status\", undefined, 2_000)) throw new Error(\"a Scopebond Agent is already running for this computer\");\n // Two agents started within moments of each other (a slow first start, then a second start) both see\n // nobody answering yet: the lock file decides, so the second exits instead of orphaning the first.\n const lock = acquireAgentLock(options.dir);\n if (!lock) throw new Error(\"a Scopebond Agent is already running for this computer\");\n const interval = options.intervalMs ?? INTERVAL_MS;\n let last: CycleResult | null = null;\n let lastMaintenance: MaintenanceResult | null = null;\n let failures = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let maintenanceTimer: ReturnType<typeof setInterval> | null = null;\n let running: Promise<CycleResult> | null = null;\n let stopped = false;\n let lastSelfCheckAt = 0;\n\n const prompt = serialized(options.prompter ?? systemPrompter);\n let shownLevel: HealthLevel | null = null;\n const health = () => healthOf(computerStatus(options.dir), lastMaintenance?.selfCheck ?? null);\n const noticeHealth = () => { const h = health(); notifyChange(shownLevel, h); shownLevel = h.level; };\n const sendReasons = async () => {\n const connection = loadConnection(options.dir);\n if (!connection) return;\n try { const sent = await flushReasons(options.dir, connection, options.fetchImpl); if (sent) log(`sent ${sent} override reason(s) to the workspace`); }\n catch { /* they wait for the next cycle */ }\n };\n const cycle = async (): Promise<CycleResult> => {\n if (running) return running;\n running = runCycle({ dir: options.dir, fetchImpl: options.fetchImpl }).then(async (result) => {\n await sendReasons();\n try { noticeHealth(); } catch { /* status is best effort */ }\n last = result;\n failures = result.deliveryError ? failures + 1 : 0;\n if (result.delivered) log(`delivered ${result.delivered} record(s); ${result.pending} waiting`);\n if (result.deliveryError) log(`delivery problem: ${result.deliveryError}`);\n return result;\n }).finally(() => { running = null; });\n return running;\n };\n\n const maintain = async (forceSelfCheck = false): Promise<MaintenanceResult> => {\n const result: MaintenanceResult = { at: Date.now(), policy: \"unknown\", updatedTo: null, hookEntries: [], selfCheck: null, error: null };\n try {\n const connection = loadConnection(options.dir);\n const harnesses = expectedHarnesses();\n const target = connection ? await fetchClientVersion(connection, options.fetchImpl) : null;\n if (target) result.policy = target.policy;\n const current = agentVersion();\n // Hook entries first: under \"recommended\" they move to the newest hook named or carried; under \"hold\" only broken ones\n // are repaired, at the version they already name or the one the agent carries. Done before a self-update, so an update\n // whose handover fails never leaves the hook behind.\n const hookTarget = target?.policy === \"recommended\" && target.hook && compareVersions(target.hook, hookVersion()) > 0 ? target.hook : hookVersion();\n result.hookEntries = maintainHookEntries(harnesses, hookTarget, target?.policy !== \"hold\");\n for (const change of result.hookEntries) log(`${change.reason}: ${change.file}`);\n if (target?.policy === \"recommended\" && target.agent && compareVersions(target.agent, current) > 0) {\n log(`updating the Scopebond Agent ${current} -> ${target.agent}`);\n const installed = await installAgent(target.agent);\n if (installed.ok) {\n result.updatedTo = target.agent;\n lastMaintenance = result;\n if (options.onUpdated) options.onUpdated(target.agent);\n else {\n spawnReplacement(options.dir);\n // Hand over: stop, then exit. A stop that never finishes (a tray or window child that will not close) must not\n // keep the old agent alive with the replacement waiting on it, so the exit has a hard deadline.\n setTimeout(() => { setTimeout(() => process.exit(0), 5_000).unref(); void stop().finally(() => process.exit(0)); }, 500);\n }\n return result;\n }\n result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;\n log(result.error);\n }\n if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {\n result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });\n lastSelfCheckAt = Date.now();\n log(`self-check ${result.selfCheck?.ok ? \"passed\" : `failed: ${result.selfCheck?.failed.join(\", \") ?? \"no result\"}`}`);\n }\n } catch (error) { result.error = (error as Error).message; }\n lastMaintenance = result;\n return result;\n };\n\n const schedule = () => {\n if (stopped) return;\n const delay = failures ? Math.min(MAX_BACKOFF_MS, interval * 2 ** Math.min(failures, 6)) : interval;\n timer = setTimeout(() => { void cycle().finally(schedule); }, delay);\n };\n\n const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {\n \"GET /status\": () => ({ ...computerStatus(options.dir), health: health(), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last, last_maintenance: lastMaintenance } }),\n \"POST /flush\": async () => ({ cycle: await cycle() }),\n \"POST /repair\": () => {\n const repaired = repairHookEntries();\n for (const r of repaired) log(`repaired the Scopebond hook entry in ${r.file}`);\n return { repaired };\n },\n \"POST /maintain\": async () => ({ maintenance: await maintain(true) }),\n // `scopebond-agent stop` and `autostart off`: answer first, then stop, so the caller hears back.\n \"POST /stop\": () => {\n log(\"stopping: asked to by this computer's user\");\n setTimeout(() => { void stop().finally(() => options.onStopped?.()); }, 50);\n return { stopping: true };\n },\n // Warn mode: the hook asks; only the window answers. The caller learns the answer, never decides it.\n \"POST /override\": async (body) => {\n const question = parseQuestion(body);\n if (!question) return { decision: \"unavailable\" };\n log(`asking whether to allow \"${question.title}\"`);\n const answer = await prompt(question);\n log(`override ${answer.decision === \"allow\" ? \"given\" : answer.decision === \"deny\" ? \"not given\" : \"not available\"} for \"${question.title}\"`);\n if (answer.decision === \"allow\" && answer.reason) { queueReason(options.dir, question.action_id, answer.reason); void sendReasons(); }\n return answer;\n },\n });\n const tray = (options.tray ?? true) && process.env.SCOPEBOND_AGENT_TRAY !== \"off\" ? startTray(options.dir) : null;\n const stop = async () => {\n stopped = true;\n try { tray?.kill(); } catch { /* already gone */ }\n if (timer) clearTimeout(timer);\n if (maintenanceTimer) clearInterval(maintenanceTimer);\n await running?.catch(() => undefined);\n await control.close();\n releaseAgentLock(lock);\n };\n log(`Scopebond Agent ${agentVersion()} running for ${options.dir} (control on 127.0.0.1:${control.endpoint.port})`);\n await cycle();\n schedule();\n if (options.maintenance !== false) {\n // Shortly after start (so a fresh sign-in is not slowed), then every six hours.\n setTimeout(() => { if (!stopped) void maintain(); }, 2 * 60_000).unref?.();\n maintenanceTimer = setInterval(() => { if (!stopped) void maintain(); }, UPDATE_EVERY_MS);\n maintenanceTimer.unref?.();\n }\n return { port: control.endpoint.port, cycleNow: cycle, maintainNow: () => maintain(true), stop };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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","sourcesContent":["// The resident loop: one cycle now, then one every interval, sooner after a change is asked for,\n// backing off while the workspace is unreachable. Exactly one agent serves a Scopebond home: a\n// second one finds the first answering and exits. Every six hours it asks the workspace which\n// versions to run, and once a day it runs the end-to-end self-check.\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { actOnBlocked, blockedQuestion, hookVersion, isSingleExecutable, isManaged, loadConnection, localActivity, readBlocked, readMeta, ruleReport, runStoreUpkeep, userHarnessFile, writeHarnessConfig, type Harness } from \"@scopebond/hook\";\nimport { computerStatus, expectedHarnesses, missingHookEntries, runCycle, type CycleResult } from \"./agent.js\";\nimport { agentCliPath } from \"./self.js\";\nimport { fetchVerifiedInstaller, installAfterExit, installKind } from \"./native-update.js\";\nimport { AGENT_LOG_ENV, launcherIsCurrent, launcherPath, refreshLauncher, startCommands } from \"./autostart.js\";\nimport { callAgent, startControl } from \"./ipc.js\";\nimport { runSelfCheck } from \"./selfcheck.js\";\nimport { flushReasons, queueReason } from \"./override-reasons.js\";\nimport { parseQuestion, serialized, systemPrompter, type Prompter } from \"./prompt.js\";\nimport { healthOf, type HealthLevel } from \"./health.js\";\nimport { notifyChange, startTray } from \"./tray.js\";\nimport { sendAllowancesAndRequests } from \"./allowance-sender.js\";\nimport { checkResult, trayModel, type RecentBlock, type TrayModel } from \"./tray-model.js\";\nimport { fetchComputerSummary, openInBrowser, sameOrigin, type ComputerSummary } from \"./summary.js\";\nimport { startReconnect, type ReconnectStart } from \"./reconnect.js\";\nimport { agentVersion, compareVersions, fetchClientVersion, installAgent, maintainHookEntries, maintainedHookCommand } from \"./update.js\";\n\nexport const AGENT_VERSION = `agent/${agentVersion()}`;\nconst INTERVAL_MS = 60_000;\nconst MAX_BACKOFF_MS = 15 * 60_000;\nconst UPDATE_EVERY_MS = 6 * 60 * 60 * 1000;\nconst SELF_CHECK_EVERY_MS = 24 * 60 * 60 * 1000;\nconst SUMMARY_EVERY_MS = 5 * 60 * 1000;\nexport const AFTER_PID_ENV = \"SCOPEBOND_AGENT_AFTER_PID\";\n/** The person's tray settings (D143: notifications about problems by default, none about blocks). */\nexport const TRAY_SETTINGS_FILE = \"agent-settings.json\";\nexport type NotificationSetting = \"all\" | \"problems\" | \"off\";\nexport interface TraySettings { notifications: NotificationSetting }\n\nexport function readTraySettings(dir: string): TraySettings {\n try {\n const parsed = JSON.parse(readFileSync(join(dir, TRAY_SETTINGS_FILE), \"utf8\")) as Partial<TraySettings>;\n return { notifications: parsed.notifications === \"all\" || parsed.notifications === \"off\" ? parsed.notifications : \"problems\" };\n } catch { return { notifications: \"problems\" }; }\n}\n\nexport function writeTraySettings(dir: string, patch: unknown): TraySettings {\n const next = readTraySettings(dir);\n const value = (patch as { notifications?: unknown } | null)?.notifications;\n if (value === \"all\" || value === \"problems\" || value === \"off\") next.notifications = value;\n writeFileSync(join(dir, TRAY_SETTINGS_FILE), JSON.stringify(next, null, 2) + \"\\n\", { mode: 0o600 });\n return next;\n}\n\n/** Set for a replacement started directly because the launcher is an older one: it rewrites the launcher once the old agent is gone. */\nexport const REFRESH_LAUNCHER_ENV = \"SCOPEBOND_AGENT_REFRESH_LAUNCHER\";\n/** The exit code that asks a service manager to start the agent again (systemd's Restart=on-failure), used where a detached replacement would not survive. */\nexport const RESTART_EXIT_CODE = 75;\n\nexport interface ServiceOptions {\n dir: string; intervalMs?: number; fetchImpl?: typeof fetch; log?: (line: string) => void;\n /** Off in tests: the update check and self-check run only when asked. */\n maintenance?: boolean;\n /** Called after a successful update instead of restarting (tests). */\n onUpdated?: (version: string) => void;\n /** Shows the Scopebond window for an override (tests replace it). */\n prompter?: Prompter;\n /** The Windows tray icon (default on Windows; SCOPEBOND_AGENT_TRAY=off turns it off). */\n tray?: boolean;\n /** Called once the agent has stopped because `stop` (or `autostart off`) asked it to; the CLI exits. */\n onStopped?: () => void;\n}\n\nexport interface Service {\n stop(): Promise<void>;\n cycleNow(): Promise<CycleResult>;\n maintainNow(): Promise<MaintenanceResult>;\n port: number;\n}\n\nexport interface MaintenanceResult {\n at: number;\n policy: \"recommended\" | \"hold\" | \"unknown\";\n updatedTo: string | null;\n hookEntries: Array<{ harness: Harness; file: string; reason: string }>;\n selfCheck: Awaited<ReturnType<typeof runSelfCheck>>;\n /** The local store's upkeep this pass (D144): rows rewritten, receipts past retention removed, space returned. */\n store?: ReturnType<typeof runStoreUpkeep>;\n error: string | null;\n}\n\n/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is\n * logged and returned, and only settings of agents this computer has are touched. */\nexport function repairHookEntries(harnesses: Harness[] = expectedHarnesses()): Array<{ harness: Harness; file: string }> {\n const repaired: Array<{ harness: Harness; file: string }> = [];\n for (const harness of missingHookEntries(harnesses)) {\n const file = writeHarnessConfig(userHarnessFile(harness), harness, maintainedHookCommand(harness));\n repaired.push({ harness, file });\n }\n return repaired;\n}\n\n/** How the updated agent takes over from this one. */\nexport type Handover =\n | { kind: \"spawn\"; command: string; args: string[]; env: Record<string, string | undefined>; verbatim: boolean }\n | { kind: \"service-restart\" };\n\n/** Decide how to start the updated agent. Through the autostart launcher when there is one, so it finds Node the same way\n * sign-in does and is restarted after a crash. On Windows an older launcher cannot do it: it redirects the agent's output\n * into agent.log, this agent's children inherit that handle, and the new launcher's own redirect then fails, which cmd\n * takes for a clean exit. So under an older launcher the updated agent starts directly and rewrites the launcher once\n * this one is gone. Under systemd a detached child is stopped with the service, so the service manager restarts it. */\nexport function handoverPlan(o: {\n dir: string; cli: string; pid: number; execPath: string; platform: NodeJS.Platform;\n /** The single executable: the program is the agent itself, with no cli.js and no Node options. */\n singleExecutable?: boolean;\n env: Record<string, string | undefined>; launcherText: string | null;\n}): Handover {\n if (o.platform === \"linux\" && o.env.INVOCATION_ID) return { kind: \"service-restart\" };\n const launcher = launcherPath(o.dir, o.platform);\n const env = { ...o.env, [AFTER_PID_ENV]: String(o.pid), [AGENT_LOG_ENV]: join(o.dir, \"agent.log\") };\n if (o.launcherText !== null && launcherIsCurrent(o.launcherText, o.platform)) {\n if (o.platform === \"win32\") {\n const [command, args] = startCommands(launcher, o.platform)[1];\n return { kind: \"spawn\", command, args, env, verbatim: true };\n }\n return { kind: \"spawn\", command: \"/bin/sh\", args: [launcher], env, verbatim: false };\n }\n return {\n kind: \"spawn\", command: o.execPath, args: o.singleExecutable ? [\"run\"] : [\"--disable-warning=ExperimentalWarning\", o.cli, \"run\"], verbatim: false,\n env: o.launcherText !== null ? { ...env, [REFRESH_LAUNCHER_ENV]: \"1\" } : env,\n };\n}\n\n/** Start the updated agent, which waits for this process to exit. Returns false when a service manager restarts it instead\n * (this process then exits with RESTART_EXIT_CODE). */\nexport function spawnReplacement(dir: string, cli = agentCliPath()): boolean {\n let launcherText: string | null = null;\n try { launcherText = readFileSync(launcherPath(dir), \"utf8\"); } catch { /* no autostart launcher */ }\n const plan = handoverPlan({ dir, cli, pid: process.pid, execPath: process.execPath, platform: process.platform, env: process.env, launcherText, singleExecutable: isSingleExecutable() });\n if (plan.kind === \"service-restart\") return false;\n const child = spawn(plan.command, plan.args, { env: plan.env, detached: true, stdio: \"ignore\", windowsHide: true, windowsVerbatimArguments: plan.verbatim });\n child.on(\"error\", () => { /* the next sign-in starts it */ });\n child.unref();\n return true;\n}\n\n/** The first thing a starting agent does: wait for the agent it replaces to exit, then, when asked, rewrite the older launcher\n * that agent ran under (by now nothing runs it). The handover variables are dropped, so a later restart waits for nothing. */\nexport async function takeOver(dir: string, cli: string, waitMs = 30_000): Promise<void> {\n const previous = Number(process.env[AFTER_PID_ENV]);\n const refresh = process.env[REFRESH_LAUNCHER_ENV] === \"1\";\n delete process.env[AFTER_PID_ENV];\n delete process.env[REFRESH_LAUNCHER_ENV];\n if (Number.isInteger(previous) && previous > 0) await waitForExit(previous, waitMs);\n if (!refresh) return;\n // The old launcher reads its last lines after its agent exits; give it a moment to finish before its file changes.\n await new Promise((r) => setTimeout(r, 2_000));\n try { if (refreshLauncher(dir, cli)) console.log(`${new Date().toISOString()} rewrote the autostart launcher for this version`); }\n catch (error) { console.log(`${new Date().toISOString()} could not rewrite the autostart launcher: ${(error as Error).message}`); }\n}\n\nexport async function waitForExit(pid: number, timeoutMs: number): Promise<void> {\n const until = Date.now() + timeoutMs;\n while (Date.now() < until) {\n try { process.kill(pid, 0); } catch { return; }\n await new Promise((r) => setTimeout(r, 250));\n }\n}\n\nexport const AGENT_LOCK = \"agent.lock\";\n\n/** How long a lock whose agent never answered on its local channel still counts as an agent starting up. */\nexport const AGENT_LOCK_STARTING_MS = 60_000;\n\n/** Take the one-agent-per-home lock: created exclusively, or taken over when the agent named in it is\n * gone. The caller has already found no agent answering on the local channel, so a lock older than\n * AGENT_LOCK_STARTING_MS is left over even when its process id is alive: Windows reuses process ids\n * soon after a restart, and a sign-in after a reboot must not find \"already running\". A newer lock\n * is an agent still starting (or one whose id is not written yet): this start gives way.\n * Returns the lock file, or null while another agent holds it. */\nexport function acquireAgentLock(dir: string, now = Date.now()): string | null {\n const file = join(dir, AGENT_LOCK);\n mkdirSync(dir, { recursive: true });\n const mine = `${process.pid} ${now}`;\n try { writeFileSync(file, mine, { flag: \"wx\" }); return file; } catch { /* held, or left behind */ }\n let text = \"\";\n let written = now;\n try { text = readFileSync(file, \"utf8\").trim(); written = statSync(file).mtimeMs; } catch { /* removed meanwhile */ }\n const [pidText, atText] = text.split(/\\s+/);\n const holder = Number(pidText);\n const at = Number(atText);\n const since = Number.isFinite(at) && at > 0 ? at : written;\n const fresh = now - since < AGENT_LOCK_STARTING_MS;\n if (holder !== process.pid && fresh) {\n if (!(Number.isInteger(holder) && holder > 0)) return null; // being written by an agent starting this moment\n try { process.kill(holder, 0); return null; } catch { /* that agent is gone: take the lock over */ }\n }\n writeFileSync(file, mine);\n return file;\n}\n\nexport function releaseAgentLock(file: string): void {\n try { if (readFileSync(file, \"utf8\").trim().split(/\\s+/)[0] === String(process.pid)) rmSync(file, { force: true }); } catch { /* already gone */ }\n}\n\nexport async function startService(options: ServiceOptions): Promise<Service> {\n const log = options.log ?? ((line: string) => console.log(`${new Date().toISOString()} ${line}`));\n if (await callAgent(options.dir, \"GET\", \"/status\", undefined, 2_000)) throw new Error(\"a Scopebond Agent is already running for this computer\");\n // Two agents started within moments of each other (a slow first start, then a second start) both see\n // nobody answering yet: the lock file decides, so the second exits instead of orphaning the first.\n const lock = acquireAgentLock(options.dir);\n if (!lock) throw new Error(\"a Scopebond Agent is already running for this computer\");\n const interval = options.intervalMs ?? INTERVAL_MS;\n let last: CycleResult | null = null;\n let lastMaintenance: MaintenanceResult | null = null;\n let failures = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let maintenanceTimer: ReturnType<typeof setInterval> | null = null;\n let running: Promise<CycleResult> | null = null;\n let stopped = false;\n let lastSelfCheckAt = 0;\n // SB388: time asleep never counts as records waiting. A gap between cycles longer than the schedule allows means the\n // computer slept (or the agent was stopped); waiting is counted again from the next cycle.\n let awakeSince = Date.now();\n let lastCycleAt = 0;\n // A long step the person should see as \"working\" (an update), or null.\n let working: string | null = null;\n // SB391: the workspace's own summary for the tray (names, links, open reviews), refreshed every five minutes; null when the\n // workspace does not answer it (a self-hosted gateway), and the tray simply leaves those rows out.\n // SB392: a sign-in started from the tray, while it waits for approval.\n let reconnecting: ReconnectStart | null = null;\n let summary: ComputerSummary | null = null;\n let summaryAt = 0;\n const refreshSummary = async () => {\n summaryAt = Date.now();\n const connection = loadConnection(options.dir);\n summary = connection ? await fetchComputerSummary(connection, options.fetchImpl) : null;\n };\n\n const prompt = serialized(options.prompter ?? systemPrompter);\n let shownLevel: HealthLevel | null = null;\n const health = () => healthOf(computerStatus(options.dir), lastMaintenance?.selfCheck ?? null);\n const noticeHealth = () => { const h = health(); notifyChange(shownLevel, h); shownLevel = h.level; };\n const sendReasons = async () => {\n const connection = loadConnection(options.dir);\n if (!connection) return;\n try { const sent = await flushReasons(options.dir, connection, options.fetchImpl); if (sent) log(`sent ${sent} override reason(s) to the workspace`); }\n catch { /* they wait for the next cycle */ }\n // D144: allowances a person made here, and their requests to an admin.\n try {\n const sent = await sendAllowancesAndRequests(options.dir, connection, options.fetchImpl);\n if (sent.allowances || sent.requests) log(`sent ${sent.allowances} allowance(s) and ${sent.requests} request(s) to the workspace`);\n } catch { /* they wait for the next cycle */ }\n };\n const cycle = async (): Promise<CycleResult> => {\n if (running) return running;\n const started = Date.now();\n const allowedGap = failures ? MAX_BACKOFF_MS + 5 * 60_000 : Math.max(3 * interval, 5 * 60_000);\n if (lastCycleAt && started - lastCycleAt > allowedGap) awakeSince = started;\n lastCycleAt = started;\n running = runCycle({ dir: options.dir, fetchImpl: options.fetchImpl }).then(async (result) => {\n await sendReasons();\n try { noticeHealth(); } catch { /* status is best effort */ }\n last = result;\n failures = result.deliveryError ? failures + 1 : 0;\n if (result.delivered) log(`delivered ${result.delivered} record(s); ${result.pending} waiting`);\n if (result.deliveryError) log(`delivery problem: ${result.deliveryError}`);\n // SB390: asked from the workspace's computer page. Send again now (the cycle sent before its rules check), or run the\n // self-check; each once.\n if (result.requested === \"flush\") { log(\"the workspace asked this computer to send now\"); setTimeout(() => { void cycle(); }, 0); }\n if (result.requested === \"self_check\") { log(\"the workspace asked this computer to check now\"); setTimeout(() => { void maintain(true); }, 0); }\n if (result.connected && Date.now() - summaryAt > SUMMARY_EVERY_MS) void refreshSummary();\n return result;\n }).finally(() => { running = null; });\n return running;\n };\n\n const maintain = async (forceSelfCheck = false): Promise<MaintenanceResult> => {\n const result: MaintenanceResult = { at: Date.now(), policy: \"unknown\", updatedTo: null, hookEntries: [], selfCheck: null, error: null };\n try {\n const connection = loadConnection(options.dir);\n const harnesses = expectedHarnesses();\n const target = connection ? await fetchClientVersion(connection, options.fetchImpl) : null;\n if (target) result.policy = target.policy;\n const current = agentVersion();\n // Hook entries first: under \"recommended\" they move to the newest hook named or carried; under \"hold\" only broken ones\n // are repaired, at the version they already name or the one the agent carries. Done before a self-update, so an update\n // whose handover fails never leaves the hook behind.\n const hookTarget = target?.policy === \"recommended\" && target.hook && compareVersions(target.hook, hookVersion()) > 0 ? target.hook : hookVersion();\n result.hookEntries = maintainHookEntries(harnesses, hookTarget, target?.policy !== \"hold\");\n for (const change of result.hookEntries) log(`${change.reason}: ${change.file}`);\n const kind = installKind();\n if (target?.policy === \"recommended\" && target.agent && compareVersions(target.agent, current) > 0 && kind === \"per-machine\") {\n // Installed for every user: the organisation that deployed it updates it.\n log(`the workspace recommends the Scopebond Agent ${target.agent}; this install is updated by your organization`);\n } else if (target?.policy === \"recommended\" && target.agent && compareVersions(target.agent, current) > 0 && kind === \"per-user\") {\n log(`updating the Scopebond Agent ${current} -> ${target.agent} (signed installer)`);\n const verified = await fetchVerifiedInstaller(target.agent, { dir: options.dir, fetchImpl: options.fetchImpl });\n if (verified.ok) {\n result.updatedTo = target.agent;\n lastMaintenance = result;\n if (options.onUpdated) options.onUpdated(target.agent);\n else {\n // The installer replaces this file, so it runs after this agent has exited, and starts the agent again.\n installAfterExit(verified.path, process.pid, existsSync(launcherPath(options.dir)) ? launcherPath(options.dir) : null);\n setTimeout(() => { setTimeout(() => process.exit(0), 5_000).unref(); void stop().finally(() => process.exit(0)); }, 500);\n }\n return result;\n }\n result.error = `update to ${target.agent} could not be verified: ${verified.reason}; nothing was installed`;\n log(result.error);\n } else if (target?.policy === \"recommended\" && target.agent && compareVersions(target.agent, current) > 0) {\n log(`updating the Scopebond Agent ${current} -> ${target.agent}`);\n const installed = await installAgent(target.agent);\n if (installed.ok) {\n result.updatedTo = target.agent;\n lastMaintenance = result;\n if (options.onUpdated) options.onUpdated(target.agent);\n else {\n const code = spawnReplacement(options.dir) ? 0 : RESTART_EXIT_CODE;\n // Hand over: stop, then exit. A stop that never finishes (a tray or window child that will not close) must not\n // keep the old agent alive with the replacement waiting on it, so the exit has a hard deadline.\n setTimeout(() => { setTimeout(() => process.exit(code), 5_000).unref(); void stop().finally(() => process.exit(code)); }, 500);\n }\n return result;\n }\n result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;\n log(result.error);\n }\n // The local store: older rows rewritten, acknowledged receipts past the retention window removed, space returned.\n // The agent may rewrite the whole file once (the hook never does that during a tool call).\n try {\n result.store = runStoreUpkeep(options.dir, { budgetMs: 30_000, allowFullVacuum: true });\n const kept = result.store;\n if (kept && (kept.migrated || kept.receiptsRemoved || kept.stateRemoved || kept.pagesFreed)) {\n log(`local store: ${kept.migrated} row(s) rewritten, ${kept.receiptsRemoved} receipt(s) past retention removed, ${kept.stateRemoved} finished check record(s) removed, ${kept.pagesFreed} page(s) returned`);\n }\n } catch (error) { log(`local store upkeep failed: ${(error as Error).message}`); }\n if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {\n result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });\n lastSelfCheckAt = Date.now();\n log(`self-check ${result.selfCheck?.ok ? \"passed\" : `failed: ${result.selfCheck?.failed.join(\", \") ?? \"no result\"}`}`);\n }\n } catch (error) { result.error = (error as Error).message; }\n lastMaintenance = result;\n return result;\n };\n\n const schedule = () => {\n if (stopped) return;\n const delay = failures ? Math.min(MAX_BACKOFF_MS, interval * 2 ** Math.min(failures, 6)) : interval;\n timer = setTimeout(() => { void cycle().finally(schedule); }, delay);\n };\n\n // D144: a block under a rule a person may allow or ask about can be acted on afterwards, from the tray.\n const actionable = (blocks: Array<{ action_id: string | null; summary: string; at: string; rule: string | null }>, now: number): RecentBlock[] => {\n const known = new Map(readBlocked(options.dir).map((b) => [b.id, b]));\n return blocks.map((b) => {\n const item = b.action_id ? known.get(b.action_id) : undefined;\n return { ...b, rule: b.rule ?? item?.rule ?? null, can_act: Boolean(item && b.action_id && blockedQuestion(options.dir, b.action_id, now)), acted: item?.acted ?? null };\n });\n };\n\n const trayState = (): TrayModel => {\n const now = Date.now();\n const status = computerStatus(options.dir);\n const meta = readMeta(options.dir);\n const report = ruleReport(options.dir);\n const modes = report ? Object.values(report).map(([mode]) => mode) : [];\n const activity = localActivity(options.dir);\n return trayModel({\n status, health: health(), now, awakeSince, working,\n rules: report ? { checked_at: meta.checked_at ? Date.parse(meta.checked_at) : null, managed: isManaged(options.dir), block: modes.filter((m) => m === \"enforce\").length, monitor: modes.filter((m) => m === \"monitor\").length } : null,\n today: activity?.today ?? null,\n recentBlocks: actionable(activity?.recent_blocks ?? [], now),\n version: { agent: agentVersion(), hook: hookVersion(), policy: lastMaintenance?.policy ?? \"unknown\", recommendedAgent: meta.recommended?.agent ?? null, recommendedHook: meta.recommended?.hook ?? null },\n workspace: summary ? { name: summary.workspace_name, environment: summary.environment_name, computer_url: summary.computer_url } : null,\n openReviews: summary?.open_reviews ?? 0,\n computerName: hostname(),\n canReconnect: true,\n });\n };\n\n const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {\n // SB387: what the tray draws, and the person's tray settings.\n \"GET /tray\": () => ({ tray: trayState(), settings: readTraySettings(options.dir) }),\n \"POST /settings\": (body) => ({ settings: writeTraySettings(options.dir, body) }),\n // SB387: \"Check now\" always says what it found.\n \"POST /check\": async () => {\n const result = await maintain(true);\n return { text: checkResult(result.selfCheck, result.error), tray: trayState() };\n },\n // SB389: install the version the workspace recommends now, instead of at the next six-hourly check.\n \"POST /update\": async () => {\n working = \"Updating Scopebond…\";\n try { const result = await maintain(true); return { updated_to: result.updatedTo, error: result.error }; }\n finally { working = null; }\n },\n // SB389: the newest blocks on this computer, summarised the way `log` prints them (never raw arguments).\n // SB391: open this computer's page in the workspace, only on the workspace this computer is connected to.\n \"POST /open-workspace\": () => {\n const connection = loadConnection(options.dir);\n const url = summary?.computer_url;\n if (!connection || !url || !sameOrigin(url, connection.url)) return { opened: false };\n openInBrowser(url);\n return { opened: true };\n },\n // SB392: sign in again for the connected workspace; the tray shows the code, the approval page opens in the browser.\n \"POST /reconnect\": async () => {\n if (reconnecting) return reconnecting;\n working = \"Signing in again…\";\n const result = await startReconnect(options.dir, (ok) => {\n reconnecting = null; working = null;\n log(ok ? \"signed in again\" : \"the sign-in did not finish\");\n if (ok) void cycle();\n });\n if (\"error\" in result) { working = null; return { error: result.error }; }\n reconnecting = result.started;\n log(`signing in again: waiting for approval of code ${result.started.user_code}`);\n return result.started;\n },\n \"GET /recent-blocks\": () => ({ recent_blocks: actionable(localActivity(options.dir, { limit: 10 })?.recent_blocks ?? [], Date.now()) }),\n // D144: allow an earlier block, or ask an admin, from the tray. The caller names the block; only the Scopebond window\n // answers (the same window the hook uses), so whoever calls this channel cannot allow anything by itself. Scopebond never\n // runs the action: the person or the coding agent runs it again.\n \"POST /blocked\": async (body) => {\n const id = typeof (body as { action_id?: unknown } | null)?.action_id === \"string\" ? String((body as { action_id: string }).action_id) : \"\";\n const q = id ? blockedQuestion(options.dir, id) : null;\n if (!q) return { outcome: \"gone\", text: \"This block can no longer be allowed or sent to an admin from here.\" };\n log(`asking about an earlier block under \"${q.title}\"`);\n const answer = await prompt({ action_id: id, rule: q.item.rule, title: q.title, summary: q.item.summary, reason_min: q.reason_min,\n lasts: \"the next time it runs\", timeout_ms: 55_000, mode: q.mode, offers: q.offers });\n if (answer.decision === \"unavailable\") return { outcome: \"unavailable\", text: \"The Scopebond window could not be shown.\" };\n const done = actOnBlocked(options.dir, id, answer);\n if (done.outcome === \"allowed\" || done.outcome === \"proposed\" || done.outcome === \"asked\") setTimeout(() => { void sendReasons(); }, 1_000);\n log(`earlier block under \"${q.title}\": ${done.outcome}`);\n return { outcome: done.outcome, text: blockedText(done.outcome) };\n },\n \"GET /status\": () => ({ ...computerStatus(options.dir), health: health(), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last, last_maintenance: lastMaintenance } }),\n \"POST /flush\": async () => ({ cycle: await cycle() }),\n \"POST /repair\": () => {\n const repaired = repairHookEntries();\n for (const r of repaired) log(`repaired the Scopebond hook entry in ${r.file}`);\n return { repaired };\n },\n \"POST /maintain\": async () => ({ maintenance: await maintain(true) }),\n // `scopebond-agent stop` and `autostart off`: answer first, then stop, so the caller hears back.\n \"POST /stop\": () => {\n log(\"stopping: asked to by this computer's user\");\n setTimeout(() => { void stop().finally(() => options.onStopped?.()); }, 50);\n return { stopping: true };\n },\n // Warn mode: the hook asks; only the window answers. The caller learns the answer, never decides it.\n \"POST /override\": async (body) => {\n const question = parseQuestion(body);\n if (!question) return { decision: \"unavailable\" };\n log(`asking whether to allow \"${question.title}\"`);\n const answer = await prompt(question);\n log(`override ${answer.decision === \"allow\" ? \"given\" : answer.decision === \"deny\" ? \"not given\" : \"not available\"} for \"${question.title}\"`);\n if (answer.decision === \"allow\" && answer.reason) { queueReason(options.dir, question.action_id, answer.reason); setTimeout(() => { void sendReasons(); }, 1_000); }\n // The hook writes the request or allowance after this answer; send it shortly after.\n if (answer.decision === \"ask\" || answer.lasts === \"always\" || answer.lasts === \"15m\") setTimeout(() => { void sendReasons(); }, 2_000);\n return answer;\n },\n });\n const tray = (options.tray ?? true) && process.env.SCOPEBOND_AGENT_TRAY !== \"off\" ? startTray(options.dir) : null;\n const stop = async () => {\n stopped = true;\n try { tray?.kill(); } catch { /* already gone */ }\n if (timer) clearTimeout(timer);\n if (maintenanceTimer) clearInterval(maintenanceTimer);\n await running?.catch(() => undefined);\n await control.close();\n releaseAgentLock(lock);\n };\n log(`Scopebond Agent ${agentVersion()} running for ${options.dir} (control on 127.0.0.1:${control.endpoint.port})`);\n await cycle();\n schedule();\n if (options.maintenance !== false) {\n // Shortly after start (so a fresh sign-in is not slowed), then every six hours.\n setTimeout(() => { if (!stopped) void maintain(); }, 2 * 60_000).unref?.();\n maintenanceTimer = setInterval(() => { if (!stopped) void maintain(); }, UPDATE_EVERY_MS);\n maintenanceTimer.unref?.();\n }\n return { port: control.endpoint.port, cycleNow: cycle, maintainNow: () => maintain(true), stop };\n}\n\n/** What the tray says after a person acted on an earlier block. */\nexport function blockedText(outcome: \"allowed\" | \"proposed\" | \"asked\" | \"declined\" | \"gone\"): string {\n switch (outcome) {\n case \"allowed\": return \"Allowed. Run it again, or let the coding agent retry.\";\n case \"proposed\": return \"Allowed for 15 minutes. Your workspace's admins decide whether it stands; run it again meanwhile.\";\n case \"asked\": return \"Asked your workspace's admins. The Scopebond icon shows their answer; then run it again.\";\n case \"declined\": return \"Nothing was allowed.\";\n case \"gone\": return \"This block can no longer be allowed or sent to an admin from here.\";\n }\n}\n"]}
|
package/dist/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAWA,OAAO,EAAuE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAMpH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,WAAW,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kFAAkF;IAClF,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+CAA+C;IAC/C,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;6DAC6D;AAC7D,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,SAAS,EAAE,CAO1G;AAED,wBAAgB,aAAa,CAAC,OAAO,SAAwB,GAAG,OAAO,CAGtE;AAED,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAEjG;AAED,6FAA6F;AAC7F,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAA6C,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,OAAO,CAQ/I;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAwBD,MAAM,WAAW,YAAY;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAE9H,wBAAsB,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAwE/D"}
|