@alan-ai-hq/agent-manager 0.1.93 → 0.1.95
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/index.cjs +533 -221
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -3404,7 +3404,7 @@ var require_websocket = __commonJS({
|
|
|
3404
3404
|
var http2 = require("http");
|
|
3405
3405
|
var net = require("net");
|
|
3406
3406
|
var tls = require("tls");
|
|
3407
|
-
var { randomBytes: randomBytes13, createHash:
|
|
3407
|
+
var { randomBytes: randomBytes13, createHash: createHash19 } = require("crypto");
|
|
3408
3408
|
var { Duplex, Readable: Readable2 } = require("stream");
|
|
3409
3409
|
var { URL: URL2 } = require("url");
|
|
3410
3410
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -4061,7 +4061,7 @@ var require_websocket = __commonJS({
|
|
|
4061
4061
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
4062
4062
|
return;
|
|
4063
4063
|
}
|
|
4064
|
-
const digest =
|
|
4064
|
+
const digest = createHash19("sha1").update(key + GUID).digest("base64");
|
|
4065
4065
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
4066
4066
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
4067
4067
|
return;
|
|
@@ -4428,7 +4428,7 @@ var require_websocket_server = __commonJS({
|
|
|
4428
4428
|
var EventEmitter = require("events");
|
|
4429
4429
|
var http2 = require("http");
|
|
4430
4430
|
var { Duplex } = require("stream");
|
|
4431
|
-
var { createHash:
|
|
4431
|
+
var { createHash: createHash19 } = require("crypto");
|
|
4432
4432
|
var extension = require_extension();
|
|
4433
4433
|
var PerMessageDeflate = require_permessage_deflate();
|
|
4434
4434
|
var subprotocol = require_subprotocol();
|
|
@@ -4725,7 +4725,7 @@ var require_websocket_server = __commonJS({
|
|
|
4725
4725
|
);
|
|
4726
4726
|
}
|
|
4727
4727
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
4728
|
-
const digest =
|
|
4728
|
+
const digest = createHash19("sha1").update(key + GUID).digest("base64");
|
|
4729
4729
|
const headers = [
|
|
4730
4730
|
"HTTP/1.1 101 Switching Protocols",
|
|
4731
4731
|
"Upgrade: websocket",
|
|
@@ -15451,7 +15451,7 @@ function describeError(error61) {
|
|
|
15451
15451
|
}
|
|
15452
15452
|
|
|
15453
15453
|
// src/version.ts
|
|
15454
|
-
var AGENT_VERSION = "0.1.
|
|
15454
|
+
var AGENT_VERSION = "0.1.95";
|
|
15455
15455
|
|
|
15456
15456
|
// src/daemon-worktree.ts
|
|
15457
15457
|
var import_node_child_process3 = require("child_process");
|
|
@@ -38394,6 +38394,10 @@ var import_crypto7 = require("crypto");
|
|
|
38394
38394
|
var import_fs11 = require("fs");
|
|
38395
38395
|
var import_path11 = require("path");
|
|
38396
38396
|
var ALAN_MCP_URL_ENV = "ALAN_MCP_URL";
|
|
38397
|
+
var ALAN_RUN_AUTHORIZATION_ENV = "ALAN_RUN_AUTHORIZATION";
|
|
38398
|
+
function managedRunAuthorizationHeader(registration) {
|
|
38399
|
+
return `Bearer ${requireManagedRunReference(registration)}`;
|
|
38400
|
+
}
|
|
38397
38401
|
function deliversRunReferenceViaEnv(backendKind) {
|
|
38398
38402
|
return backendKind === "cursor_agent_cli";
|
|
38399
38403
|
}
|
|
@@ -41683,7 +41687,10 @@ function buildClaudeManagedMcpArgs(registration, options = {}) {
|
|
|
41683
41687
|
mcpServers.alan = {
|
|
41684
41688
|
type: "http",
|
|
41685
41689
|
url: registration.url,
|
|
41686
|
-
headers: {
|
|
41690
|
+
headers: {
|
|
41691
|
+
"x-alan-run-reference": `\${${ALAN_RUN_REFERENCE_ENV}}`,
|
|
41692
|
+
Authorization: `Bearer \${${ALAN_RUN_REFERENCE_ENV}}`
|
|
41693
|
+
}
|
|
41687
41694
|
};
|
|
41688
41695
|
return [
|
|
41689
41696
|
...options.strict ? ["--strict-mcp-config"] : [],
|
|
@@ -42960,6 +42967,8 @@ function buildCodexManagedMcpArgs(registration, options = {}) {
|
|
|
42960
42967
|
"-c",
|
|
42961
42968
|
`mcp_servers.alan.env_http_headers.x-alan-run-reference=${JSON.stringify(ALAN_RUN_REFERENCE_ENV)}`,
|
|
42962
42969
|
"-c",
|
|
42970
|
+
`mcp_servers.alan.env_http_headers.Authorization=${JSON.stringify(ALAN_RUN_AUTHORIZATION_ENV)}`,
|
|
42971
|
+
"-c",
|
|
42963
42972
|
"mcp_servers.alan.required=true",
|
|
42964
42973
|
"-c",
|
|
42965
42974
|
`mcp_servers.alan.startup_timeout_sec=${ALAN_MCP_STARTUP_TIMEOUT_SEC}`
|
|
@@ -43003,7 +43012,8 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
43003
43012
|
env: {
|
|
43004
43013
|
...context.env,
|
|
43005
43014
|
...externalMcp?.env,
|
|
43006
|
-
[ALAN_RUN_REFERENCE_ENV]: requireManagedRunReference(managedRegistration)
|
|
43015
|
+
[ALAN_RUN_REFERENCE_ENV]: requireManagedRunReference(managedRegistration),
|
|
43016
|
+
[ALAN_RUN_AUTHORIZATION_ENV]: managedRunAuthorizationHeader(managedRegistration)
|
|
43007
43017
|
}
|
|
43008
43018
|
} : context;
|
|
43009
43019
|
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(
|
|
@@ -49600,6 +49610,16 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
|
|
|
49600
49610
|
const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
|
|
49601
49611
|
return probe.state === "running" ? probe.status : null;
|
|
49602
49612
|
}
|
|
49613
|
+
function requestLocalDaemonShutdown(port, token) {
|
|
49614
|
+
return fetch(`http://127.0.0.1:${port}/shutdown?force=1`, {
|
|
49615
|
+
method: "POST",
|
|
49616
|
+
headers: {
|
|
49617
|
+
authorization: `Bearer ${token}`,
|
|
49618
|
+
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
49619
|
+
},
|
|
49620
|
+
signal: AbortSignal.timeout(5e3)
|
|
49621
|
+
});
|
|
49622
|
+
}
|
|
49603
49623
|
async function adoptLocalDaemonControl(input2) {
|
|
49604
49624
|
const timeoutMs = input2.timeoutMs ?? 750;
|
|
49605
49625
|
try {
|
|
@@ -76677,13 +76697,13 @@ var RuntimeRunJournal = class {
|
|
|
76677
76697
|
* Keep the exact interruption outcome until a durable authenticated control
|
|
76678
76698
|
* channel can prove both identity and ownership.
|
|
76679
76699
|
*/
|
|
76680
|
-
assessRecovery(runId,
|
|
76700
|
+
assessRecovery(runId, isProcessAlive2) {
|
|
76681
76701
|
const entry = this.entries.get(runId);
|
|
76682
76702
|
if (!entry) return void 0;
|
|
76683
76703
|
if (entry.providerPid === void 0) {
|
|
76684
76704
|
return { reattachable: false, reason: "provider_not_recorded" };
|
|
76685
76705
|
}
|
|
76686
|
-
if (!
|
|
76706
|
+
if (!isProcessAlive2(entry.providerPid)) {
|
|
76687
76707
|
return {
|
|
76688
76708
|
reattachable: false,
|
|
76689
76709
|
reason: "provider_not_running",
|
|
@@ -80430,6 +80450,17 @@ async function runLocalDaemonRecoveryLadder(input2) {
|
|
|
80430
80450
|
error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
|
|
80431
80451
|
};
|
|
80432
80452
|
}
|
|
80453
|
+
function writeRegisteredRuntimeConfig(args, previousConfig, registered) {
|
|
80454
|
+
writeConfig({
|
|
80455
|
+
...registered,
|
|
80456
|
+
localApiPort: getLocalApiPort(args, {}),
|
|
80457
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80458
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80459
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80460
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80461
|
+
daemonEpoch: previousConfig.daemonEpoch
|
|
80462
|
+
});
|
|
80463
|
+
}
|
|
80433
80464
|
async function setupDaemon(args) {
|
|
80434
80465
|
bindConfigPath(args);
|
|
80435
80466
|
const previousConfig = readConfigForRegistration();
|
|
@@ -80490,7 +80521,7 @@ async function setupDaemon(args) {
|
|
|
80490
80521
|
credentialGeneration: body2.credentialGeneration,
|
|
80491
80522
|
configPath: getConfigPath()
|
|
80492
80523
|
});
|
|
80493
|
-
return
|
|
80524
|
+
return body2.runtimeId !== liveConfig.runtimeId;
|
|
80494
80525
|
}
|
|
80495
80526
|
if (readLocalDaemonOwnerLease(getConfigPath())) {
|
|
80496
80527
|
throw new Error(
|
|
@@ -80504,23 +80535,17 @@ async function setupDaemon(args) {
|
|
|
80504
80535
|
if (setupToken && error61 instanceof RuntimeSetupGrantExpiredError) {
|
|
80505
80536
|
console.info("[alan-agent] Setup link expired; continuing with browser authorization");
|
|
80506
80537
|
await loginWithDeviceCode(args);
|
|
80507
|
-
return
|
|
80538
|
+
return false;
|
|
80508
80539
|
}
|
|
80509
80540
|
throw error61;
|
|
80510
80541
|
}
|
|
80511
|
-
|
|
80542
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80512
80543
|
apiUrl,
|
|
80513
80544
|
wsUrl,
|
|
80514
80545
|
endpointProfile,
|
|
80515
80546
|
runtimeId: body.runtimeId,
|
|
80516
80547
|
runtimeToken: body.runtimeToken,
|
|
80517
80548
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80518
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80519
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80520
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80521
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80522
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80523
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80524
80549
|
displayName: body.displayName,
|
|
80525
80550
|
installationId
|
|
80526
80551
|
});
|
|
@@ -80529,7 +80554,7 @@ async function setupDaemon(args) {
|
|
|
80529
80554
|
runtimeId: body.runtimeId,
|
|
80530
80555
|
configPath: getConfigPath()
|
|
80531
80556
|
});
|
|
80532
|
-
return
|
|
80557
|
+
return false;
|
|
80533
80558
|
}
|
|
80534
80559
|
async function loginWithDeviceCode(args) {
|
|
80535
80560
|
bindConfigPath(args);
|
|
@@ -80618,19 +80643,13 @@ async function loginWithDeviceCode(args) {
|
|
|
80618
80643
|
if (ownedPending?.operationId !== operation.id) {
|
|
80619
80644
|
throw new Error("browser authorization sidecar ownership was lost before finalization");
|
|
80620
80645
|
}
|
|
80621
|
-
|
|
80646
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80622
80647
|
apiUrl,
|
|
80623
80648
|
wsUrl,
|
|
80624
80649
|
endpointProfile,
|
|
80625
80650
|
runtimeId: body.runtime.id,
|
|
80626
80651
|
runtimeToken: body.runtimeToken,
|
|
80627
80652
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80628
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80629
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80630
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80631
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80632
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80633
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80634
80653
|
displayName: body.runtime.displayName ?? displayName,
|
|
80635
80654
|
installationId
|
|
80636
80655
|
});
|
|
@@ -80669,14 +80688,7 @@ async function stopDaemon(args) {
|
|
|
80669
80688
|
console.info("[alan-agent] No daemon listening", { port: localApiPort });
|
|
80670
80689
|
return;
|
|
80671
80690
|
}
|
|
80672
|
-
const response = await
|
|
80673
|
-
method: "POST",
|
|
80674
|
-
headers: {
|
|
80675
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80676
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80677
|
-
},
|
|
80678
|
-
signal: AbortSignal.timeout(5e3)
|
|
80679
|
-
});
|
|
80691
|
+
const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80680
80692
|
if (!response.ok) {
|
|
80681
80693
|
throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
|
|
80682
80694
|
}
|
|
@@ -80821,14 +80833,7 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
|
|
|
80821
80833
|
`Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
|
|
80822
80834
|
);
|
|
80823
80835
|
}
|
|
80824
|
-
const shutdown = await
|
|
80825
|
-
method: "POST",
|
|
80826
|
-
headers: {
|
|
80827
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80828
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80829
|
-
},
|
|
80830
|
-
signal: AbortSignal.timeout(5e3)
|
|
80831
|
-
});
|
|
80836
|
+
const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80832
80837
|
if (!shutdown.ok) {
|
|
80833
80838
|
throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
|
|
80834
80839
|
}
|
|
@@ -80989,6 +80994,9 @@ async function printDoctor(args = []) {
|
|
|
80989
80994
|
);
|
|
80990
80995
|
}
|
|
80991
80996
|
|
|
80997
|
+
// src/install-command.ts
|
|
80998
|
+
var import_promises6 = require("timers/promises");
|
|
80999
|
+
|
|
80992
81000
|
// src/mcp-config-adapters.ts
|
|
80993
81001
|
var import_node_crypto20 = require("crypto");
|
|
80994
81002
|
var import_node_fs23 = require("fs");
|
|
@@ -82373,12 +82381,14 @@ function runMcpIntegrationCommand(args, options = {}) {
|
|
|
82373
82381
|
|
|
82374
82382
|
// src/service-manager.ts
|
|
82375
82383
|
var import_node_child_process9 = require("child_process");
|
|
82384
|
+
var import_node_crypto21 = require("crypto");
|
|
82376
82385
|
var import_node_fs25 = require("fs");
|
|
82377
82386
|
var import_node_os16 = require("os");
|
|
82378
82387
|
var import_node_path27 = require("path");
|
|
82379
82388
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
82380
82389
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
82381
82390
|
var WINDOWS_TASK = "Alan Agent";
|
|
82391
|
+
var STOP_POLL_INTERVAL_MS = 200;
|
|
82382
82392
|
function xml(value2) {
|
|
82383
82393
|
return value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
82384
82394
|
}
|
|
@@ -82388,6 +82398,18 @@ function systemdArg(value2) {
|
|
|
82388
82398
|
function powershellLiteral(value2) {
|
|
82389
82399
|
return `'${value2.replaceAll("'", "''")}'`;
|
|
82390
82400
|
}
|
|
82401
|
+
function powershellCommand(script, tolerateFailure = false) {
|
|
82402
|
+
return {
|
|
82403
|
+
command: "powershell.exe",
|
|
82404
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", script],
|
|
82405
|
+
tolerateFailure
|
|
82406
|
+
};
|
|
82407
|
+
}
|
|
82408
|
+
function windowsCommandLineArgument(value2) {
|
|
82409
|
+
if (value2 && !/[\s"]/u.test(value2)) return value2;
|
|
82410
|
+
const escaped = value2.replace(/(\\*)"/gu, (_match, slashes) => `${slashes}${slashes}\\"`).replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
|
|
82411
|
+
return `"${escaped}"`;
|
|
82412
|
+
}
|
|
82391
82413
|
function serviceNames(profile) {
|
|
82392
82414
|
if (profile === "production") {
|
|
82393
82415
|
return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
|
|
@@ -82409,7 +82431,7 @@ function buildDaemonServicePlan(input2) {
|
|
|
82409
82431
|
const domain2 = `gui/${userId}`;
|
|
82410
82432
|
const serviceTarget = `${domain2}/${names.launchd}`;
|
|
82411
82433
|
const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
|
|
82412
|
-
const
|
|
82434
|
+
const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
|
|
82413
82435
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
82414
82436
|
const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
|
|
82415
82437
|
<string>${xml(value2)}</string>`).join("\n");
|
|
@@ -82438,9 +82460,9 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82438
82460
|
<key>ThrottleInterval</key>
|
|
82439
82461
|
<integer>5</integer>
|
|
82440
82462
|
<key>StandardOutPath</key>
|
|
82441
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82463
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
|
|
82442
82464
|
<key>StandardErrorPath</key>
|
|
82443
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82465
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
|
|
82444
82466
|
</dict>
|
|
82445
82467
|
</plist>
|
|
82446
82468
|
`;
|
|
@@ -82448,7 +82470,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82448
82470
|
platform: "darwin",
|
|
82449
82471
|
manifestPath,
|
|
82450
82472
|
manifest,
|
|
82451
|
-
logDirectory:
|
|
82473
|
+
logDirectory: logDir2,
|
|
82452
82474
|
loadDefinitionCommands: [
|
|
82453
82475
|
{
|
|
82454
82476
|
command: "launchctl",
|
|
@@ -82489,23 +82511,6 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82489
82511
|
tolerateFailure: true
|
|
82490
82512
|
}
|
|
82491
82513
|
],
|
|
82492
|
-
replaceCommands: [
|
|
82493
|
-
{
|
|
82494
|
-
command: "launchctl",
|
|
82495
|
-
args: ["bootout", domain2, manifestPath],
|
|
82496
|
-
tolerateFailure: true
|
|
82497
|
-
},
|
|
82498
|
-
{
|
|
82499
|
-
command: "launchctl",
|
|
82500
|
-
args: ["bootstrap", domain2, manifestPath],
|
|
82501
|
-
tolerateFailure: false
|
|
82502
|
-
},
|
|
82503
|
-
{
|
|
82504
|
-
command: "launchctl",
|
|
82505
|
-
args: ["kickstart", "-k", serviceTarget],
|
|
82506
|
-
tolerateFailure: false
|
|
82507
|
-
}
|
|
82508
|
-
],
|
|
82509
82514
|
uninstallCommands: [
|
|
82510
82515
|
{
|
|
82511
82516
|
command: "launchctl",
|
|
@@ -82522,8 +82527,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82522
82527
|
command: "launchctl",
|
|
82523
82528
|
args: ["print", serviceTarget],
|
|
82524
82529
|
tolerateFailure: true
|
|
82525
|
-
}
|
|
82526
|
-
lingerStatusCommand: null
|
|
82530
|
+
}
|
|
82527
82531
|
};
|
|
82528
82532
|
}
|
|
82529
82533
|
if (input2.platform === "linux") {
|
|
@@ -82586,18 +82590,6 @@ WantedBy=default.target
|
|
|
82586
82590
|
tolerateFailure: true
|
|
82587
82591
|
}
|
|
82588
82592
|
],
|
|
82589
|
-
replaceCommands: [
|
|
82590
|
-
{
|
|
82591
|
-
command: "systemctl",
|
|
82592
|
-
args: ["--user", "daemon-reload"],
|
|
82593
|
-
tolerateFailure: false
|
|
82594
|
-
},
|
|
82595
|
-
{
|
|
82596
|
-
command: "systemctl",
|
|
82597
|
-
args: ["--user", "restart", names.systemd],
|
|
82598
|
-
tolerateFailure: false
|
|
82599
|
-
}
|
|
82600
|
-
],
|
|
82601
82593
|
uninstallCommands: [
|
|
82602
82594
|
{
|
|
82603
82595
|
command: "systemctl",
|
|
@@ -82619,117 +82611,179 @@ WantedBy=default.target
|
|
|
82619
82611
|
command: "systemctl",
|
|
82620
82612
|
args: ["--user", "is-active", names.systemd],
|
|
82621
82613
|
tolerateFailure: true
|
|
82622
|
-
},
|
|
82623
|
-
lingerStatusCommand: {
|
|
82624
|
-
command: "loginctl",
|
|
82625
|
-
args: ["show-user", "$(id -un)", "-p", "Linger"],
|
|
82626
|
-
tolerateFailure: true
|
|
82627
82614
|
}
|
|
82628
82615
|
};
|
|
82629
82616
|
}
|
|
82630
|
-
const
|
|
82631
|
-
const
|
|
82632
|
-
const
|
|
82633
|
-
const
|
|
82634
|
-
|
|
82617
|
+
const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
|
|
82618
|
+
const legacyEnvironmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
|
|
82619
|
+
const legacyDirectArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
|
|
82620
|
+
const legacyExecutable = legacyEnvironmentPrefix ? "powershell.exe" : input2.nodeExecutable;
|
|
82621
|
+
const legacyArguments = legacyEnvironmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${legacyEnvironmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : legacyDirectArguments;
|
|
82622
|
+
const daemonArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map(windowsCommandLineArgument).join(" ");
|
|
82623
|
+
const environmentStatements = Object.entries(input2.environment ?? {}).map(
|
|
82624
|
+
([key, value2]) => `$startInfo.EnvironmentVariables[${powershellLiteral(key)}] = ${powershellLiteral(value2)}`
|
|
82635
82625
|
);
|
|
82626
|
+
const launcherScript = [
|
|
82627
|
+
"$ErrorActionPreference = 'Stop'",
|
|
82628
|
+
"$startInfo = [System.Diagnostics.ProcessStartInfo]::new()",
|
|
82629
|
+
`$startInfo.FileName = ${powershellLiteral(input2.nodeExecutable)}`,
|
|
82630
|
+
`$startInfo.Arguments = ${powershellLiteral(daemonArguments)}`,
|
|
82631
|
+
`$startInfo.WorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
|
|
82632
|
+
"$startInfo.UseShellExecute = $false",
|
|
82633
|
+
"$startInfo.CreateNoWindow = $true",
|
|
82634
|
+
"$startInfo.RedirectStandardOutput = $true",
|
|
82635
|
+
"$startInfo.RedirectStandardError = $true",
|
|
82636
|
+
...environmentStatements,
|
|
82637
|
+
`$stdout = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
|
|
82638
|
+
`$stderr = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher-error.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
|
|
82639
|
+
"$process = [System.Diagnostics.Process]::new()",
|
|
82640
|
+
"$process.StartInfo = $startInfo",
|
|
82641
|
+
"try { if (-not $process.Start()) { throw 'Alan daemon process did not start' }; $stdoutCopy = $process.StandardOutput.BaseStream.CopyToAsync($stdout); $stderrCopy = $process.StandardError.BaseStream.CopyToAsync($stderr); $process.WaitForExit(); [System.Threading.Tasks.Task]::WaitAll([System.Threading.Tasks.Task[]]@($stdoutCopy, $stderrCopy)); $exitCode = $process.ExitCode } finally { $stdout.Dispose(); $stderr.Dispose(); $process.Dispose() }",
|
|
82642
|
+
"exit $exitCode"
|
|
82643
|
+
].join("; ");
|
|
82644
|
+
const executable = powershellLiteral("powershell.exe");
|
|
82645
|
+
const launcherArguments = `-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${Buffer.from(launcherScript, "utf16le").toString("base64")}`;
|
|
82646
|
+
const settingsScript = "New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries";
|
|
82647
|
+
const ownerMarker = `Alan daemon owner v1 ${(0, import_node_crypto21.createHash)("sha256").update(
|
|
82648
|
+
JSON.stringify({
|
|
82649
|
+
executable: input2.nodeExecutable,
|
|
82650
|
+
cliEntry: input2.cliEntry,
|
|
82651
|
+
profile: input2.profile,
|
|
82652
|
+
taskBaseName: names.windows
|
|
82653
|
+
})
|
|
82654
|
+
).digest("hex")}`;
|
|
82655
|
+
const definitionMarker = `${ownerMarker}; definition v3 ${(0, import_node_crypto21.createHash)("sha256").update(
|
|
82656
|
+
JSON.stringify({
|
|
82657
|
+
executable: "powershell.exe",
|
|
82658
|
+
launcherArguments,
|
|
82659
|
+
workingDirectory: (0, import_node_path27.dirname)(input2.cliEntry),
|
|
82660
|
+
settingsScript,
|
|
82661
|
+
taskBaseName: names.windows
|
|
82662
|
+
})
|
|
82663
|
+
).digest("hex")}`;
|
|
82664
|
+
const windowsDefinitionContext = [
|
|
82665
|
+
`$expectedLauncherArguments = ${powershellLiteral(launcherArguments)}`,
|
|
82666
|
+
`$expectedWorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
|
|
82667
|
+
`$expectedOwnerMarker = ${powershellLiteral(`${ownerMarker};`)}`,
|
|
82668
|
+
`$expectedDefinitionMarker = ${powershellLiteral(definitionMarker)}`,
|
|
82669
|
+
`$expectedLegacyExecutable = ${powershellLiteral(legacyExecutable)}`,
|
|
82670
|
+
`$expectedLegacyArguments = ${powershellLiteral(legacyArguments)}`
|
|
82671
|
+
].join("; ");
|
|
82672
|
+
const taskContext = [
|
|
82673
|
+
"$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()",
|
|
82674
|
+
"$currentUser = $currentIdentity.Name",
|
|
82675
|
+
"$currentSid = $currentIdentity.User.Value",
|
|
82676
|
+
`$taskName = ${powershellLiteral(names.windows)} + ' ' + $currentSid`
|
|
82677
|
+
].join("; ");
|
|
82678
|
+
const resolveTaskSid = "function Resolve-TaskSid([string]$value) { try { return ([System.Security.Principal.NTAccount]$value).Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { try { return [System.Security.Principal.SecurityIdentifier]::new($value).Value } catch { return '' } } }";
|
|
82679
|
+
const currentLookup = "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]";
|
|
82680
|
+
const checkCurrentOwnership = [
|
|
82681
|
+
currentLookup,
|
|
82682
|
+
"$currentOwned = $false",
|
|
82683
|
+
"if ($null -ne $task) { try {",
|
|
82684
|
+
"$taskAction = @($task.Actions)[0]",
|
|
82685
|
+
"$currentOwned = @($task.Actions).Count -eq 1 -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and (([string]$task.Description).StartsWith($expectedOwnerMarker, [System.StringComparison]::Ordinal) -or (([string]$taskAction.Execute) -ieq 'powershell.exe' -and ([string]$taskAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$taskAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
|
|
82686
|
+
"} catch { $currentOwned = $false } }"
|
|
82687
|
+
].join("; ");
|
|
82688
|
+
const legacyLookup = `$legacyTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`;
|
|
82689
|
+
const checkLegacyOwnership = [
|
|
82690
|
+
legacyLookup,
|
|
82691
|
+
"$legacyOwned = $false",
|
|
82692
|
+
"$legacyState = 'Missing'",
|
|
82693
|
+
"if ($null -ne $legacyTask) { try {",
|
|
82694
|
+
"$legacyState = [string]$legacyTask.State",
|
|
82695
|
+
"$legacySid = Resolve-TaskSid ([string]$legacyTask.Principal.UserId)",
|
|
82696
|
+
"$legacyAction = @($legacyTask.Actions)[0]",
|
|
82697
|
+
"if ($null -ne $legacyAction) {",
|
|
82698
|
+
"$legacyOwned = $legacySid -eq $currentSid -and @($legacyTask.Actions).Count -eq 1 -and ((([string]$legacyAction.Execute) -ieq $expectedLegacyExecutable -and ([string]$legacyAction.Arguments) -ieq $expectedLegacyArguments -and ([string]$legacyAction.WorkingDirectory) -eq '') -or (([string]$legacyAction.Execute) -ieq 'powershell.exe' -and ([string]$legacyAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$legacyAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
|
|
82699
|
+
"}",
|
|
82700
|
+
"} catch { $legacyOwned = $false } }"
|
|
82701
|
+
].join("; ");
|
|
82702
|
+
const assertOwnedTasks = [
|
|
82703
|
+
checkCurrentOwnership,
|
|
82704
|
+
checkLegacyOwnership,
|
|
82705
|
+
`if ($null -ne $task -and -not $currentOwned) { throw ${powershellLiteral(`${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82706
|
+
`if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82707
|
+
"if ($null -ne $task -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$task.State)) { throw 'Alan daemon task became active or has unknown state' }",
|
|
82708
|
+
"if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }"
|
|
82709
|
+
].join("; ");
|
|
82710
|
+
const cleanupLegacyTask = [
|
|
82711
|
+
checkLegacyOwnership,
|
|
82712
|
+
`if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82713
|
+
"if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }",
|
|
82714
|
+
`if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -TaskPath '\\' -Confirm:$false -ErrorAction Stop }`,
|
|
82715
|
+
`$legacyRemaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`,
|
|
82716
|
+
"if ($null -ne $legacyRemaining) { throw 'Legacy Alan daemon task still exists after migration' }"
|
|
82717
|
+
].join("; ");
|
|
82636
82718
|
const registerScript = [
|
|
82637
|
-
|
|
82638
|
-
|
|
82719
|
+
taskContext,
|
|
82720
|
+
resolveTaskSid,
|
|
82721
|
+
windowsDefinitionContext,
|
|
82722
|
+
`$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
|
|
82639
82723
|
"$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
|
|
82640
82724
|
"$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
|
|
82641
|
-
|
|
82642
|
-
|
|
82725
|
+
`$settings = ${settingsScript}`,
|
|
82726
|
+
assertOwnedTasks,
|
|
82727
|
+
"Register-ScheduledTask -TaskName $taskName -Description $expectedDefinitionMarker -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null",
|
|
82728
|
+
cleanupLegacyTask,
|
|
82729
|
+
"$registeredTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
|
|
82730
|
+
"if ($null -eq $registeredTask -or $registeredTask.Description -cne $expectedDefinitionMarker) { throw 'Alan daemon task definition was not applied' }"
|
|
82731
|
+
].join("; ");
|
|
82732
|
+
const uninstallScript = [
|
|
82733
|
+
taskContext,
|
|
82734
|
+
resolveTaskSid,
|
|
82735
|
+
windowsDefinitionContext,
|
|
82736
|
+
assertOwnedTasks,
|
|
82737
|
+
"if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName $legacyTask.TaskName -TaskPath $legacyTask.TaskPath -Confirm:$false -ErrorAction Stop }",
|
|
82738
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
|
|
82739
|
+
"if ($null -ne $task) { Unregister-ScheduledTask -TaskName $taskName -TaskPath '\\' -Confirm:$false -ErrorAction Stop }",
|
|
82740
|
+
"$remaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and ($_.TaskName -eq $taskName -or $_.TaskName -eq $legacyTask.TaskName) })",
|
|
82741
|
+
"if ($remaining.Count -gt 0) { throw 'Alan daemon task still exists after unregister' }"
|
|
82643
82742
|
].join("; ");
|
|
82743
|
+
const statusScript = [
|
|
82744
|
+
taskContext,
|
|
82745
|
+
resolveTaskSid,
|
|
82746
|
+
windowsDefinitionContext,
|
|
82747
|
+
checkCurrentOwnership,
|
|
82748
|
+
checkLegacyOwnership,
|
|
82749
|
+
"$definitionMatches = $false",
|
|
82750
|
+
"if ($null -eq $task) { Write-Output 'State: Missing' } else {",
|
|
82751
|
+
"$taskTrigger = @($task.Triggers)[0]",
|
|
82752
|
+
"$definitionMatches = @($task.Actions).Count -eq 1 -and @($task.Triggers).Count -eq 1 -and $task.Description -ceq $expectedDefinitionMarker -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and [string]$task.Principal.LogonType -eq 'Interactive' -and [string]$task.Principal.RunLevel -eq 'Limited' -and [string]$taskAction.Execute -ieq 'powershell.exe' -and [string]$taskAction.Arguments -ceq $expectedLauncherArguments -and [string]$taskAction.WorkingDirectory -ieq $expectedWorkingDirectory -and $taskTrigger.CimClass.CimClassName -eq 'MSFT_TaskLogonTrigger' -and (Resolve-TaskSid ([string]$taskTrigger.UserId)) -eq $currentSid -and -not $task.Settings.DisallowStartIfOnBatteries -and -not $task.Settings.StopIfGoingOnBatteries -and $task.Settings.RestartCount -eq 3 -and [string]$task.Settings.RestartInterval -eq 'PT1M' -and [string]$task.Settings.ExecutionTimeLimit -eq 'PT0S'",
|
|
82753
|
+
"Write-Output ('TaskName: ' + $task.TaskName); Write-Output ('State: ' + $task.State); Write-Output ('Definition: ' + $task.Description)",
|
|
82754
|
+
"}",
|
|
82755
|
+
"Write-Output ('DefinitionMatch: ' + $definitionMatches)",
|
|
82756
|
+
"Write-Output ('CurrentOwned: ' + $currentOwned)",
|
|
82757
|
+
"$legacyPresent = $null -ne $legacyTask",
|
|
82758
|
+
"Write-Output ('LegacyTask: ' + $legacyPresent)",
|
|
82759
|
+
"Write-Output ('LegacyOwned: ' + $legacyOwned)",
|
|
82760
|
+
"Write-Output ('LegacyState: ' + $legacyState)"
|
|
82761
|
+
].join("; ");
|
|
82762
|
+
const activeScript = [
|
|
82763
|
+
taskContext,
|
|
82764
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
|
|
82765
|
+
"if ($null -eq $task) { Write-Output 'Missing' } else { Write-Output $task.State }"
|
|
82766
|
+
].join("; ");
|
|
82767
|
+
const registerCommand = powershellCommand(registerScript);
|
|
82644
82768
|
return {
|
|
82645
82769
|
platform: "win32",
|
|
82646
82770
|
manifestPath: null,
|
|
82647
82771
|
manifest: null,
|
|
82648
|
-
logDirectory:
|
|
82649
|
-
loadDefinitionCommands: [
|
|
82650
|
-
|
|
82651
|
-
command: "powershell.exe",
|
|
82652
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82653
|
-
tolerateFailure: false
|
|
82654
|
-
}
|
|
82655
|
-
],
|
|
82656
|
-
reloadDefinitionCommands: [
|
|
82657
|
-
{
|
|
82658
|
-
command: "powershell.exe",
|
|
82659
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82660
|
-
tolerateFailure: false
|
|
82661
|
-
}
|
|
82662
|
-
],
|
|
82772
|
+
logDirectory: logDir,
|
|
82773
|
+
loadDefinitionCommands: [registerCommand],
|
|
82774
|
+
reloadDefinitionCommands: [registerCommand],
|
|
82663
82775
|
enableCommands: [],
|
|
82664
|
-
startCommands: [
|
|
82665
|
-
{
|
|
82666
|
-
command: "powershell.exe",
|
|
82667
|
-
args: [
|
|
82668
|
-
"-NoProfile",
|
|
82669
|
-
"-NonInteractive",
|
|
82670
|
-
"-Command",
|
|
82671
|
-
`Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82672
|
-
],
|
|
82673
|
-
tolerateFailure: false
|
|
82674
|
-
}
|
|
82675
|
-
],
|
|
82776
|
+
startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
|
|
82676
82777
|
stopCommands: [
|
|
82677
|
-
|
|
82678
|
-
|
|
82679
|
-
|
|
82680
|
-
|
|
82681
|
-
"-NonInteractive",
|
|
82682
|
-
"-Command",
|
|
82683
|
-
`Stop-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue`
|
|
82684
|
-
],
|
|
82685
|
-
tolerateFailure: true
|
|
82686
|
-
}
|
|
82687
|
-
],
|
|
82688
|
-
replaceCommands: [
|
|
82689
|
-
{
|
|
82690
|
-
command: "powershell.exe",
|
|
82691
|
-
args: [
|
|
82692
|
-
"-NoProfile",
|
|
82693
|
-
"-NonInteractive",
|
|
82694
|
-
"-Command",
|
|
82695
|
-
`${registerScript}; Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82696
|
-
],
|
|
82697
|
-
tolerateFailure: false
|
|
82698
|
-
}
|
|
82699
|
-
],
|
|
82700
|
-
uninstallCommands: [
|
|
82701
|
-
{
|
|
82702
|
-
command: "powershell.exe",
|
|
82703
|
-
args: [
|
|
82704
|
-
"-NoProfile",
|
|
82705
|
-
"-NonInteractive",
|
|
82706
|
-
"-Command",
|
|
82707
|
-
`Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Confirm:$false -ErrorAction SilentlyContinue`
|
|
82708
|
-
],
|
|
82709
|
-
tolerateFailure: true
|
|
82710
|
-
}
|
|
82778
|
+
powershellCommand(
|
|
82779
|
+
`${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
|
|
82780
|
+
true
|
|
82781
|
+
)
|
|
82711
82782
|
],
|
|
82712
|
-
|
|
82713
|
-
|
|
82714
|
-
|
|
82715
|
-
|
|
82716
|
-
"-NonInteractive",
|
|
82717
|
-
"-Command",
|
|
82718
|
-
`Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} | Format-List TaskName,State`
|
|
82719
|
-
],
|
|
82720
|
-
tolerateFailure: true
|
|
82721
|
-
},
|
|
82722
|
-
isActiveCommand: {
|
|
82723
|
-
command: "powershell.exe",
|
|
82724
|
-
args: [
|
|
82725
|
-
"-NoProfile",
|
|
82726
|
-
"-NonInteractive",
|
|
82727
|
-
"-Command",
|
|
82728
|
-
`(Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue).State`
|
|
82729
|
-
],
|
|
82730
|
-
tolerateFailure: true
|
|
82731
|
-
},
|
|
82732
|
-
lingerStatusCommand: null
|
|
82783
|
+
uninstallCommands: [powershellCommand(uninstallScript)],
|
|
82784
|
+
statusCommand: powershellCommand(statusScript, true),
|
|
82785
|
+
isActiveCommand: powershellCommand(activeScript, true),
|
|
82786
|
+
definitionMarker
|
|
82733
82787
|
};
|
|
82734
82788
|
}
|
|
82735
82789
|
function resolveCurrentPlatform() {
|
|
@@ -82739,14 +82793,14 @@ function resolveCurrentPlatform() {
|
|
|
82739
82793
|
}
|
|
82740
82794
|
return currentPlatform;
|
|
82741
82795
|
}
|
|
82742
|
-
function currentServicePlan(args, runtime) {
|
|
82743
|
-
const currentPlatform = resolveCurrentPlatform();
|
|
82796
|
+
function currentServicePlan(args, runtime, options = {}) {
|
|
82797
|
+
const currentPlatform = options.platform ?? resolveCurrentPlatform();
|
|
82744
82798
|
const profile = resolveEndpointProfileFromArgs(args);
|
|
82745
82799
|
const cliEntry = runtime?.cliEntry ?? process.argv[1];
|
|
82746
82800
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
82747
82801
|
return buildDaemonServicePlan({
|
|
82748
82802
|
platform: currentPlatform,
|
|
82749
|
-
homeDir: (0, import_node_os16.homedir)(),
|
|
82803
|
+
homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
|
|
82750
82804
|
nodeExecutable: runtime?.executable ?? process.execPath,
|
|
82751
82805
|
cliEntry: (0, import_node_path27.resolve)(cliEntry),
|
|
82752
82806
|
profile,
|
|
@@ -82769,9 +82823,13 @@ function defaultReadManifest(path2) {
|
|
|
82769
82823
|
return (0, import_node_fs25.readFileSync)(path2, "utf8");
|
|
82770
82824
|
}
|
|
82771
82825
|
function runServiceCommand(command) {
|
|
82772
|
-
const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
|
|
82826
|
+
const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
|
|
82827
|
+
encoding: "utf8",
|
|
82828
|
+
shell: false,
|
|
82829
|
+
...(0, import_node_os16.platform)() === "win32" ? { timeout: 15e3, windowsHide: true } : {}
|
|
82830
|
+
});
|
|
82773
82831
|
const status = result.status ?? 1;
|
|
82774
|
-
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
82832
|
+
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
|
|
82775
82833
|
if (status !== 0 && !command.tolerateFailure) {
|
|
82776
82834
|
throw new Error(
|
|
82777
82835
|
`Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
|
|
@@ -82784,6 +82842,23 @@ function defaultSleep(ms) {
|
|
|
82784
82842
|
setTimeout(resolveSleep, ms);
|
|
82785
82843
|
});
|
|
82786
82844
|
}
|
|
82845
|
+
function isProcessAlive(pid) {
|
|
82846
|
+
if (pid <= 0) return false;
|
|
82847
|
+
try {
|
|
82848
|
+
process.kill(pid, 0);
|
|
82849
|
+
return true;
|
|
82850
|
+
} catch (error61) {
|
|
82851
|
+
return error61.code === "EPERM";
|
|
82852
|
+
}
|
|
82853
|
+
}
|
|
82854
|
+
async function requestWindowsDaemonShutdown(owner) {
|
|
82855
|
+
try {
|
|
82856
|
+
const response = await requestLocalDaemonShutdown(owner.localApiPort, owner.localApiToken);
|
|
82857
|
+
return response.ok ? "accepted" : "rejected";
|
|
82858
|
+
} catch {
|
|
82859
|
+
return "unreachable";
|
|
82860
|
+
}
|
|
82861
|
+
}
|
|
82787
82862
|
function parseServiceHostState(servicePlatform, result) {
|
|
82788
82863
|
const output2 = result.output;
|
|
82789
82864
|
if (servicePlatform === "linux") {
|
|
@@ -82809,9 +82884,62 @@ function parseServiceHostState(servicePlatform, result) {
|
|
|
82809
82884
|
const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
|
|
82810
82885
|
const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
|
|
82811
82886
|
if (state === "running") return "running";
|
|
82812
|
-
if (state === "
|
|
82887
|
+
if (state === "queued") return "starting";
|
|
82888
|
+
if (state === "ready" || state === "disabled") return "stopped";
|
|
82813
82889
|
return "unknown";
|
|
82814
82890
|
}
|
|
82891
|
+
function parseWindowsTaskInspection(result) {
|
|
82892
|
+
if (result.status !== 0) {
|
|
82893
|
+
throw new Error("Cannot inspect Windows daemon task definition");
|
|
82894
|
+
}
|
|
82895
|
+
const value2 = (name) => {
|
|
82896
|
+
const match = result.output.match(new RegExp(`^${name}:\\s*(.*)$`, "imu"));
|
|
82897
|
+
if (!match) throw new Error(`Incomplete Windows daemon task inspection: missing ${name}`);
|
|
82898
|
+
return match[1]?.trim() ?? "";
|
|
82899
|
+
};
|
|
82900
|
+
const boolean4 = (name) => {
|
|
82901
|
+
const parsed = value2(name).toLowerCase();
|
|
82902
|
+
if (parsed === "true") return true;
|
|
82903
|
+
if (parsed === "false") return false;
|
|
82904
|
+
throw new Error(`Incomplete Windows daemon task inspection: invalid ${name}`);
|
|
82905
|
+
};
|
|
82906
|
+
const currentPresent = /^TaskName:\s*.+$/imu.test(result.output);
|
|
82907
|
+
const currentStateValue = value2("State");
|
|
82908
|
+
const currentOwned = boolean4("CurrentOwned");
|
|
82909
|
+
const definitionMatches = boolean4("DefinitionMatch");
|
|
82910
|
+
const legacyPresent = boolean4("LegacyTask");
|
|
82911
|
+
const legacyOwned = boolean4("LegacyOwned");
|
|
82912
|
+
const legacyStateValue = value2("LegacyState");
|
|
82913
|
+
if (currentPresent !== (currentStateValue.toLowerCase() !== "missing")) {
|
|
82914
|
+
throw new Error("Incomplete Windows daemon task inspection: inconsistent current task state");
|
|
82915
|
+
}
|
|
82916
|
+
if (!currentPresent && (currentOwned || definitionMatches)) {
|
|
82917
|
+
throw new Error(
|
|
82918
|
+
"Incomplete Windows daemon task inspection: absent current task is marked owned"
|
|
82919
|
+
);
|
|
82920
|
+
}
|
|
82921
|
+
if (legacyPresent !== (legacyStateValue.toLowerCase() !== "missing") || !legacyPresent && legacyOwned) {
|
|
82922
|
+
throw new Error("Incomplete Windows daemon task inspection: inconsistent legacy task state");
|
|
82923
|
+
}
|
|
82924
|
+
return {
|
|
82925
|
+
currentPresent,
|
|
82926
|
+
currentOwned,
|
|
82927
|
+
currentState: currentPresent ? parseServiceHostState("win32", { status: 0, output: currentStateValue }) : "stopped",
|
|
82928
|
+
definitionMatches,
|
|
82929
|
+
definitionMarker: result.output.match(/^Definition:\s*(.+)$/imu)?.[1]?.trim(),
|
|
82930
|
+
legacyPresent,
|
|
82931
|
+
legacyOwned,
|
|
82932
|
+
legacyState: legacyPresent ? parseServiceHostState("win32", { status: 0, output: legacyStateValue }) : "stopped"
|
|
82933
|
+
};
|
|
82934
|
+
}
|
|
82935
|
+
function assertWindowsTaskOwnership(tasks) {
|
|
82936
|
+
if (tasks.currentPresent && !tasks.currentOwned) {
|
|
82937
|
+
throw new Error("Windows daemon task is not owned by this Alan installation");
|
|
82938
|
+
}
|
|
82939
|
+
if (tasks.legacyPresent && !tasks.legacyOwned) {
|
|
82940
|
+
throw new Error("Legacy Windows daemon task is not owned by this Alan installation");
|
|
82941
|
+
}
|
|
82942
|
+
}
|
|
82815
82943
|
function resolveEnsureDefinitionOutcome(input2) {
|
|
82816
82944
|
if (!input2.definitionChanged) {
|
|
82817
82945
|
return { changed: false, updatePending: false };
|
|
@@ -82821,9 +82949,6 @@ function resolveEnsureDefinitionOutcome(input2) {
|
|
|
82821
82949
|
}
|
|
82822
82950
|
return { changed: true, updatePending: false };
|
|
82823
82951
|
}
|
|
82824
|
-
function shouldStartOnInstall(hostState) {
|
|
82825
|
-
return hostState === "stopped";
|
|
82826
|
-
}
|
|
82827
82952
|
function resolveUsername(options) {
|
|
82828
82953
|
if (options?.username) return options.username;
|
|
82829
82954
|
try {
|
|
@@ -82832,17 +82957,14 @@ function resolveUsername(options) {
|
|
|
82832
82957
|
return process.env.USER || process.env.USERNAME || "unknown";
|
|
82833
82958
|
}
|
|
82834
82959
|
}
|
|
82835
|
-
function buildLingerStatusCommand(username) {
|
|
82836
|
-
return {
|
|
82837
|
-
command: "loginctl",
|
|
82838
|
-
args: ["show-user", username, "-p", "Linger"],
|
|
82839
|
-
tolerateFailure: true
|
|
82840
|
-
};
|
|
82841
|
-
}
|
|
82842
82960
|
function assertSystemdUserLingerEnabled(options = {}) {
|
|
82843
82961
|
const username = resolveUsername(options);
|
|
82844
82962
|
const run = options.runCommand ?? runServiceCommand;
|
|
82845
|
-
const result = run(
|
|
82963
|
+
const result = run({
|
|
82964
|
+
command: "loginctl",
|
|
82965
|
+
args: ["show-user", username, "-p", "Linger"],
|
|
82966
|
+
tolerateFailure: true
|
|
82967
|
+
});
|
|
82846
82968
|
if (result.status !== 0) {
|
|
82847
82969
|
throw new Error(
|
|
82848
82970
|
`Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
|
|
@@ -82856,9 +82978,9 @@ function assertSystemdUserLingerEnabled(options = {}) {
|
|
|
82856
82978
|
}
|
|
82857
82979
|
function createHostContext(options = {}) {
|
|
82858
82980
|
const args = options.args ?? [];
|
|
82859
|
-
const plan = currentServicePlan(args, options.runtime);
|
|
82981
|
+
const plan = currentServicePlan(args, options.runtime, options);
|
|
82860
82982
|
const run = options.runCommand ?? runServiceCommand;
|
|
82861
|
-
const
|
|
82983
|
+
const sleep5 = options.sleep ?? defaultSleep;
|
|
82862
82984
|
const nowMs = options.nowMs ?? Date.now;
|
|
82863
82985
|
const readManifest = options.readManifest ?? defaultReadManifest;
|
|
82864
82986
|
const writeManifest = options.writeManifest ?? writeManifestAtomic;
|
|
@@ -82870,13 +82992,20 @@ function createHostContext(options = {}) {
|
|
|
82870
82992
|
args,
|
|
82871
82993
|
plan,
|
|
82872
82994
|
run,
|
|
82873
|
-
sleep:
|
|
82995
|
+
sleep: sleep5,
|
|
82874
82996
|
nowMs,
|
|
82875
82997
|
readManifest,
|
|
82876
82998
|
writeManifest,
|
|
82877
82999
|
removeManifest,
|
|
82878
83000
|
ensureLogDir,
|
|
82879
|
-
profile: resolveEndpointProfileFromArgs(args)
|
|
83001
|
+
configPath: options.configPath ?? resolveAgentConfigPath({ profile: resolveEndpointProfileFromArgs(args) }),
|
|
83002
|
+
readOwner: options.readOwner ?? readLocalDaemonOwnerLease,
|
|
83003
|
+
isProcessAlive: options.isProcessAlive ?? isProcessAlive,
|
|
83004
|
+
probeEndpoint: options.probeEndpoint ?? probeLocalDaemonEndpoint,
|
|
83005
|
+
requestWindowsShutdown: options.requestWindowsShutdown ?? requestWindowsDaemonShutdown,
|
|
83006
|
+
inspectJournal: options.inspectJournal ?? inspectLocalDaemonRunJournal,
|
|
83007
|
+
acquireMaintenance: options.acquireMaintenance ?? acquireLocalDaemonMaintenanceLease,
|
|
83008
|
+
releaseMaintenance: options.releaseMaintenance ?? releaseLocalDaemonMaintenanceLease
|
|
82880
83009
|
};
|
|
82881
83010
|
}
|
|
82882
83011
|
function inspectDaemonService(options = {}) {
|
|
@@ -82892,8 +83021,9 @@ function inspectDaemonService(options = {}) {
|
|
|
82892
83021
|
}
|
|
82893
83022
|
function ensureDaemonServiceDefinition(options = {}) {
|
|
82894
83023
|
const ctx = createHostContext(options);
|
|
82895
|
-
const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
83024
|
+
const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
82896
83025
|
let definitionChanged = false;
|
|
83026
|
+
let inspection;
|
|
82897
83027
|
if (plan.manifestPath && plan.manifest) {
|
|
82898
83028
|
ensureLogDir();
|
|
82899
83029
|
const existing = readManifest(plan.manifestPath);
|
|
@@ -82902,20 +83032,38 @@ function ensureDaemonServiceDefinition(options = {}) {
|
|
|
82902
83032
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
82903
83033
|
}
|
|
82904
83034
|
} else if (plan.platform === "win32") {
|
|
82905
|
-
|
|
83035
|
+
ensureLogDir();
|
|
83036
|
+
const result = run(plan.statusCommand);
|
|
83037
|
+
const tasks = parseWindowsTaskInspection(result);
|
|
83038
|
+
assertWindowsTaskOwnership(tasks);
|
|
83039
|
+
if (tasks.currentPresent && tasks.currentState === "unknown" || tasks.legacyPresent && tasks.legacyState === "unknown") {
|
|
83040
|
+
throw new Error("Cannot safely update a Windows daemon task with unknown state");
|
|
83041
|
+
}
|
|
83042
|
+
definitionChanged = tasks.definitionMarker !== plan.definitionMarker || !tasks.definitionMatches || tasks.legacyPresent;
|
|
83043
|
+
const running = tasks.currentState === "running" || tasks.legacyState === "running";
|
|
83044
|
+
inspection = {
|
|
83045
|
+
state: running ? "running" : tasks.currentState,
|
|
83046
|
+
platform: plan.platform,
|
|
83047
|
+
detail: result.output || void 0,
|
|
83048
|
+
observedAtMs: nowMs()
|
|
83049
|
+
};
|
|
82906
83050
|
}
|
|
82907
|
-
|
|
83051
|
+
inspection ??= inspectDaemonService(options);
|
|
83052
|
+
const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
|
|
82908
83053
|
const outcome = resolveEnsureDefinitionOutcome({
|
|
82909
83054
|
definitionChanged,
|
|
82910
|
-
hostState:
|
|
83055
|
+
hostState: definitionHostState
|
|
82911
83056
|
});
|
|
82912
83057
|
if (outcome.updatePending) {
|
|
82913
83058
|
return outcome;
|
|
82914
83059
|
}
|
|
82915
|
-
if (
|
|
83060
|
+
if (plan.platform === "win32" && !outcome.changed) {
|
|
82916
83061
|
return outcome;
|
|
82917
83062
|
}
|
|
82918
|
-
|
|
83063
|
+
if (definitionHostState === "running" || definitionHostState === "starting") {
|
|
83064
|
+
return outcome;
|
|
83065
|
+
}
|
|
83066
|
+
const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
|
|
82919
83067
|
for (const command of definitionCommands) run(command);
|
|
82920
83068
|
return outcome;
|
|
82921
83069
|
}
|
|
@@ -82927,10 +83075,140 @@ function startDaemonService(options = {}) {
|
|
|
82927
83075
|
const { plan, run } = createHostContext(options);
|
|
82928
83076
|
for (const command of plan.startCommands) run(command);
|
|
82929
83077
|
}
|
|
82930
|
-
function
|
|
83078
|
+
async function stopDaemonServiceAndWait(identity, timeoutMs, options = {}) {
|
|
83079
|
+
const {
|
|
83080
|
+
plan,
|
|
83081
|
+
run,
|
|
83082
|
+
sleep: sleep5,
|
|
83083
|
+
nowMs,
|
|
83084
|
+
configPath,
|
|
83085
|
+
readOwner,
|
|
83086
|
+
isProcessAlive: processIsAlive,
|
|
83087
|
+
probeEndpoint,
|
|
83088
|
+
requestWindowsShutdown: requestShutdown
|
|
83089
|
+
} = createHostContext(options);
|
|
83090
|
+
let owner;
|
|
83091
|
+
try {
|
|
83092
|
+
owner = readOwner(configPath);
|
|
83093
|
+
} catch {
|
|
83094
|
+
return "identity_changed";
|
|
83095
|
+
}
|
|
83096
|
+
const ownerMatches = (candidate) => candidate.pid === identity.pid && candidate.daemonSessionId === identity.daemonSessionId && candidate.localApiPort === identity.port && (identity.serviceId === void 0 || candidate.localServiceId === identity.serviceId) && (identity.startedAtMs === void 0 || candidate.startedAtMs === identity.startedAtMs);
|
|
83097
|
+
if (!owner || !ownerMatches(owner)) {
|
|
83098
|
+
return "identity_changed";
|
|
83099
|
+
}
|
|
83100
|
+
if (plan.platform === "win32") {
|
|
83101
|
+
const shutdown = await requestShutdown(owner);
|
|
83102
|
+
if (shutdown === "rejected") return "identity_changed";
|
|
83103
|
+
if (shutdown === "unreachable") {
|
|
83104
|
+
for (const command of plan.stopCommands) run(command);
|
|
83105
|
+
}
|
|
83106
|
+
const deadline2 = nowMs() + timeoutMs;
|
|
83107
|
+
while (nowMs() < deadline2) {
|
|
83108
|
+
let currentOwner;
|
|
83109
|
+
try {
|
|
83110
|
+
currentOwner = readOwner(configPath);
|
|
83111
|
+
} catch {
|
|
83112
|
+
return "identity_changed";
|
|
83113
|
+
}
|
|
83114
|
+
if (currentOwner && !ownerMatches(currentOwner)) {
|
|
83115
|
+
return "identity_changed";
|
|
83116
|
+
}
|
|
83117
|
+
if (!processIsAlive(identity.pid)) {
|
|
83118
|
+
const endpoint = await probeEndpoint(identity.port, owner.localApiToken, 250);
|
|
83119
|
+
if (endpoint.state === "absent") return "exited";
|
|
83120
|
+
if (endpoint.state === "listening_unauthorized" || endpoint.status.daemonSessionId !== identity.daemonSessionId) {
|
|
83121
|
+
return "identity_changed";
|
|
83122
|
+
}
|
|
83123
|
+
}
|
|
83124
|
+
await sleep5(STOP_POLL_INTERVAL_MS);
|
|
83125
|
+
}
|
|
83126
|
+
return "timeout";
|
|
83127
|
+
}
|
|
83128
|
+
for (const command of plan.stopCommands) run(command);
|
|
83129
|
+
const deadline = nowMs() + timeoutMs;
|
|
83130
|
+
while (nowMs() < deadline) {
|
|
83131
|
+
const result = run(plan.isActiveCommand);
|
|
83132
|
+
const state = parseServiceHostState(plan.platform, result);
|
|
83133
|
+
if (state === "stopped") return "exited";
|
|
83134
|
+
if (state === "unknown") {
|
|
83135
|
+
} else if (identity.pid > 0) {
|
|
83136
|
+
try {
|
|
83137
|
+
process.kill(identity.pid, 0);
|
|
83138
|
+
} catch {
|
|
83139
|
+
return "exited";
|
|
83140
|
+
}
|
|
83141
|
+
}
|
|
83142
|
+
await sleep5(STOP_POLL_INTERVAL_MS);
|
|
83143
|
+
}
|
|
83144
|
+
return "timeout";
|
|
83145
|
+
}
|
|
83146
|
+
async function uninstallDaemonService(args = [], options = {}) {
|
|
82931
83147
|
const ctx = createHostContext({ ...options, args: options.args ?? args });
|
|
82932
|
-
|
|
82933
|
-
|
|
83148
|
+
if (ctx.plan.platform === "win32") {
|
|
83149
|
+
const ownership = ctx.run(ctx.plan.statusCommand);
|
|
83150
|
+
let tasks;
|
|
83151
|
+
try {
|
|
83152
|
+
tasks = parseWindowsTaskInspection(ownership);
|
|
83153
|
+
} catch (error61) {
|
|
83154
|
+
throw new Error(
|
|
83155
|
+
`Cannot inspect Windows daemon task ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
|
|
83156
|
+
);
|
|
83157
|
+
}
|
|
83158
|
+
assertWindowsTaskOwnership(tasks);
|
|
83159
|
+
if (!tasks.currentPresent && !tasks.legacyPresent) {
|
|
83160
|
+
console.info("[alan-agent] Per-user daemon service removed");
|
|
83161
|
+
return;
|
|
83162
|
+
}
|
|
83163
|
+
const acquired = ctx.acquireMaintenance(ctx.configPath, "watchdog", 3e4);
|
|
83164
|
+
if (!acquired.acquired) {
|
|
83165
|
+
throw new Error("Cannot uninstall the Windows daemon service while maintenance is active");
|
|
83166
|
+
}
|
|
83167
|
+
try {
|
|
83168
|
+
const journal = ctx.inspectJournal(ctx.configPath);
|
|
83169
|
+
if (!journal.safeToRestart) {
|
|
83170
|
+
throw new Error(
|
|
83171
|
+
`Windows daemon service uninstall is deferred while ${journal.activeRunCount} active run${journal.activeRunCount === 1 ? "" : "s"} finish`
|
|
83172
|
+
);
|
|
83173
|
+
}
|
|
83174
|
+
let owner;
|
|
83175
|
+
try {
|
|
83176
|
+
owner = ctx.readOwner(ctx.configPath);
|
|
83177
|
+
} catch (error61) {
|
|
83178
|
+
throw new Error(
|
|
83179
|
+
`Cannot verify Windows daemon ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
|
|
83180
|
+
);
|
|
83181
|
+
}
|
|
83182
|
+
if (owner) {
|
|
83183
|
+
const stopped = await stopDaemonServiceAndWait(
|
|
83184
|
+
{
|
|
83185
|
+
pid: owner.pid,
|
|
83186
|
+
serviceId: owner.localServiceId,
|
|
83187
|
+
daemonSessionId: owner.daemonSessionId,
|
|
83188
|
+
port: owner.localApiPort,
|
|
83189
|
+
startedAtMs: owner.startedAtMs
|
|
83190
|
+
},
|
|
83191
|
+
1e4,
|
|
83192
|
+
{ ...options, args: ctx.args }
|
|
83193
|
+
);
|
|
83194
|
+
if (stopped !== "exited") {
|
|
83195
|
+
throw new Error(
|
|
83196
|
+
stopped === "identity_changed" ? "Windows daemon ownership changed before uninstall" : "Timed out waiting for the Windows daemon to stop before uninstall"
|
|
83197
|
+
);
|
|
83198
|
+
}
|
|
83199
|
+
} else {
|
|
83200
|
+
if (tasks.currentState !== "stopped" || tasks.legacyState !== "stopped") {
|
|
83201
|
+
throw new Error("Cannot uninstall a running Windows daemon without verified ownership");
|
|
83202
|
+
}
|
|
83203
|
+
}
|
|
83204
|
+
for (const command of ctx.plan.uninstallCommands) ctx.run(command);
|
|
83205
|
+
} finally {
|
|
83206
|
+
ctx.releaseMaintenance(ctx.configPath, acquired.lease.id);
|
|
83207
|
+
}
|
|
83208
|
+
} else {
|
|
83209
|
+
for (const command of ctx.plan.uninstallCommands) ctx.run(command);
|
|
83210
|
+
if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
|
|
83211
|
+
}
|
|
82934
83212
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
82935
83213
|
}
|
|
82936
83214
|
function installDaemonService(args = [], runtime, options = {}) {
|
|
@@ -82958,7 +83236,7 @@ function installDaemonService(args = [], runtime, options = {}) {
|
|
|
82958
83236
|
});
|
|
82959
83237
|
return;
|
|
82960
83238
|
}
|
|
82961
|
-
if (
|
|
83239
|
+
if (inspection.state !== "stopped") {
|
|
82962
83240
|
console.info("[alan-agent] Per-user daemon service already present", {
|
|
82963
83241
|
profile,
|
|
82964
83242
|
state: inspection.state
|
|
@@ -82982,14 +83260,48 @@ ${result.output}
|
|
|
82982
83260
|
}
|
|
82983
83261
|
|
|
82984
83262
|
// src/install-command.ts
|
|
82985
|
-
|
|
83263
|
+
var WINDOWS_SETUP_READY_TIMEOUT_MS = 9e3;
|
|
83264
|
+
async function runInstallCommand(commandArgs, currentPlatform = process.platform) {
|
|
83265
|
+
if (currentPlatform === "win32" && commandArgs.includes("--headless")) {
|
|
83266
|
+
throw new Error("--headless is only supported on Linux systemd user services");
|
|
83267
|
+
}
|
|
82986
83268
|
bindConfigPath(commandArgs);
|
|
82987
83269
|
const configured = readConfig();
|
|
82988
83270
|
const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
|
|
82989
|
-
|
|
83271
|
+
const isWindowsSetup = currentPlatform === "win32" && configurationMode === "setup";
|
|
83272
|
+
const deadline = isWindowsSetup ? Date.now() + WINDOWS_SETUP_READY_TIMEOUT_MS : 0;
|
|
83273
|
+
const runtimeIdentityChanged = configurationMode === "setup" ? await setupDaemon(commandArgs) : false;
|
|
82990
83274
|
if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
|
|
83275
|
+
if (!isWindowsSetup) {
|
|
83276
|
+
installDaemonService(commandArgs);
|
|
83277
|
+
runMcpIntegrationCommand(["reconcile", ...commandArgs]);
|
|
83278
|
+
return;
|
|
83279
|
+
}
|
|
83280
|
+
const { runtimeId, localApiPort, localApiToken } = readConfig();
|
|
83281
|
+
if (!runtimeId || !localApiPort || !localApiToken) {
|
|
83282
|
+
throw new Error("Windows runtime setup completed without a local readiness endpoint");
|
|
83283
|
+
}
|
|
83284
|
+
if (runtimeIdentityChanged) {
|
|
83285
|
+
while (Date.now() < deadline) {
|
|
83286
|
+
const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
|
|
83287
|
+
if (status?.runtimeId === runtimeId && status.connected) break;
|
|
83288
|
+
if (!status) {
|
|
83289
|
+
const service = inspectDaemonService({ args: commandArgs });
|
|
83290
|
+
if (service.state !== "running" && service.state !== "starting") break;
|
|
83291
|
+
}
|
|
83292
|
+
await (0, import_promises6.setTimeout)(100);
|
|
83293
|
+
}
|
|
83294
|
+
}
|
|
82991
83295
|
installDaemonService(commandArgs);
|
|
82992
|
-
|
|
83296
|
+
while (Date.now() < deadline) {
|
|
83297
|
+
const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
|
|
83298
|
+
if (status?.runtimeId === runtimeId && status.connected) {
|
|
83299
|
+
runMcpIntegrationCommand(["reconcile", ...commandArgs]);
|
|
83300
|
+
return;
|
|
83301
|
+
}
|
|
83302
|
+
await (0, import_promises6.setTimeout)(100);
|
|
83303
|
+
}
|
|
83304
|
+
throw new Error("Windows runtime did not become ready within 9 seconds");
|
|
82993
83305
|
}
|
|
82994
83306
|
|
|
82995
83307
|
// src/lifecycle-logger.ts
|
|
@@ -86366,7 +86678,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
|
|
|
86366
86678
|
function inputRequiredRoundsExceededMessage2(method, maxRounds) {
|
|
86367
86679
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
86368
86680
|
}
|
|
86369
|
-
function
|
|
86681
|
+
function sleep4(ms, signal) {
|
|
86370
86682
|
return new Promise((resolve15, reject) => {
|
|
86371
86683
|
if (signal?.aborted) {
|
|
86372
86684
|
reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
|
|
@@ -94633,7 +94945,7 @@ var LegacyInputRequiredShim = class {
|
|
|
94633
94945
|
} finally {
|
|
94634
94946
|
roundAbort.dispose();
|
|
94635
94947
|
}
|
|
94636
|
-
} else await
|
|
94948
|
+
} else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
|
|
94637
94949
|
let ctxNext = {
|
|
94638
94950
|
...ctx,
|
|
94639
94951
|
mcpReq: {
|
|
@@ -95430,7 +95742,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
|
|
|
95430
95742
|
}
|
|
95431
95743
|
|
|
95432
95744
|
// src/native-hook-command.ts
|
|
95433
|
-
var
|
|
95745
|
+
var import_node_crypto22 = require("crypto");
|
|
95434
95746
|
var import_node_fs27 = require("fs");
|
|
95435
95747
|
var import_node_os17 = require("os");
|
|
95436
95748
|
var import_node_path29 = require("path");
|
|
@@ -95799,7 +96111,7 @@ function parseNativeHookEnvelope(input2) {
|
|
|
95799
96111
|
const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
|
|
95800
96112
|
return {
|
|
95801
96113
|
schemaVersion: 1,
|
|
95802
|
-
eventId: (0,
|
|
96114
|
+
eventId: (0, import_node_crypto22.randomUUID)(),
|
|
95803
96115
|
provider: capability.provider,
|
|
95804
96116
|
eventType,
|
|
95805
96117
|
observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -96295,7 +96607,7 @@ function delay2(ms) {
|
|
|
96295
96607
|
}
|
|
96296
96608
|
|
|
96297
96609
|
// src/web-presenter.ts
|
|
96298
|
-
var
|
|
96610
|
+
var import_node_crypto23 = require("crypto");
|
|
96299
96611
|
var WebPresenter = class {
|
|
96300
96612
|
constructor(wsClient, conversationId, cwd) {
|
|
96301
96613
|
this.conversationId = conversationId;
|
|
@@ -96481,7 +96793,7 @@ var WebPresenter = class {
|
|
|
96481
96793
|
this.applicationProblem = createAlanProblem({
|
|
96482
96794
|
domain: "application",
|
|
96483
96795
|
code: input2.code,
|
|
96484
|
-
id: (0,
|
|
96796
|
+
id: (0, import_node_crypto23.randomUUID)(),
|
|
96485
96797
|
title: APPLICATION_PROBLEM_TITLES[input2.code],
|
|
96486
96798
|
detail: input2.message,
|
|
96487
96799
|
// The schema's slot for the technical cause, so the problem carries it
|
|
@@ -96551,7 +96863,7 @@ var WebPresenter = class {
|
|
|
96551
96863
|
};
|
|
96552
96864
|
|
|
96553
96865
|
// src/ws-client.ts
|
|
96554
|
-
var
|
|
96866
|
+
var import_node_crypto25 = require("crypto");
|
|
96555
96867
|
|
|
96556
96868
|
// ../shared/dist/agent-liveness.js
|
|
96557
96869
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -96579,13 +96891,13 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
96579
96891
|
});
|
|
96580
96892
|
|
|
96581
96893
|
// src/sandbox-outbox.ts
|
|
96582
|
-
var
|
|
96894
|
+
var import_node_crypto24 = require("crypto");
|
|
96583
96895
|
var import_node_fs28 = require("fs");
|
|
96584
96896
|
function deriveSandboxOutboxKey(sessionToken) {
|
|
96585
96897
|
if (!sessionToken) {
|
|
96586
96898
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
96587
96899
|
}
|
|
96588
|
-
return (0,
|
|
96900
|
+
return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
96589
96901
|
}
|
|
96590
96902
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
96591
96903
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -96762,7 +97074,7 @@ var WSClient = class {
|
|
|
96762
97074
|
}
|
|
96763
97075
|
socket;
|
|
96764
97076
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
96765
|
-
agentSessionId = (0,
|
|
97077
|
+
agentSessionId = (0, import_node_crypto25.randomUUID)();
|
|
96766
97078
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
96767
97079
|
heartbeatSeq = 0;
|
|
96768
97080
|
heartbeatTimer = null;
|
|
@@ -97594,7 +97906,7 @@ async function runSandbox(config2) {
|
|
|
97594
97906
|
}
|
|
97595
97907
|
|
|
97596
97908
|
// src/skills/skill-cli.ts
|
|
97597
|
-
var
|
|
97909
|
+
var import_promises7 = require("fs/promises");
|
|
97598
97910
|
var import_node_os19 = require("os");
|
|
97599
97911
|
var import_node_path30 = require("path");
|
|
97600
97912
|
async function runSkillsCommand(args) {
|
|
@@ -97641,7 +97953,7 @@ async function defaultScanInput(homeDirectory2) {
|
|
|
97641
97953
|
}
|
|
97642
97954
|
async function readProtectedJson(path2) {
|
|
97643
97955
|
const absolutePath = (0, import_node_path30.resolve)(path2);
|
|
97644
|
-
const stat2 = await (0,
|
|
97956
|
+
const stat2 = await (0, import_promises7.lstat)(absolutePath);
|
|
97645
97957
|
if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
|
|
97646
97958
|
if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
|
|
97647
97959
|
throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
|
|
@@ -97650,7 +97962,7 @@ async function readProtectedJson(path2) {
|
|
|
97650
97962
|
throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
|
|
97651
97963
|
}
|
|
97652
97964
|
if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
|
|
97653
|
-
return JSON.parse(await (0,
|
|
97965
|
+
return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
|
|
97654
97966
|
}
|
|
97655
97967
|
function option(args, name) {
|
|
97656
97968
|
const index = args.indexOf(name);
|
|
@@ -97808,7 +98120,7 @@ async function main() {
|
|
|
97808
98120
|
return;
|
|
97809
98121
|
}
|
|
97810
98122
|
if (command === "service" && commandArgs[0] === "uninstall") {
|
|
97811
|
-
uninstallDaemonService(commandArgs.slice(1));
|
|
98123
|
+
await uninstallDaemonService(commandArgs.slice(1));
|
|
97812
98124
|
return;
|
|
97813
98125
|
}
|
|
97814
98126
|
if (command === "service" && commandArgs[0] === "status") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alan-ai-hq/agent-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.95",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
|
|
6
6
|
"bin": {
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"tsup": "^8.5.1",
|
|
27
27
|
"tsx": "^4.19.0",
|
|
28
28
|
"typescript": "~5.9.3",
|
|
29
|
-
"@alan-ai/
|
|
29
|
+
"@alan-ai/agent-core": "0.1.0",
|
|
30
30
|
"@alan-ai/shared": "0.1.0",
|
|
31
|
-
"@alan-ai/
|
|
31
|
+
"@alan-ai/sandbox-runtime-spec": "0.1.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsup",
|