@scopebond/agent 0.3.0 → 0.3.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/dist/selfcheck.d.ts.map +1 -1
- package/dist/selfcheck.js +6 -0
- package/dist/selfcheck.js.map +1 -1
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/selfcheck.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selfcheck.d.ts","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,cAAc,EAClC,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AAE3E,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,GAAG,SAAa,GAAG,aAAa,EAAE,CAgB5H;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EACnF,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3E,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"selfcheck.d.ts","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,cAAc,EAClC,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AAE3E,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,GAAG,SAAa,GAAG,aAAa,EAAE,CAgB5H;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EACnF,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3E,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,CAuBzG"}
|
package/dist/selfcheck.js
CHANGED
|
@@ -43,6 +43,12 @@ export async function runSelfCheck(dir, connection, harnesses, agentVersion, opt
|
|
|
43
43
|
headers: { authorization: `Bearer ${connection.credential}`, "content-type": "application/json" },
|
|
44
44
|
body: JSON.stringify({ signature, day, checks, hook_version: hookVersion(), agent_version: agentVersion }),
|
|
45
45
|
});
|
|
46
|
+
// A workspace that does not offer the self-check yet answers 404: the computer's own checks still count; the signed
|
|
47
|
+
// workspace part is simply not available there.
|
|
48
|
+
if (res.status === 404) {
|
|
49
|
+
const failed = checks.filter((c) => !c.ok).map((c) => c.id);
|
|
50
|
+
return { ok: failed.length === 0, signature_verified: false, failed, checks };
|
|
51
|
+
}
|
|
46
52
|
if (!res.ok)
|
|
47
53
|
return { ok: false, signature_verified: false, failed: [`workspace_http_${res.status}`], checks };
|
|
48
54
|
const body = await res.json();
|
package/dist/selfcheck.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selfcheck.js","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"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","sourcesContent":["// The daily end-to-end self-check (SB347). The agent checks this computer's side (the hook entry\n// is present and starts, autostart works, nothing is stuck, the connection is not about to lapse),\n// then signs the day with the key it enrolled with and sends everything to the workspace, which\n// verifies the signature. A pass means the whole path works, end to end; a failure names what broke.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { canonical } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport {\n configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, queueStatus, readDeliveryState, userHarnessFile,\n type Harness, type HookConnection,\n} from \"@scopebond/hook\";\nimport { autostartHealth } from \"./autostart.js\";\n\nexport interface SelfCheckItem { id: string; ok: boolean; detail?: string }\n\nexport function selfCheckProof(credentialId: string, gatewayId: string, day: string): string {\n return canonical({ type: \"scopebond:self-check\", version: 1, credential_id: credentialId, gateway_id: gatewayId, day });\n}\n\nexport function localChecks(dir: string, connection: HookConnection, harnesses: Harness[], now = Date.now()): SelfCheckItem[] {\n const checks: SelfCheckItem[] = [];\n const entries = harnesses.flatMap((h) => configuredHookCommands(userHarnessFile(h)).filter((c) => isScopebondHookCommand(c)));\n checks.push({ id: \"hook_entry\", ok: entries.length > 0, ...(entries.length ? {} : { detail: \"no agent setting holds the Scopebond hook\" }) });\n const broken = entries.filter((c) => !hookCommandResolves(c));\n checks.push({ id: \"hook_starts\", ok: entries.length > 0 && broken.length === 0, ...(broken.length ? { detail: `${broken.length} hook command(s) cannot start` } : {}) });\n const autostart = autostartHealth(dir);\n checks.push({ id: \"autostart\", ok: autostart.ok, ...(autostart.ok ? {} : { detail: autostart.detail }) });\n const { pending, oldest } = queueStatus(dir);\n const state = readDeliveryState(dir);\n const stuck = pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000;\n checks.push({ id: \"queue\", ok: !stuck, ...(stuck ? { detail: `${pending} record(s) waiting since ${new Date(oldest!).toISOString()}${state.last_error ? ` (${state.last_error.slice(0, 120)})` : \"\"}` } : {}) });\n const expires = Date.parse(connection.expires_at ?? \"\");\n const lapsing = Number.isFinite(expires) && expires - now < 7 * 24 * 60 * 60 * 1000;\n checks.push({ id: \"credential\", ok: !lapsing, ...(lapsing ? { detail: `the connection expires ${new Date(expires).toISOString()} and was not renewed` } : {}) });\n return checks;\n}\n\nexport async function runSelfCheck(\n dir: string, connection: HookConnection, harnesses: Harness[], agentVersion: string,\n options: { fetchImpl?: typeof fetch; now?: number; timeoutMs?: number } = {},\n): Promise<{ ok: boolean; signature_verified: boolean; failed: string[]; checks: SelfCheckItem[] } | null> {\n const now = options.now ?? Date.now();\n const checks = localChecks(dir, connection, harnesses, now);\n const keyFile = join(dir, \"attester.key\");\n if (!existsSync(keyFile)) return { ok: false, signature_verified: false, failed: [\"signing_key\"], checks };\n const day = new Date(now).toISOString().slice(0, 10);\n const { attester } = loadOrCreateAttester({ file: keyFile });\n const signature = await attester.sign(selfCheckProof(connection.credential_id, connection.gateway_id, day));\n try {\n const res = await (options.fetchImpl ?? fetch)(new URL(\"/v1/self-check\", connection.url).toString(), {\n method: \"POST\", redirect: \"error\", signal: AbortSignal.timeout(options.timeoutMs ?? 10_000),\n headers: { authorization: `Bearer ${connection.credential}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ signature, day, checks, hook_version: hookVersion(), agent_version: agentVersion }),\n });\n if (!res.ok) return { ok: false, signature_verified: false, failed: [`workspace_http_${res.status}`], checks };\n const body = await res.json() as { ok?: boolean; signature_verified?: boolean; failed?: string[] };\n return { ok: !!body.ok, signature_verified: !!body.signature_verified, failed: Array.isArray(body.failed) ? body.failed : [], checks };\n } catch (error) {\n return { ok: false, signature_verified: false, failed: [`workspace_unreachable: ${(error as Error).message.slice(0, 80)}`], checks };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"selfcheck.js","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"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","sourcesContent":["// The daily end-to-end self-check (SB347). The agent checks this computer's side (the hook entry\n// is present and starts, autostart works, nothing is stuck, the connection is not about to lapse),\n// then signs the day with the key it enrolled with and sends everything to the workspace, which\n// verifies the signature. A pass means the whole path works, end to end; a failure names what broke.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { canonical } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport {\n configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, queueStatus, readDeliveryState, userHarnessFile,\n type Harness, type HookConnection,\n} from \"@scopebond/hook\";\nimport { autostartHealth } from \"./autostart.js\";\n\nexport interface SelfCheckItem { id: string; ok: boolean; detail?: string }\n\nexport function selfCheckProof(credentialId: string, gatewayId: string, day: string): string {\n return canonical({ type: \"scopebond:self-check\", version: 1, credential_id: credentialId, gateway_id: gatewayId, day });\n}\n\nexport function localChecks(dir: string, connection: HookConnection, harnesses: Harness[], now = Date.now()): SelfCheckItem[] {\n const checks: SelfCheckItem[] = [];\n const entries = harnesses.flatMap((h) => configuredHookCommands(userHarnessFile(h)).filter((c) => isScopebondHookCommand(c)));\n checks.push({ id: \"hook_entry\", ok: entries.length > 0, ...(entries.length ? {} : { detail: \"no agent setting holds the Scopebond hook\" }) });\n const broken = entries.filter((c) => !hookCommandResolves(c));\n checks.push({ id: \"hook_starts\", ok: entries.length > 0 && broken.length === 0, ...(broken.length ? { detail: `${broken.length} hook command(s) cannot start` } : {}) });\n const autostart = autostartHealth(dir);\n checks.push({ id: \"autostart\", ok: autostart.ok, ...(autostart.ok ? {} : { detail: autostart.detail }) });\n const { pending, oldest } = queueStatus(dir);\n const state = readDeliveryState(dir);\n const stuck = pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000;\n checks.push({ id: \"queue\", ok: !stuck, ...(stuck ? { detail: `${pending} record(s) waiting since ${new Date(oldest!).toISOString()}${state.last_error ? ` (${state.last_error.slice(0, 120)})` : \"\"}` } : {}) });\n const expires = Date.parse(connection.expires_at ?? \"\");\n const lapsing = Number.isFinite(expires) && expires - now < 7 * 24 * 60 * 60 * 1000;\n checks.push({ id: \"credential\", ok: !lapsing, ...(lapsing ? { detail: `the connection expires ${new Date(expires).toISOString()} and was not renewed` } : {}) });\n return checks;\n}\n\nexport async function runSelfCheck(\n dir: string, connection: HookConnection, harnesses: Harness[], agentVersion: string,\n options: { fetchImpl?: typeof fetch; now?: number; timeoutMs?: number } = {},\n): Promise<{ ok: boolean; signature_verified: boolean; failed: string[]; checks: SelfCheckItem[] } | null> {\n const now = options.now ?? Date.now();\n const checks = localChecks(dir, connection, harnesses, now);\n const keyFile = join(dir, \"attester.key\");\n if (!existsSync(keyFile)) return { ok: false, signature_verified: false, failed: [\"signing_key\"], checks };\n const day = new Date(now).toISOString().slice(0, 10);\n const { attester } = loadOrCreateAttester({ file: keyFile });\n const signature = await attester.sign(selfCheckProof(connection.credential_id, connection.gateway_id, day));\n try {\n const res = await (options.fetchImpl ?? fetch)(new URL(\"/v1/self-check\", connection.url).toString(), {\n method: \"POST\", redirect: \"error\", signal: AbortSignal.timeout(options.timeoutMs ?? 10_000),\n headers: { authorization: `Bearer ${connection.credential}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ signature, day, checks, hook_version: hookVersion(), agent_version: agentVersion }),\n });\n // A workspace that does not offer the self-check yet answers 404: the computer's own checks still count; the signed\n // workspace part is simply not available there.\n if (res.status === 404) { const failed = checks.filter((c) => !c.ok).map((c) => c.id); return { ok: failed.length === 0, signature_verified: false, failed, checks }; }\n if (!res.ok) return { ok: false, signature_verified: false, failed: [`workspace_http_${res.status}`], checks };\n const body = await res.json() as { ok?: boolean; signature_verified?: boolean; failed?: string[] };\n return { ok: !!body.ok, signature_verified: !!body.signature_verified, failed: Array.isArray(body.failed) ? body.failed : [], checks };\n } catch (error) {\n return { ok: false, signature_verified: false, failed: [`workspace_unreachable: ${(error as Error).message.slice(0, 80)}`], checks };\n }\n}\n"]}
|
package/dist/service.js
CHANGED
|
@@ -125,7 +125,7 @@ export async function startService(options) {
|
|
|
125
125
|
if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {
|
|
126
126
|
result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });
|
|
127
127
|
lastSelfCheckAt = Date.now();
|
|
128
|
-
log(`self-check ${result.selfCheck?.ok ? "passed" : `failed: ${result.selfCheck?.failed.join(", ")}`}`);
|
|
128
|
+
log(`self-check ${result.selfCheck?.ok ? "passed" : `failed: ${result.selfCheck?.failed.join(", ") ?? "no result"}`}`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
catch (error) {
|
package/dist/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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, realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { hookCommand, 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 } 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}\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, hookCommand(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 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 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 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 { spawnReplacement(options.dir); setTimeout(() => { void stop().finally(() => process.exit(0)); }, 500); }\n return result;\n }\n result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;\n log(result.error);\n }\n // Hook entries: under \"recommended\" they move to the newest hook named or carried; under \"hold\"\n // only broken ones are repaired, at the version they already name or the one the agent carries.\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 (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(\", \")}`}`);\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 // 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 };\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, realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { hookCommand, 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 } 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}\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, hookCommand(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 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 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 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 { spawnReplacement(options.dir); setTimeout(() => { void stop().finally(() => process.exit(0)); }, 500); }\n return result;\n }\n result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;\n log(result.error);\n }\n // Hook entries: under \"recommended\" they move to the newest hook named or carried; under \"hold\"\n // only broken ones are repaired, at the version they already name or the one the agent carries.\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 (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 // 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 };\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scopebond/agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "The Scopebond Agent — one resident process per user that delivers this computer's signed records to its Scopebond workspace, keeps its rules and connection current, and reports its health, so nobody has to run a command to keep it working.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Avouro LLC",
|