@alan-ai-hq/agent-manager 0.1.96 → 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 +823 -256
- package/package.json +1 -1
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",
|
|
@@ -14583,6 +14583,15 @@ var import_node_os = require("os");
|
|
|
14583
14583
|
var import_node_path = require("path");
|
|
14584
14584
|
|
|
14585
14585
|
// ../sandbox-runtime-spec/dist/index.js
|
|
14586
|
+
var SANDBOX_AGENT_BROWSER_PACKAGE = "agent-browser";
|
|
14587
|
+
var SANDBOX_AGENT_BROWSER_VERSION_POLICY = ">=0.37.1 <1.0.0";
|
|
14588
|
+
var SANDBOX_AGENT_BROWSER_NPM_SPEC = `${SANDBOX_AGENT_BROWSER_PACKAGE}@${SANDBOX_AGENT_BROWSER_VERSION_POLICY}`;
|
|
14589
|
+
var SANDBOX_DESKTOP_BROWSER_ENV = Object.freeze({
|
|
14590
|
+
DISPLAY: ":0",
|
|
14591
|
+
AGENT_BROWSER_HEADED: "true",
|
|
14592
|
+
AGENT_BROWSER_NO_XVFB: "1",
|
|
14593
|
+
AGENT_BROWSER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable"
|
|
14594
|
+
});
|
|
14586
14595
|
var definitions = [
|
|
14587
14596
|
{
|
|
14588
14597
|
id: "claude",
|
|
@@ -15451,7 +15460,7 @@ function describeError(error61) {
|
|
|
15451
15460
|
}
|
|
15452
15461
|
|
|
15453
15462
|
// src/version.ts
|
|
15454
|
-
var AGENT_VERSION = "0.1.
|
|
15463
|
+
var AGENT_VERSION = "0.1.98";
|
|
15455
15464
|
|
|
15456
15465
|
// src/daemon-worktree.ts
|
|
15457
15466
|
var import_node_child_process3 = require("child_process");
|
|
@@ -20292,9 +20301,9 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
20292
20301
|
}`;
|
|
20293
20302
|
doc.write(`const input = payload.value;`);
|
|
20294
20303
|
const ids = /* @__PURE__ */ Object.create(null);
|
|
20295
|
-
let
|
|
20304
|
+
let counter2 = 0;
|
|
20296
20305
|
for (const key of normalized.allKeys) {
|
|
20297
|
-
ids[key] = `key_${
|
|
20306
|
+
ids[key] = `key_${counter2++}`;
|
|
20298
20307
|
}
|
|
20299
20308
|
doc.write(memo4 ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);
|
|
20300
20309
|
for (const key of normalized.allKeys) {
|
|
@@ -49610,6 +49619,16 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
|
|
|
49610
49619
|
const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
|
|
49611
49620
|
return probe.state === "running" ? probe.status : null;
|
|
49612
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
|
+
}
|
|
49613
49632
|
async function adoptLocalDaemonControl(input2) {
|
|
49614
49633
|
const timeoutMs = input2.timeoutMs ?? 750;
|
|
49615
49634
|
try {
|
|
@@ -74429,6 +74448,14 @@ var skillVersionIdSchema = external_exports.uuid().transform((value2) => value2)
|
|
|
74429
74448
|
var runtimeIdSchema = external_exports.uuid().transform((value2) => value2);
|
|
74430
74449
|
var skillPreviewIdSchema = external_exports.uuid().transform((value2) => value2);
|
|
74431
74450
|
var skillCommandIdSchema = external_exports.uuid().transform((value2) => value2);
|
|
74451
|
+
var skillPackageFileSchema = defineWireSchema()(external_exports.object({
|
|
74452
|
+
path: external_exports.string().min(1),
|
|
74453
|
+
kind: external_exports.enum(["instructions", "script", "reference", "asset", "license", "other"]),
|
|
74454
|
+
size: external_exports.number().int().nonnegative(),
|
|
74455
|
+
mode: external_exports.union([external_exports.literal(420), external_exports.literal(493)]),
|
|
74456
|
+
sha256: sha256Schema,
|
|
74457
|
+
preview: external_exports.enum(["markdown", "text", "code", "image", "binary"])
|
|
74458
|
+
}));
|
|
74432
74459
|
var skillInvocationSchema = defineWireSchema()(external_exports.discriminatedUnion("kind", [
|
|
74433
74460
|
external_exports.object({
|
|
74434
74461
|
kind: external_exports.literal("skill_version"),
|
|
@@ -74530,6 +74557,7 @@ function projectSkillFrontmatter(admitted) {
|
|
|
74530
74557
|
};
|
|
74531
74558
|
const license = optionalText("license", 1024);
|
|
74532
74559
|
const compatibility = optionalText("compatibility", 500);
|
|
74560
|
+
const version2 = optionalText("version", 64);
|
|
74533
74561
|
const allowedTools = optionalToolList("allowed-tools", 1024);
|
|
74534
74562
|
const metadata = {};
|
|
74535
74563
|
const rawMetadata = raw.metadata;
|
|
@@ -74578,6 +74606,7 @@ function projectSkillFrontmatter(admitted) {
|
|
|
74578
74606
|
description: admitted.description,
|
|
74579
74607
|
...license ? { license } : {},
|
|
74580
74608
|
...compatibility ? { compatibility } : {},
|
|
74609
|
+
...version2 ? { version: version2 } : {},
|
|
74581
74610
|
metadata,
|
|
74582
74611
|
...allowedTools ? { allowedTools } : {}
|
|
74583
74612
|
},
|
|
@@ -76687,13 +76716,13 @@ var RuntimeRunJournal = class {
|
|
|
76687
76716
|
* Keep the exact interruption outcome until a durable authenticated control
|
|
76688
76717
|
* channel can prove both identity and ownership.
|
|
76689
76718
|
*/
|
|
76690
|
-
assessRecovery(runId,
|
|
76719
|
+
assessRecovery(runId, isProcessAlive2) {
|
|
76691
76720
|
const entry = this.entries.get(runId);
|
|
76692
76721
|
if (!entry) return void 0;
|
|
76693
76722
|
if (entry.providerPid === void 0) {
|
|
76694
76723
|
return { reattachable: false, reason: "provider_not_recorded" };
|
|
76695
76724
|
}
|
|
76696
|
-
if (!
|
|
76725
|
+
if (!isProcessAlive2(entry.providerPid)) {
|
|
76697
76726
|
return {
|
|
76698
76727
|
reattachable: false,
|
|
76699
76728
|
reason: "provider_not_running",
|
|
@@ -76747,7 +76776,8 @@ var RuntimeSetupGrantExpiredError = class extends Error {
|
|
|
76747
76776
|
this.name = "RuntimeSetupGrantExpiredError";
|
|
76748
76777
|
}
|
|
76749
76778
|
};
|
|
76750
|
-
async function createRuntimeSetupGrant(input2) {
|
|
76779
|
+
async function createRuntimeSetupGrant(input2, signal) {
|
|
76780
|
+
signal?.throwIfAborted();
|
|
76751
76781
|
const response = await fetch(`${input2.apiUrl}/public/runtimes/setup-tokens`, {
|
|
76752
76782
|
method: "POST",
|
|
76753
76783
|
headers: {
|
|
@@ -76759,7 +76789,7 @@ async function createRuntimeSetupGrant(input2) {
|
|
|
76759
76789
|
...input2.scope === "team" ? { teamId: input2.teamId } : {},
|
|
76760
76790
|
ttlMinutes: 5
|
|
76761
76791
|
}),
|
|
76762
|
-
signal: AbortSignal.timeout(1e4)
|
|
76792
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(1e4)]) : AbortSignal.timeout(1e4)
|
|
76763
76793
|
});
|
|
76764
76794
|
const body = await response.json();
|
|
76765
76795
|
if (!response.ok || typeof body.setupToken !== "string") {
|
|
@@ -76768,9 +76798,10 @@ async function createRuntimeSetupGrant(input2) {
|
|
|
76768
76798
|
}
|
|
76769
76799
|
return body.setupToken;
|
|
76770
76800
|
}
|
|
76771
|
-
async function exchangeRuntimeSetupGrant(input2) {
|
|
76801
|
+
async function exchangeRuntimeSetupGrant(input2, setupSignal) {
|
|
76802
|
+
setupSignal?.throwIfAborted();
|
|
76772
76803
|
const registrationAttemptId = getOrCreateSetupAttemptId(input2.setupToken);
|
|
76773
|
-
const signal = AbortSignal.timeout(15e3);
|
|
76804
|
+
const signal = setupSignal ? AbortSignal.any([setupSignal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3);
|
|
76774
76805
|
const registrationInit = {
|
|
76775
76806
|
method: "POST",
|
|
76776
76807
|
headers: { "content-type": "application/json" },
|
|
@@ -76795,6 +76826,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76795
76826
|
let response;
|
|
76796
76827
|
let transportError;
|
|
76797
76828
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
76829
|
+
signal.throwIfAborted();
|
|
76798
76830
|
try {
|
|
76799
76831
|
response = await fetch(
|
|
76800
76832
|
`${input2.apiUrl}/public/runtimes/register-with-setup-token`,
|
|
@@ -76802,6 +76834,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76802
76834
|
);
|
|
76803
76835
|
transportError = void 0;
|
|
76804
76836
|
} catch (error61) {
|
|
76837
|
+
signal.throwIfAborted();
|
|
76805
76838
|
transportError = error61;
|
|
76806
76839
|
}
|
|
76807
76840
|
const retriable = response && (response.status === 408 || response.status === 429 || response.status >= 500);
|
|
@@ -76839,6 +76872,7 @@ async function exchangeRuntimeSetupGrant(input2) {
|
|
|
76839
76872
|
};
|
|
76840
76873
|
}
|
|
76841
76874
|
async function handoffRuntimeSetupGrant(input2) {
|
|
76875
|
+
input2.signal?.throwIfAborted();
|
|
76842
76876
|
const response = await fetch(`http://127.0.0.1:${input2.port}/credentials/setup`, {
|
|
76843
76877
|
method: "POST",
|
|
76844
76878
|
headers: {
|
|
@@ -76846,7 +76880,7 @@ async function handoffRuntimeSetupGrant(input2) {
|
|
|
76846
76880
|
"content-type": "application/json"
|
|
76847
76881
|
},
|
|
76848
76882
|
body: JSON.stringify(input2.setup),
|
|
76849
|
-
signal: AbortSignal.timeout(input2.timeoutMs ?? 2e4)
|
|
76883
|
+
signal: input2.signal ? AbortSignal.any([input2.signal, AbortSignal.timeout(input2.timeoutMs ?? 2e4)]) : AbortSignal.timeout(input2.timeoutMs ?? 2e4)
|
|
76850
76884
|
});
|
|
76851
76885
|
const body = await response.json();
|
|
76852
76886
|
if (!response.ok) {
|
|
@@ -80440,7 +80474,20 @@ async function runLocalDaemonRecoveryLadder(input2) {
|
|
|
80440
80474
|
error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
|
|
80441
80475
|
};
|
|
80442
80476
|
}
|
|
80443
|
-
|
|
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();
|
|
80444
80491
|
bindConfigPath(args);
|
|
80445
80492
|
const previousConfig = readConfigForRegistration();
|
|
80446
80493
|
const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
|
|
@@ -80458,12 +80505,15 @@ async function setupDaemon(args) {
|
|
|
80458
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"
|
|
80459
80506
|
);
|
|
80460
80507
|
}
|
|
80461
|
-
const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
|
|
80462
|
-
|
|
80463
|
-
|
|
80464
|
-
|
|
80465
|
-
|
|
80466
|
-
|
|
80508
|
+
const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
|
|
80509
|
+
{
|
|
80510
|
+
apiUrl,
|
|
80511
|
+
accessToken: token,
|
|
80512
|
+
scope,
|
|
80513
|
+
teamId
|
|
80514
|
+
},
|
|
80515
|
+
signal
|
|
80516
|
+
);
|
|
80467
80517
|
const setup = {
|
|
80468
80518
|
apiUrl,
|
|
80469
80519
|
wsUrl,
|
|
@@ -80473,13 +80523,16 @@ async function setupDaemon(args) {
|
|
|
80473
80523
|
displayName,
|
|
80474
80524
|
installationId
|
|
80475
80525
|
};
|
|
80526
|
+
signal?.throwIfAborted();
|
|
80476
80527
|
let liveConfig = previousConfig;
|
|
80477
80528
|
const localApiPort = getLocalApiPort(args, liveConfig);
|
|
80478
80529
|
let live = liveConfig.localApiToken ? await probeLocalDaemon(localApiPort, liveConfig.localApiToken) : null;
|
|
80530
|
+
signal?.throwIfAborted();
|
|
80479
80531
|
if (!live && liveConfig.localServiceId && liveConfig.localServiceSecret) {
|
|
80480
80532
|
live = await adoptConfiguredLocalDaemon(args);
|
|
80481
80533
|
liveConfig = readConfigForRegistration();
|
|
80482
80534
|
}
|
|
80535
|
+
signal?.throwIfAborted();
|
|
80483
80536
|
if (live) {
|
|
80484
80537
|
if (!liveConfig.localApiToken) {
|
|
80485
80538
|
throw new Error("running daemon could not be authenticated for credential setup");
|
|
@@ -80493,14 +80546,16 @@ async function setupDaemon(args) {
|
|
|
80493
80546
|
const body2 = await handoffRuntimeSetupGrant({
|
|
80494
80547
|
port: localApiPort,
|
|
80495
80548
|
localApiToken: liveConfig.localApiToken,
|
|
80496
|
-
setup: liveSetup
|
|
80549
|
+
setup: liveSetup,
|
|
80550
|
+
signal
|
|
80497
80551
|
});
|
|
80552
|
+
signal?.throwIfAborted();
|
|
80498
80553
|
console.info("[alan-agent] Runtime credentials adopted by running daemon", {
|
|
80499
80554
|
runtimeId: body2.runtimeId,
|
|
80500
80555
|
credentialGeneration: body2.credentialGeneration,
|
|
80501
80556
|
configPath: getConfigPath()
|
|
80502
80557
|
});
|
|
80503
|
-
return
|
|
80558
|
+
return body2.runtimeId !== liveConfig.runtimeId;
|
|
80504
80559
|
}
|
|
80505
80560
|
if (readLocalDaemonOwnerLease(getConfigPath())) {
|
|
80506
80561
|
throw new Error(
|
|
@@ -80509,28 +80564,27 @@ async function setupDaemon(args) {
|
|
|
80509
80564
|
}
|
|
80510
80565
|
let body;
|
|
80511
80566
|
try {
|
|
80512
|
-
body = await exchangeRuntimeSetupGrant(setup);
|
|
80567
|
+
body = await exchangeRuntimeSetupGrant(setup, signal);
|
|
80513
80568
|
} catch (error61) {
|
|
80569
|
+
signal?.throwIfAborted();
|
|
80514
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
|
+
}
|
|
80515
80574
|
console.info("[alan-agent] Setup link expired; continuing with browser authorization");
|
|
80516
80575
|
await loginWithDeviceCode(args);
|
|
80517
|
-
return
|
|
80576
|
+
return false;
|
|
80518
80577
|
}
|
|
80519
80578
|
throw error61;
|
|
80520
80579
|
}
|
|
80521
|
-
|
|
80580
|
+
signal?.throwIfAborted();
|
|
80581
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80522
80582
|
apiUrl,
|
|
80523
80583
|
wsUrl,
|
|
80524
80584
|
endpointProfile,
|
|
80525
80585
|
runtimeId: body.runtimeId,
|
|
80526
80586
|
runtimeToken: body.runtimeToken,
|
|
80527
80587
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80528
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80529
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80530
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80531
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80532
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80533
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80534
80588
|
displayName: body.displayName,
|
|
80535
80589
|
installationId
|
|
80536
80590
|
});
|
|
@@ -80539,7 +80593,7 @@ async function setupDaemon(args) {
|
|
|
80539
80593
|
runtimeId: body.runtimeId,
|
|
80540
80594
|
configPath: getConfigPath()
|
|
80541
80595
|
});
|
|
80542
|
-
return
|
|
80596
|
+
return false;
|
|
80543
80597
|
}
|
|
80544
80598
|
async function loginWithDeviceCode(args) {
|
|
80545
80599
|
bindConfigPath(args);
|
|
@@ -80628,19 +80682,13 @@ async function loginWithDeviceCode(args) {
|
|
|
80628
80682
|
if (ownedPending?.operationId !== operation.id) {
|
|
80629
80683
|
throw new Error("browser authorization sidecar ownership was lost before finalization");
|
|
80630
80684
|
}
|
|
80631
|
-
|
|
80685
|
+
writeRegisteredRuntimeConfig(args, previousConfig, {
|
|
80632
80686
|
apiUrl,
|
|
80633
80687
|
wsUrl,
|
|
80634
80688
|
endpointProfile,
|
|
80635
80689
|
runtimeId: body.runtime.id,
|
|
80636
80690
|
runtimeToken: body.runtimeToken,
|
|
80637
80691
|
runtimeRenewalToken: body.runtimeRenewalToken,
|
|
80638
|
-
localApiPort: getLocalApiPort(args, {}),
|
|
80639
|
-
localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
|
|
80640
|
-
localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
|
|
80641
|
-
localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80642
|
-
eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
|
|
80643
|
-
daemonEpoch: previousConfig.daemonEpoch,
|
|
80644
80692
|
displayName: body.runtime.displayName ?? displayName,
|
|
80645
80693
|
installationId
|
|
80646
80694
|
});
|
|
@@ -80679,14 +80727,7 @@ async function stopDaemon(args) {
|
|
|
80679
80727
|
console.info("[alan-agent] No daemon listening", { port: localApiPort });
|
|
80680
80728
|
return;
|
|
80681
80729
|
}
|
|
80682
|
-
const response = await
|
|
80683
|
-
method: "POST",
|
|
80684
|
-
headers: {
|
|
80685
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80686
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80687
|
-
},
|
|
80688
|
-
signal: AbortSignal.timeout(5e3)
|
|
80689
|
-
});
|
|
80730
|
+
const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80690
80731
|
if (!response.ok) {
|
|
80691
80732
|
throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
|
|
80692
80733
|
}
|
|
@@ -80831,14 +80872,7 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
|
|
|
80831
80872
|
`Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
|
|
80832
80873
|
);
|
|
80833
80874
|
}
|
|
80834
|
-
const shutdown = await
|
|
80835
|
-
method: "POST",
|
|
80836
|
-
headers: {
|
|
80837
|
-
authorization: `Bearer ${localApiToken}`,
|
|
80838
|
-
"x-alan-shutdown-reason": "runtime_shutdown"
|
|
80839
|
-
},
|
|
80840
|
-
signal: AbortSignal.timeout(5e3)
|
|
80841
|
-
});
|
|
80875
|
+
const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
|
|
80842
80876
|
if (!shutdown.ok) {
|
|
80843
80877
|
throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
|
|
80844
80878
|
}
|
|
@@ -80999,6 +81033,9 @@ async function printDoctor(args = []) {
|
|
|
80999
81033
|
);
|
|
81000
81034
|
}
|
|
81001
81035
|
|
|
81036
|
+
// src/install-command.ts
|
|
81037
|
+
var import_promises6 = require("timers/promises");
|
|
81038
|
+
|
|
81002
81039
|
// src/mcp-config-adapters.ts
|
|
81003
81040
|
var import_node_crypto20 = require("crypto");
|
|
81004
81041
|
var import_node_fs23 = require("fs");
|
|
@@ -82383,12 +82420,14 @@ function runMcpIntegrationCommand(args, options = {}) {
|
|
|
82383
82420
|
|
|
82384
82421
|
// src/service-manager.ts
|
|
82385
82422
|
var import_node_child_process9 = require("child_process");
|
|
82423
|
+
var import_node_crypto21 = require("crypto");
|
|
82386
82424
|
var import_node_fs25 = require("fs");
|
|
82387
82425
|
var import_node_os16 = require("os");
|
|
82388
82426
|
var import_node_path27 = require("path");
|
|
82389
82427
|
var SERVICE_LABEL = "ai.tryalan.agent";
|
|
82390
82428
|
var SYSTEMD_UNIT = "alan-agent.service";
|
|
82391
82429
|
var WINDOWS_TASK = "Alan Agent";
|
|
82430
|
+
var STOP_POLL_INTERVAL_MS = 200;
|
|
82392
82431
|
function xml(value2) {
|
|
82393
82432
|
return value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
82394
82433
|
}
|
|
@@ -82398,6 +82437,18 @@ function systemdArg(value2) {
|
|
|
82398
82437
|
function powershellLiteral(value2) {
|
|
82399
82438
|
return `'${value2.replaceAll("'", "''")}'`;
|
|
82400
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
|
+
}
|
|
82401
82452
|
function serviceNames(profile) {
|
|
82402
82453
|
if (profile === "production") {
|
|
82403
82454
|
return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
|
|
@@ -82419,7 +82470,7 @@ function buildDaemonServicePlan(input2) {
|
|
|
82419
82470
|
const domain2 = `gui/${userId}`;
|
|
82420
82471
|
const serviceTarget = `${domain2}/${names.launchd}`;
|
|
82421
82472
|
const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
|
|
82422
|
-
const
|
|
82473
|
+
const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
|
|
82423
82474
|
const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
|
|
82424
82475
|
const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
|
|
82425
82476
|
<string>${xml(value2)}</string>`).join("\n");
|
|
@@ -82448,9 +82499,9 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82448
82499
|
<key>ThrottleInterval</key>
|
|
82449
82500
|
<integer>5</integer>
|
|
82450
82501
|
<key>StandardOutPath</key>
|
|
82451
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82502
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
|
|
82452
82503
|
<key>StandardErrorPath</key>
|
|
82453
|
-
<string>${xml((0, import_node_path27.join)(
|
|
82504
|
+
<string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
|
|
82454
82505
|
</dict>
|
|
82455
82506
|
</plist>
|
|
82456
82507
|
`;
|
|
@@ -82458,7 +82509,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82458
82509
|
platform: "darwin",
|
|
82459
82510
|
manifestPath,
|
|
82460
82511
|
manifest,
|
|
82461
|
-
logDirectory:
|
|
82512
|
+
logDirectory: logDir2,
|
|
82462
82513
|
loadDefinitionCommands: [
|
|
82463
82514
|
{
|
|
82464
82515
|
command: "launchctl",
|
|
@@ -82499,23 +82550,6 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82499
82550
|
tolerateFailure: true
|
|
82500
82551
|
}
|
|
82501
82552
|
],
|
|
82502
|
-
replaceCommands: [
|
|
82503
|
-
{
|
|
82504
|
-
command: "launchctl",
|
|
82505
|
-
args: ["bootout", domain2, manifestPath],
|
|
82506
|
-
tolerateFailure: true
|
|
82507
|
-
},
|
|
82508
|
-
{
|
|
82509
|
-
command: "launchctl",
|
|
82510
|
-
args: ["bootstrap", domain2, manifestPath],
|
|
82511
|
-
tolerateFailure: false
|
|
82512
|
-
},
|
|
82513
|
-
{
|
|
82514
|
-
command: "launchctl",
|
|
82515
|
-
args: ["kickstart", "-k", serviceTarget],
|
|
82516
|
-
tolerateFailure: false
|
|
82517
|
-
}
|
|
82518
|
-
],
|
|
82519
82553
|
uninstallCommands: [
|
|
82520
82554
|
{
|
|
82521
82555
|
command: "launchctl",
|
|
@@ -82532,8 +82566,7 @@ ${environmentBlock} <key>RunAtLoad</key>
|
|
|
82532
82566
|
command: "launchctl",
|
|
82533
82567
|
args: ["print", serviceTarget],
|
|
82534
82568
|
tolerateFailure: true
|
|
82535
|
-
}
|
|
82536
|
-
lingerStatusCommand: null
|
|
82569
|
+
}
|
|
82537
82570
|
};
|
|
82538
82571
|
}
|
|
82539
82572
|
if (input2.platform === "linux") {
|
|
@@ -82596,18 +82629,6 @@ WantedBy=default.target
|
|
|
82596
82629
|
tolerateFailure: true
|
|
82597
82630
|
}
|
|
82598
82631
|
],
|
|
82599
|
-
replaceCommands: [
|
|
82600
|
-
{
|
|
82601
|
-
command: "systemctl",
|
|
82602
|
-
args: ["--user", "daemon-reload"],
|
|
82603
|
-
tolerateFailure: false
|
|
82604
|
-
},
|
|
82605
|
-
{
|
|
82606
|
-
command: "systemctl",
|
|
82607
|
-
args: ["--user", "restart", names.systemd],
|
|
82608
|
-
tolerateFailure: false
|
|
82609
|
-
}
|
|
82610
|
-
],
|
|
82611
82632
|
uninstallCommands: [
|
|
82612
82633
|
{
|
|
82613
82634
|
command: "systemctl",
|
|
@@ -82629,117 +82650,174 @@ WantedBy=default.target
|
|
|
82629
82650
|
command: "systemctl",
|
|
82630
82651
|
args: ["--user", "is-active", names.systemd],
|
|
82631
82652
|
tolerateFailure: true
|
|
82632
|
-
},
|
|
82633
|
-
lingerStatusCommand: {
|
|
82634
|
-
command: "loginctl",
|
|
82635
|
-
args: ["show-user", "$(id -un)", "-p", "Linger"],
|
|
82636
|
-
tolerateFailure: true
|
|
82637
82653
|
}
|
|
82638
82654
|
};
|
|
82639
82655
|
}
|
|
82640
|
-
const
|
|
82641
|
-
const
|
|
82642
|
-
const
|
|
82643
|
-
const
|
|
82644
|
-
|
|
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)}`
|
|
82645
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("; ");
|
|
82646
82757
|
const registerScript = [
|
|
82647
|
-
|
|
82648
|
-
|
|
82758
|
+
taskContext,
|
|
82759
|
+
resolveTaskSid,
|
|
82760
|
+
windowsDefinitionContext,
|
|
82761
|
+
`$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
|
|
82649
82762
|
"$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
|
|
82650
82763
|
"$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
|
|
82651
|
-
|
|
82652
|
-
|
|
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' }"
|
|
82653
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);
|
|
82654
82802
|
return {
|
|
82655
82803
|
platform: "win32",
|
|
82656
82804
|
manifestPath: null,
|
|
82657
82805
|
manifest: null,
|
|
82658
|
-
logDirectory:
|
|
82659
|
-
loadDefinitionCommands: [
|
|
82660
|
-
|
|
82661
|
-
command: "powershell.exe",
|
|
82662
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82663
|
-
tolerateFailure: false
|
|
82664
|
-
}
|
|
82665
|
-
],
|
|
82666
|
-
reloadDefinitionCommands: [
|
|
82667
|
-
{
|
|
82668
|
-
command: "powershell.exe",
|
|
82669
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
|
|
82670
|
-
tolerateFailure: false
|
|
82671
|
-
}
|
|
82672
|
-
],
|
|
82806
|
+
logDirectory: logDir,
|
|
82807
|
+
loadDefinitionCommands: [registerCommand],
|
|
82808
|
+
reloadDefinitionCommands: [registerCommand],
|
|
82673
82809
|
enableCommands: [],
|
|
82674
|
-
startCommands: [
|
|
82675
|
-
{
|
|
82676
|
-
command: "powershell.exe",
|
|
82677
|
-
args: [
|
|
82678
|
-
"-NoProfile",
|
|
82679
|
-
"-NonInteractive",
|
|
82680
|
-
"-Command",
|
|
82681
|
-
`Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82682
|
-
],
|
|
82683
|
-
tolerateFailure: false
|
|
82684
|
-
}
|
|
82685
|
-
],
|
|
82810
|
+
startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
|
|
82686
82811
|
stopCommands: [
|
|
82687
|
-
|
|
82688
|
-
|
|
82689
|
-
|
|
82690
|
-
|
|
82691
|
-
"-NonInteractive",
|
|
82692
|
-
"-Command",
|
|
82693
|
-
`Stop-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue`
|
|
82694
|
-
],
|
|
82695
|
-
tolerateFailure: true
|
|
82696
|
-
}
|
|
82697
|
-
],
|
|
82698
|
-
replaceCommands: [
|
|
82699
|
-
{
|
|
82700
|
-
command: "powershell.exe",
|
|
82701
|
-
args: [
|
|
82702
|
-
"-NoProfile",
|
|
82703
|
-
"-NonInteractive",
|
|
82704
|
-
"-Command",
|
|
82705
|
-
`${registerScript}; Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
|
|
82706
|
-
],
|
|
82707
|
-
tolerateFailure: false
|
|
82708
|
-
}
|
|
82709
|
-
],
|
|
82710
|
-
uninstallCommands: [
|
|
82711
|
-
{
|
|
82712
|
-
command: "powershell.exe",
|
|
82713
|
-
args: [
|
|
82714
|
-
"-NoProfile",
|
|
82715
|
-
"-NonInteractive",
|
|
82716
|
-
"-Command",
|
|
82717
|
-
`Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Confirm:$false -ErrorAction SilentlyContinue`
|
|
82718
|
-
],
|
|
82719
|
-
tolerateFailure: true
|
|
82720
|
-
}
|
|
82812
|
+
powershellCommand(
|
|
82813
|
+
`${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
|
|
82814
|
+
true
|
|
82815
|
+
)
|
|
82721
82816
|
],
|
|
82722
|
-
|
|
82723
|
-
|
|
82724
|
-
|
|
82725
|
-
|
|
82726
|
-
"-NonInteractive",
|
|
82727
|
-
"-Command",
|
|
82728
|
-
`Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} | Format-List TaskName,State`
|
|
82729
|
-
],
|
|
82730
|
-
tolerateFailure: true
|
|
82731
|
-
},
|
|
82732
|
-
isActiveCommand: {
|
|
82733
|
-
command: "powershell.exe",
|
|
82734
|
-
args: [
|
|
82735
|
-
"-NoProfile",
|
|
82736
|
-
"-NonInteractive",
|
|
82737
|
-
"-Command",
|
|
82738
|
-
`(Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue).State`
|
|
82739
|
-
],
|
|
82740
|
-
tolerateFailure: true
|
|
82741
|
-
},
|
|
82742
|
-
lingerStatusCommand: null
|
|
82817
|
+
uninstallCommands: [powershellCommand(uninstallScript)],
|
|
82818
|
+
statusCommand: powershellCommand(statusScript, true),
|
|
82819
|
+
isActiveCommand: powershellCommand(statusScript, true),
|
|
82820
|
+
definitionMarker
|
|
82743
82821
|
};
|
|
82744
82822
|
}
|
|
82745
82823
|
function resolveCurrentPlatform() {
|
|
@@ -82749,16 +82827,16 @@ function resolveCurrentPlatform() {
|
|
|
82749
82827
|
}
|
|
82750
82828
|
return currentPlatform;
|
|
82751
82829
|
}
|
|
82752
|
-
function currentServicePlan(args, runtime) {
|
|
82753
|
-
const currentPlatform = resolveCurrentPlatform();
|
|
82830
|
+
function currentServicePlan(args, runtime, options = {}) {
|
|
82831
|
+
const currentPlatform = options.platform ?? resolveCurrentPlatform();
|
|
82754
82832
|
const profile = resolveEndpointProfileFromArgs(args);
|
|
82755
82833
|
const cliEntry = runtime?.cliEntry ?? process.argv[1];
|
|
82756
82834
|
if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
|
|
82757
82835
|
return buildDaemonServicePlan({
|
|
82758
82836
|
platform: currentPlatform,
|
|
82759
|
-
homeDir: (0, import_node_os16.homedir)(),
|
|
82837
|
+
homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
|
|
82760
82838
|
nodeExecutable: runtime?.executable ?? process.execPath,
|
|
82761
|
-
cliEntry: (0, import_node_path27.resolve)(cliEntry),
|
|
82839
|
+
cliEntry: currentPlatform === "win32" ? import_node_path27.win32.resolve(cliEntry) : (0, import_node_path27.resolve)(cliEntry),
|
|
82762
82840
|
profile,
|
|
82763
82841
|
environment: runtime?.environment
|
|
82764
82842
|
});
|
|
@@ -82778,10 +82856,14 @@ function defaultReadManifest(path2) {
|
|
|
82778
82856
|
if (!(0, import_node_fs25.existsSync)(path2)) return null;
|
|
82779
82857
|
return (0, import_node_fs25.readFileSync)(path2, "utf8");
|
|
82780
82858
|
}
|
|
82781
|
-
function runServiceCommand(command) {
|
|
82782
|
-
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
|
+
});
|
|
82783
82865
|
const status = result.status ?? 1;
|
|
82784
|
-
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
82866
|
+
const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
|
|
82785
82867
|
if (status !== 0 && !command.tolerateFailure) {
|
|
82786
82868
|
throw new Error(
|
|
82787
82869
|
`Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
|
|
@@ -82794,6 +82876,23 @@ function defaultSleep(ms) {
|
|
|
82794
82876
|
setTimeout(resolveSleep, ms);
|
|
82795
82877
|
});
|
|
82796
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
|
+
}
|
|
82797
82896
|
function parseServiceHostState(servicePlatform, result) {
|
|
82798
82897
|
const output2 = result.output;
|
|
82799
82898
|
if (servicePlatform === "linux") {
|
|
@@ -82819,9 +82918,69 @@ function parseServiceHostState(servicePlatform, result) {
|
|
|
82819
82918
|
const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
|
|
82820
82919
|
const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
|
|
82821
82920
|
if (state === "running") return "running";
|
|
82822
|
-
if (state === "
|
|
82921
|
+
if (state === "queued") return "starting";
|
|
82922
|
+
if (state === "ready" || state === "disabled") return "stopped";
|
|
82823
82923
|
return "unknown";
|
|
82824
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
|
+
}
|
|
82825
82984
|
function resolveEnsureDefinitionOutcome(input2) {
|
|
82826
82985
|
if (!input2.definitionChanged) {
|
|
82827
82986
|
return { changed: false, updatePending: false };
|
|
@@ -82831,9 +82990,6 @@ function resolveEnsureDefinitionOutcome(input2) {
|
|
|
82831
82990
|
}
|
|
82832
82991
|
return { changed: true, updatePending: false };
|
|
82833
82992
|
}
|
|
82834
|
-
function shouldStartOnInstall(hostState) {
|
|
82835
|
-
return hostState === "stopped";
|
|
82836
|
-
}
|
|
82837
82993
|
function resolveUsername(options) {
|
|
82838
82994
|
if (options?.username) return options.username;
|
|
82839
82995
|
try {
|
|
@@ -82842,17 +82998,14 @@ function resolveUsername(options) {
|
|
|
82842
82998
|
return process.env.USER || process.env.USERNAME || "unknown";
|
|
82843
82999
|
}
|
|
82844
83000
|
}
|
|
82845
|
-
function buildLingerStatusCommand(username) {
|
|
82846
|
-
return {
|
|
82847
|
-
command: "loginctl",
|
|
82848
|
-
args: ["show-user", username, "-p", "Linger"],
|
|
82849
|
-
tolerateFailure: true
|
|
82850
|
-
};
|
|
82851
|
-
}
|
|
82852
83001
|
function assertSystemdUserLingerEnabled(options = {}) {
|
|
82853
83002
|
const username = resolveUsername(options);
|
|
82854
83003
|
const run = options.runCommand ?? runServiceCommand;
|
|
82855
|
-
const result = run(
|
|
83004
|
+
const result = run({
|
|
83005
|
+
command: "loginctl",
|
|
83006
|
+
args: ["show-user", username, "-p", "Linger"],
|
|
83007
|
+
tolerateFailure: true
|
|
83008
|
+
});
|
|
82856
83009
|
if (result.status !== 0) {
|
|
82857
83010
|
throw new Error(
|
|
82858
83011
|
`Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
|
|
@@ -82866,10 +83019,18 @@ function assertSystemdUserLingerEnabled(options = {}) {
|
|
|
82866
83019
|
}
|
|
82867
83020
|
function createHostContext(options = {}) {
|
|
82868
83021
|
const args = options.args ?? [];
|
|
82869
|
-
const plan = currentServicePlan(args, options.runtime);
|
|
82870
|
-
const
|
|
82871
|
-
const sleep4 = options.sleep ?? defaultSleep;
|
|
83022
|
+
const plan = currentServicePlan(args, options.runtime, options);
|
|
83023
|
+
const sleep5 = options.sleep ?? defaultSleep;
|
|
82872
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
|
+
};
|
|
82873
83034
|
const readManifest = options.readManifest ?? defaultReadManifest;
|
|
82874
83035
|
const writeManifest = options.writeManifest ?? writeManifestAtomic;
|
|
82875
83036
|
const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs25.rmSync)(path2, { force: true }));
|
|
@@ -82880,19 +83041,37 @@ function createHostContext(options = {}) {
|
|
|
82880
83041
|
args,
|
|
82881
83042
|
plan,
|
|
82882
83043
|
run,
|
|
82883
|
-
sleep:
|
|
83044
|
+
sleep: sleep5,
|
|
82884
83045
|
nowMs,
|
|
82885
83046
|
readManifest,
|
|
82886
83047
|
writeManifest,
|
|
82887
83048
|
removeManifest,
|
|
82888
83049
|
ensureLogDir,
|
|
82889
|
-
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
|
|
82890
83058
|
};
|
|
82891
83059
|
}
|
|
82892
83060
|
function inspectDaemonService(options = {}) {
|
|
82893
83061
|
const { plan, run, nowMs } = createHostContext(options);
|
|
82894
83062
|
const result = run(plan.isActiveCommand);
|
|
82895
|
-
|
|
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
|
+
}
|
|
82896
83075
|
return {
|
|
82897
83076
|
state,
|
|
82898
83077
|
platform: plan.platform,
|
|
@@ -82902,8 +83081,9 @@ function inspectDaemonService(options = {}) {
|
|
|
82902
83081
|
}
|
|
82903
83082
|
function ensureDaemonServiceDefinition(options = {}) {
|
|
82904
83083
|
const ctx = createHostContext(options);
|
|
82905
|
-
const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
83084
|
+
const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
|
|
82906
83085
|
let definitionChanged = false;
|
|
83086
|
+
let inspection;
|
|
82907
83087
|
if (plan.manifestPath && plan.manifest) {
|
|
82908
83088
|
ensureLogDir();
|
|
82909
83089
|
const existing = readManifest(plan.manifestPath);
|
|
@@ -82912,20 +83092,37 @@ function ensureDaemonServiceDefinition(options = {}) {
|
|
|
82912
83092
|
writeManifest(plan.manifestPath, plan.manifest);
|
|
82913
83093
|
}
|
|
82914
83094
|
} else if (plan.platform === "win32") {
|
|
82915
|
-
|
|
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
|
+
};
|
|
82916
83109
|
}
|
|
82917
|
-
|
|
83110
|
+
inspection ??= inspectDaemonService(options);
|
|
83111
|
+
const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
|
|
82918
83112
|
const outcome = resolveEnsureDefinitionOutcome({
|
|
82919
83113
|
definitionChanged,
|
|
82920
|
-
hostState:
|
|
83114
|
+
hostState: definitionHostState
|
|
82921
83115
|
});
|
|
82922
83116
|
if (outcome.updatePending) {
|
|
82923
83117
|
return outcome;
|
|
82924
83118
|
}
|
|
82925
|
-
if (
|
|
83119
|
+
if (plan.platform === "win32" && !outcome.changed) {
|
|
83120
|
+
return outcome;
|
|
83121
|
+
}
|
|
83122
|
+
if (definitionHostState === "running" || definitionHostState === "starting") {
|
|
82926
83123
|
return outcome;
|
|
82927
83124
|
}
|
|
82928
|
-
const definitionCommands =
|
|
83125
|
+
const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
|
|
82929
83126
|
for (const command of definitionCommands) run(command);
|
|
82930
83127
|
return outcome;
|
|
82931
83128
|
}
|
|
@@ -82937,10 +83134,140 @@ function startDaemonService(options = {}) {
|
|
|
82937
83134
|
const { plan, run } = createHostContext(options);
|
|
82938
83135
|
for (const command of plan.startCommands) run(command);
|
|
82939
83136
|
}
|
|
82940
|
-
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 = {}) {
|
|
82941
83206
|
const ctx = createHostContext({ ...options, args: options.args ?? args });
|
|
82942
|
-
|
|
82943
|
-
|
|
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
|
+
}
|
|
82944
83271
|
console.info("[alan-agent] Per-user daemon service removed");
|
|
82945
83272
|
}
|
|
82946
83273
|
function installDaemonService(args = [], runtime, options = {}) {
|
|
@@ -82968,7 +83295,7 @@ function installDaemonService(args = [], runtime, options = {}) {
|
|
|
82968
83295
|
});
|
|
82969
83296
|
return;
|
|
82970
83297
|
}
|
|
82971
|
-
if (
|
|
83298
|
+
if (inspection.state !== "stopped") {
|
|
82972
83299
|
console.info("[alan-agent] Per-user daemon service already present", {
|
|
82973
83300
|
profile,
|
|
82974
83301
|
state: inspection.state
|
|
@@ -82992,13 +83319,81 @@ ${result.output}
|
|
|
82992
83319
|
}
|
|
82993
83320
|
|
|
82994
83321
|
// src/install-command.ts
|
|
82995
|
-
|
|
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
|
+
}
|
|
82996
83387
|
bindConfigPath(commandArgs);
|
|
82997
83388
|
const configured = readConfig();
|
|
82998
83389
|
const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
|
|
82999
|
-
if (configurationMode === "setup")
|
|
83000
|
-
|
|
83001
|
-
|
|
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
|
+
}
|
|
83002
83397
|
runMcpIntegrationCommand(["reconcile", ...commandArgs]);
|
|
83003
83398
|
}
|
|
83004
83399
|
|
|
@@ -86376,7 +86771,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
|
|
|
86376
86771
|
function inputRequiredRoundsExceededMessage2(method, maxRounds) {
|
|
86377
86772
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
86378
86773
|
}
|
|
86379
|
-
function
|
|
86774
|
+
function sleep4(ms, signal) {
|
|
86380
86775
|
return new Promise((resolve15, reject) => {
|
|
86381
86776
|
if (signal?.aborted) {
|
|
86382
86777
|
reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
|
|
@@ -94643,7 +95038,7 @@ var LegacyInputRequiredShim = class {
|
|
|
94643
95038
|
} finally {
|
|
94644
95039
|
roundAbort.dispose();
|
|
94645
95040
|
}
|
|
94646
|
-
} else await
|
|
95041
|
+
} else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
|
|
94647
95042
|
let ctxNext = {
|
|
94648
95043
|
...ctx,
|
|
94649
95044
|
mcpReq: {
|
|
@@ -95440,7 +95835,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
|
|
|
95440
95835
|
}
|
|
95441
95836
|
|
|
95442
95837
|
// src/native-hook-command.ts
|
|
95443
|
-
var
|
|
95838
|
+
var import_node_crypto22 = require("crypto");
|
|
95444
95839
|
var import_node_fs27 = require("fs");
|
|
95445
95840
|
var import_node_os17 = require("os");
|
|
95446
95841
|
var import_node_path29 = require("path");
|
|
@@ -95809,7 +96204,7 @@ function parseNativeHookEnvelope(input2) {
|
|
|
95809
96204
|
const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
|
|
95810
96205
|
return {
|
|
95811
96206
|
schemaVersion: 1,
|
|
95812
|
-
eventId: (0,
|
|
96207
|
+
eventId: (0, import_node_crypto22.randomUUID)(),
|
|
95813
96208
|
provider: capability.provider,
|
|
95814
96209
|
eventType,
|
|
95815
96210
|
observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -95976,7 +96371,7 @@ function decodeResumeFallbackContext(encoded) {
|
|
|
95976
96371
|
}
|
|
95977
96372
|
|
|
95978
96373
|
// src/sandbox.ts
|
|
95979
|
-
var
|
|
96374
|
+
var import_node_os19 = require("os");
|
|
95980
96375
|
|
|
95981
96376
|
// src/execution-activity-tracker.ts
|
|
95982
96377
|
var ExecutionActivityTracker = class {
|
|
@@ -96305,7 +96700,7 @@ function delay2(ms) {
|
|
|
96305
96700
|
}
|
|
96306
96701
|
|
|
96307
96702
|
// src/web-presenter.ts
|
|
96308
|
-
var
|
|
96703
|
+
var import_node_crypto23 = require("crypto");
|
|
96309
96704
|
var WebPresenter = class {
|
|
96310
96705
|
constructor(wsClient, conversationId, cwd) {
|
|
96311
96706
|
this.conversationId = conversationId;
|
|
@@ -96491,7 +96886,7 @@ var WebPresenter = class {
|
|
|
96491
96886
|
this.applicationProblem = createAlanProblem({
|
|
96492
96887
|
domain: "application",
|
|
96493
96888
|
code: input2.code,
|
|
96494
|
-
id: (0,
|
|
96889
|
+
id: (0, import_node_crypto23.randomUUID)(),
|
|
96495
96890
|
title: APPLICATION_PROBLEM_TITLES[input2.code],
|
|
96496
96891
|
detail: input2.message,
|
|
96497
96892
|
// The schema's slot for the technical cause, so the problem carries it
|
|
@@ -96561,7 +96956,7 @@ var WebPresenter = class {
|
|
|
96561
96956
|
};
|
|
96562
96957
|
|
|
96563
96958
|
// src/ws-client.ts
|
|
96564
|
-
var
|
|
96959
|
+
var import_node_crypto25 = require("crypto");
|
|
96565
96960
|
|
|
96566
96961
|
// ../shared/dist/agent-liveness.js
|
|
96567
96962
|
var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
|
|
@@ -96572,6 +96967,34 @@ var agentRunStateSchema = external_exports.object({
|
|
|
96572
96967
|
/** Run ids currently executing (cloud sandbox agent runs one at a time, but keep it a list). */
|
|
96573
96968
|
activeRunIds: external_exports.array(external_exports.string()).default([])
|
|
96574
96969
|
});
|
|
96970
|
+
var AGENT_RESOURCE_SAMPLE_INTERVAL_MS = 6e4;
|
|
96971
|
+
var resourceCounter = external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
96972
|
+
var resourceMeasurement = external_exports.number().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
96973
|
+
var pressurePercentage = external_exports.number().min(0).max(100);
|
|
96974
|
+
var agentResourceDiagnosticsSchema = external_exports.object({
|
|
96975
|
+
sampledAtMs: resourceCounter,
|
|
96976
|
+
memoryTotalBytes: resourceCounter,
|
|
96977
|
+
memoryFreeBytes: resourceCounter,
|
|
96978
|
+
/** Linux MemAvailable includes reclaimable memory; free memory alone does not. */
|
|
96979
|
+
memoryAvailableBytes: resourceCounter.optional(),
|
|
96980
|
+
swapTotalBytes: resourceCounter.optional(),
|
|
96981
|
+
swapFreeBytes: resourceCounter.optional(),
|
|
96982
|
+
processRssBytes: resourceCounter,
|
|
96983
|
+
cpuCount: external_exports.number().int().positive().max(65536),
|
|
96984
|
+
/** Omitted on Windows, where os.loadavg() always returns zero. */
|
|
96985
|
+
loadAverage1m: resourceMeasurement.optional(),
|
|
96986
|
+
eventLoopDelayMaxMs: resourceMeasurement.optional(),
|
|
96987
|
+
eventLoopDelayP99Ms: resourceMeasurement.optional(),
|
|
96988
|
+
memoryPressureSomeAvg10: pressurePercentage.optional(),
|
|
96989
|
+
memoryPressureFullAvg10: pressurePercentage.optional(),
|
|
96990
|
+
cpuPressureSomeAvg10: pressurePercentage.optional(),
|
|
96991
|
+
ioPressureSomeAvg10: pressurePercentage.optional(),
|
|
96992
|
+
/** Counters for the agent's current cgroup v2, including its child workload. */
|
|
96993
|
+
cgroupMemoryCurrentBytes: resourceCounter.optional(),
|
|
96994
|
+
cgroupMemoryLimitBytes: resourceCounter.optional(),
|
|
96995
|
+
cgroupMemoryOomEvents: resourceCounter.optional(),
|
|
96996
|
+
cgroupMemoryOomKillEvents: resourceCounter.optional()
|
|
96997
|
+
});
|
|
96575
96998
|
var agentHeartbeatSchema = external_exports.object({
|
|
96576
96999
|
/** Stable id for this agent OS process; changes only on a fresh agent start. */
|
|
96577
97000
|
agentSessionId: external_exports.string().min(1),
|
|
@@ -96580,7 +97003,9 @@ var agentHeartbeatSchema = external_exports.object({
|
|
|
96580
97003
|
/** Capability marker — presence is what distinguishes a new agent from a legacy one. */
|
|
96581
97004
|
protocolVersion: external_exports.number().int().positive(),
|
|
96582
97005
|
runState: agentRunStateSchema,
|
|
96583
|
-
agentVersion: external_exports.string().optional()
|
|
97006
|
+
agentVersion: external_exports.string().optional(),
|
|
97007
|
+
// Bad optional diagnostics must never discard otherwise valid liveness evidence.
|
|
97008
|
+
resources: agentResourceDiagnosticsSchema.optional().catch(void 0)
|
|
96584
97009
|
});
|
|
96585
97010
|
var agentProbeAckSchema = external_exports.object({
|
|
96586
97011
|
agentSessionId: external_exports.string().min(1),
|
|
@@ -96588,14 +97013,152 @@ var agentProbeAckSchema = external_exports.object({
|
|
|
96588
97013
|
protocolVersion: external_exports.number().int().positive().optional()
|
|
96589
97014
|
});
|
|
96590
97015
|
|
|
96591
|
-
// src/
|
|
96592
|
-
var import_node_crypto23 = require("crypto");
|
|
97016
|
+
// src/resource-diagnostics.ts
|
|
96593
97017
|
var import_node_fs28 = require("fs");
|
|
97018
|
+
var import_node_os18 = require("os");
|
|
97019
|
+
var import_node_path30 = require("path");
|
|
97020
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
97021
|
+
var MAX_RESOURCE_FILE_BYTES = 16384;
|
|
97022
|
+
var RESOURCE_READ_TIMEOUT_MS = 500;
|
|
97023
|
+
async function readResourceFile(path2) {
|
|
97024
|
+
try {
|
|
97025
|
+
const stream = (0, import_node_fs28.createReadStream)(path2, {
|
|
97026
|
+
encoding: "utf8",
|
|
97027
|
+
start: 0,
|
|
97028
|
+
end: MAX_RESOURCE_FILE_BYTES,
|
|
97029
|
+
signal: AbortSignal.timeout(RESOURCE_READ_TIMEOUT_MS)
|
|
97030
|
+
});
|
|
97031
|
+
let text = "";
|
|
97032
|
+
for await (const chunk of stream) {
|
|
97033
|
+
text += chunk;
|
|
97034
|
+
if (text.length > MAX_RESOURCE_FILE_BYTES) return void 0;
|
|
97035
|
+
}
|
|
97036
|
+
return text;
|
|
97037
|
+
} catch {
|
|
97038
|
+
return void 0;
|
|
97039
|
+
}
|
|
97040
|
+
}
|
|
97041
|
+
function counter(value2) {
|
|
97042
|
+
if (!value2 || !/^\d+$/.test(value2)) return void 0;
|
|
97043
|
+
const parsed = Number(value2);
|
|
97044
|
+
return Number.isSafeInteger(parsed) ? parsed : void 0;
|
|
97045
|
+
}
|
|
97046
|
+
function pressure(text, kind) {
|
|
97047
|
+
const value2 = text?.match(new RegExp(`^${kind} avg10=(\\d+(?:\\.\\d+)?) `, "m"))?.[1];
|
|
97048
|
+
if (value2 === void 0) return void 0;
|
|
97049
|
+
const parsed = Number(value2);
|
|
97050
|
+
return Number.isFinite(parsed) && parsed <= 100 ? parsed : void 0;
|
|
97051
|
+
}
|
|
97052
|
+
function meminfoBytes(text, field) {
|
|
97053
|
+
const kb = counter(text?.match(new RegExp(`^${field}:\\s+(\\d+) kB$`, "m"))?.[1]);
|
|
97054
|
+
return kb !== void 0 && Number.isSafeInteger(kb * 1024) ? kb * 1024 : void 0;
|
|
97055
|
+
}
|
|
97056
|
+
function cgroupDirectory(text) {
|
|
97057
|
+
const path2 = text?.match(/^0::(\/[^\n]*)$/m)?.[1];
|
|
97058
|
+
if (!path2 || path2.split("/").includes("..")) return void 0;
|
|
97059
|
+
return import_node_path30.posix.join("/sys/fs/cgroup", path2);
|
|
97060
|
+
}
|
|
97061
|
+
async function collectAgentResourceDiagnostics(options = {}) {
|
|
97062
|
+
const platform6 = options.platform ?? process.platform;
|
|
97063
|
+
const readFile7 = async (path2) => {
|
|
97064
|
+
try {
|
|
97065
|
+
return await (options.readFile ?? readResourceFile)(path2);
|
|
97066
|
+
} catch {
|
|
97067
|
+
return void 0;
|
|
97068
|
+
}
|
|
97069
|
+
};
|
|
97070
|
+
const sample = {
|
|
97071
|
+
sampledAtMs: Date.now(),
|
|
97072
|
+
memoryTotalBytes: (0, import_node_os18.totalmem)(),
|
|
97073
|
+
memoryFreeBytes: (0, import_node_os18.freemem)(),
|
|
97074
|
+
processRssBytes: process.memoryUsage.rss(),
|
|
97075
|
+
cpuCount: (0, import_node_os18.availableParallelism)(),
|
|
97076
|
+
...platform6 === "win32" ? {} : { loadAverage1m: (0, import_node_os18.loadavg)()[0] }
|
|
97077
|
+
};
|
|
97078
|
+
if (platform6 !== "linux") return sample;
|
|
97079
|
+
const [meminfo, memoryPsi, cpuPsi, ioPsi, cgroup] = await Promise.all([
|
|
97080
|
+
readFile7("/proc/meminfo"),
|
|
97081
|
+
readFile7("/proc/pressure/memory"),
|
|
97082
|
+
readFile7("/proc/pressure/cpu"),
|
|
97083
|
+
readFile7("/proc/pressure/io"),
|
|
97084
|
+
readFile7("/proc/self/cgroup")
|
|
97085
|
+
]);
|
|
97086
|
+
sample.memoryAvailableBytes = meminfoBytes(meminfo, "MemAvailable");
|
|
97087
|
+
sample.swapTotalBytes = meminfoBytes(meminfo, "SwapTotal");
|
|
97088
|
+
sample.swapFreeBytes = meminfoBytes(meminfo, "SwapFree");
|
|
97089
|
+
sample.memoryPressureSomeAvg10 = pressure(memoryPsi, "some");
|
|
97090
|
+
sample.memoryPressureFullAvg10 = pressure(memoryPsi, "full");
|
|
97091
|
+
sample.cpuPressureSomeAvg10 = pressure(cpuPsi, "some");
|
|
97092
|
+
sample.ioPressureSomeAvg10 = pressure(ioPsi, "some");
|
|
97093
|
+
const directory = cgroupDirectory(cgroup);
|
|
97094
|
+
if (!directory) return sample;
|
|
97095
|
+
const [current, limit, events] = await Promise.all([
|
|
97096
|
+
readFile7(`${directory}/memory.current`),
|
|
97097
|
+
readFile7(`${directory}/memory.max`),
|
|
97098
|
+
readFile7(`${directory}/memory.events`)
|
|
97099
|
+
]);
|
|
97100
|
+
sample.cgroupMemoryCurrentBytes = counter(current?.trim());
|
|
97101
|
+
sample.cgroupMemoryLimitBytes = counter(limit?.trim());
|
|
97102
|
+
sample.cgroupMemoryOomEvents = counter(events?.match(/^oom (\d+)$/m)?.[1]);
|
|
97103
|
+
sample.cgroupMemoryOomKillEvents = counter(events?.match(/^oom_kill (\d+)$/m)?.[1]);
|
|
97104
|
+
return sample;
|
|
97105
|
+
}
|
|
97106
|
+
var AgentResourceSampler = class {
|
|
97107
|
+
constructor(collect = collectAgentResourceDiagnostics) {
|
|
97108
|
+
this.collect = collect;
|
|
97109
|
+
}
|
|
97110
|
+
delay = (0, import_node_perf_hooks.monitorEventLoopDelay)({ resolution: 20 });
|
|
97111
|
+
timer;
|
|
97112
|
+
inFlight = false;
|
|
97113
|
+
generation = 0;
|
|
97114
|
+
sample;
|
|
97115
|
+
start() {
|
|
97116
|
+
if (this.timer) return;
|
|
97117
|
+
this.generation += 1;
|
|
97118
|
+
this.delay.reset();
|
|
97119
|
+
this.delay.enable();
|
|
97120
|
+
this.timer = setInterval(() => void this.refresh(), AGENT_RESOURCE_SAMPLE_INTERVAL_MS);
|
|
97121
|
+
this.timer.unref();
|
|
97122
|
+
void this.refresh();
|
|
97123
|
+
}
|
|
97124
|
+
stop() {
|
|
97125
|
+
this.generation += 1;
|
|
97126
|
+
if (this.timer) clearInterval(this.timer);
|
|
97127
|
+
this.timer = void 0;
|
|
97128
|
+
this.delay.disable();
|
|
97129
|
+
this.sample = void 0;
|
|
97130
|
+
}
|
|
97131
|
+
latest() {
|
|
97132
|
+
return this.sample;
|
|
97133
|
+
}
|
|
97134
|
+
async refresh() {
|
|
97135
|
+
if (this.inFlight) return;
|
|
97136
|
+
this.inFlight = true;
|
|
97137
|
+
const generation = this.generation;
|
|
97138
|
+
const delay3 = this.delay.count > 0 ? {
|
|
97139
|
+
eventLoopDelayMaxMs: this.delay.max / 1e6,
|
|
97140
|
+
eventLoopDelayP99Ms: this.delay.percentile(99) / 1e6
|
|
97141
|
+
} : {};
|
|
97142
|
+
this.delay.reset();
|
|
97143
|
+
try {
|
|
97144
|
+
const sample = await this.collect();
|
|
97145
|
+
if (generation === this.generation) this.sample = { ...sample, ...delay3 };
|
|
97146
|
+
} catch {
|
|
97147
|
+
if (generation === this.generation) this.sample = void 0;
|
|
97148
|
+
} finally {
|
|
97149
|
+
this.inFlight = false;
|
|
97150
|
+
}
|
|
97151
|
+
}
|
|
97152
|
+
};
|
|
97153
|
+
|
|
97154
|
+
// src/sandbox-outbox.ts
|
|
97155
|
+
var import_node_crypto24 = require("crypto");
|
|
97156
|
+
var import_node_fs29 = require("fs");
|
|
96594
97157
|
function deriveSandboxOutboxKey(sessionToken) {
|
|
96595
97158
|
if (!sessionToken) {
|
|
96596
97159
|
throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
|
|
96597
97160
|
}
|
|
96598
|
-
return (0,
|
|
97161
|
+
return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
|
|
96599
97162
|
}
|
|
96600
97163
|
function sandboxOutboxSpoolPath(conversationId) {
|
|
96601
97164
|
return `/tmp/alan-agent-outbox-${conversationId}.enc`;
|
|
@@ -96611,24 +97174,24 @@ function assertValidOutboxKey(key, path2) {
|
|
|
96611
97174
|
}
|
|
96612
97175
|
function loadOrCreateSandboxOutboxKey(input2) {
|
|
96613
97176
|
const keyPath = sandboxOutboxKeyPath(input2.spoolPath);
|
|
96614
|
-
if ((0,
|
|
96615
|
-
(0,
|
|
96616
|
-
return assertValidOutboxKey((0,
|
|
97177
|
+
if ((0, import_node_fs29.existsSync)(keyPath)) {
|
|
97178
|
+
(0, import_node_fs29.chmodSync)(keyPath, 384);
|
|
97179
|
+
return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
|
|
96617
97180
|
}
|
|
96618
97181
|
const initialKey = deriveSandboxOutboxKey(input2.sessionToken);
|
|
96619
97182
|
try {
|
|
96620
|
-
(0,
|
|
97183
|
+
(0, import_node_fs29.writeFileSync)(keyPath, initialKey, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
96621
97184
|
return initialKey;
|
|
96622
97185
|
} catch (error61) {
|
|
96623
97186
|
if (error61.code !== "EEXIST") throw error61;
|
|
96624
|
-
(0,
|
|
96625
|
-
return assertValidOutboxKey((0,
|
|
97187
|
+
(0, import_node_fs29.chmodSync)(keyPath, 384);
|
|
97188
|
+
return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
|
|
96626
97189
|
}
|
|
96627
97190
|
}
|
|
96628
97191
|
function quarantineUnusableOutbox(path2) {
|
|
96629
97192
|
const suffix = `corrupt-${Date.now()}-${process.pid}`;
|
|
96630
97193
|
for (const candidate of [path2, eventJournalDir(path2)]) {
|
|
96631
|
-
if ((0,
|
|
97194
|
+
if ((0, import_node_fs29.existsSync)(candidate)) (0, import_node_fs29.renameSync)(candidate, `${candidate}.${suffix}`);
|
|
96632
97195
|
}
|
|
96633
97196
|
}
|
|
96634
97197
|
function createSandboxEventOutbox(input2) {
|
|
@@ -96772,10 +97335,11 @@ var WSClient = class {
|
|
|
96772
97335
|
}
|
|
96773
97336
|
socket;
|
|
96774
97337
|
/** Stable id for this agent process; fences stale heartbeats server-side. */
|
|
96775
|
-
agentSessionId = (0,
|
|
97338
|
+
agentSessionId = (0, import_node_crypto25.randomUUID)();
|
|
96776
97339
|
/** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
|
|
96777
97340
|
heartbeatSeq = 0;
|
|
96778
97341
|
heartbeatTimer = null;
|
|
97342
|
+
resourceSampler = new AgentResourceSampler();
|
|
96779
97343
|
supervisedReconnectTimer = null;
|
|
96780
97344
|
supervisedReconnectAttempt = 0;
|
|
96781
97345
|
connectionDirective = null;
|
|
@@ -96824,11 +97388,13 @@ var WSClient = class {
|
|
|
96824
97388
|
seq: this.heartbeatSeq,
|
|
96825
97389
|
protocolVersion: AGENT_LIVENESS_PROTOCOL_VERSION,
|
|
96826
97390
|
runState: this.currentRunState(),
|
|
96827
|
-
agentVersion: this.agentVersion || void 0
|
|
97391
|
+
agentVersion: this.agentVersion || void 0,
|
|
97392
|
+
resources: this.resourceSampler.latest()
|
|
96828
97393
|
};
|
|
96829
97394
|
}
|
|
96830
97395
|
/** Emit `agent.hello` on (re)connect and (re)start the heartbeat interval. */
|
|
96831
97396
|
startHeartbeat() {
|
|
97397
|
+
this.resourceSampler.start();
|
|
96832
97398
|
if (this.socket.connected) {
|
|
96833
97399
|
this.socket.emit("agent.hello", this.buildHeartbeat());
|
|
96834
97400
|
}
|
|
@@ -96840,6 +97406,7 @@ var WSClient = class {
|
|
|
96840
97406
|
this.heartbeatTimer.unref?.();
|
|
96841
97407
|
}
|
|
96842
97408
|
stopHeartbeat() {
|
|
97409
|
+
this.resourceSampler.stop();
|
|
96843
97410
|
if (this.heartbeatTimer) {
|
|
96844
97411
|
clearInterval(this.heartbeatTimer);
|
|
96845
97412
|
this.heartbeatTimer = null;
|
|
@@ -97304,7 +97871,7 @@ async function runSandbox(config2) {
|
|
|
97304
97871
|
}
|
|
97305
97872
|
const presenter = new WebPresenter(wsClient, config2.sessionId, config2.projectPath);
|
|
97306
97873
|
presenter.setSuppressSessionLifecycle(true);
|
|
97307
|
-
const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0,
|
|
97874
|
+
const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0, import_node_os19.homedir)() });
|
|
97308
97875
|
let lastProviderSessionId = config2.providerSessionId;
|
|
97309
97876
|
let resumeFallbackContext = config2.resumeFallbackContext;
|
|
97310
97877
|
let activeBackendKind = config2.backendKind || "claude_cli";
|
|
@@ -97604,12 +98171,12 @@ async function runSandbox(config2) {
|
|
|
97604
98171
|
}
|
|
97605
98172
|
|
|
97606
98173
|
// src/skills/skill-cli.ts
|
|
97607
|
-
var
|
|
97608
|
-
var
|
|
97609
|
-
var
|
|
98174
|
+
var import_promises7 = require("fs/promises");
|
|
98175
|
+
var import_node_os20 = require("os");
|
|
98176
|
+
var import_node_path31 = require("path");
|
|
97610
98177
|
async function runSkillsCommand(args) {
|
|
97611
98178
|
const [subcommand, ...options] = args;
|
|
97612
|
-
const homeDirectory2 = option(options, "--home") ?? (0,
|
|
98179
|
+
const homeDirectory2 = option(options, "--home") ?? (0, import_node_os20.homedir)();
|
|
97613
98180
|
const manager = await RuntimeSkillManager.open({ homeDirectory: homeDirectory2 });
|
|
97614
98181
|
if (subcommand === "scan") {
|
|
97615
98182
|
const inputPath = option(options, "--input");
|
|
@@ -97650,8 +98217,8 @@ async function defaultScanInput(homeDirectory2) {
|
|
|
97650
98217
|
});
|
|
97651
98218
|
}
|
|
97652
98219
|
async function readProtectedJson(path2) {
|
|
97653
|
-
const absolutePath = (0,
|
|
97654
|
-
const stat2 = await (0,
|
|
98220
|
+
const absolutePath = (0, import_node_path31.resolve)(path2);
|
|
98221
|
+
const stat2 = await (0, import_promises7.lstat)(absolutePath);
|
|
97655
98222
|
if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
|
|
97656
98223
|
if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
|
|
97657
98224
|
throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
|
|
@@ -97660,7 +98227,7 @@ async function readProtectedJson(path2) {
|
|
|
97660
98227
|
throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
|
|
97661
98228
|
}
|
|
97662
98229
|
if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
|
|
97663
|
-
return JSON.parse(await (0,
|
|
98230
|
+
return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
|
|
97664
98231
|
}
|
|
97665
98232
|
function option(args, name) {
|
|
97666
98233
|
const index = args.indexOf(name);
|
|
@@ -97818,7 +98385,7 @@ async function main() {
|
|
|
97818
98385
|
return;
|
|
97819
98386
|
}
|
|
97820
98387
|
if (command === "service" && commandArgs[0] === "uninstall") {
|
|
97821
|
-
uninstallDaemonService(commandArgs.slice(1));
|
|
98388
|
+
await uninstallDaemonService(commandArgs.slice(1));
|
|
97822
98389
|
return;
|
|
97823
98390
|
}
|
|
97824
98391
|
if (command === "service" && commandArgs[0] === "status") {
|