@alan-ai-hq/agent-manager 0.1.97 → 0.1.98
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 +614 -238
- 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",
|
|
@@ -15460,7 +15460,7 @@ function describeError(error61) {
|
|
|
15460
15460
|
}
|
|
15461
15461
|
|
|
15462
15462
|
// src/version.ts
|
|
15463
|
-
var AGENT_VERSION = "0.1.
|
|
15463
|
+
var AGENT_VERSION = "0.1.98";
|
|
15464
15464
|
|
|
15465
15465
|
// src/daemon-worktree.ts
|
|
15466
15466
|
var import_node_child_process3 = require("child_process");
|
|
@@ -49619,6 +49619,16 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
|
|
|
49619
49619
|
const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
|
|
49620
49620
|
return probe.state === "running" ? probe.status : null;
|
|
49621
49621
|
}
|
|
49622
|
+
function requestLocalDaemonShutdown(port, token) {
|
|
49623
|
+
return fetch(`http://127.0.0.1:${port}/shutdown?force=1`, {
|
|
49624
|
+
method: "POST",
|
|
49625
|
+
headers: {
|
|
49626
|
+
authorization: `Bearer ${token}`,
|
|
49627
|
+
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
49628
|
+
},
|
|
49629
|
+
signal: AbortSignal.timeout(5e3)
|
|
49630
|
+
});
|
|
49631
|
+
}
|
|
49622
49632
|
async function adoptLocalDaemonControl(input2) {
|
|
49623
49633
|
const timeoutMs = input2.timeoutMs ?? 750;
|
|
49624
49634
|
try {
|
|
@@ -76706,13 +76716,13 @@ var RuntimeRunJournal = class {
|
|
|
76706
76716
|
* Keep the exact interruption outcome until a durable authenticated control
|
|
76707
76717
|
* channel can prove both identity and ownership.
|
|
76708
76718
|
*/
|
|
76709
|
-
assessRecovery(runId,
|
|
76719
|
+
assessRecovery(runId, isProcessAlive2) {
|
|
76710
76720
|
const entry = this.entries.get(runId);
|
|
76711
76721
|
if (!entry) return void 0;
|
|
76712
76722
|
if (entry.providerPid === void 0) {
|
|
76713
76723
|
return { reattachable: false, reason: "provider_not_recorded" };
|
|
76714
76724
|
}
|
|
76715
|
-
if (!
|
|
76725
|
+
if (!isProcessAlive2(entry.providerPid)) {
|
|
76716
76726
|
return {
|
|
76717
76727
|
reattachable: false,
|
|
76718
76728
|
reason: "provider_not_running",
|
|
@@ -76766,7 +76776,8 @@ var RuntimeSetupGrantExpiredError = class extends Error {
|
|
|
76766
76776
|
this.name = "RuntimeSetupGrantExpiredError";
|
|
76767
76777
|
}
|
|
76768
76778
|
};
|
|
76769
|
-
async function createRuntimeSetupGrant(input2) {
|
|
76779
|
+
async function createRuntimeSetupGrant(input2, signal) {
|
|
76780
|
+
signal?.throwIfAborted();
|
|
76770
76781
|
const response = await fetch(`${input2.apiUrl}/public/runtimes/setup-tokens`, {
|
|
76771
76782
|
method: "POST",
|
|
76772
76783
|
headers: {
|
|
@@ -76778,7 +76789,7 @@ async function createRuntimeSetupGrant(input2) {
|
|
|
76778
76789
|
...input2.scope === "team" ? { teamId: input2.teamId } : {},
|
|
76779
76790
|
ttlMinutes: 5
|
|
76780
76791
|
}),
|
|
76781
|
-
signal: AbortSignal.timeout(1e4)
|
|
76792
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(1e4)]) : AbortSignal.timeout(1e4)
|
|
76782
76793
|
});
|
|
76783
76794
|
const body = await response.json();
|
|
76784
76795
|
if (!response.ok || typeof body.setupToken !== "string") {
|
|
@@ -76787,9 +76798,10 @@ async function createRuntimeSetupGrant(input2) {
|
|
|
76787
76798
|
}
|
|
76788
76799
|
return body.setupToken;
|
|
76789
76800
|
}
|
|
76790
|
-
async function exchangeRuntimeSetupGrant(input2) {
|
|
76801
|
+
async function exchangeRuntimeSetupGrant(input2, setupSignal) {
|
|
76802
|
+
setupSignal?.throwIfAborted();
|
|
76791
76803
|
const registrationAttemptId = getOrCreateSetupAttemptId(input2.setupToken);
|
|
76792
|
-
const signal = AbortSignal.timeout(15e3);
|
|
76804
|
+
const signal = setupSignal ? AbortSignal.any([setupSignal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3);
|
|
76793
76805
|
const registrationInit = {
|
|
76794
76806
|
method: "POST",
|
|
76795
76807
|
headers: { "content-type": "application/json" },
|
|
@@ -76814,6 +76826,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76814
76826
|
let response;
|
|
76815
76827
|
let transportError;
|
|
76816
76828
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
76829
|
+
signal.throwIfAborted();
|
|
76817
76830
|
try {
|
|
76818
76831
|
response = await fetch(
|
|
76819
76832
|
`${input2.apiUrl}/public/runtimes/register-with-setup-token`,
|
|
@@ -76821,6 +76834,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76821
76834
|
);
|
|
76822
76835
|
transportError = void 0;
|
|
76823
76836
|
} catch (error61) {
|
|
76837
|
+
signal.throwIfAborted();
|
|
76824
76838
|
transportError = error61;
|
|
76825
76839
|
}
|
|
76826
76840
|
const retriable = response && (response.status === 408 || response.status === 429 || response.status >= 500);
|
|
@@ -76858,6 +76872,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76858
76872
|
};
|
|
76859
76873
|
}
|
|
76860
76874
|
async function handoffRuntimeSetupGrant(input2) {
|
|
76875
|
+
input2.signal?.throwIfAborted();
|
|
76861
76876
|
const response = await fetch(`http://127.0.0.1:${input2.port}/credentials/setup`, {
|
|
76862
76877
|
method: "POST",
|
|
76863
76878
|
headers: {
|
|
@@ -76865,7 +76880,7 @@ async function handoffRuntimeSetupGrant(input2) {
|
|
|
76865
76880
|
"content-type": "application/json"
|
|
76866
76881
|
},
|
|
76867
76882
|
body: JSON.stringify(input2.setup),
|
|
76868
|
-
signal: AbortSignal.timeout(input2.timeoutMs ?? 2e4)
|
|
76883
|
+
signal: input2.signal ? AbortSignal.any([input2.signal, AbortSignal.timeout(input2.timeoutMs ?? 2e4)]) : AbortSignal.timeout(input2.timeoutMs ?? 2e4)
|
|
76869
76884
|
});
|
|
76870
76885
|
const body = await response.json();
|
|
76871
76886
|
if (!response.ok) {
|
|
@@ -80459,7 +80474,20 @@ async function runLocalDaemonRecoveryLadder(input2) {
|
|
|
80459
80474
|
error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
|
|
80460
80475
|
};
|
|
80461
80476
|
}
|
|
80462
|
-
|
|
80477
|
+
function writeRegisteredRuntimeConfig(args, previousConfig, registered) {
|
|
80478
|
+
writeConfig({
|
|
80479
|
+
...registered,
|
|
80480
|
+
localApiPort: getLocalApiPort(args, {}),
|
|
80481
|
+
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80482
|
+
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80483
|
+
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80484
|
+
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80485
|
+
daemonEpoch: previousConfig.daemonEpoch
|
|
80486
|
+
});
|
|
80487
|
+
}
|
|
80488
|
+
async function setupDaemon(args, options = {}) {
|
|
80489
|
+
const { signal } = options;
|
|
80490
|
+
signal?.throwIfAborted();
|
|
80463
80491
|
bindConfigPath(args);
|
|
80464
80492
|
const previousConfig = readConfigForRegistration();
|
|
80465
80493
|
const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
|
|
@@ -80477,12 +80505,15 @@ async function setupDaemon(args) {
|
|
|
80477
80505
|
"setup requires --setup-token <token> or --token <Alan access token>.\nFor normal setup, copy the setup token from Alan and run: alan-agent setup --setup-token <token>\nFor browser authorization, run: alan-agent login"
|
|
80478
80506
|
);
|
|
80479
80507
|
}
|
|
80480
|
-
const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
|
|
80481
|
-
|
|
80482
|
-
|
|
80483
|
-
|
|
80484
|
-
|
|
80485
|
-
|
|
80508
|
+
const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
|
|
80509
|
+
{
|
|
80510
|
+
apiUrl,
|
|
80511
|
+
accessToken: token,
|
|
80512
|
+
scope,
|
|
80513
|
+
teamId
|
|
80514
|
+
},
|
|
80515
|
+
signal
|
|
80516
|
+
);
|
|
80486
80517
|
const setup = {
|
|
80487
80518
|
apiUrl,
|
|
80488
80519
|
wsUrl,
|
|
@@ -80492,13 +80523,16 @@ async function setupDaemon(args) {
|
|
|
80492
80523
|
displayName,
|
|
80493
80524
|
installationId
|
|
80494
80525
|
};
|
|
80526
|
+
signal?.throwIfAborted();
|
|
80495
80527
|
let liveConfig = previousConfig;
|
|
80496
80528
|
const localApiPort = getLocalApiPort(args, liveConfig);
|
|
80497
80529
|
let live = liveConfig.localApiToken ? await probeLocalDaemon(localApiPort, liveConfig.localApiToken) : null;
|
|
80530
|
+
signal?.throwIfAborted();
|
|
80498
80531
|
if (!live && liveConfig.localServiceId && liveConfig.localServiceSecret) {
|
|
80499
80532
|
live = await adoptConfiguredLocalDaemon(args);
|
|
80500
80533
|
liveConfig = readConfigForRegistration();
|
|
80501
80534
|
}
|
|
80535
|
+
signal?.throwIfAborted();
|
|
80502
80536
|
if (live) {
|
|
80503
80537
|
if (!liveConfig.localApiToken) {
|
|
80504
80538
|
throw new Error("running daemon could not be authenticated for credential setup");
|
|
@@ -80512,14 +80546,16 @@ async function setupDaemon(args) {
|
|
|
80512
80546
|
const body2 = await handoffRuntimeSetupGrant({
|
|
80513
80547
|
port: localApiPort,
|
|
80514
80548
|
localApiToken: liveConfig.localApiToken,
|
|
80515
|
-
setup: liveSetup
|
|
80549
|
+
setup: liveSetup,
|
|
80550
|
+
signal
|
|
80516
80551
|
});
|
|
80552
|
+
signal?.throwIfAborted();
|
|
80517
80553
|
console.info("[alan-agent] Runtime credentials adopted by running daemon", {
|
|
80518
80554
|
runtimeId: body2.runtimeId,
|
|
80519
80555
|
credentialGeneration: body2.credentialGeneration,
|
|
80520
80556
|
configPath: getConfigPath()
|
|
80521
80557
|
});
|
|
80522
|
-
return
|
|
80558
|
+
return body2.runtimeId !== liveConfig.runtimeId;
|
|
80523
80559
|
}
|
|
80524
80560
|
if (readLocalDaemonOwnerLease(getConfigPath())) {
|
|
80525
80561
|
throw new Error(
|
|
@@ -80528,28 +80564,27 @@ async function setupDaemon(args) {
|
|
|
80528
80564
|
}
|
|
80529
80565
|
let body;
|
|
80530
80566
|
try {
|
|
80531
|
-
body = await exchangeRuntimeSetupGrant(setup);
|
|
80567
|
+
body = await exchangeRuntimeSetupGrant(setup, signal);
|
|
80532
80568
|
} catch (error61) {
|
|
80569
|
+
signal?.throwIfAborted();
|
|
80533
80570
|
if (setupToken && error61 instanceof RuntimeSetupGrantExpiredError) {
|
|
80571
|
+
if (signal) {
|
|
80572
|
+
throw new Error("Setup link expired. Run alan-agent login to authorize in your browser.");
|
|
80573
|
+
}
|
|
80534
80574
|
console.info("[alan-agent] Setup link expired; continuing with browser authorization");
|
|
80535
80575
|
await loginWithDeviceCode(args);
|
|
80536
|
-
return
|
|
80576
|
+
return false;
|
|
80537
80577
|
}
|
|
80538
80578
|
throw error61;
|
|
80539
80579
|
}
|
|
80540
|
-
|
|
80580
|
+
signal?.throwIfAborted();
|
|
80581
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80541
80582
|
apiUrl,
|
|
80542
80583
|
wsUrl,
|
|
80543
80584
|
endpointProfile,
|
|
80544
80585
|
runtimeId: body.runtimeId,
|
|
80545
80586
|
runtimeToken: body.runtimeToken,
|
|
80546
80587
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80547
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80548
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80549
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80550
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80551
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80552
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80553
80588
|
displayName: body.displayName,
|
|
80554
80589
|
installationId
|
|
80555
80590
|
});
|
|
@@ -80558,7 +80593,7 @@ async function setupDaemon(args) {
|
|
|
80558
80593
|
runtimeId: body.runtimeId,
|
|
80559
80594
|
configPath: getConfigPath()
|
|
80560
80595
|
});
|
|
80561
|
-
return
|
|
80596
|
+
return false;
|
|
80562
80597
|
}
|
|
80563
80598
|
async function loginWithDeviceCode(args) {
|
|
80564
80599
|
bindConfigPath(args);
|
|
@@ -80647,19 +80682,13 @@ async function loginWithDeviceCode(args) {
|
|
|
80647
80682
|
if (ownedPending?.operationId !== operation.id) {
|
|
80648
80683
|
throw new Error("browser authorization sidecar ownership was lost before finalization");
|
|
80649
80684
|
}
|
|
80650
|
-
|
|
80685
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80651
80686
|
apiUrl,
|
|
80652
80687
|
wsUrl,
|
|
80653
80688
|
endpointProfile,
|
|
80654
80689
|
runtimeId: body.runtime.id,
|
|
80655
80690
|
runtimeToken: body.runtimeToken,
|
|
80656
80691
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80657
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80658
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80659
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80660
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80661
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80662
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80663
80692
|
displayName: body.runtime.displayName ?? displayName,
|
|
80664
80693
|
installationId
|
|
80665
80694
|
});
|
|
@@ -80698,14 +80727,7 @@ async function stopDaemon(args) {
|
|
|
80698
80727
|
console.info("[alan-agent] No daemon listening", { port: localApiPort });
|
|
80699
80728
|
return;
|
|
80700
80729
|
}
|
|
80701
|
-
const response = await
|
|
80702
|
-
method: "POST",
|
|
80703
|
-
headers: {
|
|
80704
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80705
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80706
|
-
},
|
|
80707
|
-
signal: AbortSignal.timeout(5e3)
|
|
80708
|
-
});
|
|
80730
|
+
const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80709
80731
|
if (!response.ok) {
|
|
80710
80732
|
throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
|
|
80711
80733
|
}
|
|
@@ -80850,14 +80872,7 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
|
|
|
80850
80872
|
`Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
|
|
80851
80873
|
);
|
|
80852
80874
|
}
|
|
80853
|
-
const shutdown = await
|
|
80854
|
-
method: "POST",
|
|
80855
|
-
headers: {
|
|
80856
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80857
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80858
|
-
},
|
|
80859
|
-
signal: AbortSignal.timeout(5e3)
|
|
80860
|
-
});
|
|
80875
|
+
const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80861
80876
|
if (!shutdown.ok) {
|
|
80862
80877
|
throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
|
|
80863
80878
|
}
|
|
@@ -81018,6 +81033,9 @@ async function printDoctor(args = []) {
|
|
|
81018
81033
|
);
|
|
81019
81034
|
}
|
|
81020
81035
|
|
|
81036
|
+
// src/install-command.ts
|
|
81037
|
+
var import_promises6 = require("timers/promises");
|
|
81038
|
+
|
|
81021
81039
|
// src/mcp-config-adapters.ts
|
|
81022
81040
|
var import_node_crypto20 = require("crypto");
|
|
81023
81041
|
var import_node_fs23 = require("fs");
|
|
@@ -82402,12 +82420,14 @@ function runMcpIntegrationCommand(args, options = {}) {
|
|
|
82402
82420
|
|
|
82403
82421
|
// src/service-manager.ts
|
|
82404
82422
|
var import_node_child_process9 = require("child_process");
|
|
82423
|
+
var import_node_crypto21 = require("crypto");
|
|
82405
82424
|
var import_node_fs25 = require("fs");
|
|
82406
82425
|
var import_node_os16 = require("os");
|
|
82407
82426
|
var import_node_path27 = require("path");
|
|
82408
82427
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
82409
82428
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
82410
82429
|
var WINDOWS_TASK = "Alan Agent";
|
|
82430
|
+
var STOP_POLL_INTERVAL_MS = 200;
|
|
82411
82431
|
function xml(value2) {
|
|
82412
82432
|
return value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
82413
82433
|
}
|
|
@@ -82417,6 +82437,18 @@ function systemdArg(value2) {
|
|
|
82417
82437
|
function powershellLiteral(value2) {
|
|
82418
82438
|
return `'${value2.replaceAll("'", "''")}'`;
|
|
82419
82439
|
}
|
|
82440
|
+
function powershellCommand(script, tolerateFailure = false) {
|
|
82441
|
+
return {
|
|
82442
|
+
command: "powershell.exe",
|
|
82443
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", script],
|
|
82444
|
+
tolerateFailure
|
|
82445
|
+
};
|
|
82446
|
+
}
|
|
82447
|
+
function windowsCommandLineArgument(value2) {
|
|
82448
|
+
if (value2 && !/[\s"]/u.test(value2)) return value2;
|
|
82449
|
+
const escaped = value2.replace(/(\\*)"/gu, (_match, slashes) => `${slashes}${slashes}\\"`).replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
|
|
82450
|
+
return `"${escaped}"`;
|
|
82451
|
+
}
|
|
82420
82452
|
function serviceNames(profile) {
|
|
82421
82453
|
if (profile === "production") {
|
|
82422
82454
|
return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
|
|
@@ -82438,7 +82470,7 @@ function buildDaemonServicePlan(input2) {
|
|
|
82438
82470
|
const domain2 = `gui/${userId}`;
|
|
82439
82471
|
const serviceTarget = `${domain2}/${names.launchd}`;
|
|
82440
82472
|
const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
|
|
82441
|
-
const
|
|
82473
|
+
const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
|
|
82442
82474
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
82443
82475
|
const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
|
|
82444
82476
|
<string>${xml(value2)}</string>`).join("\n");
|
|
@@ -82467,9 +82499,9 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82467
82499
|
<key>ThrottleInterval</key>
|
|
82468
82500
|
<integer>5</integer>
|
|
82469
82501
|
<key>StandardOutPath</key>
|
|
82470
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82502
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
|
|
82471
82503
|
<key>StandardErrorPath</key>
|
|
82472
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82504
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
|
|
82473
82505
|
</dict>
|
|
82474
82506
|
</plist>
|
|
82475
82507
|
`;
|
|
@@ -82477,7 +82509,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82477
82509
|
platform: "darwin",
|
|
82478
82510
|
manifestPath,
|
|
82479
82511
|
manifest,
|
|
82480
|
-
logDirectory:
|
|
82512
|
+
logDirectory: logDir2,
|
|
82481
82513
|
loadDefinitionCommands: [
|
|
82482
82514
|
{
|
|
82483
82515
|
command: "launchctl",
|
|
@@ -82518,23 +82550,6 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82518
82550
|
tolerateFailure: true
|
|
82519
82551
|
}
|
|
82520
82552
|
],
|
|
82521
|
-
replaceCommands: [
|
|
82522
|
-
{
|
|
82523
|
-
command: "launchctl",
|
|
82524
|
-
args: ["bootout", domain2, manifestPath],
|
|
82525
|
-
tolerateFailure: true
|
|
82526
|
-
},
|
|
82527
|
-
{
|
|
82528
|
-
command: "launchctl",
|
|
82529
|
-
args: ["bootstrap", domain2, manifestPath],
|
|
82530
|
-
tolerateFailure: false
|
|
82531
|
-
},
|
|
82532
|
-
{
|
|
82533
|
-
command: "launchctl",
|
|
82534
|
-
args: ["kickstart", "-k", serviceTarget],
|
|
82535
|
-
tolerateFailure: false
|
|
82536
|
-
}
|
|
82537
|
-
],
|
|
82538
82553
|
uninstallCommands: [
|
|
82539
82554
|
{
|
|
82540
82555
|
command: "launchctl",
|
|
@@ -82551,8 +82566,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82551
82566
|
command: "launchctl",
|
|
82552
82567
|
args: ["print", serviceTarget],
|
|
82553
82568
|
tolerateFailure: true
|
|
82554
|
-
}
|
|
82555
|
-
lingerStatusCommand: null
|
|
82569
|
+
}
|
|
82556
82570
|
};
|
|
82557
82571
|
}
|
|
82558
82572
|
if (input2.platform === "linux") {
|
|
@@ -82615,18 +82629,6 @@ WantedBy=default.target
|
|
|
82615
82629
|
tolerateFailure: true
|
|
82616
82630
|
}
|
|
82617
82631
|
],
|
|
82618
|
-
replaceCommands: [
|
|
82619
|
-
{
|
|
82620
|
-
command: "systemctl",
|
|
82621
|
-
args: ["--user", "daemon-reload"],
|
|
82622
|
-
tolerateFailure: false
|
|
82623
|
-
},
|
|
82624
|
-
{
|
|
82625
|
-
command: "systemctl",
|
|
82626
|
-
args: ["--user", "restart", names.systemd],
|
|
82627
|
-
tolerateFailure: false
|
|
82628
|
-
}
|
|
82629
|
-
],
|
|
82630
82632
|
uninstallCommands: [
|
|
82631
82633
|
{
|
|
82632
82634
|
command: "systemctl",
|
|
@@ -82648,117 +82650,174 @@ WantedBy=default.target
|
|
|
82648
82650
|
command: "systemctl",
|
|
82649
82651
|
args: ["--user", "is-active", names.systemd],
|
|
82650
82652
|
tolerateFailure: true
|
|
82651
|
-
},
|
|
82652
|
-
lingerStatusCommand: {
|
|
82653
|
-
command: "loginctl",
|
|
82654
|
-
args: ["show-user", "$(id -un)", "-p", "Linger"],
|
|
82655
|
-
tolerateFailure: true
|
|
82656
82653
|
}
|
|
82657
82654
|
};
|
|
82658
82655
|
}
|
|
82659
|
-
const
|
|
82660
|
-
const
|
|
82661
|
-
const
|
|
82662
|
-
const
|
|
82663
|
-
|
|
82656
|
+
const logDir = import_node_path27.win32.join(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
|
|
82657
|
+
const legacyEnvironmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
|
|
82658
|
+
const legacyDirectArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
|
|
82659
|
+
const legacyExecutable = legacyEnvironmentPrefix ? "powershell.exe" : input2.nodeExecutable;
|
|
82660
|
+
const legacyArguments = legacyEnvironmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${legacyEnvironmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : legacyDirectArguments;
|
|
82661
|
+
const daemonArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map(windowsCommandLineArgument).join(" ");
|
|
82662
|
+
const environmentStatements = Object.entries(input2.environment ?? {}).map(
|
|
82663
|
+
([key, value2]) => `$startInfo.EnvironmentVariables[${powershellLiteral(key)}] = ${powershellLiteral(value2)}`
|
|
82664
82664
|
);
|
|
82665
|
+
const launcherScript = [
|
|
82666
|
+
"$ErrorActionPreference = 'Stop'",
|
|
82667
|
+
"$startInfo = [System.Diagnostics.ProcessStartInfo]::new()",
|
|
82668
|
+
`$startInfo.FileName = ${powershellLiteral(input2.nodeExecutable)}`,
|
|
82669
|
+
`$startInfo.Arguments = ${powershellLiteral(daemonArguments)}`,
|
|
82670
|
+
`$startInfo.WorkingDirectory = ${powershellLiteral(import_node_path27.win32.dirname(input2.cliEntry))}`,
|
|
82671
|
+
"$startInfo.UseShellExecute = $false",
|
|
82672
|
+
"$startInfo.CreateNoWindow = $true",
|
|
82673
|
+
"$startInfo.RedirectStandardOutput = $true",
|
|
82674
|
+
"$startInfo.RedirectStandardError = $true",
|
|
82675
|
+
...environmentStatements,
|
|
82676
|
+
`$stdout = [System.IO.File]::Open(${powershellLiteral(import_node_path27.win32.join(logDir, "daemon-launcher.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
|
|
82677
|
+
`$stderr = [System.IO.File]::Open(${powershellLiteral(import_node_path27.win32.join(logDir, "daemon-launcher-error.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
|
|
82678
|
+
"$process = [System.Diagnostics.Process]::new()",
|
|
82679
|
+
"$process.StartInfo = $startInfo",
|
|
82680
|
+
"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() }",
|
|
82681
|
+
"exit $exitCode"
|
|
82682
|
+
].join("; ");
|
|
82683
|
+
const executable = powershellLiteral("powershell.exe");
|
|
82684
|
+
const launcherArguments = `-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${Buffer.from(launcherScript, "utf16le").toString("base64")}`;
|
|
82685
|
+
const settingsScript = "New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries";
|
|
82686
|
+
const ownerMarker = `Alan daemon owner v1 ${(0, import_node_crypto21.createHash)("sha256").update(
|
|
82687
|
+
JSON.stringify({
|
|
82688
|
+
executable: input2.nodeExecutable,
|
|
82689
|
+
cliEntry: input2.cliEntry,
|
|
82690
|
+
profile: input2.profile,
|
|
82691
|
+
taskBaseName: names.windows
|
|
82692
|
+
})
|
|
82693
|
+
).digest("hex")}`;
|
|
82694
|
+
const definitionMarker = `${ownerMarker}; definition v3 ${(0, import_node_crypto21.createHash)("sha256").update(
|
|
82695
|
+
JSON.stringify({
|
|
82696
|
+
executable: "powershell.exe",
|
|
82697
|
+
launcherArguments,
|
|
82698
|
+
workingDirectory: import_node_path27.win32.dirname(input2.cliEntry),
|
|
82699
|
+
settingsScript,
|
|
82700
|
+
taskBaseName: names.windows
|
|
82701
|
+
})
|
|
82702
|
+
).digest("hex")}`;
|
|
82703
|
+
const windowsDefinitionContext = [
|
|
82704
|
+
`$expectedLauncherArguments = ${powershellLiteral(launcherArguments)}`,
|
|
82705
|
+
`$expectedWorkingDirectory = ${powershellLiteral(import_node_path27.win32.dirname(input2.cliEntry))}`,
|
|
82706
|
+
`$expectedOwnerMarker = ${powershellLiteral(`${ownerMarker};`)}`,
|
|
82707
|
+
`$expectedDefinitionMarker = ${powershellLiteral(definitionMarker)}`,
|
|
82708
|
+
`$expectedLegacyExecutable = ${powershellLiteral(legacyExecutable)}`,
|
|
82709
|
+
`$expectedLegacyArguments = ${powershellLiteral(legacyArguments)}`
|
|
82710
|
+
].join("; ");
|
|
82711
|
+
const taskContext = [
|
|
82712
|
+
"$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()",
|
|
82713
|
+
"$currentUser = $currentIdentity.Name",
|
|
82714
|
+
"$currentSid = $currentIdentity.User.Value",
|
|
82715
|
+
`$taskName = ${powershellLiteral(names.windows)} + ' ' + $currentSid`
|
|
82716
|
+
].join("; ");
|
|
82717
|
+
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 '' } } }";
|
|
82718
|
+
const currentLookup = "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]";
|
|
82719
|
+
const checkCurrentOwnership = [
|
|
82720
|
+
currentLookup,
|
|
82721
|
+
"$currentOwned = $false",
|
|
82722
|
+
"if ($null -ne $task) { try {",
|
|
82723
|
+
"$taskAction = @($task.Actions)[0]",
|
|
82724
|
+
"$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))",
|
|
82725
|
+
"} catch { $currentOwned = $false } }"
|
|
82726
|
+
].join("; ");
|
|
82727
|
+
const legacyLookup = `$legacyTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`;
|
|
82728
|
+
const checkLegacyOwnership = [
|
|
82729
|
+
legacyLookup,
|
|
82730
|
+
"$legacyOwned = $false",
|
|
82731
|
+
"$legacyState = 'Missing'",
|
|
82732
|
+
"if ($null -ne $legacyTask) { try {",
|
|
82733
|
+
"$legacyState = [string]$legacyTask.State",
|
|
82734
|
+
"$legacySid = Resolve-TaskSid ([string]$legacyTask.Principal.UserId)",
|
|
82735
|
+
"$legacyAction = @($legacyTask.Actions)[0]",
|
|
82736
|
+
"if ($null -ne $legacyAction) {",
|
|
82737
|
+
"$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))",
|
|
82738
|
+
"}",
|
|
82739
|
+
"} catch { $legacyOwned = $false } }"
|
|
82740
|
+
].join("; ");
|
|
82741
|
+
const assertOwnedTasks = [
|
|
82742
|
+
checkCurrentOwnership,
|
|
82743
|
+
checkLegacyOwnership,
|
|
82744
|
+
`if ($null -ne $task -and -not $currentOwned) { throw ${powershellLiteral(`${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82745
|
+
`if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82746
|
+
"if ($null -ne $task -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$task.State)) { throw 'Alan daemon task became active or has unknown state' }",
|
|
82747
|
+
"if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }"
|
|
82748
|
+
].join("; ");
|
|
82749
|
+
const cleanupLegacyTask = [
|
|
82750
|
+
checkLegacyOwnership,
|
|
82751
|
+
`if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
|
|
82752
|
+
"if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }",
|
|
82753
|
+
`if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -TaskPath '\\' -Confirm:$false -ErrorAction Stop }`,
|
|
82754
|
+
`$legacyRemaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`,
|
|
82755
|
+
"if ($null -ne $legacyRemaining) { throw 'Legacy Alan daemon task still exists after migration' }"
|
|
82756
|
+
].join("; ");
|
|
82665
82757
|
const registerScript = [
|
|
82666
|
-
|
|
82667
|
-
|
|
82758
|
+
taskContext,
|
|
82759
|
+
resolveTaskSid,
|
|
82760
|
+
windowsDefinitionContext,
|
|
82761
|
+
`$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
|
|
82668
82762
|
"$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
|
|
82669
82763
|
"$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
|
|
82670
|
-
|
|
82671
|
-
|
|
82764
|
+
`$settings = ${settingsScript}`,
|
|
82765
|
+
assertOwnedTasks,
|
|
82766
|
+
"Register-ScheduledTask -TaskName $taskName -Description $expectedDefinitionMarker -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null",
|
|
82767
|
+
cleanupLegacyTask,
|
|
82768
|
+
"$registeredTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
|
|
82769
|
+
"if ($null -eq $registeredTask -or $registeredTask.Description -cne $expectedDefinitionMarker) { throw 'Alan daemon task definition was not applied' }"
|
|
82770
|
+
].join("; ");
|
|
82771
|
+
const uninstallScript = [
|
|
82772
|
+
taskContext,
|
|
82773
|
+
resolveTaskSid,
|
|
82774
|
+
windowsDefinitionContext,
|
|
82775
|
+
assertOwnedTasks,
|
|
82776
|
+
"if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName $legacyTask.TaskName -TaskPath $legacyTask.TaskPath -Confirm:$false -ErrorAction Stop }",
|
|
82777
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
|
|
82778
|
+
"if ($null -ne $task) { Unregister-ScheduledTask -TaskName $taskName -TaskPath '\\' -Confirm:$false -ErrorAction Stop }",
|
|
82779
|
+
"$remaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and ($_.TaskName -eq $taskName -or $_.TaskName -eq $legacyTask.TaskName) })",
|
|
82780
|
+
"if ($remaining.Count -gt 0) { throw 'Alan daemon task still exists after unregister' }"
|
|
82672
82781
|
].join("; ");
|
|
82782
|
+
const statusScript = [
|
|
82783
|
+
taskContext,
|
|
82784
|
+
resolveTaskSid,
|
|
82785
|
+
windowsDefinitionContext,
|
|
82786
|
+
checkCurrentOwnership,
|
|
82787
|
+
checkLegacyOwnership,
|
|
82788
|
+
"$definitionMatches = $false",
|
|
82789
|
+
"if ($null -eq $task) { Write-Output 'State: Missing' } else {",
|
|
82790
|
+
"$taskTrigger = @($task.Triggers)[0]",
|
|
82791
|
+
"$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'",
|
|
82792
|
+
"Write-Output ('TaskName: ' + $task.TaskName); Write-Output ('State: ' + $task.State); Write-Output ('Definition: ' + $task.Description)",
|
|
82793
|
+
"}",
|
|
82794
|
+
"Write-Output ('DefinitionMatch: ' + $definitionMatches)",
|
|
82795
|
+
"Write-Output ('CurrentOwned: ' + $currentOwned)",
|
|
82796
|
+
"$legacyPresent = $null -ne $legacyTask",
|
|
82797
|
+
"Write-Output ('LegacyTask: ' + $legacyPresent)",
|
|
82798
|
+
"Write-Output ('LegacyOwned: ' + $legacyOwned)",
|
|
82799
|
+
"Write-Output ('LegacyState: ' + $legacyState)"
|
|
82800
|
+
].join("; ");
|
|
82801
|
+
const registerCommand = powershellCommand(registerScript);
|
|
82673
82802
|
return {
|
|
82674
82803
|
platform: "win32",
|
|
82675
82804
|
manifestPath: null,
|
|
82676
82805
|
manifest: null,
|
|
82677
|
-
logDirectory:
|
|
82678
|
-
loadDefinitionCommands: [
|
|
82679
|
-
|
|
82680
|
-
command: "powershell.exe",
|
|
82681
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82682
|
-
tolerateFailure: false
|
|
82683
|
-
}
|
|
82684
|
-
],
|
|
82685
|
-
reloadDefinitionCommands: [
|
|
82686
|
-
{
|
|
82687
|
-
command: "powershell.exe",
|
|
82688
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82689
|
-
tolerateFailure: false
|
|
82690
|
-
}
|
|
82691
|
-
],
|
|
82806
|
+
logDirectory: logDir,
|
|
82807
|
+
loadDefinitionCommands: [registerCommand],
|
|
82808
|
+
reloadDefinitionCommands: [registerCommand],
|
|
82692
82809
|
enableCommands: [],
|
|
82693
|
-
startCommands: [
|
|
82694
|
-
{
|
|
82695
|
-
command: "powershell.exe",
|
|
82696
|
-
args: [
|
|
82697
|
-
"-NoProfile",
|
|
82698
|
-
"-NonInteractive",
|
|
82699
|
-
"-Command",
|
|
82700
|
-
`Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82701
|
-
],
|
|
82702
|
-
tolerateFailure: false
|
|
82703
|
-
}
|
|
82704
|
-
],
|
|
82810
|
+
startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
|
|
82705
82811
|
stopCommands: [
|
|
82706
|
-
|
|
82707
|
-
|
|
82708
|
-
|
|
82709
|
-
|
|
82710
|
-
"-NonInteractive",
|
|
82711
|
-
"-Command",
|
|
82712
|
-
`Stop-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue`
|
|
82713
|
-
],
|
|
82714
|
-
tolerateFailure: true
|
|
82715
|
-
}
|
|
82716
|
-
],
|
|
82717
|
-
replaceCommands: [
|
|
82718
|
-
{
|
|
82719
|
-
command: "powershell.exe",
|
|
82720
|
-
args: [
|
|
82721
|
-
"-NoProfile",
|
|
82722
|
-
"-NonInteractive",
|
|
82723
|
-
"-Command",
|
|
82724
|
-
`${registerScript}; Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82725
|
-
],
|
|
82726
|
-
tolerateFailure: false
|
|
82727
|
-
}
|
|
82728
|
-
],
|
|
82729
|
-
uninstallCommands: [
|
|
82730
|
-
{
|
|
82731
|
-
command: "powershell.exe",
|
|
82732
|
-
args: [
|
|
82733
|
-
"-NoProfile",
|
|
82734
|
-
"-NonInteractive",
|
|
82735
|
-
"-Command",
|
|
82736
|
-
`Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Confirm:$false -ErrorAction SilentlyContinue`
|
|
82737
|
-
],
|
|
82738
|
-
tolerateFailure: true
|
|
82739
|
-
}
|
|
82812
|
+
powershellCommand(
|
|
82813
|
+
`${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
|
|
82814
|
+
true
|
|
82815
|
+
)
|
|
82740
82816
|
],
|
|
82741
|
-
|
|
82742
|
-
|
|
82743
|
-
|
|
82744
|
-
|
|
82745
|
-
"-NonInteractive",
|
|
82746
|
-
"-Command",
|
|
82747
|
-
`Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} | Format-List TaskName,State`
|
|
82748
|
-
],
|
|
82749
|
-
tolerateFailure: true
|
|
82750
|
-
},
|
|
82751
|
-
isActiveCommand: {
|
|
82752
|
-
command: "powershell.exe",
|
|
82753
|
-
args: [
|
|
82754
|
-
"-NoProfile",
|
|
82755
|
-
"-NonInteractive",
|
|
82756
|
-
"-Command",
|
|
82757
|
-
`(Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue).State`
|
|
82758
|
-
],
|
|
82759
|
-
tolerateFailure: true
|
|
82760
|
-
},
|
|
82761
|
-
lingerStatusCommand: null
|
|
82817
|
+
uninstallCommands: [powershellCommand(uninstallScript)],
|
|
82818
|
+
statusCommand: powershellCommand(statusScript, true),
|
|
82819
|
+
isActiveCommand: powershellCommand(statusScript, true),
|
|
82820
|
+
definitionMarker
|
|
82762
82821
|
};
|
|
82763
82822
|
}
|
|
82764
82823
|
function resolveCurrentPlatform() {
|
|
@@ -82768,16 +82827,16 @@ function resolveCurrentPlatform() {
|
|
|
82768
82827
|
}
|
|
82769
82828
|
return currentPlatform;
|
|
82770
82829
|
}
|
|
82771
|
-
function currentServicePlan(args, runtime) {
|
|
82772
|
-
const currentPlatform = resolveCurrentPlatform();
|
|
82830
|
+
function currentServicePlan(args, runtime, options = {}) {
|
|
82831
|
+
const currentPlatform = options.platform ?? resolveCurrentPlatform();
|
|
82773
82832
|
const profile = resolveEndpointProfileFromArgs(args);
|
|
82774
82833
|
const cliEntry = runtime?.cliEntry ?? process.argv[1];
|
|
82775
82834
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
82776
82835
|
return buildDaemonServicePlan({
|
|
82777
82836
|
platform: currentPlatform,
|
|
82778
|
-
homeDir: (0, import_node_os16.homedir)(),
|
|
82837
|
+
homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
|
|
82779
82838
|
nodeExecutable: runtime?.executable ?? process.execPath,
|
|
82780
|
-
cliEntry: (0, import_node_path27.resolve)(cliEntry),
|
|
82839
|
+
cliEntry: currentPlatform === "win32" ? import_node_path27.win32.resolve(cliEntry) : (0, import_node_path27.resolve)(cliEntry),
|
|
82781
82840
|
profile,
|
|
82782
82841
|
environment: runtime?.environment
|
|
82783
82842
|
});
|
|
@@ -82797,10 +82856,14 @@ function defaultReadManifest(path2) {
|
|
|
82797
82856
|
if (!(0, import_node_fs25.existsSync)(path2)) return null;
|
|
82798
82857
|
return (0, import_node_fs25.readFileSync)(path2, "utf8");
|
|
82799
82858
|
}
|
|
82800
|
-
function runServiceCommand(command) {
|
|
82801
|
-
const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
|
|
82859
|
+
function runServiceCommand(command, timeoutMs = 15e3) {
|
|
82860
|
+
const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
|
|
82861
|
+
encoding: "utf8",
|
|
82862
|
+
shell: false,
|
|
82863
|
+
...(0, import_node_os16.platform)() === "win32" ? { timeout: timeoutMs, windowsHide: true } : {}
|
|
82864
|
+
});
|
|
82802
82865
|
const status = result.status ?? 1;
|
|
82803
|
-
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
82866
|
+
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
|
|
82804
82867
|
if (status !== 0 && !command.tolerateFailure) {
|
|
82805
82868
|
throw new Error(
|
|
82806
82869
|
`Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
|
|
@@ -82813,6 +82876,23 @@ function defaultSleep(ms) {
|
|
|
82813
82876
|
setTimeout(resolveSleep, ms);
|
|
82814
82877
|
});
|
|
82815
82878
|
}
|
|
82879
|
+
function isProcessAlive(pid) {
|
|
82880
|
+
if (pid <= 0) return false;
|
|
82881
|
+
try {
|
|
82882
|
+
process.kill(pid, 0);
|
|
82883
|
+
return true;
|
|
82884
|
+
} catch (error61) {
|
|
82885
|
+
return error61.code === "EPERM";
|
|
82886
|
+
}
|
|
82887
|
+
}
|
|
82888
|
+
async function requestWindowsDaemonShutdown(owner) {
|
|
82889
|
+
try {
|
|
82890
|
+
const response = await requestLocalDaemonShutdown(owner.localApiPort, owner.localApiToken);
|
|
82891
|
+
return response.ok ? "accepted" : "rejected";
|
|
82892
|
+
} catch {
|
|
82893
|
+
return "unreachable";
|
|
82894
|
+
}
|
|
82895
|
+
}
|
|
82816
82896
|
function parseServiceHostState(servicePlatform, result) {
|
|
82817
82897
|
const output2 = result.output;
|
|
82818
82898
|
if (servicePlatform === "linux") {
|
|
@@ -82838,9 +82918,69 @@ function parseServiceHostState(servicePlatform, result) {
|
|
|
82838
82918
|
const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
|
|
82839
82919
|
const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
|
|
82840
82920
|
if (state === "running") return "running";
|
|
82841
|
-
if (state === "
|
|
82921
|
+
if (state === "queued") return "starting";
|
|
82922
|
+
if (state === "ready" || state === "disabled") return "stopped";
|
|
82842
82923
|
return "unknown";
|
|
82843
82924
|
}
|
|
82925
|
+
function parseWindowsTaskInspection(result) {
|
|
82926
|
+
if (result.status !== 0) {
|
|
82927
|
+
throw new Error("Cannot inspect Windows daemon task definition");
|
|
82928
|
+
}
|
|
82929
|
+
const value2 = (name) => {
|
|
82930
|
+
const match = result.output.match(new RegExp(`^${name}:\\s*(.*)$`, "imu"));
|
|
82931
|
+
if (!match) throw new Error(`Incomplete Windows daemon task inspection: missing ${name}`);
|
|
82932
|
+
return match[1]?.trim() ?? "";
|
|
82933
|
+
};
|
|
82934
|
+
const boolean4 = (name) => {
|
|
82935
|
+
const parsed = value2(name).toLowerCase();
|
|
82936
|
+
if (parsed === "true") return true;
|
|
82937
|
+
if (parsed === "false") return false;
|
|
82938
|
+
throw new Error(`Incomplete Windows daemon task inspection: invalid ${name}`);
|
|
82939
|
+
};
|
|
82940
|
+
const currentPresent = /^TaskName:\s*.+$/imu.test(result.output);
|
|
82941
|
+
const currentStateValue = value2("State");
|
|
82942
|
+
const currentOwned = boolean4("CurrentOwned");
|
|
82943
|
+
const definitionMatches = boolean4("DefinitionMatch");
|
|
82944
|
+
const legacyPresent = boolean4("LegacyTask");
|
|
82945
|
+
const legacyOwned = boolean4("LegacyOwned");
|
|
82946
|
+
const legacyStateValue = value2("LegacyState");
|
|
82947
|
+
if (currentPresent !== (currentStateValue.toLowerCase() !== "missing")) {
|
|
82948
|
+
throw new Error("Incomplete Windows daemon task inspection: inconsistent current task state");
|
|
82949
|
+
}
|
|
82950
|
+
if (!currentPresent && (currentOwned || definitionMatches)) {
|
|
82951
|
+
throw new Error(
|
|
82952
|
+
"Incomplete Windows daemon task inspection: absent current task is marked owned"
|
|
82953
|
+
);
|
|
82954
|
+
}
|
|
82955
|
+
if (legacyPresent !== (legacyStateValue.toLowerCase() !== "missing") || !legacyPresent && legacyOwned) {
|
|
82956
|
+
throw new Error("Incomplete Windows daemon task inspection: inconsistent legacy task state");
|
|
82957
|
+
}
|
|
82958
|
+
return {
|
|
82959
|
+
currentPresent,
|
|
82960
|
+
currentOwned,
|
|
82961
|
+
currentState: currentPresent ? parseServiceHostState("win32", { status: 0, output: currentStateValue }) : "stopped",
|
|
82962
|
+
definitionMatches,
|
|
82963
|
+
definitionMarker: result.output.match(/^Definition:\s*(.+)$/imu)?.[1]?.trim(),
|
|
82964
|
+
legacyPresent,
|
|
82965
|
+
legacyOwned,
|
|
82966
|
+
legacyState: legacyPresent ? parseServiceHostState("win32", { status: 0, output: legacyStateValue }) : "stopped"
|
|
82967
|
+
};
|
|
82968
|
+
}
|
|
82969
|
+
function windowsTaskHostState(tasks) {
|
|
82970
|
+
const states = [tasks.currentState, tasks.legacyState];
|
|
82971
|
+
if (states.includes("unknown")) return "unknown";
|
|
82972
|
+
if (states.includes("running")) return "running";
|
|
82973
|
+
if (states.includes("starting")) return "starting";
|
|
82974
|
+
return "stopped";
|
|
82975
|
+
}
|
|
82976
|
+
function assertWindowsTaskOwnership(tasks) {
|
|
82977
|
+
if (tasks.currentPresent && !tasks.currentOwned) {
|
|
82978
|
+
throw new Error("Windows daemon task is not owned by this Alan installation");
|
|
82979
|
+
}
|
|
82980
|
+
if (tasks.legacyPresent && !tasks.legacyOwned) {
|
|
82981
|
+
throw new Error("Legacy Windows daemon task is not owned by this Alan installation");
|
|
82982
|
+
}
|
|
82983
|
+
}
|
|
82844
82984
|
function resolveEnsureDefinitionOutcome(input2) {
|
|
82845
82985
|
if (!input2.definitionChanged) {
|
|
82846
82986
|
return { changed: false, updatePending: false };
|
|
@@ -82850,9 +82990,6 @@ function resolveEnsureDefinitionOutcome(input2) {
|
|
|
82850
82990
|
}
|
|
82851
82991
|
return { changed: true, updatePending: false };
|
|
82852
82992
|
}
|
|
82853
|
-
function shouldStartOnInstall(hostState) {
|
|
82854
|
-
return hostState === "stopped";
|
|
82855
|
-
}
|
|
82856
82993
|
function resolveUsername(options) {
|
|
82857
82994
|
if (options?.username) return options.username;
|
|
82858
82995
|
try {
|
|
@@ -82861,17 +82998,14 @@ function resolveUsername(options) {
|
|
|
82861
82998
|
return process.env.USER || process.env.USERNAME || "unknown";
|
|
82862
82999
|
}
|
|
82863
83000
|
}
|
|
82864
|
-
function buildLingerStatusCommand(username) {
|
|
82865
|
-
return {
|
|
82866
|
-
command: "loginctl",
|
|
82867
|
-
args: ["show-user", username, "-p", "Linger"],
|
|
82868
|
-
tolerateFailure: true
|
|
82869
|
-
};
|
|
82870
|
-
}
|
|
82871
83001
|
function assertSystemdUserLingerEnabled(options = {}) {
|
|
82872
83002
|
const username = resolveUsername(options);
|
|
82873
83003
|
const run = options.runCommand ?? runServiceCommand;
|
|
82874
|
-
const result = run(
|
|
83004
|
+
const result = run({
|
|
83005
|
+
command: "loginctl",
|
|
83006
|
+
args: ["show-user", username, "-p", "Linger"],
|
|
83007
|
+
tolerateFailure: true
|
|
83008
|
+
});
|
|
82875
83009
|
if (result.status !== 0) {
|
|
82876
83010
|
throw new Error(
|
|
82877
83011
|
`Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
|
|
@@ -82885,10 +83019,18 @@ function assertSystemdUserLingerEnabled(options = {}) {
|
|
|
82885
83019
|
}
|
|
82886
83020
|
function createHostContext(options = {}) {
|
|
82887
83021
|
const args = options.args ?? [];
|
|
82888
|
-
const plan = currentServicePlan(args, options.runtime);
|
|
82889
|
-
const
|
|
82890
|
-
const sleep4 = options.sleep ?? defaultSleep;
|
|
83022
|
+
const plan = currentServicePlan(args, options.runtime, options);
|
|
83023
|
+
const sleep5 = options.sleep ?? defaultSleep;
|
|
82891
83024
|
const nowMs = options.nowMs ?? Date.now;
|
|
83025
|
+
const run = (command) => {
|
|
83026
|
+
const remainingMs = options.deadlineMs === void 0 ? 15e3 : options.deadlineMs - nowMs();
|
|
83027
|
+
if (remainingMs <= 0) throw new Error("Windows service setup deadline expired");
|
|
83028
|
+
const result = options.runCommand ? options.runCommand(command) : runServiceCommand(command, Math.min(15e3, remainingMs));
|
|
83029
|
+
if (options.deadlineMs !== void 0 && nowMs() >= options.deadlineMs) {
|
|
83030
|
+
throw new Error("Windows service setup deadline expired");
|
|
83031
|
+
}
|
|
83032
|
+
return result;
|
|
83033
|
+
};
|
|
82892
83034
|
const readManifest = options.readManifest ?? defaultReadManifest;
|
|
82893
83035
|
const writeManifest = options.writeManifest ?? writeManifestAtomic;
|
|
82894
83036
|
const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs25.rmSync)(path2, { force: true }));
|
|
@@ -82899,19 +83041,37 @@ function createHostContext(options = {}) {
|
|
|
82899
83041
|
args,
|
|
82900
83042
|
plan,
|
|
82901
83043
|
run,
|
|
82902
|
-
sleep:
|
|
83044
|
+
sleep: sleep5,
|
|
82903
83045
|
nowMs,
|
|
82904
83046
|
readManifest,
|
|
82905
83047
|
writeManifest,
|
|
82906
83048
|
removeManifest,
|
|
82907
83049
|
ensureLogDir,
|
|
82908
|
-
profile: resolveEndpointProfileFromArgs(args)
|
|
83050
|
+
configPath: options.configPath ?? resolveAgentConfigPath({ profile: resolveEndpointProfileFromArgs(args) }),
|
|
83051
|
+
readOwner: options.readOwner ?? readLocalDaemonOwnerLease,
|
|
83052
|
+
isProcessAlive: options.isProcessAlive ?? isProcessAlive,
|
|
83053
|
+
probeEndpoint: options.probeEndpoint ?? probeLocalDaemonEndpoint,
|
|
83054
|
+
requestWindowsShutdown: options.requestWindowsShutdown ?? requestWindowsDaemonShutdown,
|
|
83055
|
+
inspectJournal: options.inspectJournal ?? inspectLocalDaemonRunJournal,
|
|
83056
|
+
acquireMaintenance: options.acquireMaintenance ?? acquireLocalDaemonMaintenanceLease,
|
|
83057
|
+
releaseMaintenance: options.releaseMaintenance ?? releaseLocalDaemonMaintenanceLease
|
|
82909
83058
|
};
|
|
82910
83059
|
}
|
|
82911
83060
|
function inspectDaemonService(options = {}) {
|
|
82912
83061
|
const { plan, run, nowMs } = createHostContext(options);
|
|
82913
83062
|
const result = run(plan.isActiveCommand);
|
|
82914
|
-
|
|
83063
|
+
let state;
|
|
83064
|
+
if (plan.platform === "win32") {
|
|
83065
|
+
try {
|
|
83066
|
+
const tasks = parseWindowsTaskInspection(result);
|
|
83067
|
+
assertWindowsTaskOwnership(tasks);
|
|
83068
|
+
state = windowsTaskHostState(tasks);
|
|
83069
|
+
} catch {
|
|
83070
|
+
state = "unknown";
|
|
83071
|
+
}
|
|
83072
|
+
} else {
|
|
83073
|
+
state = parseServiceHostState(plan.platform, result);
|
|
83074
|
+
}
|
|
82915
83075
|
return {
|
|
82916
83076
|
state,
|
|
82917
83077
|
platform: plan.platform,
|
|
@@ -82921,8 +83081,9 @@ function inspectDaemonService(options = {}) {
|
|
|
82921
83081
|
}
|
|
82922
83082
|
function ensureDaemonServiceDefinition(options = {}) {
|
|
82923
83083
|
const ctx = createHostContext(options);
|
|
82924
|
-
const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
83084
|
+
const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
82925
83085
|
let definitionChanged = false;
|
|
83086
|
+
let inspection;
|
|
82926
83087
|
if (plan.manifestPath && plan.manifest) {
|
|
82927
83088
|
ensureLogDir();
|
|
82928
83089
|
const existing = readManifest(plan.manifestPath);
|
|
@@ -82931,20 +83092,37 @@ function ensureDaemonServiceDefinition(options = {}) {
|
|
|
82931
83092
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
82932
83093
|
}
|
|
82933
83094
|
} else if (plan.platform === "win32") {
|
|
82934
|
-
|
|
83095
|
+
ensureLogDir();
|
|
83096
|
+
const result = run(plan.statusCommand);
|
|
83097
|
+
const tasks = parseWindowsTaskInspection(result);
|
|
83098
|
+
assertWindowsTaskOwnership(tasks);
|
|
83099
|
+
if (tasks.currentPresent && tasks.currentState === "unknown" || tasks.legacyPresent && tasks.legacyState === "unknown") {
|
|
83100
|
+
throw new Error("Cannot safely update a Windows daemon task with unknown state");
|
|
83101
|
+
}
|
|
83102
|
+
definitionChanged = tasks.definitionMarker !== plan.definitionMarker || !tasks.definitionMatches || tasks.legacyPresent;
|
|
83103
|
+
inspection = {
|
|
83104
|
+
state: windowsTaskHostState(tasks),
|
|
83105
|
+
platform: plan.platform,
|
|
83106
|
+
detail: result.output || void 0,
|
|
83107
|
+
observedAtMs: nowMs()
|
|
83108
|
+
};
|
|
82935
83109
|
}
|
|
82936
|
-
|
|
83110
|
+
inspection ??= inspectDaemonService(options);
|
|
83111
|
+
const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
|
|
82937
83112
|
const outcome = resolveEnsureDefinitionOutcome({
|
|
82938
83113
|
definitionChanged,
|
|
82939
|
-
hostState:
|
|
83114
|
+
hostState: definitionHostState
|
|
82940
83115
|
});
|
|
82941
83116
|
if (outcome.updatePending) {
|
|
82942
83117
|
return outcome;
|
|
82943
83118
|
}
|
|
82944
|
-
if (
|
|
83119
|
+
if (plan.platform === "win32" && !outcome.changed) {
|
|
82945
83120
|
return outcome;
|
|
82946
83121
|
}
|
|
82947
|
-
|
|
83122
|
+
if (definitionHostState === "running" || definitionHostState === "starting") {
|
|
83123
|
+
return outcome;
|
|
83124
|
+
}
|
|
83125
|
+
const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
|
|
82948
83126
|
for (const command of definitionCommands) run(command);
|
|
82949
83127
|
return outcome;
|
|
82950
83128
|
}
|
|
@@ -82956,10 +83134,140 @@ function startDaemonService(options = {}) {
|
|
|
82956
83134
|
const { plan, run } = createHostContext(options);
|
|
82957
83135
|
for (const command of plan.startCommands) run(command);
|
|
82958
83136
|
}
|
|
82959
|
-
function
|
|
83137
|
+
async function stopDaemonServiceAndWait(identity, timeoutMs, options = {}) {
|
|
83138
|
+
const {
|
|
83139
|
+
plan,
|
|
83140
|
+
run,
|
|
83141
|
+
sleep: sleep5,
|
|
83142
|
+
nowMs,
|
|
83143
|
+
configPath,
|
|
83144
|
+
readOwner,
|
|
83145
|
+
isProcessAlive: processIsAlive,
|
|
83146
|
+
probeEndpoint,
|
|
83147
|
+
requestWindowsShutdown: requestShutdown
|
|
83148
|
+
} = createHostContext(options);
|
|
83149
|
+
let owner;
|
|
83150
|
+
try {
|
|
83151
|
+
owner = readOwner(configPath);
|
|
83152
|
+
} catch {
|
|
83153
|
+
return "identity_changed";
|
|
83154
|
+
}
|
|
83155
|
+
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);
|
|
83156
|
+
if (!owner || !ownerMatches(owner)) {
|
|
83157
|
+
return "identity_changed";
|
|
83158
|
+
}
|
|
83159
|
+
if (plan.platform === "win32") {
|
|
83160
|
+
const shutdown = await requestShutdown(owner);
|
|
83161
|
+
if (shutdown === "rejected") return "identity_changed";
|
|
83162
|
+
if (shutdown === "unreachable") {
|
|
83163
|
+
for (const command of plan.stopCommands) run(command);
|
|
83164
|
+
}
|
|
83165
|
+
const deadline2 = nowMs() + timeoutMs;
|
|
83166
|
+
while (nowMs() < deadline2) {
|
|
83167
|
+
let currentOwner;
|
|
83168
|
+
try {
|
|
83169
|
+
currentOwner = readOwner(configPath);
|
|
83170
|
+
} catch {
|
|
83171
|
+
return "identity_changed";
|
|
83172
|
+
}
|
|
83173
|
+
if (currentOwner && !ownerMatches(currentOwner)) {
|
|
83174
|
+
return "identity_changed";
|
|
83175
|
+
}
|
|
83176
|
+
if (!processIsAlive(identity.pid)) {
|
|
83177
|
+
const endpoint = await probeEndpoint(identity.port, owner.localApiToken, 250);
|
|
83178
|
+
if (endpoint.state === "absent") return "exited";
|
|
83179
|
+
if (endpoint.state === "listening_unauthorized" || endpoint.status.daemonSessionId !== identity.daemonSessionId) {
|
|
83180
|
+
return "identity_changed";
|
|
83181
|
+
}
|
|
83182
|
+
}
|
|
83183
|
+
await sleep5(STOP_POLL_INTERVAL_MS);
|
|
83184
|
+
}
|
|
83185
|
+
return "timeout";
|
|
83186
|
+
}
|
|
83187
|
+
for (const command of plan.stopCommands) run(command);
|
|
83188
|
+
const deadline = nowMs() + timeoutMs;
|
|
83189
|
+
while (nowMs() < deadline) {
|
|
83190
|
+
const result = run(plan.isActiveCommand);
|
|
83191
|
+
const state = parseServiceHostState(plan.platform, result);
|
|
83192
|
+
if (state === "stopped") return "exited";
|
|
83193
|
+
if (state === "unknown") {
|
|
83194
|
+
} else if (identity.pid > 0) {
|
|
83195
|
+
try {
|
|
83196
|
+
process.kill(identity.pid, 0);
|
|
83197
|
+
} catch {
|
|
83198
|
+
return "exited";
|
|
83199
|
+
}
|
|
83200
|
+
}
|
|
83201
|
+
await sleep5(STOP_POLL_INTERVAL_MS);
|
|
83202
|
+
}
|
|
83203
|
+
return "timeout";
|
|
83204
|
+
}
|
|
83205
|
+
async function uninstallDaemonService(args = [], options = {}) {
|
|
82960
83206
|
const ctx = createHostContext({ ...options, args: options.args ?? args });
|
|
82961
|
-
|
|
82962
|
-
|
|
83207
|
+
if (ctx.plan.platform === "win32") {
|
|
83208
|
+
const ownership = ctx.run(ctx.plan.statusCommand);
|
|
83209
|
+
let tasks;
|
|
83210
|
+
try {
|
|
83211
|
+
tasks = parseWindowsTaskInspection(ownership);
|
|
83212
|
+
} catch (error61) {
|
|
83213
|
+
throw new Error(
|
|
83214
|
+
`Cannot inspect Windows daemon task ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
|
|
83215
|
+
);
|
|
83216
|
+
}
|
|
83217
|
+
assertWindowsTaskOwnership(tasks);
|
|
83218
|
+
if (!tasks.currentPresent && !tasks.legacyPresent) {
|
|
83219
|
+
console.info("[alan-agent] Per-user daemon service removed");
|
|
83220
|
+
return;
|
|
83221
|
+
}
|
|
83222
|
+
const acquired = ctx.acquireMaintenance(ctx.configPath, "watchdog", 3e4);
|
|
83223
|
+
if (!acquired.acquired) {
|
|
83224
|
+
throw new Error("Cannot uninstall the Windows daemon service while maintenance is active");
|
|
83225
|
+
}
|
|
83226
|
+
try {
|
|
83227
|
+
const journal = ctx.inspectJournal(ctx.configPath);
|
|
83228
|
+
if (!journal.safeToRestart) {
|
|
83229
|
+
throw new Error(
|
|
83230
|
+
`Windows daemon service uninstall is deferred while ${journal.activeRunCount} active run${journal.activeRunCount === 1 ? "" : "s"} finish`
|
|
83231
|
+
);
|
|
83232
|
+
}
|
|
83233
|
+
let owner;
|
|
83234
|
+
try {
|
|
83235
|
+
owner = ctx.readOwner(ctx.configPath);
|
|
83236
|
+
} catch (error61) {
|
|
83237
|
+
throw new Error(
|
|
83238
|
+
`Cannot verify Windows daemon ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
|
|
83239
|
+
);
|
|
83240
|
+
}
|
|
83241
|
+
if (owner) {
|
|
83242
|
+
const stopped = await stopDaemonServiceAndWait(
|
|
83243
|
+
{
|
|
83244
|
+
pid: owner.pid,
|
|
83245
|
+
serviceId: owner.localServiceId,
|
|
83246
|
+
daemonSessionId: owner.daemonSessionId,
|
|
83247
|
+
port: owner.localApiPort,
|
|
83248
|
+
startedAtMs: owner.startedAtMs
|
|
83249
|
+
},
|
|
83250
|
+
1e4,
|
|
83251
|
+
{ ...options, args: ctx.args }
|
|
83252
|
+
);
|
|
83253
|
+
if (stopped !== "exited") {
|
|
83254
|
+
throw new Error(
|
|
83255
|
+
stopped === "identity_changed" ? "Windows daemon ownership changed before uninstall" : "Timed out waiting for the Windows daemon to stop before uninstall"
|
|
83256
|
+
);
|
|
83257
|
+
}
|
|
83258
|
+
} else {
|
|
83259
|
+
if (tasks.currentState !== "stopped" || tasks.legacyState !== "stopped") {
|
|
83260
|
+
throw new Error("Cannot uninstall a running Windows daemon without verified ownership");
|
|
83261
|
+
}
|
|
83262
|
+
}
|
|
83263
|
+
for (const command of ctx.plan.uninstallCommands) ctx.run(command);
|
|
83264
|
+
} finally {
|
|
83265
|
+
ctx.releaseMaintenance(ctx.configPath, acquired.lease.id);
|
|
83266
|
+
}
|
|
83267
|
+
} else {
|
|
83268
|
+
for (const command of ctx.plan.uninstallCommands) ctx.run(command);
|
|
83269
|
+
if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
|
|
83270
|
+
}
|
|
82963
83271
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
82964
83272
|
}
|
|
82965
83273
|
function installDaemonService(args = [], runtime, options = {}) {
|
|
@@ -82987,7 +83295,7 @@ function installDaemonService(args = [], runtime, options = {}) {
|
|
|
82987
83295
|
});
|
|
82988
83296
|
return;
|
|
82989
83297
|
}
|
|
82990
|
-
if (
|
|
83298
|
+
if (inspection.state !== "stopped") {
|
|
82991
83299
|
console.info("[alan-agent] Per-user daemon service already present", {
|
|
82992
83300
|
profile,
|
|
82993
83301
|
state: inspection.state
|
|
@@ -83011,13 +83319,81 @@ ${result.output}
|
|
|
83011
83319
|
}
|
|
83012
83320
|
|
|
83013
83321
|
// src/install-command.ts
|
|
83014
|
-
|
|
83322
|
+
var WINDOWS_SETUP_READY_TIMEOUT_MS = 9e3;
|
|
83323
|
+
async function setupWindowsRuntime(commandArgs) {
|
|
83324
|
+
const deadlineMs = Date.now() + WINDOWS_SETUP_READY_TIMEOUT_MS;
|
|
83325
|
+
const controller = new AbortController();
|
|
83326
|
+
const timeoutError = new Error(
|
|
83327
|
+
"Windows runtime did not become ready within 9 seconds. Credential setup may still complete in the running daemon; it has not been rolled back. Check runtime status and retry the same setup command to reconcile."
|
|
83328
|
+
);
|
|
83329
|
+
let timer;
|
|
83330
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
83331
|
+
timer = setTimeout(() => {
|
|
83332
|
+
controller.abort(timeoutError);
|
|
83333
|
+
reject(timeoutError);
|
|
83334
|
+
}, WINDOWS_SETUP_READY_TIMEOUT_MS);
|
|
83335
|
+
});
|
|
83336
|
+
const remainingMs = () => {
|
|
83337
|
+
const remaining = deadlineMs - Date.now();
|
|
83338
|
+
if (remaining <= 0) throw timeoutError;
|
|
83339
|
+
controller.signal.throwIfAborted();
|
|
83340
|
+
return remaining;
|
|
83341
|
+
};
|
|
83342
|
+
try {
|
|
83343
|
+
const runtimeIdentityChanged = await Promise.race([
|
|
83344
|
+
setupDaemon(commandArgs, { signal: controller.signal }),
|
|
83345
|
+
timeout
|
|
83346
|
+
]);
|
|
83347
|
+
remainingMs();
|
|
83348
|
+
const { runtimeId, localApiPort, localApiToken } = readConfig();
|
|
83349
|
+
if (!runtimeId || !localApiPort || !localApiToken) {
|
|
83350
|
+
throw new Error("Windows runtime setup completed without a local readiness endpoint");
|
|
83351
|
+
}
|
|
83352
|
+
const probe = () => probeLocalDaemon(localApiPort, localApiToken, Math.min(250, remainingMs()));
|
|
83353
|
+
const poll = () => (0, import_promises6.setTimeout)(Math.min(100, remainingMs()), void 0, { signal: controller.signal });
|
|
83354
|
+
if (runtimeIdentityChanged) {
|
|
83355
|
+
while (true) {
|
|
83356
|
+
const status = await probe();
|
|
83357
|
+
if (status?.runtimeId === runtimeId && status.connected) break;
|
|
83358
|
+
if (!status) {
|
|
83359
|
+
const service = inspectDaemonService({ args: commandArgs, deadlineMs });
|
|
83360
|
+
if (service.state !== "running" && service.state !== "starting") break;
|
|
83361
|
+
}
|
|
83362
|
+
await poll();
|
|
83363
|
+
}
|
|
83364
|
+
}
|
|
83365
|
+
remainingMs();
|
|
83366
|
+
installDaemonService(commandArgs, void 0, { deadlineMs });
|
|
83367
|
+
while (true) {
|
|
83368
|
+
const status = await probe();
|
|
83369
|
+
remainingMs();
|
|
83370
|
+
if (status?.runtimeId === runtimeId && status.connected) return;
|
|
83371
|
+
await poll();
|
|
83372
|
+
}
|
|
83373
|
+
} catch (error61) {
|
|
83374
|
+
if (controller.signal.aborted || Date.now() >= deadlineMs) {
|
|
83375
|
+
controller.abort(timeoutError);
|
|
83376
|
+
throw timeoutError;
|
|
83377
|
+
}
|
|
83378
|
+
throw error61;
|
|
83379
|
+
} finally {
|
|
83380
|
+
clearTimeout(timer);
|
|
83381
|
+
}
|
|
83382
|
+
}
|
|
83383
|
+
async function runInstallCommand(commandArgs, currentPlatform = process.platform) {
|
|
83384
|
+
if (currentPlatform === "win32" && commandArgs.includes("--headless")) {
|
|
83385
|
+
throw new Error("--headless is only supported on Linux systemd user services");
|
|
83386
|
+
}
|
|
83015
83387
|
bindConfigPath(commandArgs);
|
|
83016
83388
|
const configured = readConfig();
|
|
83017
83389
|
const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
|
|
83018
|
-
if (configurationMode === "setup")
|
|
83019
|
-
|
|
83020
|
-
|
|
83390
|
+
if (currentPlatform === "win32" && configurationMode === "setup") {
|
|
83391
|
+
await setupWindowsRuntime(commandArgs);
|
|
83392
|
+
} else {
|
|
83393
|
+
if (configurationMode === "setup") await setupDaemon(commandArgs);
|
|
83394
|
+
if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
|
|
83395
|
+
installDaemonService(commandArgs);
|
|
83396
|
+
}
|
|
83021
83397
|
runMcpIntegrationCommand(["reconcile", ...commandArgs]);
|
|
83022
83398
|
}
|
|
83023
83399
|
|
|
@@ -86395,7 +86771,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
|
|
|
86395
86771
|
function inputRequiredRoundsExceededMessage2(method, maxRounds) {
|
|
86396
86772
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
86397
86773
|
}
|
|
86398
|
-
function
|
|
86774
|
+
function sleep4(ms, signal) {
|
|
86399
86775
|
return new Promise((resolve15, reject) => {
|
|
86400
86776
|
if (signal?.aborted) {
|
|
86401
86777
|
reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
|
|
@@ -94662,7 +95038,7 @@ var LegacyInputRequiredShim = class {
|
|
|
94662
95038
|
} finally {
|
|
94663
95039
|
roundAbort.dispose();
|
|
94664
95040
|
}
|
|
94665
|
-
} else await
|
|
95041
|
+
} else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
|
|
94666
95042
|
let ctxNext = {
|
|
94667
95043
|
...ctx,
|
|
94668
95044
|
mcpReq: {
|
|
@@ -95459,7 +95835,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
|
|
|
95459
95835
|
}
|
|
95460
95836
|
|
|
95461
95837
|
// src/native-hook-command.ts
|
|
95462
|
-
var
|
|
95838
|
+
var import_node_crypto22 = require("crypto");
|
|
95463
95839
|
var import_node_fs27 = require("fs");
|
|
95464
95840
|
var import_node_os17 = require("os");
|
|
95465
95841
|
var import_node_path29 = require("path");
|
|
@@ -95828,7 +96204,7 @@ function parseNativeHookEnvelope(input2) {
|
|
|
95828
96204
|
const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
|
|
95829
96205
|
return {
|
|
95830
96206
|
schemaVersion: 1,
|
|
95831
|
-
eventId: (0,
|
|
96207
|
+
eventId: (0, import_node_crypto22.randomUUID)(),
|
|
95832
96208
|
provider: capability.provider,
|
|
95833
96209
|
eventType,
|
|
95834
96210
|
observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -96324,7 +96700,7 @@ function delay2(ms) {
|
|
|
96324
96700
|
}
|
|
96325
96701
|
|
|
96326
96702
|
// src/web-presenter.ts
|
|
96327
|
-
var
|
|
96703
|
+
var import_node_crypto23 = require("crypto");
|
|
96328
96704
|
var WebPresenter = class {
|
|
96329
96705
|
constructor(wsClient, conversationId, cwd) {
|
|
96330
96706
|
this.conversationId = conversationId;
|
|
@@ -96510,7 +96886,7 @@ var WebPresenter = class {
|
|
|
96510
96886
|
this.applicationProblem = createAlanProblem({
|
|
96511
96887
|
domain: "application",
|
|
96512
96888
|
code: input2.code,
|
|
96513
|
-
id: (0,
|
|
96889
|
+
id: (0, import_node_crypto23.randomUUID)(),
|
|
96514
96890
|
title: APPLICATION_PROBLEM_TITLES[input2.code],
|
|
96515
96891
|
detail: input2.message,
|
|
96516
96892
|
// The schema's slot for the technical cause, so the problem carries it
|
|
@@ -96580,7 +96956,7 @@ var WebPresenter = class {
|
|
|
96580
96956
|
};
|
|
96581
96957
|
|
|
96582
96958
|
// src/ws-client.ts
|
|
96583
|
-
var
|
|
96959
|
+
var import_node_crypto25 = require("crypto");
|
|
96584
96960
|
|
|
96585
96961
|
// ../shared/dist/agent-liveness.js
|
|
96586
96962
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -96776,13 +97152,13 @@ var AgentResourceSampler = class {
|
|
|
96776
97152
|
};
|
|
96777
97153
|
|
|
96778
97154
|
// src/sandbox-outbox.ts
|
|
96779
|
-
var
|
|
97155
|
+
var import_node_crypto24 = require("crypto");
|
|
96780
97156
|
var import_node_fs29 = require("fs");
|
|
96781
97157
|
function deriveSandboxOutboxKey(sessionToken) {
|
|
96782
97158
|
if (!sessionToken) {
|
|
96783
97159
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
96784
97160
|
}
|
|
96785
|
-
return (0,
|
|
97161
|
+
return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
96786
97162
|
}
|
|
96787
97163
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
96788
97164
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -96959,7 +97335,7 @@ var WSClient = class {
|
|
|
96959
97335
|
}
|
|
96960
97336
|
socket;
|
|
96961
97337
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
96962
|
-
agentSessionId = (0,
|
|
97338
|
+
agentSessionId = (0, import_node_crypto25.randomUUID)();
|
|
96963
97339
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
96964
97340
|
heartbeatSeq = 0;
|
|
96965
97341
|
heartbeatTimer = null;
|
|
@@ -97795,7 +98171,7 @@ async function runSandbox(config2) {
|
|
|
97795
98171
|
}
|
|
97796
98172
|
|
|
97797
98173
|
// src/skills/skill-cli.ts
|
|
97798
|
-
var
|
|
98174
|
+
var import_promises7 = require("fs/promises");
|
|
97799
98175
|
var import_node_os20 = require("os");
|
|
97800
98176
|
var import_node_path31 = require("path");
|
|
97801
98177
|
async function runSkillsCommand(args) {
|
|
@@ -97842,7 +98218,7 @@ async function defaultScanInput(homeDirectory2) {
|
|
|
97842
98218
|
}
|
|
97843
98219
|
async function readProtectedJson(path2) {
|
|
97844
98220
|
const absolutePath = (0, import_node_path31.resolve)(path2);
|
|
97845
|
-
const stat2 = await (0,
|
|
98221
|
+
const stat2 = await (0, import_promises7.lstat)(absolutePath);
|
|
97846
98222
|
if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
|
|
97847
98223
|
if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
|
|
97848
98224
|
throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
|
|
@@ -97851,7 +98227,7 @@ async function readProtectedJson(path2) {
|
|
|
97851
98227
|
throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
|
|
97852
98228
|
}
|
|
97853
98229
|
if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
|
|
97854
|
-
return JSON.parse(await (0,
|
|
98230
|
+
return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
|
|
97855
98231
|
}
|
|
97856
98232
|
function option(args, name) {
|
|
97857
98233
|
const index = args.indexOf(name);
|
|
@@ -98009,7 +98385,7 @@ async function main() {
|
|
|
98009
98385
|
return;
|
|
98010
98386
|
}
|
|
98011
98387
|
if (command === "service" && commandArgs[0] === "uninstall") {
|
|
98012
|
-
uninstallDaemonService(commandArgs.slice(1));
|
|
98388
|
+
await uninstallDaemonService(commandArgs.slice(1));
|
|
98013
98389
|
return;
|
|
98014
98390
|
}
|
|
98015
98391
|
if (command === "service" && commandArgs[0] === "status") {
|