@themoltnet/agent-daemon 0.56.1 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -39
- package/dist/cli.js +2118 -1969
- package/package.json +12 -11
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,9 @@ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
|
12
12
|
import { parseArgs, promisify } from "node:util";
|
|
13
13
|
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
|
|
14
14
|
import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
|
|
15
|
-
import { FILE_SECRET_PROVIDER, FileSecretProvider, connect, createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
|
|
16
|
-
import { constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError,
|
|
15
|
+
import { FILE_SECRET_PROVIDER, FileSecretProvider, RegisterIdentityError, boundedIdentitySignal, connect, createNodeSecretProviderRegistry, register } from "@themoltnet/sdk/node";
|
|
16
|
+
import { constants, copyFileSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
|
|
18
18
|
import { execFile, spawn } from "node:child_process";
|
|
19
19
|
import { X509Certificate, createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID, timingSafeEqual, webcrypto } from "node:crypto";
|
|
20
20
|
import { once } from "node:events";
|
|
@@ -26,6 +26,8 @@ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk
|
|
|
26
26
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
27
27
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
28
28
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
29
|
+
import { homedir, platform, tmpdir } from "node:os";
|
|
30
|
+
import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
|
|
29
31
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
30
32
|
import { mkdir, open, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
31
33
|
import { pipeline } from "node:stream/promises";
|
|
@@ -37,13 +39,11 @@ import { isIP } from "node:net";
|
|
|
37
39
|
import cors from "@fastify/cors";
|
|
38
40
|
import helmet from "@fastify/helmet";
|
|
39
41
|
import { Transform, Writable } from "node:stream";
|
|
40
|
-
import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
|
|
41
42
|
import { pathToFileURL } from "node:url";
|
|
42
43
|
import { StringDecoder } from "node:string_decoder";
|
|
43
44
|
import rateLimit from "@fastify/rate-limit";
|
|
44
45
|
import Fastify from "fastify";
|
|
45
46
|
import "reflect-metadata";
|
|
46
|
-
import { homedir, platform } from "node:os";
|
|
47
47
|
import { BasicConstraintsExtension, ExtendedKeyUsage, ExtendedKeyUsageExtension, IP, KeyUsageFlags, KeyUsagesExtension, SubjectAlternativeNameExtension, X509CertificateGenerator } from "@peculiar/x509";
|
|
48
48
|
import { createGzip } from "node:zlib";
|
|
49
49
|
//#region ../../libs/tasks/src/rubric.ts
|
|
@@ -796,6 +796,8 @@ var AGENT_CREDENTIAL_SCOPES = [
|
|
|
796
796
|
CREDENTIAL_SCOPES.TaskClaim,
|
|
797
797
|
CREDENTIAL_SCOPES.TaskExecute
|
|
798
798
|
];
|
|
799
|
+
CREDENTIAL_SCOPES.AgentProfile, CREDENTIAL_SCOPES.TaskRead, CREDENTIAL_SCOPES.TaskWrite;
|
|
800
|
+
CREDENTIAL_SCOPES.AgentProfile, CREDENTIAL_SCOPES.DiaryRead, CREDENTIAL_SCOPES.PackRead, CREDENTIAL_SCOPES.RuntimeRead, CREDENTIAL_SCOPES.TaskRead, CREDENTIAL_SCOPES.TeamRead;
|
|
799
801
|
Object.freeze(ALL_CREDENTIAL_SCOPES.filter((scope) => scope !== CREDENTIAL_SCOPES.HumanProfile));
|
|
800
802
|
/**
|
|
801
803
|
* REST capabilities exercised by the current MCP tool surface.
|
|
@@ -1005,6 +1007,12 @@ var FingerprintSchema = Type.String({
|
|
|
1005
1007
|
pattern: "^[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}$",
|
|
1006
1008
|
description: "Key fingerprint (A1B2-C3D4-E5F6-G7H8)"
|
|
1007
1009
|
});
|
|
1010
|
+
var AgentAliasSchema = Type.String({
|
|
1011
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$",
|
|
1012
|
+
minLength: 1,
|
|
1013
|
+
maxLength: 63,
|
|
1014
|
+
description: "Case-preserving network alias; self-asserted, not unique, never used for authorization or lookup"
|
|
1015
|
+
});
|
|
1008
1016
|
Type.Object({
|
|
1009
1017
|
title: Type.Optional(Type.String({ maxLength: 255 })),
|
|
1010
1018
|
content: Type.String({
|
|
@@ -1209,6 +1217,7 @@ var TeamMemberSchema = Type.Object({
|
|
|
1209
1217
|
subjectType: Type.Union([Type.Literal("agent"), Type.Literal("human")]),
|
|
1210
1218
|
role: TeamRoleSchema,
|
|
1211
1219
|
displayName: Type.String(),
|
|
1220
|
+
alias: Type.Optional(AgentAliasSchema),
|
|
1212
1221
|
fingerprint: Type.Optional(Type.String()),
|
|
1213
1222
|
email: Type.Optional(Type.String())
|
|
1214
1223
|
});
|
|
@@ -1603,6 +1612,55 @@ Type.Object({
|
|
|
1603
1612
|
edges: Type.Array(ProvenanceGraphEdgeSchema)
|
|
1604
1613
|
}, { $id: "ProvenanceGraph" });
|
|
1605
1614
|
//#endregion
|
|
1615
|
+
//#region ../../libs/models/src/render-method.ts
|
|
1616
|
+
/**
|
|
1617
|
+
* The `renderMethod` convention for rendered packs (#1857).
|
|
1618
|
+
*
|
|
1619
|
+
* `renderedPacks.renderMethod` is a free-text `varchar(100)`. The server
|
|
1620
|
+
* bifurcates on exactly one thing — whether the label starts with `server:`
|
|
1621
|
+
* — and everything else is a caller-authored render whose markdown the
|
|
1622
|
+
* caller must supply. This module is the single owner of that convention:
|
|
1623
|
+
* the service, the API schemas, the runtime default and the console's
|
|
1624
|
+
* trust-tier derivation all read from here.
|
|
1625
|
+
*
|
|
1626
|
+
* The Go CLI (`apps/moltnet-cli/cobra_pack.go`) cannot import this module;
|
|
1627
|
+
* it carries a pointer comment and its default must be kept in sync with
|
|
1628
|
+
* `DEFAULT_SERVER_RENDER_METHOD` by hand.
|
|
1629
|
+
*
|
|
1630
|
+
* Values observed in production data and accepted unchanged:
|
|
1631
|
+
* `server:pack-to-docs-v1`, `agent:pack-to-docs-v1`, `agent-refined`.
|
|
1632
|
+
* `pi:pack-to-docs-v1` is the live pi-runtime default.
|
|
1633
|
+
*/
|
|
1634
|
+
/** Labels carrying this prefix are rendered deterministically by the server. */
|
|
1635
|
+
var SERVER_RENDER_PREFIX = "server:";
|
|
1636
|
+
/**
|
|
1637
|
+
* Prefixes that identify caller-authored markdown.
|
|
1638
|
+
*
|
|
1639
|
+
* `agent:` is the canonical documented label, `pi:` is what the pi-runtime
|
|
1640
|
+
* emits by default, and `agent-` covers the `agent-refined` family that is
|
|
1641
|
+
* live in production data.
|
|
1642
|
+
*/
|
|
1643
|
+
var CALLER_AUTHORED_PREFIXES = [
|
|
1644
|
+
"agent:",
|
|
1645
|
+
"pi:",
|
|
1646
|
+
"agent-"
|
|
1647
|
+
];
|
|
1648
|
+
var DEFAULT_SERVER_RENDER_METHOD = "server:pack-to-docs-v1";
|
|
1649
|
+
var DEFAULT_AGENT_RENDER_METHOD = "agent:pack-to-docs-v1";
|
|
1650
|
+
/**
|
|
1651
|
+
* Write-side validation pattern: a known prefix followed by at least one
|
|
1652
|
+
* non-whitespace character. Stored rows are never re-validated against
|
|
1653
|
+
* this; it applies to new writes at the API boundary only.
|
|
1654
|
+
*/
|
|
1655
|
+
var RENDER_METHOD_PATTERN = `^(${[SERVER_RENDER_PREFIX, ...CALLER_AUTHORED_PREFIXES].join("|")})\\S+$`;
|
|
1656
|
+
var RenderMethodSchema = Type.String({
|
|
1657
|
+
minLength: 1,
|
|
1658
|
+
maxLength: 100,
|
|
1659
|
+
pattern: RENDER_METHOD_PATTERN,
|
|
1660
|
+
description: "Render method label. Server render methods start with \"server:\" and must omit renderedMarkdown; caller-authored methods start with \"agent:\", \"pi:\" or \"agent-\" and require it.",
|
|
1661
|
+
examples: [DEFAULT_SERVER_RENDER_METHOD, DEFAULT_AGENT_RENDER_METHOD]
|
|
1662
|
+
});
|
|
1663
|
+
//#endregion
|
|
1606
1664
|
//#region ../../libs/models/src/signer-constraint.ts
|
|
1607
1665
|
var SIGNER_CONSTRAINT_TYPE = {
|
|
1608
1666
|
Human: "human",
|
|
@@ -2710,7 +2768,7 @@ var RenderPackInput = Type.Object({
|
|
|
2710
2768
|
var RenderPackOutput = Type.Object({
|
|
2711
2769
|
renderedPackId: Type.Union([Type.String({ format: "uuid" }), Type.Null()]),
|
|
2712
2770
|
renderedCid: Type.String({ minLength: 1 }),
|
|
2713
|
-
renderMethod:
|
|
2771
|
+
renderMethod: RenderMethodSchema,
|
|
2714
2772
|
byteSize: Type.Number({ minimum: 0 }),
|
|
2715
2773
|
entriesRendered: Type.Number({ minimum: 0 }),
|
|
2716
2774
|
summary: Type.String({ minLength: 1 }),
|
|
@@ -3375,8 +3433,7 @@ Type.Object({
|
|
|
3375
3433
|
outputCid: Type.Union([Cid, Type.Null()]),
|
|
3376
3434
|
usage: TaskUsage,
|
|
3377
3435
|
durationMs: Type.Number({ minimum: 0 }),
|
|
3378
|
-
error: Type.Optional(TaskError)
|
|
3379
|
-
contentSignature: Type.Optional(Type.String())
|
|
3436
|
+
error: Type.Optional(TaskError)
|
|
3380
3437
|
}, {
|
|
3381
3438
|
$id: "TaskOutput",
|
|
3382
3439
|
additionalProperties: false
|
|
@@ -3884,6 +3941,7 @@ function loadConfig() {
|
|
|
3884
3941
|
profilePrerequisiteEnv: process.env,
|
|
3885
3942
|
profilePrerequisitePath: process.env.PATH ?? "",
|
|
3886
3943
|
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
|
|
3944
|
+
agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
|
|
3887
3945
|
credentialSource: detectCredentialSource(process.env),
|
|
3888
3946
|
apiUrl: process.env["MOLTNET_API_URL"] ?? "",
|
|
3889
3947
|
signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
|
|
@@ -3927,8 +3985,9 @@ function readBoolean(name, value) {
|
|
|
3927
3985
|
if (value === "false") return false;
|
|
3928
3986
|
throw new Error(`${name} must be either true or false`);
|
|
3929
3987
|
}
|
|
3930
|
-
function activatePiCodingAgentDir(path) {
|
|
3988
|
+
function activatePiCodingAgentDir(path, env = {}) {
|
|
3931
3989
|
process.env["PI_CODING_AGENT_DIR"] = path;
|
|
3990
|
+
Object.assign(process.env, env);
|
|
3932
3991
|
}
|
|
3933
3992
|
function loadAgentServerEnvConfig() {
|
|
3934
3993
|
return {
|
|
@@ -4273,7 +4332,7 @@ function createExecutionPlanCache(args) {
|
|
|
4273
4332
|
const existing = cache.get(key);
|
|
4274
4333
|
if (existing) return existing;
|
|
4275
4334
|
const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy, claimedTask.attemptN), args.stateDirs, args.slotRegistry, runtimeSessionStore, sourceAttemptResolver);
|
|
4276
|
-
assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy);
|
|
4335
|
+
assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy, args.slotIdentity.runtimeProfileId);
|
|
4277
4336
|
cache.set(key, plan);
|
|
4278
4337
|
return plan;
|
|
4279
4338
|
},
|
|
@@ -4303,13 +4362,14 @@ function createNullRuntimeSessionStore() {
|
|
|
4303
4362
|
async uploadAttemptFinal() {}
|
|
4304
4363
|
};
|
|
4305
4364
|
}
|
|
4306
|
-
function assertPlanAllowedByWorkspacePolicy(plan, policy) {
|
|
4365
|
+
function assertPlanAllowedByWorkspacePolicy(plan, policy, runtimeProfileId) {
|
|
4307
4366
|
const allowed = new Set(policy?.allowedWorkspaceModes && policy.allowedWorkspaceModes.length > 0 ? policy.allowedWorkspaceModes : [
|
|
4308
4367
|
"none",
|
|
4309
4368
|
"shared_mount",
|
|
4310
4369
|
"dedicated_worktree"
|
|
4311
4370
|
]);
|
|
4312
4371
|
const effectiveMode = planToRuntimeProfileWorkspaceMode(plan);
|
|
4372
|
+
if (plan.workspaceRevision && effectiveMode !== "dedicated_worktree") throw new Error(`Runtime profile "${runtimeProfileId}" does not allow "dedicated_worktree", required by a revision-pinned task (resolved workspace mode "${effectiveMode}")`);
|
|
4313
4373
|
if (!allowed.has(effectiveMode)) throw new Error(`Runtime profile forbids final workspace mode "${effectiveMode}" for this task`);
|
|
4314
4374
|
}
|
|
4315
4375
|
function planToRuntimeProfileWorkspaceMode(plan) {
|
|
@@ -4793,7 +4853,6 @@ async function finalizeTask(agent, output, ctx = {}) {
|
|
|
4793
4853
|
output: output.output,
|
|
4794
4854
|
outputCid: output.outputCid,
|
|
4795
4855
|
usage: output.usage,
|
|
4796
|
-
...output.contentSignature ? { contentSignature: output.contentSignature } : {},
|
|
4797
4856
|
...daemonState ? { daemonState } : {},
|
|
4798
4857
|
...attestation
|
|
4799
4858
|
});
|
|
@@ -5371,1345 +5430,1173 @@ function initWorkerOtel(options) {
|
|
|
5371
5430
|
})]);
|
|
5372
5431
|
});
|
|
5373
5432
|
}
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
function
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
source: "env"
|
|
5382
|
-
};
|
|
5433
|
+
var NAME_RE = IDENTITY_ALIAS_PATTERN;
|
|
5434
|
+
var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
5435
|
+
function assertStoreName(kind, value) {
|
|
5436
|
+
try {
|
|
5437
|
+
return assertIdentityAlias(value);
|
|
5438
|
+
} catch {
|
|
5439
|
+
throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
|
|
5383
5440
|
}
|
|
5384
|
-
const path = join(repoRoot, ".pi");
|
|
5385
|
-
mkdirSync(path, { recursive: true });
|
|
5386
|
-
return {
|
|
5387
|
-
path,
|
|
5388
|
-
source: "repo"
|
|
5389
|
-
};
|
|
5390
|
-
}
|
|
5391
|
-
//#endregion
|
|
5392
|
-
//#region src/lib/runtime-context.ts
|
|
5393
|
-
var storage = new AsyncLocalStorage();
|
|
5394
|
-
function runWithDaemonRuntimeContext(context, callback) {
|
|
5395
|
-
return storage.run(context, callback);
|
|
5396
5441
|
}
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
return async (input) => {
|
|
5401
|
-
const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
|
|
5402
|
-
return createPiRetryTriage({
|
|
5403
|
-
model: modelHandle,
|
|
5404
|
-
modelRuntime,
|
|
5405
|
-
thinkingLevel: options.runtimeProfile.thinkingLevel,
|
|
5406
|
-
piAgentDir: options.piAgentDir,
|
|
5407
|
-
timeoutMs: options.timeoutMs,
|
|
5408
|
-
cwd: options.cwd
|
|
5409
|
-
})(input);
|
|
5410
|
-
};
|
|
5442
|
+
function assertProviderId(value) {
|
|
5443
|
+
if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
|
|
5444
|
+
return value;
|
|
5411
5445
|
}
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
"cancelled",
|
|
5420
|
-
"aborted",
|
|
5421
|
-
"timed_out"
|
|
5422
|
-
]);
|
|
5446
|
+
var AgentServerStoreError = class extends Error {
|
|
5447
|
+
name = "AgentServerStoreError";
|
|
5448
|
+
constructor(code, message, options) {
|
|
5449
|
+
super(message, options);
|
|
5450
|
+
this.code = code;
|
|
5451
|
+
}
|
|
5452
|
+
};
|
|
5423
5453
|
/**
|
|
5424
|
-
*
|
|
5425
|
-
*
|
|
5426
|
-
*
|
|
5427
|
-
*
|
|
5454
|
+
* `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
|
|
5455
|
+
*
|
|
5456
|
+
* Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
|
|
5457
|
+
* and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
|
|
5458
|
+
* so honouring XDG here gave one application two config roots: on a machine
|
|
5459
|
+
* with the variable set, the daemon wrote identities the CLI and SDK could not
|
|
5460
|
+
* read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
|
|
5461
|
+
* genuinely custom location.
|
|
5428
5462
|
*/
|
|
5429
|
-
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
const idle = idlePage.items;
|
|
5434
|
-
const slots = [...active, ...idle];
|
|
5435
|
-
const result = {
|
|
5436
|
-
failed: 0,
|
|
5437
|
-
failures: [],
|
|
5438
|
-
removedSessions: 0,
|
|
5439
|
-
removedWorkspaces: 0,
|
|
5440
|
-
scanned: slots.length,
|
|
5441
|
-
truncated: activePage.truncated || idlePage.truncated,
|
|
5442
|
-
unsafePathDetails: [],
|
|
5443
|
-
unsafePaths: 0
|
|
5444
|
-
};
|
|
5445
|
-
const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
|
|
5446
|
-
for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
|
|
5447
|
-
const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
|
|
5448
|
-
const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
|
|
5449
|
-
const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
|
|
5450
|
-
kind: "ready",
|
|
5451
|
-
paths: /* @__PURE__ */ new Set()
|
|
5452
|
-
};
|
|
5453
|
-
for (const item of slots) {
|
|
5454
|
-
if (!reaped.has(item.slot.id)) continue;
|
|
5455
|
-
if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
|
|
5456
|
-
const sessionDir = item.session?.sessionDir;
|
|
5457
|
-
if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
|
|
5458
|
-
else try {
|
|
5459
|
-
rmSync(sessionDir, {
|
|
5460
|
-
force: true,
|
|
5461
|
-
recursive: true
|
|
5462
|
-
});
|
|
5463
|
-
result.removedSessions++;
|
|
5464
|
-
} catch (err) {
|
|
5465
|
-
recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
|
|
5466
|
-
}
|
|
5467
|
-
const workspacePath = item.workspace?.worktreePath;
|
|
5468
|
-
if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
|
|
5469
|
-
const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
|
|
5470
|
-
if (!workspaceRoot) {
|
|
5471
|
-
recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
|
|
5472
|
-
continue;
|
|
5473
|
-
}
|
|
5474
|
-
await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
|
|
5475
|
-
}
|
|
5476
|
-
if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
|
|
5477
|
-
return result;
|
|
5463
|
+
function resolveAgentServerRoot(input) {
|
|
5464
|
+
const override = input.root?.trim();
|
|
5465
|
+
if (override) return override;
|
|
5466
|
+
return getConfigDir();
|
|
5478
5467
|
}
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
agentName: input.agentName,
|
|
5482
|
-
limit: RUNTIME_SLOT_PAGE_LIMIT,
|
|
5483
|
-
runtimeProfileId: input.runtimeProfileId,
|
|
5484
|
-
state,
|
|
5485
|
-
teamId: input.teamId
|
|
5486
|
-
});
|
|
5487
|
-
return {
|
|
5488
|
-
items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
|
|
5489
|
-
truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
|
|
5490
|
-
};
|
|
5468
|
+
function providerEnvName(providerId) {
|
|
5469
|
+
return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
|
|
5491
5470
|
}
|
|
5492
|
-
|
|
5493
|
-
const
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
taskSlots.push(item);
|
|
5497
|
-
byTask.set(item.slot.lastTaskId, taskSlots);
|
|
5498
|
-
}
|
|
5499
|
-
const terminal = /* @__PURE__ */ new Set();
|
|
5500
|
-
await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
|
|
5501
|
-
let attempts;
|
|
5502
|
-
try {
|
|
5503
|
-
attempts = await deps.taskReader.listAttempts(taskId);
|
|
5504
|
-
} catch (err) {
|
|
5505
|
-
recordFailure(deps, result, null, `task:${taskId}`, null, err);
|
|
5506
|
-
return;
|
|
5507
|
-
}
|
|
5508
|
-
for (const item of taskSlots) {
|
|
5509
|
-
const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
|
|
5510
|
-
if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
|
|
5511
|
-
}
|
|
5512
|
-
});
|
|
5513
|
-
return terminal;
|
|
5471
|
+
function assertProviderEnvName(providerId, value) {
|
|
5472
|
+
const expected = providerEnvName(providerId);
|
|
5473
|
+
if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
|
|
5474
|
+
return value;
|
|
5514
5475
|
}
|
|
5515
|
-
|
|
5516
|
-
let
|
|
5476
|
+
function readJson(path) {
|
|
5477
|
+
let raw;
|
|
5517
5478
|
try {
|
|
5518
|
-
|
|
5519
|
-
} catch (
|
|
5520
|
-
|
|
5521
|
-
|
|
5479
|
+
raw = readFileSync(path, "utf8");
|
|
5480
|
+
} catch (cause) {
|
|
5481
|
+
if (cause.code === "ENOENT") return null;
|
|
5482
|
+
throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
|
|
5522
5483
|
}
|
|
5523
|
-
const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
|
|
5524
|
-
if (!current) return false;
|
|
5525
|
-
if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
|
|
5526
|
-
return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
|
|
5527
|
-
}
|
|
5528
|
-
async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
|
|
5529
|
-
if (!input.mainWorktree) return;
|
|
5530
|
-
const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
|
|
5531
|
-
if (!existsSync(workspaceRoot)) return;
|
|
5532
|
-
let entries;
|
|
5533
5484
|
try {
|
|
5534
|
-
|
|
5535
|
-
} catch (
|
|
5536
|
-
|
|
5537
|
-
return;
|
|
5538
|
-
}
|
|
5539
|
-
for (const entry of entries) {
|
|
5540
|
-
if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
|
|
5541
|
-
const workspacePath = resolve(workspaceRoot, entry.name);
|
|
5542
|
-
if (retainedWorkspacePaths.has(workspacePath)) continue;
|
|
5543
|
-
const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
|
|
5544
|
-
if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
|
|
5545
|
-
await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
|
|
5485
|
+
return JSON.parse(raw);
|
|
5486
|
+
} catch (cause) {
|
|
5487
|
+
throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
|
|
5546
5488
|
}
|
|
5547
5489
|
}
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
}
|
|
5554
|
-
if (scratch || !mainWorktree) {
|
|
5490
|
+
function writeJsonAtomic(path, value) {
|
|
5491
|
+
const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
5492
|
+
try {
|
|
5493
|
+
writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
5494
|
+
renameSync(temp, path);
|
|
5495
|
+
} catch (cause) {
|
|
5555
5496
|
try {
|
|
5556
|
-
rmSync(
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
});
|
|
5560
|
-
result.removedWorkspaces++;
|
|
5561
|
-
} catch (err) {
|
|
5562
|
-
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
5563
|
-
}
|
|
5564
|
-
return;
|
|
5565
|
-
}
|
|
5566
|
-
if (worktrees.kind === "unavailable") {
|
|
5567
|
-
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
|
|
5568
|
-
return;
|
|
5497
|
+
rmSync(temp, { force: true });
|
|
5498
|
+
} catch {}
|
|
5499
|
+
throw cause;
|
|
5569
5500
|
}
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5501
|
+
}
|
|
5502
|
+
var AgentServerStore = class {
|
|
5503
|
+
root;
|
|
5504
|
+
identitiesDir;
|
|
5505
|
+
runsDir;
|
|
5506
|
+
secretsDir;
|
|
5507
|
+
/** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
|
|
5508
|
+
piDir;
|
|
5509
|
+
constructor(root) {
|
|
5510
|
+
this.root = root;
|
|
5511
|
+
this.identitiesDir = join(root, "identities");
|
|
5512
|
+
this.runsDir = join(root, "runs");
|
|
5513
|
+
this.secretsDir = join(root, "secrets");
|
|
5514
|
+
this.piDir = join(root, "pi");
|
|
5515
|
+
}
|
|
5516
|
+
get piAuthJsonPath() {
|
|
5517
|
+
return join(this.piDir, "auth.json");
|
|
5518
|
+
}
|
|
5519
|
+
/** Create the directory layout (0700) if missing. Idempotent. */
|
|
5520
|
+
ensure() {
|
|
5521
|
+
for (const dir of [
|
|
5522
|
+
this.root,
|
|
5523
|
+
this.identitiesDir,
|
|
5524
|
+
this.runsDir,
|
|
5525
|
+
this.secretsDir
|
|
5526
|
+
]) mkdirSync(dir, {
|
|
5527
|
+
recursive: true,
|
|
5528
|
+
mode: 448
|
|
5585
5529
|
});
|
|
5586
|
-
|
|
5587
|
-
} catch (err) {
|
|
5588
|
-
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
5530
|
+
return this;
|
|
5589
5531
|
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
]);
|
|
5600
|
-
return {
|
|
5601
|
-
kind: "ready",
|
|
5602
|
-
paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
|
|
5532
|
+
get statePath() {
|
|
5533
|
+
return join(this.root, "agent-server.json");
|
|
5534
|
+
}
|
|
5535
|
+
readAgentServerState() {
|
|
5536
|
+
const state = readJson(this.statePath);
|
|
5537
|
+
if (!state) return {
|
|
5538
|
+
version: 2,
|
|
5539
|
+
pendingRegistrations: {},
|
|
5540
|
+
activations: {}
|
|
5603
5541
|
};
|
|
5604
|
-
|
|
5542
|
+
if (!isRecord$1(state) || state.version !== 2) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported; move agent-server.json aside, run \`moltnet config migrate\`, then add or attach the agents again`);
|
|
5543
|
+
if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; move agent-server.json aside and configure the agent server again");
|
|
5544
|
+
if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 2 activation map; move agent-server.json aside, run `moltnet config migrate`, then add or attach the agents again");
|
|
5545
|
+
for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
|
|
5546
|
+
for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
|
|
5547
|
+
assertStoreName("agent name", alias);
|
|
5548
|
+
if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new AgentServerStoreError("invalid_state", `pending registration "${alias}" is not valid`);
|
|
5549
|
+
}
|
|
5605
5550
|
return {
|
|
5606
|
-
|
|
5607
|
-
|
|
5551
|
+
version: 2,
|
|
5552
|
+
pendingRegistrations: state.pendingRegistrations,
|
|
5553
|
+
activations: state.activations
|
|
5608
5554
|
};
|
|
5609
5555
|
}
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
try {
|
|
5613
|
-
return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
|
|
5614
|
-
} catch {
|
|
5615
|
-
return false;
|
|
5556
|
+
writeAgentServerState(state) {
|
|
5557
|
+
writeJsonAtomic(this.statePath, state);
|
|
5616
5558
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
try {
|
|
5620
|
-
return realpathSync(path);
|
|
5621
|
-
} catch {
|
|
5622
|
-
return resolve(path);
|
|
5559
|
+
agentPath(name) {
|
|
5560
|
+
return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
|
|
5623
5561
|
}
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
root,
|
|
5643
|
-
slotId
|
|
5644
|
-
};
|
|
5645
|
-
result.failed++;
|
|
5646
|
-
result.failures.push(issue);
|
|
5647
|
-
emitIssue(deps, issue);
|
|
5648
|
-
}
|
|
5649
|
-
function emitIssue(deps, issue) {
|
|
5650
|
-
try {
|
|
5651
|
-
deps.onIssue?.(issue);
|
|
5652
|
-
} catch {}
|
|
5653
|
-
}
|
|
5654
|
-
function errorMessage(err) {
|
|
5655
|
-
return err instanceof Error ? err.message : String(err);
|
|
5656
|
-
}
|
|
5657
|
-
async function execFileText(file, args) {
|
|
5658
|
-
return new Promise((resolvePromise, reject) => {
|
|
5659
|
-
execFile(file, args, {
|
|
5660
|
-
encoding: "utf8",
|
|
5661
|
-
maxBuffer: 10 * 1024 * 1024
|
|
5662
|
-
}, (err, stdout) => {
|
|
5663
|
-
if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
|
|
5664
|
-
else resolvePromise(stdout);
|
|
5562
|
+
/** Central identity directory, shared with the Go CLI layout. */
|
|
5563
|
+
identityDir(name) {
|
|
5564
|
+
return storeChildPath(this.identitiesDir, "identity alias", name);
|
|
5565
|
+
}
|
|
5566
|
+
get identitySelectorPath() {
|
|
5567
|
+
return join(this.root, "identity-selector.json");
|
|
5568
|
+
}
|
|
5569
|
+
readIdentitySelector() {
|
|
5570
|
+
const selector = readJson(this.identitySelectorPath);
|
|
5571
|
+
if (!selector) return null;
|
|
5572
|
+
if (!isRecord$1(selector) || selector.version !== 1 || selector.default_identity !== void 0 && typeof selector.default_identity !== "string") throw new AgentServerStoreError("invalid_state", "identity-selector.json is not a supported selector document");
|
|
5573
|
+
if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
|
|
5574
|
+
return selector;
|
|
5575
|
+
}
|
|
5576
|
+
writeIdentitySelector(alias) {
|
|
5577
|
+
writeJsonAtomic(this.identitySelectorPath, {
|
|
5578
|
+
version: 1,
|
|
5579
|
+
default_identity: assertStoreName("identity alias", alias)
|
|
5665
5580
|
});
|
|
5666
|
-
}
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
}
|
|
5698
|
-
/**
|
|
5699
|
-
* Transient faults worth retrying in-attempt: network-level errors
|
|
5700
|
-
* (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
|
|
5701
|
-
* validation, not-found) will not heal on retry.
|
|
5702
|
-
*/
|
|
5703
|
-
function isTransientUploadError(error) {
|
|
5704
|
-
if (!(error instanceof Error)) return false;
|
|
5705
|
-
const statusCode = error.statusCode;
|
|
5706
|
-
if (typeof statusCode !== "number") return true;
|
|
5707
|
-
return statusCode >= 500 || statusCode === 429;
|
|
5708
|
-
}
|
|
5709
|
-
var defaultSleep = (ms) => new Promise((resolve) => {
|
|
5710
|
-
setTimeout(resolve, ms);
|
|
5711
|
-
});
|
|
5712
|
-
function createApiRuntimeSessionStore(args) {
|
|
5713
|
-
const { agent, logger, uploadRetry } = args;
|
|
5714
|
-
return {
|
|
5715
|
-
async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
|
|
5716
|
-
return agent.runtimeSessions.getForAttempt({
|
|
5717
|
-
attemptN,
|
|
5718
|
-
taskId
|
|
5719
|
-
}, { teamId });
|
|
5720
|
-
},
|
|
5721
|
-
async hydrateSession(input) {
|
|
5722
|
-
const downloaded = await agent.runtimeSessions.download({
|
|
5723
|
-
attemptN: input.attemptN,
|
|
5724
|
-
taskId: input.taskId
|
|
5725
|
-
}, { teamId: input.teamId });
|
|
5726
|
-
await mkdir(input.destinationDir, { recursive: true });
|
|
5727
|
-
const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
|
|
5728
|
-
await pipeline(downloaded, createWriteStream(sessionPath));
|
|
5729
|
-
return sessionPath;
|
|
5730
|
-
},
|
|
5731
|
-
async uploadAttemptFinal(input) {
|
|
5732
|
-
const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
|
|
5733
|
-
if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
|
|
5734
|
-
const maxTries = uploadRetry?.maxTries ?? 3;
|
|
5735
|
-
const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
|
|
5736
|
-
const sleep = uploadRetry?.sleep ?? defaultSleep;
|
|
5737
|
-
for (let tryN = 1;; tryN += 1) try {
|
|
5738
|
-
await agent.runtimeSessions.upload({
|
|
5739
|
-
attemptN: input.attemptN,
|
|
5740
|
-
taskId: input.taskId
|
|
5741
|
-
}, createReadStream(sessionPath), {
|
|
5742
|
-
parentSessionId: input.parentSessionId ?? void 0,
|
|
5743
|
-
sessionKind: input.sessionKind,
|
|
5744
|
-
sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
|
|
5745
|
-
sourceSlotId: input.sourceSlotId ?? void 0
|
|
5746
|
-
}, { teamId: input.teamId });
|
|
5747
|
-
return;
|
|
5748
|
-
} catch (error) {
|
|
5749
|
-
if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
|
|
5750
|
-
const delayMs = baseDelayMs * tryN;
|
|
5751
|
-
logger?.warn({
|
|
5752
|
-
event: "agent-daemon.runtime_session_upload_retry",
|
|
5753
|
-
attemptN: input.attemptN,
|
|
5754
|
-
delayMs,
|
|
5755
|
-
statusCode: uploadStatusCode(error),
|
|
5756
|
-
taskId: input.taskId,
|
|
5757
|
-
tryN
|
|
5758
|
-
}, "Retrying durable runtime session upload");
|
|
5759
|
-
await sleep(delayMs);
|
|
5760
|
-
}
|
|
5761
|
-
}
|
|
5762
|
-
};
|
|
5763
|
-
}
|
|
5764
|
-
function uploadStatusCode(error) {
|
|
5765
|
-
const statusCode = error?.statusCode;
|
|
5766
|
-
return typeof statusCode === "number" ? statusCode : void 0;
|
|
5767
|
-
}
|
|
5768
|
-
function resolveContinueFrom(claimedTask) {
|
|
5769
|
-
return claimedTask.task.input.continueFrom;
|
|
5770
|
-
}
|
|
5771
|
-
//#endregion
|
|
5772
|
-
//#region src/lib/runtime-slots.ts
|
|
5773
|
-
function createApiRuntimeSlotStore(args) {
|
|
5774
|
-
const { agent } = args;
|
|
5775
|
-
return {
|
|
5776
|
-
async beginSlot(input) {
|
|
5777
|
-
await agent.runtimeSlots.begin({
|
|
5778
|
-
agentName: input.agentName,
|
|
5779
|
-
runtimeProfileId: input.runtimeProfileId,
|
|
5780
|
-
lastAttemptN: input.lastAttemptN,
|
|
5781
|
-
lastTaskId: input.lastTaskId,
|
|
5782
|
-
model: input.model,
|
|
5783
|
-
provider: input.provider,
|
|
5784
|
-
sessionDir: input.sessionDir ?? void 0,
|
|
5785
|
-
sessionPath: input.sessionPath ?? void 0,
|
|
5786
|
-
slotKey: input.slotKey,
|
|
5787
|
-
taskType: input.taskType,
|
|
5788
|
-
workspaceId: input.workspaceId ?? void 0,
|
|
5789
|
-
workspaceKind: input.workspaceKind,
|
|
5790
|
-
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
5791
|
-
worktreePath: input.worktreePath ?? void 0
|
|
5792
|
-
}, { teamId: input.teamId });
|
|
5793
|
-
},
|
|
5794
|
-
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
5795
|
-
await agent.runtimeSlots.finish({
|
|
5796
|
-
agentName: identity.agentName,
|
|
5797
|
-
attemptN,
|
|
5798
|
-
runtimeProfileId: identity.runtimeProfileId,
|
|
5799
|
-
model,
|
|
5800
|
-
provider,
|
|
5801
|
-
sessionPath: sessionPath ?? void 0,
|
|
5802
|
-
slotKey,
|
|
5803
|
-
taskId
|
|
5804
|
-
}, { teamId });
|
|
5805
|
-
},
|
|
5806
|
-
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
5807
|
-
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
5808
|
-
attemptN,
|
|
5809
|
-
taskId
|
|
5810
|
-
}, { teamId });
|
|
5811
|
-
if (!resolved) return null;
|
|
5812
|
-
return {
|
|
5813
|
-
slot: {
|
|
5814
|
-
expiresAtMs: resolved.slot.expiresAtMs,
|
|
5815
|
-
id: resolved.slot.id,
|
|
5816
|
-
runtimeProfileId: resolved.slot.runtimeProfileId
|
|
5817
|
-
},
|
|
5818
|
-
session: resolved.slot.sessionDir ? {
|
|
5819
|
-
sessionDir: resolved.slot.sessionDir,
|
|
5820
|
-
sessionPath: resolved.slot.sessionPath
|
|
5821
|
-
} : null,
|
|
5822
|
-
workspace: resolved.workspace ? {
|
|
5823
|
-
kind: resolved.workspace.kind,
|
|
5824
|
-
workspaceId: resolved.workspace.workspaceId,
|
|
5825
|
-
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
5826
|
-
worktreePath: resolved.workspace.worktreePath
|
|
5827
|
-
} : null
|
|
5828
|
-
};
|
|
5829
|
-
},
|
|
5830
|
-
async listSlots(input) {
|
|
5831
|
-
const query = {
|
|
5832
|
-
agentName: input.agentName,
|
|
5833
|
-
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
5834
|
-
...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
|
|
5835
|
-
...input.state === void 0 ? {} : { state: input.state }
|
|
5836
|
-
};
|
|
5837
|
-
return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
|
|
5838
|
-
slot: {
|
|
5839
|
-
expiresAtMs: resolved.slot.expiresAtMs,
|
|
5840
|
-
id: resolved.slot.id,
|
|
5841
|
-
lastAttemptN: resolved.slot.lastAttemptN,
|
|
5842
|
-
lastTaskId: resolved.slot.lastTaskId,
|
|
5843
|
-
runtimeProfileId: resolved.slot.runtimeProfileId,
|
|
5844
|
-
slotKey: resolved.slot.slotKey,
|
|
5845
|
-
state: resolved.slot.state,
|
|
5846
|
-
taskType: resolved.slot.taskType
|
|
5847
|
-
},
|
|
5848
|
-
session: resolved.slot.sessionDir ? {
|
|
5849
|
-
sessionDir: resolved.slot.sessionDir,
|
|
5850
|
-
sessionPath: resolved.slot.sessionPath
|
|
5851
|
-
} : null,
|
|
5852
|
-
workspace: resolved.workspace ? {
|
|
5853
|
-
kind: resolved.workspace.kind,
|
|
5854
|
-
workspaceId: resolved.workspace.workspaceId,
|
|
5855
|
-
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
5856
|
-
worktreePath: resolved.workspace.worktreePath
|
|
5857
|
-
} : null
|
|
5858
|
-
}));
|
|
5859
|
-
},
|
|
5860
|
-
async close() {}
|
|
5861
|
-
};
|
|
5862
|
-
}
|
|
5863
|
-
//#endregion
|
|
5864
|
-
//#region src/lib/secret-redaction.ts
|
|
5865
|
-
var REDACTED = "[REDACTED]";
|
|
5866
|
-
var MIN_SECRET_LENGTH = 8;
|
|
5867
|
-
/**
|
|
5868
|
-
* Redact required runtime-profile environment values before task output leaves
|
|
5869
|
-
* the daemon. This is defense in depth for shell-capable profiles: a model may
|
|
5870
|
-
* read a forwarded provider key, but it must not be able to publish that exact
|
|
5871
|
-
* value through a task result.
|
|
5872
|
-
*/
|
|
5873
|
-
function redactRequiredEnvValues(value, requiredEnv, env) {
|
|
5874
|
-
const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
|
|
5875
|
-
if (secrets.length === 0) return value;
|
|
5876
|
-
const redact = (candidate) => {
|
|
5877
|
-
if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
|
|
5878
|
-
if (Array.isArray(candidate)) return candidate.map(redact);
|
|
5879
|
-
if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
|
|
5880
|
-
return candidate;
|
|
5881
|
-
};
|
|
5882
|
-
return redact(value);
|
|
5883
|
-
}
|
|
5884
|
-
//#endregion
|
|
5885
|
-
//#region src/lib/shutdown-signal.ts
|
|
5886
|
-
function installShutdownSignalHandlers(opts) {
|
|
5887
|
-
const proc = opts.proc ?? process;
|
|
5888
|
-
let drainingSignal = null;
|
|
5889
|
-
const onSignal = (signal) => {
|
|
5890
|
-
if (drainingSignal) {
|
|
5891
|
-
proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
|
|
5892
|
-
proc.exitCode = signalExitCode(signal);
|
|
5581
|
+
}
|
|
5582
|
+
/** Identity aliases available in the shared user-level store. */
|
|
5583
|
+
listIdentityAliases() {
|
|
5584
|
+
return readdirSync(this.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && IDENTITY_ALIAS_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
5585
|
+
}
|
|
5586
|
+
resolveIdentityAlias(explicit, active) {
|
|
5587
|
+
const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
|
|
5588
|
+
if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
|
|
5589
|
+
return assertStoreName("identity alias", alias);
|
|
5590
|
+
}
|
|
5591
|
+
readAgentConfig(alias) {
|
|
5592
|
+
return readJson(this.agentPath(alias));
|
|
5593
|
+
}
|
|
5594
|
+
writeAgentConfig(alias, config) {
|
|
5595
|
+
mkdirSync(this.identityDir(alias), {
|
|
5596
|
+
recursive: true,
|
|
5597
|
+
mode: 448
|
|
5598
|
+
});
|
|
5599
|
+
writeJsonAtomic(this.agentPath(alias), config);
|
|
5600
|
+
if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
|
|
5601
|
+
}
|
|
5602
|
+
removeAgentConfig(alias) {
|
|
5603
|
+
rmSync(this.agentPath(alias), { force: true });
|
|
5604
|
+
this.clearIdentitySelectorIfDefault(alias);
|
|
5605
|
+
}
|
|
5606
|
+
/** Clears the persisted default when it names `alias`. */
|
|
5607
|
+
clearIdentitySelectorIfDefault(alias) {
|
|
5608
|
+
let selector = null;
|
|
5609
|
+
try {
|
|
5610
|
+
selector = this.readIdentitySelector();
|
|
5611
|
+
} catch {
|
|
5893
5612
|
return;
|
|
5894
5613
|
}
|
|
5895
|
-
|
|
5896
|
-
|
|
5614
|
+
if (!selector || selector.default_identity !== alias) return;
|
|
5615
|
+
writeJsonAtomic(this.identitySelectorPath, { version: 1 });
|
|
5616
|
+
}
|
|
5617
|
+
readActivation(alias) {
|
|
5618
|
+
return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
|
|
5619
|
+
}
|
|
5620
|
+
hasPendingRegistration(alias) {
|
|
5621
|
+
return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
|
|
5622
|
+
}
|
|
5623
|
+
reserveRegistration(alias, apiUrl) {
|
|
5624
|
+
const name = assertStoreName("agent name", alias);
|
|
5625
|
+
const state = this.readAgentServerState();
|
|
5626
|
+
if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
|
|
5627
|
+
state.pendingRegistrations[name] = {
|
|
5628
|
+
apiUrl,
|
|
5629
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5630
|
+
};
|
|
5631
|
+
this.writeAgentServerState(state);
|
|
5632
|
+
}
|
|
5633
|
+
clearPendingRegistration(alias) {
|
|
5634
|
+
const name = assertStoreName("agent name", alias);
|
|
5635
|
+
const state = this.readAgentServerState();
|
|
5636
|
+
delete state.pendingRegistrations[name];
|
|
5637
|
+
this.writeAgentServerState(state);
|
|
5638
|
+
}
|
|
5639
|
+
writeActivation(activation) {
|
|
5640
|
+
const alias = assertStoreName("agent name", activation.alias);
|
|
5641
|
+
validateActivation(alias, activation);
|
|
5642
|
+
const state = this.readAgentServerState();
|
|
5643
|
+
state.activations[alias] = activation;
|
|
5644
|
+
if (activation.source === "managed") delete state.pendingRegistrations[alias];
|
|
5645
|
+
this.writeAgentServerState(state);
|
|
5646
|
+
}
|
|
5647
|
+
listActivations() {
|
|
5648
|
+
return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
|
|
5649
|
+
}
|
|
5650
|
+
get providersPath() {
|
|
5651
|
+
return join(this.root, "providers.json");
|
|
5652
|
+
}
|
|
5653
|
+
readProviders() {
|
|
5654
|
+
const state = readJson(this.providersPath) ?? {};
|
|
5655
|
+
this.validateProviders(state);
|
|
5656
|
+
return state;
|
|
5657
|
+
}
|
|
5658
|
+
writeProviders(state) {
|
|
5659
|
+
this.validateProviders(state);
|
|
5660
|
+
writeJsonAtomic(this.providersPath, state);
|
|
5661
|
+
}
|
|
5662
|
+
validateProviders(state) {
|
|
5663
|
+
for (const [id, provider] of Object.entries(state)) {
|
|
5664
|
+
assertProviderId(id);
|
|
5665
|
+
assertProviderEnvName(id, provider.envName);
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
runDir(id) {
|
|
5669
|
+
return storeChildPath(this.runsDir, "run id", id);
|
|
5670
|
+
}
|
|
5671
|
+
resolveRunLogPath(id) {
|
|
5672
|
+
let root;
|
|
5673
|
+
let runDir;
|
|
5897
5674
|
try {
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5675
|
+
root = realpathSync(this.runsDir);
|
|
5676
|
+
runDir = realpathSync(this.runDir(id));
|
|
5677
|
+
} catch (cause) {
|
|
5678
|
+
throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
|
|
5901
5679
|
}
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
} };
|
|
5912
|
-
}
|
|
5913
|
-
function signalExitCode(signal) {
|
|
5914
|
-
return signal === "SIGINT" ? 130 : 143;
|
|
5915
|
-
}
|
|
5916
|
-
//#endregion
|
|
5917
|
-
//#region src/lib/source-attempts.ts
|
|
5918
|
-
function createApiSourceAttemptResolver(args) {
|
|
5919
|
-
const { agent } = args;
|
|
5920
|
-
return {
|
|
5921
|
-
async findOutputBranch(input) {
|
|
5922
|
-
const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
|
|
5923
|
-
if (!attempt || attempt.status !== "completed") return null;
|
|
5924
|
-
return resolveOutputBranch(attempt.output);
|
|
5925
|
-
},
|
|
5926
|
-
async findInputRevision(input) {
|
|
5927
|
-
const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
|
|
5928
|
-
if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
|
|
5929
|
-
return resolveTaskWorkspaceRevision(task.input);
|
|
5680
|
+
if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
|
|
5681
|
+
const logPath = join(runDir, "daemon.log");
|
|
5682
|
+
try {
|
|
5683
|
+
if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
|
|
5684
|
+
const resolvedLog = realpathSync(logPath);
|
|
5685
|
+
if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
|
|
5686
|
+
} catch (cause) {
|
|
5687
|
+
if (cause instanceof AgentServerStoreError) throw cause;
|
|
5688
|
+
if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
|
|
5930
5689
|
}
|
|
5931
|
-
|
|
5932
|
-
}
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5690
|
+
return logPath;
|
|
5691
|
+
}
|
|
5692
|
+
createRunDir(id) {
|
|
5693
|
+
const dir = this.runDir(id);
|
|
5694
|
+
const piDir = join(dir, "pi");
|
|
5695
|
+
mkdirSync(piDir, {
|
|
5696
|
+
recursive: true,
|
|
5697
|
+
mode: 448
|
|
5698
|
+
});
|
|
5699
|
+
return {
|
|
5700
|
+
dir,
|
|
5701
|
+
piDir,
|
|
5702
|
+
logPath: join(dir, "daemon.log")
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
readRun(id) {
|
|
5706
|
+
return readJson(join(this.runDir(id), "run.json"));
|
|
5707
|
+
}
|
|
5708
|
+
writeRun(record) {
|
|
5709
|
+
writeJsonAtomic(join(this.runDir(record.id), "run.json"), record);
|
|
5710
|
+
}
|
|
5711
|
+
listRuns(limit = Number.POSITIVE_INFINITY) {
|
|
5712
|
+
let ids;
|
|
5713
|
+
try {
|
|
5714
|
+
ids = readdirSync(this.runsDir);
|
|
5715
|
+
} catch {
|
|
5716
|
+
return [];
|
|
5717
|
+
}
|
|
5718
|
+
const sortedIds = ids.filter((id) => NAME_RE.test(id)).sort().reverse();
|
|
5719
|
+
const selectedIds = Number.isFinite(limit) ? sortedIds.slice(0, Math.max(0, limit)) : sortedIds;
|
|
5720
|
+
const records = [];
|
|
5721
|
+
for (const id of selectedIds) {
|
|
5722
|
+
const record = this.readRun(id);
|
|
5723
|
+
if (record) records.push(record);
|
|
5724
|
+
}
|
|
5725
|
+
return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
5726
|
+
}
|
|
5727
|
+
/** Remove only completed run directories outside the configured budget. */
|
|
5728
|
+
pruneCompletedRuns(options) {
|
|
5729
|
+
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5730
|
+
let retainedBytes = 0;
|
|
5731
|
+
let retainedCount = 0;
|
|
5732
|
+
const removed = [];
|
|
5733
|
+
for (const record of this.listRuns()) {
|
|
5734
|
+
if (record.status === "running") continue;
|
|
5735
|
+
const dir = this.runDir(record.id);
|
|
5736
|
+
const bytes = directoryBytes(dir);
|
|
5737
|
+
const endedAt = Date.parse(record.endedAt ?? record.startedAt);
|
|
5738
|
+
const expired = !Number.isFinite(endedAt) || now - endedAt > options.maxAgeMs;
|
|
5739
|
+
const overCount = retainedCount >= options.maxCount;
|
|
5740
|
+
const overBytes = retainedBytes + bytes > options.maxBytes;
|
|
5741
|
+
if (expired || overCount || overBytes) {
|
|
5742
|
+
rmSync(dir, {
|
|
5743
|
+
recursive: true,
|
|
5744
|
+
force: true
|
|
5745
|
+
});
|
|
5746
|
+
removed.push(record.id);
|
|
5747
|
+
continue;
|
|
5748
|
+
}
|
|
5749
|
+
retainedCount += 1;
|
|
5750
|
+
retainedBytes += bytes;
|
|
5751
|
+
}
|
|
5752
|
+
return removed;
|
|
5753
|
+
}
|
|
5754
|
+
};
|
|
5755
|
+
function directoryBytes(path) {
|
|
5756
|
+
let info;
|
|
5757
|
+
try {
|
|
5758
|
+
info = lstatSync(path);
|
|
5759
|
+
} catch {
|
|
5760
|
+
return 0;
|
|
5761
|
+
}
|
|
5762
|
+
if (info.isSymbolicLink()) return 0;
|
|
5763
|
+
if (!info.isDirectory()) return info.size;
|
|
5764
|
+
let total = 0;
|
|
5765
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
|
|
5766
|
+
return total;
|
|
5937
5767
|
}
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
const
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5768
|
+
function isRecord$1(value) {
|
|
5769
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5770
|
+
}
|
|
5771
|
+
function storeChildPath(root, kind, value, suffix = "") {
|
|
5772
|
+
const name = assertStoreName(kind, value);
|
|
5773
|
+
const normalizedRoot = resolve(root);
|
|
5774
|
+
const candidate = resolve(normalizedRoot, `${name}${suffix}`);
|
|
5775
|
+
if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
|
|
5776
|
+
return candidate;
|
|
5777
|
+
}
|
|
5778
|
+
function isStrictDescendant(root, candidate) {
|
|
5779
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
5780
|
+
return candidate !== root && candidate.startsWith(rootPrefix);
|
|
5781
|
+
}
|
|
5782
|
+
function validateActivation(alias, value) {
|
|
5783
|
+
const invalid = () => {
|
|
5784
|
+
throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
|
|
5947
5785
|
};
|
|
5786
|
+
if (!isRecord$1(value)) invalid();
|
|
5787
|
+
const activation = value;
|
|
5788
|
+
if (activation.alias !== alias) invalid();
|
|
5789
|
+
if (![
|
|
5790
|
+
"subjectId",
|
|
5791
|
+
"publicKey",
|
|
5792
|
+
"fingerprint",
|
|
5793
|
+
"createdAt"
|
|
5794
|
+
].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
|
|
5795
|
+
if (activation.source === "managed") {
|
|
5796
|
+
if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
if (activation.source !== "external" || typeof activation.configPath !== "string" || activation.configPath.length === 0 || typeof activation.configApiUrl !== "string" || activation.configApiUrl.length === 0 || activation.apiUrl !== void 0 && typeof activation.apiUrl !== "string") invalid();
|
|
5948
5800
|
}
|
|
5949
5801
|
//#endregion
|
|
5950
|
-
//#region src/lib/
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5802
|
+
//#region src/lib/agent-server/pi-store-config.ts
|
|
5803
|
+
/**
|
|
5804
|
+
* Materialise the Agent Server provider store as a Pi agent dir. Shared by
|
|
5805
|
+
* supervised runs (`runs.ts`) and direct `once`/`poll`/`drain` runs
|
|
5806
|
+
* (`pi-agent-dir.ts`) so both generate the same `models.json`.
|
|
5807
|
+
*/
|
|
5808
|
+
/**
|
|
5809
|
+
* Write `models.json` + `settings.json` for the stored providers. API keys
|
|
5810
|
+
* appear only as `$MOLTNET_PROVIDER_<ID>_API_KEY` placeholders.
|
|
5811
|
+
*/
|
|
5812
|
+
function writeStorePiConfig(piDir, providers) {
|
|
5813
|
+
writePiConfig({
|
|
5814
|
+
piDir,
|
|
5815
|
+
providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
|
|
5816
|
+
api: provider.api,
|
|
5817
|
+
...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
|
|
5818
|
+
baseUrl: provider.baseUrl,
|
|
5819
|
+
models: provider.models
|
|
5820
|
+
}]))
|
|
5955
5821
|
});
|
|
5956
|
-
return (event, summary) => {
|
|
5957
|
-
if (event === "text_delta") return;
|
|
5958
|
-
log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
|
|
5959
|
-
event,
|
|
5960
|
-
...summary
|
|
5961
|
-
}, `turn.${event}`);
|
|
5962
|
-
};
|
|
5963
5822
|
}
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5823
|
+
/**
|
|
5824
|
+
* Link a shared Pi `auth.json` into `piDir`. Pi lockfiles the target and
|
|
5825
|
+
* rotates tokens in place, so a link (never a copy) keeps one credential
|
|
5826
|
+
* document. A dangling link is fine — Pi treats a missing auth.json as "no
|
|
5827
|
+
* subscription auth". Failing to create the link is not: the run would start
|
|
5828
|
+
* with a connected subscription silently unavailable.
|
|
5829
|
+
*/
|
|
5830
|
+
function linkPiAuth(authPath, piDir, symlinkImpl = symlinkSync) {
|
|
5831
|
+
try {
|
|
5832
|
+
symlinkImpl(authPath, join(piDir, "auth.json"));
|
|
5833
|
+
} catch (cause) {
|
|
5834
|
+
throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
|
|
5835
|
+
}
|
|
5969
5836
|
}
|
|
5970
5837
|
//#endregion
|
|
5971
|
-
//#region src/
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
profile: {
|
|
5992
|
-
type: "string",
|
|
5993
|
-
multiple: true
|
|
5994
|
-
}
|
|
5995
|
-
}
|
|
5996
|
-
});
|
|
5997
|
-
if (!values.team) {
|
|
5998
|
-
console.error("Missing required flag: --team\n");
|
|
5999
|
-
console.error(opts.helpText);
|
|
6000
|
-
return 1;
|
|
6001
|
-
}
|
|
6002
|
-
const teamId = values.team;
|
|
6003
|
-
const profileValues = parseProfileValues(values.profile);
|
|
6004
|
-
if (profileValues.length === 0) {
|
|
6005
|
-
console.error("Missing required flag: --profile\n");
|
|
6006
|
-
console.error(opts.helpText);
|
|
6007
|
-
return 1;
|
|
5838
|
+
//#region src/lib/pi-agent-dir.ts
|
|
5839
|
+
/**
|
|
5840
|
+
* Pick the Pi agent dir for direct `once`/`poll`/`drain` runs:
|
|
5841
|
+
*
|
|
5842
|
+
* 1. `PI_CODING_AGENT_DIR`, unchanged (`env`).
|
|
5843
|
+
* 2. When the Agent Server store has providers or a subscription login, a
|
|
5844
|
+
* private dir built like an Agent Server run, with `<agentRoot>/.pi`
|
|
5845
|
+
* config the store does not define layered in (`store`).
|
|
5846
|
+
* 3. `<agentRoot>/.pi` (`repo`).
|
|
5847
|
+
*/
|
|
5848
|
+
async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
|
|
5849
|
+
const noop = () => void 0;
|
|
5850
|
+
if (cfg.piCodingAgentDir) {
|
|
5851
|
+
mkdirSync(cfg.piCodingAgentDir, { recursive: true });
|
|
5852
|
+
return {
|
|
5853
|
+
path: cfg.piCodingAgentDir,
|
|
5854
|
+
source: "env",
|
|
5855
|
+
env: {},
|
|
5856
|
+
cleanup: noop
|
|
5857
|
+
};
|
|
6008
5858
|
}
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
return
|
|
5859
|
+
const repoPiDir = join(agentRoot, ".pi");
|
|
5860
|
+
const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
|
|
5861
|
+
const providers = store.readProviders();
|
|
5862
|
+
const storeHasAuth = existsSync(store.piAuthJsonPath);
|
|
5863
|
+
if (Object.keys(providers).length === 0 && !storeHasAuth) {
|
|
5864
|
+
mkdirSync(repoPiDir, { recursive: true });
|
|
5865
|
+
return {
|
|
5866
|
+
path: repoPiDir,
|
|
5867
|
+
source: "repo",
|
|
5868
|
+
env: {},
|
|
5869
|
+
cleanup: noop
|
|
5870
|
+
};
|
|
6016
5871
|
}
|
|
6017
|
-
const
|
|
6018
|
-
|
|
5872
|
+
const path = mkdtempSync(join(options.tempRoot ?? tmpdir(), "moltnet-pi-"));
|
|
5873
|
+
const cleanup = () => rmSync(path, {
|
|
5874
|
+
recursive: true,
|
|
5875
|
+
force: true
|
|
5876
|
+
});
|
|
6019
5877
|
try {
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
if (
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
5878
|
+
writeStorePiConfig(path, providers);
|
|
5879
|
+
const repoModelsPath = join(repoPiDir, "models.json");
|
|
5880
|
+
if (existsSync(repoModelsPath)) {
|
|
5881
|
+
const modelsPath = join(path, "models.json");
|
|
5882
|
+
const merged = mergePiModels(readPiModels(modelsPath), readPiModels(repoModelsPath));
|
|
5883
|
+
writeFileSync(modelsPath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
|
|
5884
|
+
}
|
|
5885
|
+
const repoSettingsPath = join(repoPiDir, "settings.json");
|
|
5886
|
+
if (existsSync(repoSettingsPath)) copyFileSync(repoSettingsPath, join(path, "settings.json"));
|
|
5887
|
+
const repoAuthPath = join(repoPiDir, "auth.json");
|
|
5888
|
+
linkPiAuth(storeHasAuth || !existsSync(repoAuthPath) ? store.piAuthJsonPath : repoAuthPath, path);
|
|
5889
|
+
const secretProviders = options.secretProviders ?? createNodeSecretProviderRegistry().register(new FileSecretProvider({ root: store.secretsDir }));
|
|
5890
|
+
const env = {};
|
|
5891
|
+
for (const providerId of new Set(profiles.map((p) => p.provider))) {
|
|
5892
|
+
const provider = providers[providerId];
|
|
5893
|
+
if (!provider?.apiKeyRef) continue;
|
|
5894
|
+
if (cfg.profilePrerequisiteEnv[provider.envName]) continue;
|
|
5895
|
+
try {
|
|
5896
|
+
env[provider.envName] = await secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
|
|
5897
|
+
} catch {
|
|
5898
|
+
throw new Error(`provider "${providerId}" API key could not be resolved from the Agent Server store`);
|
|
5899
|
+
}
|
|
6026
5900
|
}
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
return 1;
|
|
6037
|
-
}
|
|
6038
|
-
if (values.sandbox) {
|
|
6039
|
-
console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
|
|
6040
|
-
return 1;
|
|
5901
|
+
return {
|
|
5902
|
+
path,
|
|
5903
|
+
source: "store",
|
|
5904
|
+
env,
|
|
5905
|
+
cleanup
|
|
5906
|
+
};
|
|
5907
|
+
} catch (error) {
|
|
5908
|
+
cleanup();
|
|
5909
|
+
throw error;
|
|
6041
5910
|
}
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
5911
|
+
}
|
|
5912
|
+
function readPiModels(path) {
|
|
5913
|
+
try {
|
|
5914
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
5915
|
+
if (typeof parsed?.providers === "object" && parsed.providers !== null) return parsed;
|
|
5916
|
+
} catch {}
|
|
5917
|
+
throw new Error(`${path} is not a valid Pi models document`);
|
|
5918
|
+
}
|
|
5919
|
+
/**
|
|
5920
|
+
* Store entries win per provider id. Repo models the store lacks are appended
|
|
5921
|
+
* with their metadata; repo-only providers are added unchanged.
|
|
5922
|
+
*/
|
|
5923
|
+
function mergePiModels(store, repo) {
|
|
5924
|
+
const providers = {
|
|
5925
|
+
...repo.providers,
|
|
5926
|
+
...store.providers
|
|
6047
5927
|
};
|
|
6048
|
-
const
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
5928
|
+
for (const [id, storeProvider] of Object.entries(store.providers)) {
|
|
5929
|
+
const repoModels = repo.providers[id]?.models ?? [];
|
|
5930
|
+
const known = new Set((storeProvider.models ?? []).map((m) => m.id));
|
|
5931
|
+
providers[id] = {
|
|
5932
|
+
...storeProvider,
|
|
5933
|
+
models: [...storeProvider.models ?? [], ...repoModels.filter((m) => !known.has(m.id))]
|
|
5934
|
+
};
|
|
5935
|
+
}
|
|
5936
|
+
return { providers };
|
|
5937
|
+
}
|
|
5938
|
+
//#endregion
|
|
5939
|
+
//#region src/lib/runtime-context.ts
|
|
5940
|
+
var storage = new AsyncLocalStorage();
|
|
5941
|
+
function runWithDaemonRuntimeContext(context, callback) {
|
|
5942
|
+
return storage.run(context, callback);
|
|
5943
|
+
}
|
|
5944
|
+
//#endregion
|
|
5945
|
+
//#region src/lib/runtime-profile-retry-triage.ts
|
|
5946
|
+
function createRuntimeProfileRetryTriage(options) {
|
|
5947
|
+
return async (input) => {
|
|
5948
|
+
const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
|
|
5949
|
+
return createPiRetryTriage({
|
|
5950
|
+
model: modelHandle,
|
|
5951
|
+
modelRuntime,
|
|
5952
|
+
thinkingLevel: options.runtimeProfile.thinkingLevel,
|
|
5953
|
+
piAgentDir: options.piAgentDir,
|
|
5954
|
+
timeoutMs: options.timeoutMs,
|
|
5955
|
+
cwd: options.cwd
|
|
5956
|
+
})(input);
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
//#endregion
|
|
5960
|
+
//#region src/lib/runtime-resource-reaper.ts
|
|
5961
|
+
var RUNTIME_SLOT_PAGE_LIMIT = 200;
|
|
5962
|
+
var ATTEMPT_LOOKUP_CONCURRENCY = 8;
|
|
5963
|
+
var TERMINAL_ATTEMPT_STATUSES = new Set([
|
|
5964
|
+
"completed",
|
|
5965
|
+
"failed",
|
|
5966
|
+
"cancelled",
|
|
5967
|
+
"aborted",
|
|
5968
|
+
"timed_out"
|
|
5969
|
+
]);
|
|
5970
|
+
/**
|
|
5971
|
+
* Reap daemon-owned local resources whose runtime slot is no longer usable.
|
|
5972
|
+
* The destructive path is deliberately conservative: uncertainty about slot
|
|
5973
|
+
* ownership, attempt state, listing completeness, or real filesystem
|
|
5974
|
+
* containment retains resources rather than risking another daemon's data.
|
|
5975
|
+
*/
|
|
5976
|
+
async function reapRuntimeSlotResources(deps, input) {
|
|
5977
|
+
const now = input.now ?? Date.now();
|
|
5978
|
+
const [activePage, idlePage] = await Promise.all([listOwnedSlots(deps.runtimeSlotStore, input, "active"), listOwnedSlots(deps.runtimeSlotStore, input, "idle")]);
|
|
5979
|
+
const active = activePage.items;
|
|
5980
|
+
const idle = idlePage.items;
|
|
5981
|
+
const slots = [...active, ...idle];
|
|
5982
|
+
const result = {
|
|
5983
|
+
failed: 0,
|
|
5984
|
+
failures: [],
|
|
5985
|
+
removedSessions: 0,
|
|
5986
|
+
removedWorkspaces: 0,
|
|
5987
|
+
scanned: slots.length,
|
|
5988
|
+
truncated: activePage.truncated || idlePage.truncated,
|
|
5989
|
+
unsafePathDetails: [],
|
|
5990
|
+
unsafePaths: 0
|
|
5991
|
+
};
|
|
5992
|
+
const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
|
|
5993
|
+
for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
|
|
5994
|
+
const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
|
|
5995
|
+
const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
|
|
5996
|
+
const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
|
|
5997
|
+
kind: "ready",
|
|
5998
|
+
paths: /* @__PURE__ */ new Set()
|
|
5999
|
+
};
|
|
6000
|
+
for (const item of slots) {
|
|
6001
|
+
if (!reaped.has(item.slot.id)) continue;
|
|
6002
|
+
if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
|
|
6003
|
+
const sessionDir = item.session?.sessionDir;
|
|
6004
|
+
if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
|
|
6005
|
+
else try {
|
|
6006
|
+
rmSync(sessionDir, {
|
|
6007
|
+
force: true,
|
|
6008
|
+
recursive: true
|
|
6105
6009
|
});
|
|
6106
|
-
|
|
6010
|
+
result.removedSessions++;
|
|
6011
|
+
} catch (err) {
|
|
6012
|
+
recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
|
|
6107
6013
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
|
|
6117
|
-
const preparedRuntimes = /* @__PURE__ */ new Map();
|
|
6118
|
-
const profiles = [];
|
|
6119
|
-
const skippedProfileBoundaries = [];
|
|
6120
|
-
for (const profile of resolvedProfiles) try {
|
|
6121
|
-
assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
|
|
6122
|
-
const prepared = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
|
|
6123
|
-
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
|
|
6124
|
-
tools: prepared.tools,
|
|
6125
|
-
executables: prepared.executables
|
|
6126
|
-
});
|
|
6127
|
-
assertGuestEnvironmentBoundary({
|
|
6128
|
-
forwardEnv: profile.requiredEnv,
|
|
6129
|
-
sandboxEnv: profile.sandboxConfig.env
|
|
6130
|
-
});
|
|
6131
|
-
await ctx.agent.tasks.registerExecutorManifest(await prepared.attestor.registration());
|
|
6132
|
-
preparedRuntimes.set(profile.id, prepared);
|
|
6133
|
-
profiles.push(profile);
|
|
6134
|
-
} catch (error) {
|
|
6135
|
-
if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
|
|
6136
|
-
skippedProfileBoundaries.push({
|
|
6137
|
-
profile,
|
|
6138
|
-
error
|
|
6139
|
-
});
|
|
6140
|
-
}
|
|
6141
|
-
if (profiles.length === 0) {
|
|
6142
|
-
const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
|
|
6143
|
-
throw new Error(`No safe runtime profiles remain. ${details}`);
|
|
6014
|
+
const workspacePath = item.workspace?.worktreePath;
|
|
6015
|
+
if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
|
|
6016
|
+
const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
|
|
6017
|
+
if (!workspaceRoot) {
|
|
6018
|
+
recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
|
|
6019
|
+
continue;
|
|
6020
|
+
}
|
|
6021
|
+
await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
|
|
6144
6022
|
}
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6023
|
+
if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
|
|
6024
|
+
return result;
|
|
6025
|
+
}
|
|
6026
|
+
async function listOwnedSlots(store, input, state) {
|
|
6027
|
+
const listed = await store.listSlots({
|
|
6028
|
+
agentName: input.agentName,
|
|
6029
|
+
limit: RUNTIME_SLOT_PAGE_LIMIT,
|
|
6030
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
6031
|
+
state,
|
|
6032
|
+
teamId: input.teamId
|
|
6149
6033
|
});
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
} });
|
|
6162
|
-
const sandbox = {
|
|
6163
|
-
config: profile.sandboxConfig,
|
|
6164
|
-
rootDir: profile.mountPath,
|
|
6165
|
-
path: profile.source
|
|
6166
|
-
};
|
|
6167
|
-
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
6168
|
-
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
6169
|
-
const slotIdentity = {
|
|
6170
|
-
agentName: common.agent,
|
|
6171
|
-
runtimeProfileId: profile.id,
|
|
6172
|
-
runtimeInstanceId
|
|
6173
|
-
};
|
|
6174
|
-
const executionPlans = createExecutionPlanCache({
|
|
6175
|
-
stateDirs,
|
|
6176
|
-
slotIdentity,
|
|
6177
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
6178
|
-
workspacePolicy: {
|
|
6179
|
-
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6180
|
-
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6181
|
-
},
|
|
6182
|
-
slotRegistry,
|
|
6183
|
-
runtimeSessionStore,
|
|
6184
|
-
sourceAttemptResolver
|
|
6185
|
-
});
|
|
6186
|
-
runtimes.set(profile.id, {
|
|
6187
|
-
common,
|
|
6188
|
-
profile,
|
|
6189
|
-
sandbox,
|
|
6190
|
-
stateDirs,
|
|
6191
|
-
piAgentDir,
|
|
6192
|
-
slotIdentity,
|
|
6193
|
-
executionPlans,
|
|
6194
|
-
preparedRuntime: preparedRuntimes.get(profile.id)
|
|
6195
|
-
});
|
|
6034
|
+
return {
|
|
6035
|
+
items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
|
|
6036
|
+
truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
6039
|
+
async function findTerminalActiveSlotIds(deps, active, result) {
|
|
6040
|
+
const byTask = /* @__PURE__ */ new Map();
|
|
6041
|
+
for (const item of active) {
|
|
6042
|
+
const taskSlots = byTask.get(item.slot.lastTaskId) ?? [];
|
|
6043
|
+
taskSlots.push(item);
|
|
6044
|
+
byTask.set(item.slot.lastTaskId, taskSlots);
|
|
6196
6045
|
}
|
|
6197
|
-
const
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
resourceAttributes: {
|
|
6206
|
-
"moltnet.team.id": teamId,
|
|
6207
|
-
"moltnet.agent.name": baseCommon.agent,
|
|
6208
|
-
"moltnet.credential.source": ctx.credentialSource,
|
|
6209
|
-
"moltnet.runtime_profile.count": String(profiles.length),
|
|
6210
|
-
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
6046
|
+
const terminal = /* @__PURE__ */ new Set();
|
|
6047
|
+
await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
|
|
6048
|
+
let attempts;
|
|
6049
|
+
try {
|
|
6050
|
+
attempts = await deps.taskReader.listAttempts(taskId);
|
|
6051
|
+
} catch (err) {
|
|
6052
|
+
recordFailure(deps, result, null, `task:${taskId}`, null, err);
|
|
6053
|
+
return;
|
|
6211
6054
|
}
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
|
|
6216
|
-
});
|
|
6217
|
-
const rootLogger = logger.child({
|
|
6218
|
-
mode: opts.modeLabel,
|
|
6219
|
-
agent: baseCommon.agent,
|
|
6220
|
-
teamId,
|
|
6221
|
-
runtimeProfileIds: profiles.map((p) => p.id),
|
|
6222
|
-
runtimeProfileNames: profiles.map((p) => p.name)
|
|
6223
|
-
});
|
|
6224
|
-
for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
|
|
6225
|
-
runtimeProfileId: profile.id,
|
|
6226
|
-
runtimeProfileName: profile.name,
|
|
6227
|
-
refusedEnvironmentNames: error.refusedNames
|
|
6228
|
-
}, "agent-daemon.runtime_profile_skipped_credential_boundary");
|
|
6229
|
-
const abort = new AbortController();
|
|
6230
|
-
let runtime = null;
|
|
6231
|
-
let active = null;
|
|
6232
|
-
const signalHandlers = installShutdownSignalHandlers({
|
|
6233
|
-
logDrain: (signal) => {
|
|
6234
|
-
rootLogger.warn({ signal }, "agent-daemon.draining");
|
|
6235
|
-
},
|
|
6236
|
-
drain: (signal) => {
|
|
6237
|
-
abort.abort();
|
|
6238
|
-
runtime?.stop(`agent-daemon received ${signal}`);
|
|
6239
|
-
abortActiveAttemptOnSignal({
|
|
6240
|
-
active,
|
|
6241
|
-
signal,
|
|
6242
|
-
abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
|
|
6243
|
-
logFailure: (err, current) => {
|
|
6244
|
-
try {
|
|
6245
|
-
rootLogger.warn({
|
|
6246
|
-
err: err instanceof Error ? err.message : String(err),
|
|
6247
|
-
taskId: current.taskId,
|
|
6248
|
-
attemptN: current.attemptN
|
|
6249
|
-
}, "agent-daemon.abort_on_signal_failed");
|
|
6250
|
-
} catch {}
|
|
6251
|
-
}
|
|
6252
|
-
});
|
|
6055
|
+
for (const item of taskSlots) {
|
|
6056
|
+
const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
|
|
6057
|
+
if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
|
|
6253
6058
|
}
|
|
6254
6059
|
});
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6060
|
+
return terminal;
|
|
6061
|
+
}
|
|
6062
|
+
async function slotStillQualifiesForReaping(deps, input, original, now, result) {
|
|
6063
|
+
let page;
|
|
6064
|
+
try {
|
|
6065
|
+
page = await listOwnedSlots(deps.runtimeSlotStore, input, original.slot.state);
|
|
6066
|
+
} catch (err) {
|
|
6067
|
+
recordFailure(deps, result, original.slot.id, `slot:${original.slot.id}`, null, err);
|
|
6068
|
+
return false;
|
|
6069
|
+
}
|
|
6070
|
+
const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
|
|
6071
|
+
if (!current) return false;
|
|
6072
|
+
if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
|
|
6073
|
+
return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
|
|
6074
|
+
}
|
|
6075
|
+
async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
|
|
6076
|
+
if (!input.mainWorktree) return;
|
|
6077
|
+
const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
|
|
6078
|
+
if (!existsSync(workspaceRoot)) return;
|
|
6079
|
+
let entries;
|
|
6080
|
+
try {
|
|
6081
|
+
entries = readdirSync(workspaceRoot, { withFileTypes: true });
|
|
6082
|
+
} catch (err) {
|
|
6083
|
+
recordFailure(deps, result, null, workspaceRoot, workspaceRoot, err);
|
|
6084
|
+
return;
|
|
6085
|
+
}
|
|
6086
|
+
for (const entry of entries) {
|
|
6087
|
+
if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
|
|
6088
|
+
const workspacePath = resolve(workspaceRoot, entry.name);
|
|
6089
|
+
if (retainedWorkspacePaths.has(workspacePath)) continue;
|
|
6090
|
+
const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
|
|
6091
|
+
if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
|
|
6092
|
+
await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
async function removeWorkspace(deps, result, worktrees, mainWorktree, slotId, workspacePath, workspaceRoot, scratch) {
|
|
6096
|
+
if (!existsSync(workspacePath)) return;
|
|
6097
|
+
if (!isRealPathInsideRoot(workspacePath, workspaceRoot)) {
|
|
6098
|
+
recordUnsafePath(deps, result, slotId, workspacePath, workspaceRoot);
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
if (scratch || !mainWorktree) {
|
|
6297
6102
|
try {
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
...issue,
|
|
6304
|
-
runtimeProfileId: profile.id
|
|
6305
|
-
}, "agent-daemon.runtime_resource_reap_issue");
|
|
6306
|
-
},
|
|
6307
|
-
runtimeSlotStore: slotRegistry,
|
|
6308
|
-
taskReader: ctx.agent.tasks
|
|
6309
|
-
}, {
|
|
6310
|
-
agentName: selected.common.agent,
|
|
6311
|
-
mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
|
|
6312
|
-
runtimeInstanceId,
|
|
6313
|
-
runtimeProfileId: profile.id,
|
|
6314
|
-
sessionRootDir: selected.stateDirs.piSessionsDir,
|
|
6315
|
-
scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
|
|
6316
|
-
teamId
|
|
6317
|
-
});
|
|
6318
|
-
if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
|
|
6319
|
-
...result,
|
|
6320
|
-
runtimeProfileId: profile.id
|
|
6321
|
-
}, "agent-daemon.runtime_resources_reaped");
|
|
6322
|
-
}
|
|
6103
|
+
rmSync(workspacePath, {
|
|
6104
|
+
force: true,
|
|
6105
|
+
recursive: true
|
|
6106
|
+
});
|
|
6107
|
+
result.removedWorkspaces++;
|
|
6323
6108
|
} catch (err) {
|
|
6324
|
-
|
|
6325
|
-
} finally {
|
|
6326
|
-
reaperRunning = false;
|
|
6109
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
6327
6110
|
}
|
|
6111
|
+
return;
|
|
6112
|
+
}
|
|
6113
|
+
if (worktrees.kind === "unavailable") {
|
|
6114
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
try {
|
|
6118
|
+
const canonical = realpathSync(workspacePath);
|
|
6119
|
+
if (worktrees.paths.has(canonical)) {
|
|
6120
|
+
await execFileText("git", [
|
|
6121
|
+
"-C",
|
|
6122
|
+
mainWorktree,
|
|
6123
|
+
"worktree",
|
|
6124
|
+
"remove",
|
|
6125
|
+
"--force",
|
|
6126
|
+
workspacePath
|
|
6127
|
+
]);
|
|
6128
|
+
worktrees.paths.delete(canonical);
|
|
6129
|
+
} else rmSync(workspacePath, {
|
|
6130
|
+
force: true,
|
|
6131
|
+
recursive: true
|
|
6132
|
+
});
|
|
6133
|
+
result.removedWorkspaces++;
|
|
6134
|
+
} catch (err) {
|
|
6135
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
async function listRegisteredWorktrees(mainWorktree) {
|
|
6139
|
+
try {
|
|
6140
|
+
const list = await execFileText("git", [
|
|
6141
|
+
"-C",
|
|
6142
|
+
mainWorktree,
|
|
6143
|
+
"worktree",
|
|
6144
|
+
"list",
|
|
6145
|
+
"--porcelain"
|
|
6146
|
+
]);
|
|
6147
|
+
return {
|
|
6148
|
+
kind: "ready",
|
|
6149
|
+
paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
|
|
6150
|
+
};
|
|
6151
|
+
} catch (err) {
|
|
6152
|
+
return {
|
|
6153
|
+
kind: "unavailable",
|
|
6154
|
+
reason: errorMessage(err)
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
function isRealPathInsideRoot(path, root) {
|
|
6159
|
+
try {
|
|
6160
|
+
return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
|
|
6161
|
+
} catch {
|
|
6162
|
+
return false;
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
function canonicalPath(path) {
|
|
6166
|
+
try {
|
|
6167
|
+
return realpathSync(path);
|
|
6168
|
+
} catch {
|
|
6169
|
+
return resolve(path);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
function recordUnsafePath(deps, result, slotId, path, root) {
|
|
6173
|
+
const issue = {
|
|
6174
|
+
kind: "unsafe_path",
|
|
6175
|
+
path,
|
|
6176
|
+
reason: root === null ? "No daemon-owned root was available for this workspace." : "Candidate or root could not be resolved inside the daemon-owned root.",
|
|
6177
|
+
root,
|
|
6178
|
+
slotId
|
|
6328
6179
|
};
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
const
|
|
6180
|
+
result.unsafePaths++;
|
|
6181
|
+
result.unsafePathDetails.push(issue);
|
|
6182
|
+
emitIssue(deps, issue);
|
|
6183
|
+
}
|
|
6184
|
+
function recordFailure(deps, result, slotId, path, root, err) {
|
|
6185
|
+
const issue = {
|
|
6186
|
+
kind: "failure",
|
|
6187
|
+
path,
|
|
6188
|
+
reason: errorMessage(err),
|
|
6189
|
+
root,
|
|
6190
|
+
slotId
|
|
6191
|
+
};
|
|
6192
|
+
result.failed++;
|
|
6193
|
+
result.failures.push(issue);
|
|
6194
|
+
emitIssue(deps, issue);
|
|
6195
|
+
}
|
|
6196
|
+
function emitIssue(deps, issue) {
|
|
6335
6197
|
try {
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
pollIntervalMs,
|
|
6351
|
-
maxPollIntervalMs,
|
|
6352
|
-
signal: abort.signal,
|
|
6353
|
-
stopWhenEmpty: opts.stopWhenEmpty,
|
|
6354
|
-
waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
|
|
6355
|
-
waitAfterTaskMs: waitAfterTaskSec * 1e3,
|
|
6356
|
-
debug: baseCommon.debug,
|
|
6357
|
-
traceIdlePolling: cfg.traceIdlePolling,
|
|
6358
|
-
logger: rootLogger,
|
|
6359
|
-
executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
|
|
6360
|
-
slotRegistry,
|
|
6361
|
-
sessionRegistry: runtimeSessionStore,
|
|
6362
|
-
sourceAttemptResolver
|
|
6363
|
-
}),
|
|
6364
|
-
makeReporter: (claimedTask) => {
|
|
6365
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6366
|
-
return new ApiTaskReporter({
|
|
6367
|
-
tasks: ctx.agent.tasks,
|
|
6368
|
-
teamId: claimedTask.task.teamId,
|
|
6369
|
-
leaseTtlSec: selected.common.leaseTtlSec,
|
|
6370
|
-
heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
|
|
6371
|
-
maxBatchSize: selected.common.maxBatchSize,
|
|
6372
|
-
flushIntervalMs: selected.common.flushIntervalMs
|
|
6373
|
-
});
|
|
6374
|
-
},
|
|
6375
|
-
onTaskFinished: async (output, claimedTask) => {
|
|
6376
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6377
|
-
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
6378
|
-
let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
6379
|
-
if (resolved?.session?.sessionDir) try {
|
|
6380
|
-
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
6381
|
-
await runtimeSessionStore.uploadAttemptFinal({
|
|
6382
|
-
attemptN: claimedTask.attemptN,
|
|
6383
|
-
parentSessionId: parentSession?.id ?? null,
|
|
6384
|
-
sessionDir: resolved.session.sessionDir,
|
|
6385
|
-
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
6386
|
-
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
6387
|
-
sourceSlotId: resolved.slot.id,
|
|
6388
|
-
taskId: claimedTask.task.id,
|
|
6389
|
-
teamId: claimedTask.task.teamId
|
|
6390
|
-
});
|
|
6391
|
-
} catch (err) {
|
|
6392
|
-
rootLogger.error({
|
|
6393
|
-
err,
|
|
6394
|
-
taskId: claimedTask.task.id,
|
|
6395
|
-
attemptN: claimedTask.attemptN
|
|
6396
|
-
}, "agent-daemon.runtime_session_upload_failed");
|
|
6397
|
-
terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
|
|
6398
|
-
}
|
|
6399
|
-
return finalizeTask(ctx.agent, terminalOutput, {
|
|
6400
|
-
task: claimedTask.task,
|
|
6401
|
-
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
6402
|
-
retryTriage: createRuntimeProfileRetryTriage({
|
|
6403
|
-
runtimeProfile: selected.profile,
|
|
6404
|
-
piAgentDir: selected.piAgentDir.path,
|
|
6405
|
-
cwd: ctx.agentRootDir
|
|
6406
|
-
}),
|
|
6407
|
-
executorAttestor: selected.preparedRuntime.attestor,
|
|
6408
|
-
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
6409
|
-
gh: createGhCliClient(),
|
|
6410
|
-
logger: rootLogger.child({
|
|
6411
|
-
runtimeProfileId: selected.profile.id,
|
|
6412
|
-
runtimeProfileName: selected.profile.name
|
|
6413
|
-
})
|
|
6414
|
-
}),
|
|
6415
|
-
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
6416
|
-
});
|
|
6417
|
-
},
|
|
6418
|
-
executeTask: async (claimedTask, reporter) => {
|
|
6419
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6420
|
-
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
|
|
6421
|
-
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6422
|
-
config: runtimeCredentialConfig,
|
|
6423
|
-
profile,
|
|
6424
|
-
offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
|
|
6425
|
-
registry: createNodeSecretProviderRegistry(),
|
|
6426
|
-
executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
|
|
6427
|
-
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6428
|
-
taskId: claimedTask.task.id,
|
|
6429
|
-
attemptN: claimedTask.attemptN,
|
|
6430
|
-
logger: rootLogger
|
|
6431
|
-
});
|
|
6432
|
-
const taskLogger = rootLogger.child({
|
|
6433
|
-
runtimeProfileId: profile.id,
|
|
6434
|
-
runtimeProfileName: profile.name,
|
|
6435
|
-
provider: profile.provider,
|
|
6436
|
-
model: profile.model,
|
|
6437
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6438
|
-
temperature: profile.temperature,
|
|
6439
|
-
topP: profile.topP,
|
|
6440
|
-
topK: profile.topK,
|
|
6441
|
-
maxOutputTokens: profile.maxOutputTokens
|
|
6442
|
-
});
|
|
6443
|
-
let executionPlan;
|
|
6444
|
-
try {
|
|
6445
|
-
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
6446
|
-
} catch (err) {
|
|
6447
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6448
|
-
taskLogger.warn({
|
|
6449
|
-
taskId: claimedTask.task.id,
|
|
6450
|
-
attemptN: claimedTask.attemptN,
|
|
6451
|
-
err: message
|
|
6452
|
-
}, "agent-daemon.execution_plan_failed");
|
|
6453
|
-
return {
|
|
6454
|
-
taskId: claimedTask.task.id,
|
|
6455
|
-
attemptN: claimedTask.attemptN,
|
|
6456
|
-
status: "failed",
|
|
6457
|
-
output: null,
|
|
6458
|
-
outputCid: null,
|
|
6459
|
-
usage: {
|
|
6460
|
-
inputTokens: 0,
|
|
6461
|
-
outputTokens: 0
|
|
6462
|
-
},
|
|
6463
|
-
durationMs: 0,
|
|
6464
|
-
error: {
|
|
6465
|
-
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
6466
|
-
message,
|
|
6467
|
-
retryable: false
|
|
6468
|
-
}
|
|
6469
|
-
};
|
|
6470
|
-
}
|
|
6471
|
-
const sessionDescriptor = executionPlan.descriptor;
|
|
6472
|
-
taskLogger.debug({
|
|
6473
|
-
taskId: claimedTask.task.id,
|
|
6474
|
-
taskType: claimedTask.task.taskType,
|
|
6475
|
-
resumable: sessionDescriptor.policy.resumable,
|
|
6476
|
-
workspaceMode: executionPlan.workspaceMode,
|
|
6477
|
-
workspaceScope: sessionDescriptor.policy.workspaceScope,
|
|
6478
|
-
sessionScope: sessionDescriptor.policy.sessionScope,
|
|
6479
|
-
slotKey: executionPlan.slotKey,
|
|
6480
|
-
slotId: executionPlan.slotId,
|
|
6481
|
-
sessionKey: executionPlan.sessionKey,
|
|
6482
|
-
piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
|
|
6483
|
-
workspaceId: executionPlan.workspaceId
|
|
6484
|
-
}, "agent-daemon.task_execution_policy");
|
|
6485
|
-
if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
|
|
6486
|
-
taskId: claimedTask.task.id,
|
|
6487
|
-
attemptN: claimedTask.attemptN,
|
|
6488
|
-
status: "failed",
|
|
6489
|
-
output: null,
|
|
6490
|
-
outputCid: null,
|
|
6491
|
-
usage: {
|
|
6492
|
-
inputTokens: 0,
|
|
6493
|
-
outputTokens: 0
|
|
6494
|
-
},
|
|
6495
|
-
durationMs: 0,
|
|
6496
|
-
error: {
|
|
6497
|
-
code: "unsupported_task_type",
|
|
6498
|
-
message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
|
|
6499
|
-
retryable: true
|
|
6500
|
-
}
|
|
6501
|
-
};
|
|
6502
|
-
if (reporter.cancelSignal.aborted) return {
|
|
6503
|
-
taskId: claimedTask.task.id,
|
|
6504
|
-
attemptN: claimedTask.attemptN,
|
|
6505
|
-
status: "cancelled",
|
|
6506
|
-
output: null,
|
|
6507
|
-
outputCid: null,
|
|
6508
|
-
usage: {
|
|
6509
|
-
inputTokens: 0,
|
|
6510
|
-
outputTokens: 0
|
|
6511
|
-
},
|
|
6512
|
-
durationMs: 0,
|
|
6513
|
-
error: {
|
|
6514
|
-
code: "task_cancelled",
|
|
6515
|
-
message: reporter.cancelReason ?? "Task cancelled before executor started.",
|
|
6516
|
-
retryable: false
|
|
6517
|
-
}
|
|
6518
|
-
};
|
|
6519
|
-
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
6520
|
-
...slotIdentity,
|
|
6521
|
-
runtimeProfileId: profile.id,
|
|
6522
|
-
provider: profile.provider,
|
|
6523
|
-
model: profile.model,
|
|
6524
|
-
teamId: claimedTask.task.teamId,
|
|
6525
|
-
slotKey: executionPlan.slotKey,
|
|
6526
|
-
taskType: claimedTask.task.taskType,
|
|
6527
|
-
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
6528
|
-
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
6529
|
-
workspaceId: executionPlan.workspaceId,
|
|
6530
|
-
worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
6531
|
-
worktreeBranch: executionPlan.worktreeBranch,
|
|
6532
|
-
workspaceKind: executionPlan.workspaceKind,
|
|
6533
|
-
lastTaskId: claimedTask.task.id,
|
|
6534
|
-
lastAttemptN: claimedTask.attemptN
|
|
6535
|
-
});
|
|
6536
|
-
const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
|
|
6537
|
-
agentName: common.agent,
|
|
6538
|
-
moltnetAgent: ctx.agent,
|
|
6539
|
-
agentIdentity,
|
|
6540
|
-
hostCapabilitySigner,
|
|
6541
|
-
hostCapabilityLogger: taskLogger,
|
|
6542
|
-
agentRootDir: ctx.agentRootDir,
|
|
6543
|
-
mountPath: sandbox.rootDir,
|
|
6544
|
-
provider: profile.provider,
|
|
6545
|
-
model: profile.model,
|
|
6546
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6547
|
-
temperature: profile.temperature,
|
|
6548
|
-
topP: profile.topP,
|
|
6549
|
-
topK: profile.topK,
|
|
6550
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6551
|
-
sandboxConfig: sandbox.config,
|
|
6552
|
-
forwardEnv: profile.requiredEnv,
|
|
6553
|
-
onVmDiagnostic: (diagnostic) => {
|
|
6554
|
-
const fields = {
|
|
6555
|
-
event: diagnostic.event,
|
|
6556
|
-
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
6557
|
-
};
|
|
6558
|
-
if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
|
|
6559
|
-
else taskLogger.info(fields, diagnostic.message);
|
|
6560
|
-
},
|
|
6561
|
-
runtimeProfileContext: profile.context,
|
|
6562
|
-
runtimeProfileId: profile.id,
|
|
6563
|
-
toolEnforcement: profile.toolEnforcement,
|
|
6564
|
-
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
6565
|
-
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
6566
|
-
toolPolicyLogger: taskLogger,
|
|
6567
|
-
maxTurns: common.maxTurns,
|
|
6568
|
-
maxBashTimeouts: common.maxBashTimeouts
|
|
6569
|
-
});
|
|
6570
|
-
try {
|
|
6571
|
-
active = {
|
|
6572
|
-
taskId: claimedTask.task.id,
|
|
6573
|
-
attemptN: claimedTask.attemptN
|
|
6574
|
-
};
|
|
6575
|
-
return await runWithDaemonRuntimeContext({
|
|
6576
|
-
profileId: profile.id,
|
|
6577
|
-
profileName: profile.name,
|
|
6578
|
-
provider: profile.provider,
|
|
6579
|
-
model: profile.model,
|
|
6580
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6581
|
-
temperature: profile.temperature,
|
|
6582
|
-
topP: profile.topP,
|
|
6583
|
-
topK: profile.topK,
|
|
6584
|
-
maxOutputTokens: profile.maxOutputTokens
|
|
6585
|
-
}, () => rawExecuteTask(claimedTask, reporter));
|
|
6586
|
-
} finally {
|
|
6587
|
-
active = null;
|
|
6588
|
-
executionPlans.delete(claimedTask);
|
|
6589
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
6590
|
-
}
|
|
6591
|
-
}
|
|
6198
|
+
deps.onIssue?.(issue);
|
|
6199
|
+
} catch {}
|
|
6200
|
+
}
|
|
6201
|
+
function errorMessage(err) {
|
|
6202
|
+
return err instanceof Error ? err.message : String(err);
|
|
6203
|
+
}
|
|
6204
|
+
async function execFileText(file, args) {
|
|
6205
|
+
return new Promise((resolvePromise, reject) => {
|
|
6206
|
+
execFile(file, args, {
|
|
6207
|
+
encoding: "utf8",
|
|
6208
|
+
maxBuffer: 10 * 1024 * 1024
|
|
6209
|
+
}, (err, stdout) => {
|
|
6210
|
+
if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
|
|
6211
|
+
else resolvePromise(stdout);
|
|
6592
6212
|
});
|
|
6593
|
-
|
|
6594
|
-
outputs.push(...drained);
|
|
6595
|
-
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
6596
|
-
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
6597
|
-
} finally {
|
|
6598
|
-
clearInterval(reaperTimer);
|
|
6599
|
-
signalHandlers.dispose();
|
|
6600
|
-
await slotRegistry.close();
|
|
6601
|
-
await otelShutdown();
|
|
6602
|
-
await shutdownLogger();
|
|
6603
|
-
}
|
|
6213
|
+
});
|
|
6604
6214
|
}
|
|
6605
|
-
function
|
|
6606
|
-
|
|
6607
|
-
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
6215
|
+
async function mapWithConcurrency(items, concurrency, worker) {
|
|
6216
|
+
for (let index = 0; index < items.length; index += concurrency) await Promise.all(items.slice(index, index + concurrency).map(worker));
|
|
6608
6217
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6218
|
+
//#endregion
|
|
6219
|
+
//#region src/lib/runtime-sessions.ts
|
|
6220
|
+
function resolveRuntimeSessionKind(claimedTask) {
|
|
6221
|
+
const continueFrom = resolveContinueFrom(claimedTask);
|
|
6222
|
+
if (!continueFrom) return "root";
|
|
6223
|
+
return continueFrom.mode === "fork" ? "fork" : "extend";
|
|
6224
|
+
}
|
|
6225
|
+
async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
|
|
6226
|
+
const continueFrom = resolveContinueFrom(claimedTask);
|
|
6227
|
+
if (!continueFrom) return null;
|
|
6228
|
+
return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
|
|
6229
|
+
}
|
|
6230
|
+
function applyRuntimeSessionUploadFailure(output, err) {
|
|
6231
|
+
if (output.status !== "completed") return output;
|
|
6232
|
+
return {
|
|
6233
|
+
...output,
|
|
6234
|
+
error: {
|
|
6235
|
+
code: "runtime_session_upload_failed",
|
|
6236
|
+
message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
|
|
6237
|
+
retryable: isTransientUploadError(err)
|
|
6238
|
+
},
|
|
6239
|
+
output: null,
|
|
6240
|
+
outputCid: null,
|
|
6241
|
+
status: "failed"
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
/**
|
|
6245
|
+
* Transient faults worth retrying in-attempt: network-level errors
|
|
6246
|
+
* (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
|
|
6247
|
+
* validation, not-found) will not heal on retry.
|
|
6248
|
+
*/
|
|
6249
|
+
function isTransientUploadError(error) {
|
|
6250
|
+
if (!(error instanceof Error)) return false;
|
|
6251
|
+
const statusCode = error.statusCode;
|
|
6252
|
+
if (typeof statusCode !== "number") return true;
|
|
6253
|
+
return statusCode >= 500 || statusCode === 429;
|
|
6254
|
+
}
|
|
6255
|
+
var defaultSleep = (ms) => new Promise((resolve) => {
|
|
6256
|
+
setTimeout(resolve, ms);
|
|
6257
|
+
});
|
|
6258
|
+
function createApiRuntimeSessionStore(args) {
|
|
6259
|
+
const { agent, logger, uploadRetry } = args;
|
|
6260
|
+
return {
|
|
6261
|
+
async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
|
|
6262
|
+
return agent.runtimeSessions.getForAttempt({
|
|
6263
|
+
attemptN,
|
|
6264
|
+
taskId
|
|
6265
|
+
}, { teamId });
|
|
6266
|
+
},
|
|
6267
|
+
async hydrateSession(input) {
|
|
6268
|
+
const downloaded = await agent.runtimeSessions.download({
|
|
6269
|
+
attemptN: input.attemptN,
|
|
6270
|
+
taskId: input.taskId
|
|
6271
|
+
}, { teamId: input.teamId });
|
|
6272
|
+
await mkdir(input.destinationDir, { recursive: true });
|
|
6273
|
+
const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
|
|
6274
|
+
await pipeline(downloaded, createWriteStream(sessionPath));
|
|
6275
|
+
return sessionPath;
|
|
6276
|
+
},
|
|
6277
|
+
async uploadAttemptFinal(input) {
|
|
6278
|
+
const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
|
|
6279
|
+
if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
|
|
6280
|
+
const maxTries = uploadRetry?.maxTries ?? 3;
|
|
6281
|
+
const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
|
|
6282
|
+
const sleep = uploadRetry?.sleep ?? defaultSleep;
|
|
6283
|
+
for (let tryN = 1;; tryN += 1) try {
|
|
6284
|
+
await agent.runtimeSessions.upload({
|
|
6285
|
+
attemptN: input.attemptN,
|
|
6286
|
+
taskId: input.taskId
|
|
6287
|
+
}, createReadStream(sessionPath), {
|
|
6288
|
+
parentSessionId: input.parentSessionId ?? void 0,
|
|
6289
|
+
sessionKind: input.sessionKind,
|
|
6290
|
+
sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
|
|
6291
|
+
sourceSlotId: input.sourceSlotId ?? void 0
|
|
6292
|
+
}, { teamId: input.teamId });
|
|
6293
|
+
return;
|
|
6294
|
+
} catch (error) {
|
|
6295
|
+
if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
|
|
6296
|
+
const delayMs = baseDelayMs * tryN;
|
|
6297
|
+
logger?.warn({
|
|
6298
|
+
event: "agent-daemon.runtime_session_upload_retry",
|
|
6299
|
+
attemptN: input.attemptN,
|
|
6300
|
+
delayMs,
|
|
6301
|
+
statusCode: uploadStatusCode(error),
|
|
6302
|
+
taskId: input.taskId,
|
|
6303
|
+
tryN
|
|
6304
|
+
}, "Retrying durable runtime session upload");
|
|
6305
|
+
await sleep(delayMs);
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6310
|
+
function uploadStatusCode(error) {
|
|
6311
|
+
const statusCode = error?.statusCode;
|
|
6312
|
+
return typeof statusCode === "number" ? statusCode : void 0;
|
|
6313
|
+
}
|
|
6314
|
+
function resolveContinueFrom(claimedTask) {
|
|
6315
|
+
return claimedTask.task.input.continueFrom;
|
|
6316
|
+
}
|
|
6317
|
+
//#endregion
|
|
6318
|
+
//#region src/lib/runtime-slots.ts
|
|
6319
|
+
function createApiRuntimeSlotStore(args) {
|
|
6320
|
+
const { agent } = args;
|
|
6321
|
+
return {
|
|
6322
|
+
async beginSlot(input) {
|
|
6323
|
+
await agent.runtimeSlots.begin({
|
|
6324
|
+
agentName: input.agentName,
|
|
6325
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
6326
|
+
lastAttemptN: input.lastAttemptN,
|
|
6327
|
+
lastTaskId: input.lastTaskId,
|
|
6328
|
+
model: input.model,
|
|
6329
|
+
provider: input.provider,
|
|
6330
|
+
sessionDir: input.sessionDir ?? void 0,
|
|
6331
|
+
sessionPath: input.sessionPath ?? void 0,
|
|
6332
|
+
slotKey: input.slotKey,
|
|
6333
|
+
taskType: input.taskType,
|
|
6334
|
+
workspaceId: input.workspaceId ?? void 0,
|
|
6335
|
+
workspaceKind: input.workspaceKind,
|
|
6336
|
+
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
6337
|
+
worktreePath: input.worktreePath ?? void 0
|
|
6338
|
+
}, { teamId: input.teamId });
|
|
6339
|
+
},
|
|
6340
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
6341
|
+
await agent.runtimeSlots.finish({
|
|
6342
|
+
agentName: identity.agentName,
|
|
6343
|
+
attemptN,
|
|
6344
|
+
runtimeProfileId: identity.runtimeProfileId,
|
|
6345
|
+
model,
|
|
6346
|
+
provider,
|
|
6347
|
+
sessionPath: sessionPath ?? void 0,
|
|
6348
|
+
slotKey,
|
|
6349
|
+
taskId
|
|
6350
|
+
}, { teamId });
|
|
6351
|
+
},
|
|
6352
|
+
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
6353
|
+
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
6354
|
+
attemptN,
|
|
6355
|
+
taskId
|
|
6356
|
+
}, { teamId });
|
|
6357
|
+
if (!resolved) return null;
|
|
6358
|
+
return {
|
|
6359
|
+
slot: {
|
|
6360
|
+
expiresAtMs: resolved.slot.expiresAtMs,
|
|
6361
|
+
id: resolved.slot.id,
|
|
6362
|
+
runtimeProfileId: resolved.slot.runtimeProfileId
|
|
6363
|
+
},
|
|
6364
|
+
session: resolved.slot.sessionDir ? {
|
|
6365
|
+
sessionDir: resolved.slot.sessionDir,
|
|
6366
|
+
sessionPath: resolved.slot.sessionPath
|
|
6367
|
+
} : null,
|
|
6368
|
+
workspace: resolved.workspace ? {
|
|
6369
|
+
kind: resolved.workspace.kind,
|
|
6370
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
6371
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
6372
|
+
worktreePath: resolved.workspace.worktreePath
|
|
6373
|
+
} : null
|
|
6374
|
+
};
|
|
6375
|
+
},
|
|
6376
|
+
async listSlots(input) {
|
|
6377
|
+
const query = {
|
|
6378
|
+
agentName: input.agentName,
|
|
6379
|
+
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
6380
|
+
...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
|
|
6381
|
+
...input.state === void 0 ? {} : { state: input.state }
|
|
6382
|
+
};
|
|
6383
|
+
return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
|
|
6384
|
+
slot: {
|
|
6385
|
+
expiresAtMs: resolved.slot.expiresAtMs,
|
|
6386
|
+
id: resolved.slot.id,
|
|
6387
|
+
lastAttemptN: resolved.slot.lastAttemptN,
|
|
6388
|
+
lastTaskId: resolved.slot.lastTaskId,
|
|
6389
|
+
runtimeProfileId: resolved.slot.runtimeProfileId,
|
|
6390
|
+
slotKey: resolved.slot.slotKey,
|
|
6391
|
+
state: resolved.slot.state,
|
|
6392
|
+
taskType: resolved.slot.taskType
|
|
6393
|
+
},
|
|
6394
|
+
session: resolved.slot.sessionDir ? {
|
|
6395
|
+
sessionDir: resolved.slot.sessionDir,
|
|
6396
|
+
sessionPath: resolved.slot.sessionPath
|
|
6397
|
+
} : null,
|
|
6398
|
+
workspace: resolved.workspace ? {
|
|
6399
|
+
kind: resolved.workspace.kind,
|
|
6400
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
6401
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
6402
|
+
worktreePath: resolved.workspace.worktreePath
|
|
6403
|
+
} : null
|
|
6404
|
+
}));
|
|
6405
|
+
},
|
|
6406
|
+
async close() {}
|
|
6407
|
+
};
|
|
6615
6408
|
}
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6409
|
+
//#endregion
|
|
6410
|
+
//#region src/lib/secret-redaction.ts
|
|
6411
|
+
var REDACTED = "[REDACTED]";
|
|
6412
|
+
var MIN_SECRET_LENGTH = 8;
|
|
6413
|
+
/**
|
|
6414
|
+
* Redact required runtime-profile environment values before task output leaves
|
|
6415
|
+
* the daemon. This is defense in depth for shell-capable profiles: a model may
|
|
6416
|
+
* read a forwarded provider key, but it must not be able to publish that exact
|
|
6417
|
+
* value through a task result.
|
|
6418
|
+
*/
|
|
6419
|
+
function redactRequiredEnvValues(value, requiredEnv, env) {
|
|
6420
|
+
const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
|
|
6421
|
+
if (secrets.length === 0) return value;
|
|
6422
|
+
const redact = (candidate) => {
|
|
6423
|
+
if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
|
|
6424
|
+
if (Array.isArray(candidate)) return candidate.map(redact);
|
|
6425
|
+
if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
|
|
6426
|
+
return candidate;
|
|
6427
|
+
};
|
|
6428
|
+
return redact(value);
|
|
6619
6429
|
}
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6430
|
+
//#endregion
|
|
6431
|
+
//#region src/lib/shutdown-signal.ts
|
|
6432
|
+
function installShutdownSignalHandlers(opts) {
|
|
6433
|
+
const proc = opts.proc ?? process;
|
|
6434
|
+
let drainingSignal = null;
|
|
6435
|
+
const onSignal = (signal) => {
|
|
6436
|
+
if (drainingSignal) {
|
|
6437
|
+
proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
|
|
6438
|
+
proc.exitCode = signalExitCode(signal);
|
|
6439
|
+
return;
|
|
6440
|
+
}
|
|
6441
|
+
drainingSignal = signal;
|
|
6442
|
+
proc.exitCode = signalExitCode(signal);
|
|
6443
|
+
try {
|
|
6444
|
+
opts.logDrain(signal);
|
|
6445
|
+
} catch (err) {
|
|
6446
|
+
proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
6447
|
+
}
|
|
6448
|
+
opts.drain(signal);
|
|
6449
|
+
};
|
|
6450
|
+
const handleSigint = () => onSignal("SIGINT");
|
|
6451
|
+
const handleSigterm = () => onSignal("SIGTERM");
|
|
6452
|
+
proc.on("SIGINT", handleSigint);
|
|
6453
|
+
proc.on("SIGTERM", handleSigterm);
|
|
6454
|
+
return { dispose: () => {
|
|
6455
|
+
proc.off("SIGINT", handleSigint);
|
|
6456
|
+
proc.off("SIGTERM", handleSigterm);
|
|
6457
|
+
} };
|
|
6624
6458
|
}
|
|
6625
|
-
function
|
|
6626
|
-
return
|
|
6459
|
+
function signalExitCode(signal) {
|
|
6460
|
+
return signal === "SIGINT" ? 130 : 143;
|
|
6627
6461
|
}
|
|
6628
|
-
|
|
6629
|
-
|
|
6462
|
+
//#endregion
|
|
6463
|
+
//#region src/lib/source-attempts.ts
|
|
6464
|
+
function createApiSourceAttemptResolver(args) {
|
|
6465
|
+
const { agent } = args;
|
|
6466
|
+
return {
|
|
6467
|
+
async findOutputBranch(input) {
|
|
6468
|
+
const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
|
|
6469
|
+
if (!attempt || attempt.status !== "completed") return null;
|
|
6470
|
+
return resolveOutputBranch(attempt.output);
|
|
6471
|
+
},
|
|
6472
|
+
async findInputRevision(input) {
|
|
6473
|
+
const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
|
|
6474
|
+
if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
|
|
6475
|
+
return resolveTaskWorkspaceRevision(task.input);
|
|
6476
|
+
}
|
|
6477
|
+
};
|
|
6630
6478
|
}
|
|
6631
|
-
function
|
|
6632
|
-
if (
|
|
6633
|
-
const
|
|
6634
|
-
|
|
6635
|
-
return v;
|
|
6479
|
+
function resolveOutputBranch(output) {
|
|
6480
|
+
if (!output || typeof output !== "object") return null;
|
|
6481
|
+
const branch = output.branch;
|
|
6482
|
+
return typeof branch === "string" && branch.length > 0 ? branch : null;
|
|
6636
6483
|
}
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6484
|
+
//#endregion
|
|
6485
|
+
//#region src/lib/state-dir.ts
|
|
6486
|
+
function ensureDaemonStateDirs(mountPath) {
|
|
6487
|
+
const rootDir = join(mountPath, ".moltnet", "d");
|
|
6488
|
+
const piSessionsDir = join(rootDir, "pi-sessions");
|
|
6489
|
+
mkdirSync(piSessionsDir, { recursive: true });
|
|
6490
|
+
return {
|
|
6491
|
+
rootDir,
|
|
6492
|
+
piSessionsDir
|
|
6493
|
+
};
|
|
6642
6494
|
}
|
|
6643
6495
|
//#endregion
|
|
6644
|
-
//#region src/
|
|
6645
|
-
function
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6496
|
+
//#region src/lib/turn-event-logger.ts
|
|
6497
|
+
function makeTurnEventHandler(base, context = {}) {
|
|
6498
|
+
const log = base.child({
|
|
6499
|
+
name: "agent-daemon.turn",
|
|
6500
|
+
...context
|
|
6501
|
+
});
|
|
6502
|
+
return (event, summary) => {
|
|
6503
|
+
if (event === "text_delta") return;
|
|
6504
|
+
log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
|
|
6505
|
+
event,
|
|
6506
|
+
...summary
|
|
6507
|
+
}, `turn.${event}`);
|
|
6508
|
+
};
|
|
6509
|
+
}
|
|
6510
|
+
function makeTurnEventHandlerFactory(base) {
|
|
6511
|
+
return (claimedTask) => makeTurnEventHandler(base, {
|
|
6512
|
+
taskId: claimedTask.task.id,
|
|
6513
|
+
attemptN: claimedTask.attemptN
|
|
6653
6514
|
});
|
|
6654
6515
|
}
|
|
6655
6516
|
//#endregion
|
|
6656
|
-
//#region src/cli/
|
|
6657
|
-
async function
|
|
6658
|
-
if (isHelpFlag(argv)) {
|
|
6659
|
-
console.log(
|
|
6517
|
+
//#region src/cli/poll-shared.ts
|
|
6518
|
+
async function runPolling(opts) {
|
|
6519
|
+
if (isHelpFlag(opts.argv)) {
|
|
6520
|
+
console.log(opts.helpText);
|
|
6660
6521
|
return 0;
|
|
6661
6522
|
}
|
|
6662
6523
|
const { values } = parseArgs({
|
|
6663
|
-
args: argv,
|
|
6524
|
+
args: opts.argv,
|
|
6664
6525
|
options: {
|
|
6665
6526
|
...commonOptionDefs(),
|
|
6666
|
-
"task-id": {
|
|
6667
|
-
type: "string",
|
|
6668
|
-
short: "t"
|
|
6669
|
-
},
|
|
6670
6527
|
team: { type: "string" },
|
|
6528
|
+
"task-types": { type: "string" },
|
|
6529
|
+
"correlation-id": { type: "string" },
|
|
6530
|
+
"diary-ids": { type: "string" },
|
|
6531
|
+
"wait-for-first-task-sec": { type: "string" },
|
|
6532
|
+
"wait-after-task-sec": { type: "string" },
|
|
6533
|
+
"poll-interval-ms": { type: "string" },
|
|
6534
|
+
"max-poll-interval-ms": { type: "string" },
|
|
6535
|
+
"list-limit": { type: "string" },
|
|
6671
6536
|
sandbox: { type: "string" },
|
|
6672
|
-
profile: {
|
|
6537
|
+
profile: {
|
|
6538
|
+
type: "string",
|
|
6539
|
+
multiple: true
|
|
6540
|
+
}
|
|
6673
6541
|
}
|
|
6674
6542
|
});
|
|
6675
|
-
if (!values
|
|
6676
|
-
console.error("Missing required flag: --
|
|
6677
|
-
console.error(
|
|
6543
|
+
if (!values.team) {
|
|
6544
|
+
console.error("Missing required flag: --team\n");
|
|
6545
|
+
console.error(opts.helpText);
|
|
6678
6546
|
return 1;
|
|
6679
6547
|
}
|
|
6680
|
-
const
|
|
6681
|
-
|
|
6548
|
+
const teamId = values.team;
|
|
6549
|
+
const profileValues = parseProfileValues(values.profile);
|
|
6550
|
+
if (profileValues.length === 0) {
|
|
6682
6551
|
console.error("Missing required flag: --profile\n");
|
|
6683
|
-
console.error(
|
|
6552
|
+
console.error(opts.helpText);
|
|
6684
6553
|
return 1;
|
|
6685
6554
|
}
|
|
6686
|
-
let
|
|
6555
|
+
let taskTypes;
|
|
6687
6556
|
try {
|
|
6688
|
-
|
|
6557
|
+
taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
|
|
6558
|
+
} catch (err) {
|
|
6559
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6560
|
+
console.error(message);
|
|
6561
|
+
return 1;
|
|
6562
|
+
}
|
|
6563
|
+
const diaryIds = parseCsv(values["diary-ids"]);
|
|
6564
|
+
let baseCommon;
|
|
6565
|
+
try {
|
|
6566
|
+
baseCommon = parseCommonOptions(values);
|
|
6689
6567
|
} catch (err) {
|
|
6690
6568
|
if (err instanceof MissingRequiredOptionError) {
|
|
6691
6569
|
console.error(`${err.message}\n`);
|
|
6692
|
-
console.error(
|
|
6570
|
+
console.error(opts.helpText);
|
|
6693
6571
|
return 1;
|
|
6694
6572
|
}
|
|
6695
6573
|
throw err;
|
|
6696
6574
|
}
|
|
6575
|
+
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
6576
|
+
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
6577
|
+
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
6578
|
+
const waitForFirstTaskSec = optionalNonNegativeInt(values["wait-for-first-task-sec"], "wait-for-first-task-sec", 0);
|
|
6579
|
+
const waitAfterTaskSec = optionalNonNegativeInt(values["wait-after-task-sec"], "wait-after-task-sec", 0);
|
|
6580
|
+
if (!opts.stopWhenEmpty && (waitForFirstTaskSec > 0 || waitAfterTaskSec > 0)) {
|
|
6581
|
+
console.error(`[${opts.modeLabel}] --wait-for-first-task-sec and --wait-after-task-sec are only valid with drain.`);
|
|
6582
|
+
return 1;
|
|
6583
|
+
}
|
|
6697
6584
|
if (values.sandbox) {
|
|
6698
|
-
console.error(
|
|
6585
|
+
console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
|
|
6699
6586
|
return 1;
|
|
6700
6587
|
}
|
|
6588
|
+
if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
|
|
6701
6589
|
const cfg = loadConfig();
|
|
6702
6590
|
const credentialSources = {
|
|
6703
6591
|
profileRequirements: cfg.profileCredentialRequirements,
|
|
6704
6592
|
bindings: cfg.credentialBindings
|
|
6705
6593
|
};
|
|
6706
6594
|
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
6707
|
-
const initialOpts = opts;
|
|
6708
6595
|
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
6709
|
-
const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
6596
|
+
const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
6710
6597
|
let gate = "resolve_agent_context";
|
|
6711
6598
|
try {
|
|
6712
|
-
const resolvedContext = await resolveAgentContext(
|
|
6599
|
+
const resolvedContext = await resolveAgentContext(baseCommon.agent, {
|
|
6713
6600
|
agentRootDir: explicitAgentRootDir,
|
|
6714
6601
|
credentialSource: cfg.credentialSource,
|
|
6715
6602
|
envApiUrl: cfg.apiUrl
|
|
@@ -6717,7 +6604,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6717
6604
|
gate = "authenticate_and_bind";
|
|
6718
6605
|
const whoami = await validateStartupBinding({
|
|
6719
6606
|
agent: resolvedContext.agent,
|
|
6720
|
-
teamId
|
|
6607
|
+
teamId,
|
|
6721
6608
|
expectedAgent: cfg.expectedAgent
|
|
6722
6609
|
});
|
|
6723
6610
|
gate = "resolve_signing_material";
|
|
@@ -6736,7 +6623,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6736
6623
|
});
|
|
6737
6624
|
gate = "resolve_agent_identity";
|
|
6738
6625
|
const agentIdentity = await resolveDaemonAgentIdentity({
|
|
6739
|
-
agentName:
|
|
6626
|
+
agentName: baseCommon.agent,
|
|
6740
6627
|
whoami,
|
|
6741
6628
|
credentialSource: cfg.credentialSource,
|
|
6742
6629
|
agentDir: resolvedContext.agentDir,
|
|
@@ -6745,6 +6632,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6745
6632
|
return {
|
|
6746
6633
|
ctx: resolvedContext,
|
|
6747
6634
|
signingPrivateKey: privateKey,
|
|
6635
|
+
startupWhoami: whoami,
|
|
6748
6636
|
agentIdentity,
|
|
6749
6637
|
hostCapabilitySigner: createLocalSeedSigner({
|
|
6750
6638
|
privateKeySeed: privateKey,
|
|
@@ -6754,10 +6642,10 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6754
6642
|
};
|
|
6755
6643
|
} catch (error) {
|
|
6756
6644
|
await logDaemonStartupFailure({
|
|
6757
|
-
serviceName:
|
|
6758
|
-
level: cfg.logLevel || (
|
|
6645
|
+
serviceName: `agent-daemon.${opts.modeLabel}`,
|
|
6646
|
+
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info"),
|
|
6759
6647
|
gate,
|
|
6760
|
-
agent:
|
|
6648
|
+
agent: baseCommon.agent,
|
|
6761
6649
|
credentialSource: cfg.credentialSource,
|
|
6762
6650
|
error
|
|
6763
6651
|
});
|
|
@@ -6765,39 +6653,41 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6765
6653
|
}
|
|
6766
6654
|
})();
|
|
6767
6655
|
const daemonRootDir = explicitAgentRootDir ?? process.cwd();
|
|
6768
|
-
const
|
|
6656
|
+
const resolvedProfiles = await resolveRuntimeProfiles({
|
|
6769
6657
|
agent: ctx.agent,
|
|
6770
|
-
|
|
6771
|
-
teamId
|
|
6658
|
+
profiles: profileValues,
|
|
6659
|
+
teamId,
|
|
6772
6660
|
cwd: daemonRootDir
|
|
6773
6661
|
});
|
|
6774
|
-
|
|
6775
|
-
const
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
}
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6662
|
+
const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
|
|
6663
|
+
const preparedRuntimes = /* @__PURE__ */ new Map();
|
|
6664
|
+
const profiles = [];
|
|
6665
|
+
const skippedProfileBoundaries = [];
|
|
6666
|
+
for (const profile of resolvedProfiles) try {
|
|
6667
|
+
assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
|
|
6668
|
+
const prepared = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
|
|
6669
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
|
|
6670
|
+
tools: prepared.tools,
|
|
6671
|
+
executables: prepared.executables
|
|
6672
|
+
});
|
|
6673
|
+
assertGuestEnvironmentBoundary({
|
|
6674
|
+
forwardEnv: profile.requiredEnv,
|
|
6675
|
+
sandboxEnv: profile.sandboxConfig.env
|
|
6676
|
+
});
|
|
6677
|
+
await ctx.agent.tasks.registerExecutorManifest(await prepared.attestor.registration());
|
|
6678
|
+
preparedRuntimes.set(profile.id, prepared);
|
|
6679
|
+
profiles.push(profile);
|
|
6680
|
+
} catch (error) {
|
|
6681
|
+
if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
|
|
6682
|
+
skippedProfileBoundaries.push({
|
|
6683
|
+
profile,
|
|
6684
|
+
error
|
|
6685
|
+
});
|
|
6686
|
+
}
|
|
6687
|
+
if (profiles.length === 0) {
|
|
6688
|
+
const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
|
|
6689
|
+
throw new Error(`No safe runtime profiles remain. ${details}`);
|
|
6690
|
+
}
|
|
6801
6691
|
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
6802
6692
|
const runtimeSessionStore = createApiRuntimeSessionStore({
|
|
6803
6693
|
agent: ctx.agent,
|
|
@@ -6805,260 +6695,232 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6805
6695
|
});
|
|
6806
6696
|
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
6807
6697
|
const runtimeInstanceId = createRuntimeInstanceId();
|
|
6808
|
-
const
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
}
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6698
|
+
const piAgentDir = await resolvePiAgentDir(cfg, profiles[0].mountPath, profiles);
|
|
6699
|
+
process.once("exit", piAgentDir.cleanup);
|
|
6700
|
+
activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
|
|
6701
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
6702
|
+
for (const profile of profiles) {
|
|
6703
|
+
const common = parseCommonOptions(values, { runtimeDefaults: {
|
|
6704
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
6705
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
6706
|
+
maxBatchSize: profile.maxBatchSize,
|
|
6707
|
+
maxTurns: profile.maxTurns,
|
|
6708
|
+
maxBashTimeouts: profile.maxBashTimeouts,
|
|
6709
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
6710
|
+
} });
|
|
6711
|
+
const sandbox = {
|
|
6712
|
+
config: profile.sandboxConfig,
|
|
6713
|
+
rootDir: profile.mountPath,
|
|
6714
|
+
path: profile.source
|
|
6715
|
+
};
|
|
6716
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
6717
|
+
const slotIdentity = {
|
|
6718
|
+
agentName: common.agent,
|
|
6719
|
+
runtimeProfileId: profile.id,
|
|
6720
|
+
runtimeInstanceId
|
|
6721
|
+
};
|
|
6722
|
+
const executionPlans = createExecutionPlanCache({
|
|
6723
|
+
stateDirs,
|
|
6724
|
+
slotIdentity,
|
|
6725
|
+
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
6726
|
+
workspacePolicy: {
|
|
6727
|
+
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6728
|
+
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6729
|
+
},
|
|
6730
|
+
slotRegistry,
|
|
6731
|
+
runtimeSessionStore,
|
|
6732
|
+
sourceAttemptResolver
|
|
6733
|
+
});
|
|
6734
|
+
runtimes.set(profile.id, {
|
|
6735
|
+
common,
|
|
6736
|
+
profile,
|
|
6737
|
+
sandbox,
|
|
6738
|
+
stateDirs,
|
|
6739
|
+
slotIdentity,
|
|
6740
|
+
executionPlans,
|
|
6741
|
+
preparedRuntime: preparedRuntimes.get(profile.id)
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
6744
|
+
const firstRuntime = runtimes.get(profiles[0].id);
|
|
6745
|
+
if (!firstRuntime) throw new Error("No runtime profiles resolved");
|
|
6825
6746
|
const otelShutdown = await initWorkerOtel({
|
|
6826
|
-
serviceName:
|
|
6747
|
+
serviceName: opts.serviceName,
|
|
6827
6748
|
agent: ctx.agent,
|
|
6828
6749
|
endpoint: cfg.otelEndpoint,
|
|
6829
6750
|
resourceAttributes: {
|
|
6830
|
-
"moltnet.
|
|
6831
|
-
"moltnet.agent.name":
|
|
6751
|
+
"moltnet.team.id": teamId,
|
|
6752
|
+
"moltnet.agent.name": baseCommon.agent,
|
|
6832
6753
|
"moltnet.credential.source": ctx.credentialSource,
|
|
6833
|
-
"moltnet.
|
|
6834
|
-
"moltnet.
|
|
6835
|
-
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
6836
|
-
...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
|
|
6837
|
-
...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
|
|
6838
|
-
...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
|
|
6839
|
-
...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
|
|
6840
|
-
"moltnet.runtime_profile.id": profile.id
|
|
6754
|
+
"moltnet.runtime_profile.count": String(profiles.length),
|
|
6755
|
+
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
6841
6756
|
}
|
|
6842
6757
|
});
|
|
6843
6758
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
6844
|
-
name:
|
|
6845
|
-
level: cfg.logLevel || (
|
|
6759
|
+
name: `agent-daemon.${opts.modeLabel}`,
|
|
6760
|
+
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
|
|
6846
6761
|
});
|
|
6847
6762
|
const rootLogger = logger.child({
|
|
6848
|
-
mode:
|
|
6849
|
-
agent:
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
temperature: profile.temperature,
|
|
6854
|
-
topP: profile.topP,
|
|
6855
|
-
topK: profile.topK,
|
|
6856
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6857
|
-
runtimeProfileId: profile.id,
|
|
6858
|
-
runtimeProfileName: profile.name
|
|
6763
|
+
mode: opts.modeLabel,
|
|
6764
|
+
agent: baseCommon.agent,
|
|
6765
|
+
teamId,
|
|
6766
|
+
runtimeProfileIds: profiles.map((p) => p.id),
|
|
6767
|
+
runtimeProfileNames: profiles.map((p) => p.name)
|
|
6859
6768
|
});
|
|
6860
|
-
rootLogger.
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
maxBashTimeouts: opts.maxBashTimeouts,
|
|
6867
|
-
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
6868
|
-
profileId: profile.id,
|
|
6869
|
-
profileSessionTtlSec: profile.sessionTtlSec,
|
|
6870
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
6871
|
-
piAgentDir: piAgentDir.path,
|
|
6872
|
-
piAgentDirSource: piAgentDir.source
|
|
6873
|
-
}, "agent-daemon.starting");
|
|
6874
|
-
try {
|
|
6875
|
-
const reaped = await reapRuntimeSlotResources({
|
|
6876
|
-
onIssue: (issue) => {
|
|
6877
|
-
rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
|
|
6878
|
-
},
|
|
6879
|
-
runtimeSlotStore: slotRegistry,
|
|
6880
|
-
taskReader: ctx.agent.tasks
|
|
6881
|
-
}, {
|
|
6882
|
-
agentName: opts.agent,
|
|
6883
|
-
mainWorktree: resolveMainWorktree(sandbox.rootDir),
|
|
6884
|
-
runtimeInstanceId,
|
|
6885
|
-
runtimeProfileId: profile.id,
|
|
6886
|
-
sessionRootDir: stateDirs.piSessionsDir,
|
|
6887
|
-
scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
|
|
6888
|
-
teamId: profile.teamId
|
|
6889
|
-
});
|
|
6890
|
-
if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
|
|
6891
|
-
} catch (err) {
|
|
6892
|
-
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
6893
|
-
}
|
|
6769
|
+
for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
|
|
6770
|
+
runtimeProfileId: profile.id,
|
|
6771
|
+
runtimeProfileName: profile.name,
|
|
6772
|
+
refusedEnvironmentNames: error.refusedNames
|
|
6773
|
+
}, "agent-daemon.runtime_profile_skipped_credential_boundary");
|
|
6774
|
+
const abort = new AbortController();
|
|
6894
6775
|
let runtime = null;
|
|
6895
|
-
let
|
|
6776
|
+
let active = null;
|
|
6896
6777
|
const signalHandlers = installShutdownSignalHandlers({
|
|
6897
6778
|
logDrain: (signal) => {
|
|
6898
|
-
rootLogger.warn({
|
|
6899
|
-
signal,
|
|
6900
|
-
taskId
|
|
6901
|
-
}, "agent-daemon.draining");
|
|
6779
|
+
rootLogger.warn({ signal }, "agent-daemon.draining");
|
|
6902
6780
|
},
|
|
6903
6781
|
drain: (signal) => {
|
|
6782
|
+
abort.abort();
|
|
6904
6783
|
runtime?.stop(`agent-daemon received ${signal}`);
|
|
6905
6784
|
abortActiveAttemptOnSignal({
|
|
6906
|
-
active
|
|
6907
|
-
taskId,
|
|
6908
|
-
attemptN: activeAttemptN
|
|
6909
|
-
},
|
|
6785
|
+
active,
|
|
6910
6786
|
signal,
|
|
6911
|
-
abortAttempt: (
|
|
6787
|
+
abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
|
|
6912
6788
|
logFailure: (err, current) => {
|
|
6913
6789
|
try {
|
|
6914
6790
|
rootLogger.warn({
|
|
6915
6791
|
err: err instanceof Error ? err.message : String(err),
|
|
6916
|
-
taskId: current.taskId,
|
|
6917
|
-
attemptN: current.attemptN
|
|
6918
|
-
}, "agent-daemon.abort_on_signal_failed");
|
|
6919
|
-
} catch
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
topK: profile.topK,
|
|
6941
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6942
|
-
sandboxConfig: sandbox.config,
|
|
6943
|
-
forwardEnv: profile.requiredEnv,
|
|
6944
|
-
onVmDiagnostic: (diagnostic) => {
|
|
6945
|
-
const fields = {
|
|
6946
|
-
event: diagnostic.event,
|
|
6947
|
-
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
6948
|
-
};
|
|
6949
|
-
if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
|
|
6950
|
-
else rootLogger.info(fields, diagnostic.message);
|
|
6951
|
-
},
|
|
6952
|
-
runtimeProfileContext: profile.context,
|
|
6953
|
-
runtimeProfileId: profile.id,
|
|
6954
|
-
toolEnforcement: profile.toolEnforcement,
|
|
6955
|
-
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
6956
|
-
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
6957
|
-
toolPolicyLogger: rootLogger,
|
|
6958
|
-
maxTurns: opts.maxTurns,
|
|
6959
|
-
maxBashTimeouts: opts.maxBashTimeouts
|
|
6960
|
-
});
|
|
6961
|
-
const executeTask = async (claimedTask, reporter) => {
|
|
6962
|
-
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6963
|
-
config: runtimeCredentialConfig,
|
|
6964
|
-
profile,
|
|
6965
|
-
offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
|
|
6966
|
-
registry: createNodeSecretProviderRegistry(),
|
|
6967
|
-
executorFingerprint: preparedRuntime.attestor.fingerprint,
|
|
6968
|
-
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6969
|
-
taskId: claimedTask.task.id,
|
|
6970
|
-
attemptN: claimedTask.attemptN,
|
|
6971
|
-
logger: rootLogger
|
|
6972
|
-
});
|
|
6973
|
-
let executionPlan;
|
|
6974
|
-
try {
|
|
6975
|
-
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
6976
|
-
} catch (err) {
|
|
6977
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6978
|
-
rootLogger.warn({
|
|
6979
|
-
taskId: claimedTask.task.id,
|
|
6980
|
-
attemptN: claimedTask.attemptN,
|
|
6981
|
-
err: message
|
|
6982
|
-
}, "agent-daemon.execution_plan_failed");
|
|
6983
|
-
return {
|
|
6984
|
-
taskId: claimedTask.task.id,
|
|
6985
|
-
attemptN: claimedTask.attemptN,
|
|
6986
|
-
status: "failed",
|
|
6987
|
-
output: null,
|
|
6988
|
-
outputCid: null,
|
|
6989
|
-
usage: {
|
|
6990
|
-
inputTokens: 0,
|
|
6991
|
-
outputTokens: 0
|
|
6992
|
-
},
|
|
6993
|
-
durationMs: 0,
|
|
6994
|
-
error: {
|
|
6995
|
-
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
6996
|
-
message,
|
|
6997
|
-
retryable: false
|
|
6998
|
-
}
|
|
6999
|
-
};
|
|
7000
|
-
}
|
|
7001
|
-
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
7002
|
-
...slotIdentity,
|
|
7003
|
-
runtimeProfileId: profile.id,
|
|
6792
|
+
taskId: current.taskId,
|
|
6793
|
+
attemptN: current.attemptN
|
|
6794
|
+
}, "agent-daemon.abort_on_signal_failed");
|
|
6795
|
+
} catch {}
|
|
6796
|
+
}
|
|
6797
|
+
});
|
|
6798
|
+
}
|
|
6799
|
+
});
|
|
6800
|
+
rootLogger.info({
|
|
6801
|
+
credentialSource: cfg.credentialSource,
|
|
6802
|
+
subjectType: startupWhoami.subjectType,
|
|
6803
|
+
bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
|
|
6804
|
+
credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
|
|
6805
|
+
boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
|
|
6806
|
+
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
6807
|
+
correlationId: values["correlation-id"] ?? null,
|
|
6808
|
+
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
6809
|
+
pollIntervalMs,
|
|
6810
|
+
maxPollIntervalMs,
|
|
6811
|
+
profiles: profiles.map((profile) => {
|
|
6812
|
+
const runtime = requireRuntime(runtimes, profile.id);
|
|
6813
|
+
return {
|
|
6814
|
+
id: profile.id,
|
|
6815
|
+
name: profile.name,
|
|
7004
6816
|
provider: profile.provider,
|
|
7005
6817
|
model: profile.model,
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
6818
|
+
thinkingLevel: profile.thinkingLevel,
|
|
6819
|
+
temperature: profile.temperature,
|
|
6820
|
+
topP: profile.topP,
|
|
6821
|
+
topK: profile.topK,
|
|
6822
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
6823
|
+
sandbox: runtime.sandbox.path,
|
|
6824
|
+
leaseTtlSec: runtime.common.leaseTtlSec,
|
|
6825
|
+
heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
|
|
6826
|
+
maxTurns: runtime.common.maxTurns,
|
|
6827
|
+
maxBashTimeouts: runtime.common.maxBashTimeouts,
|
|
6828
|
+
warmSessionTtlSec: runtime.common.warmSessionTtlSec,
|
|
6829
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
6830
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
6831
|
+
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6832
|
+
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6833
|
+
};
|
|
6834
|
+
}),
|
|
6835
|
+
piAgentDir: piAgentDir.path,
|
|
6836
|
+
piAgentDirSource: piAgentDir.source
|
|
6837
|
+
}, "agent-daemon.starting");
|
|
6838
|
+
let reaperRunning = false;
|
|
6839
|
+
const reapRuntimeResources = async () => {
|
|
6840
|
+
if (reaperRunning) return;
|
|
6841
|
+
reaperRunning = true;
|
|
6842
|
+
try {
|
|
6843
|
+
for (const profile of profiles) {
|
|
6844
|
+
const selected = requireRuntime(runtimes, profile.id);
|
|
6845
|
+
const result = await reapRuntimeSlotResources({
|
|
6846
|
+
onIssue: (issue) => {
|
|
6847
|
+
rootLogger.warn({
|
|
6848
|
+
...issue,
|
|
6849
|
+
runtimeProfileId: profile.id
|
|
6850
|
+
}, "agent-daemon.runtime_resource_reap_issue");
|
|
6851
|
+
},
|
|
6852
|
+
runtimeSlotStore: slotRegistry,
|
|
6853
|
+
taskReader: ctx.agent.tasks
|
|
6854
|
+
}, {
|
|
6855
|
+
agentName: selected.common.agent,
|
|
6856
|
+
mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
|
|
6857
|
+
runtimeInstanceId,
|
|
6858
|
+
runtimeProfileId: profile.id,
|
|
6859
|
+
sessionRootDir: selected.stateDirs.piSessionsDir,
|
|
6860
|
+
scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
|
|
6861
|
+
teamId
|
|
6862
|
+
});
|
|
6863
|
+
if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
|
|
6864
|
+
...result,
|
|
6865
|
+
runtimeProfileId: profile.id
|
|
6866
|
+
}, "agent-daemon.runtime_resources_reaped");
|
|
7035
6867
|
}
|
|
7036
|
-
}
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
}
|
|
6868
|
+
} catch (err) {
|
|
6869
|
+
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
6870
|
+
} finally {
|
|
6871
|
+
reaperRunning = false;
|
|
6872
|
+
}
|
|
6873
|
+
};
|
|
6874
|
+
await reapRuntimeResources();
|
|
6875
|
+
const reaperTimer = setInterval(() => {
|
|
6876
|
+
reapRuntimeResources();
|
|
6877
|
+
}, 6e4);
|
|
6878
|
+
reaperTimer.unref();
|
|
6879
|
+
const outputs = [];
|
|
6880
|
+
try {
|
|
7041
6881
|
runtime = new AgentRuntime({
|
|
7042
6882
|
logger: rootLogger,
|
|
7043
|
-
source: new
|
|
6883
|
+
source: new PollingApiTaskSource({
|
|
7044
6884
|
agent: ctx.agent,
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
6885
|
+
teamId,
|
|
6886
|
+
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
6887
|
+
correlationId: values["correlation-id"],
|
|
6888
|
+
profiles: profiles.map((profile) => ({
|
|
6889
|
+
profileId: profile.id,
|
|
6890
|
+
leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
|
|
6891
|
+
})),
|
|
6892
|
+
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
6893
|
+
leaseTtlSec: firstRuntime.common.leaseTtlSec,
|
|
6894
|
+
listLimit,
|
|
6895
|
+
pollIntervalMs,
|
|
6896
|
+
maxPollIntervalMs,
|
|
6897
|
+
signal: abort.signal,
|
|
6898
|
+
stopWhenEmpty: opts.stopWhenEmpty,
|
|
6899
|
+
waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
|
|
6900
|
+
waitAfterTaskMs: waitAfterTaskSec * 1e3,
|
|
6901
|
+
debug: baseCommon.debug,
|
|
6902
|
+
traceIdlePolling: cfg.traceIdlePolling,
|
|
6903
|
+
logger: rootLogger,
|
|
6904
|
+
executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
|
|
6905
|
+
slotRegistry,
|
|
6906
|
+
sessionRegistry: runtimeSessionStore,
|
|
6907
|
+
sourceAttemptResolver
|
|
7058
6908
|
}),
|
|
6909
|
+
makeReporter: (claimedTask) => {
|
|
6910
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6911
|
+
return new ApiTaskReporter({
|
|
6912
|
+
tasks: ctx.agent.tasks,
|
|
6913
|
+
teamId: claimedTask.task.teamId,
|
|
6914
|
+
leaseTtlSec: selected.common.leaseTtlSec,
|
|
6915
|
+
heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
|
|
6916
|
+
maxBatchSize: selected.common.maxBatchSize,
|
|
6917
|
+
flushIntervalMs: selected.common.flushIntervalMs
|
|
6918
|
+
});
|
|
6919
|
+
},
|
|
7059
6920
|
onTaskFinished: async (output, claimedTask) => {
|
|
6921
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
7060
6922
|
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
7061
|
-
let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
6923
|
+
let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
7062
6924
|
if (resolved?.session?.sessionDir) try {
|
|
7063
6925
|
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
7064
6926
|
await runtimeSessionStore.uploadAttemptFinal({
|
|
@@ -7083,422 +6945,738 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
7083
6945
|
task: claimedTask.task,
|
|
7084
6946
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
7085
6947
|
retryTriage: createRuntimeProfileRetryTriage({
|
|
7086
|
-
runtimeProfile: profile,
|
|
6948
|
+
runtimeProfile: selected.profile,
|
|
7087
6949
|
piAgentDir: piAgentDir.path,
|
|
7088
6950
|
cwd: ctx.agentRootDir
|
|
7089
6951
|
}),
|
|
7090
|
-
executorAttestor: preparedRuntime.attestor,
|
|
7091
|
-
writeCorrelationAnchors
|
|
6952
|
+
executorAttestor: selected.preparedRuntime.attestor,
|
|
6953
|
+
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
6954
|
+
gh: createGhCliClient(),
|
|
6955
|
+
logger: rootLogger.child({
|
|
6956
|
+
runtimeProfileId: selected.profile.id,
|
|
6957
|
+
runtimeProfileName: selected.profile.name
|
|
6958
|
+
})
|
|
6959
|
+
}),
|
|
7092
6960
|
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
7093
6961
|
});
|
|
7094
|
-
},
|
|
7095
|
-
executeTask
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
}
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
}
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
return join(this.root, "identity-selector.json");
|
|
7270
|
-
}
|
|
7271
|
-
readIdentitySelector() {
|
|
7272
|
-
const selector = readJson(this.identitySelectorPath);
|
|
7273
|
-
if (!selector) return null;
|
|
7274
|
-
if (!isRecord$1(selector) || selector.version !== 1 || selector.default_identity !== void 0 && typeof selector.default_identity !== "string") throw new AgentServerStoreError("invalid_state", "identity-selector.json is not a supported selector document");
|
|
7275
|
-
if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
|
|
7276
|
-
return selector;
|
|
7277
|
-
}
|
|
7278
|
-
writeIdentitySelector(alias) {
|
|
7279
|
-
writeJsonAtomic(this.identitySelectorPath, {
|
|
7280
|
-
version: 1,
|
|
7281
|
-
default_identity: assertStoreName("identity alias", alias)
|
|
6962
|
+
},
|
|
6963
|
+
executeTask: async (claimedTask, reporter) => {
|
|
6964
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6965
|
+
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
|
|
6966
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6967
|
+
config: runtimeCredentialConfig,
|
|
6968
|
+
profile,
|
|
6969
|
+
offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
|
|
6970
|
+
registry: createNodeSecretProviderRegistry(),
|
|
6971
|
+
executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
|
|
6972
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6973
|
+
taskId: claimedTask.task.id,
|
|
6974
|
+
attemptN: claimedTask.attemptN,
|
|
6975
|
+
logger: rootLogger
|
|
6976
|
+
});
|
|
6977
|
+
const taskLogger = rootLogger.child({
|
|
6978
|
+
runtimeProfileId: profile.id,
|
|
6979
|
+
runtimeProfileName: profile.name,
|
|
6980
|
+
provider: profile.provider,
|
|
6981
|
+
model: profile.model,
|
|
6982
|
+
thinkingLevel: profile.thinkingLevel,
|
|
6983
|
+
temperature: profile.temperature,
|
|
6984
|
+
topP: profile.topP,
|
|
6985
|
+
topK: profile.topK,
|
|
6986
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
6987
|
+
});
|
|
6988
|
+
let executionPlan;
|
|
6989
|
+
try {
|
|
6990
|
+
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
6991
|
+
} catch (err) {
|
|
6992
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6993
|
+
taskLogger.warn({
|
|
6994
|
+
taskId: claimedTask.task.id,
|
|
6995
|
+
attemptN: claimedTask.attemptN,
|
|
6996
|
+
err: message
|
|
6997
|
+
}, "agent-daemon.execution_plan_failed");
|
|
6998
|
+
return {
|
|
6999
|
+
taskId: claimedTask.task.id,
|
|
7000
|
+
attemptN: claimedTask.attemptN,
|
|
7001
|
+
status: "failed",
|
|
7002
|
+
output: null,
|
|
7003
|
+
outputCid: null,
|
|
7004
|
+
usage: {
|
|
7005
|
+
inputTokens: 0,
|
|
7006
|
+
outputTokens: 0
|
|
7007
|
+
},
|
|
7008
|
+
durationMs: 0,
|
|
7009
|
+
error: {
|
|
7010
|
+
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
7011
|
+
message,
|
|
7012
|
+
retryable: false
|
|
7013
|
+
}
|
|
7014
|
+
};
|
|
7015
|
+
}
|
|
7016
|
+
const sessionDescriptor = executionPlan.descriptor;
|
|
7017
|
+
taskLogger.debug({
|
|
7018
|
+
taskId: claimedTask.task.id,
|
|
7019
|
+
taskType: claimedTask.task.taskType,
|
|
7020
|
+
resumable: sessionDescriptor.policy.resumable,
|
|
7021
|
+
workspaceMode: executionPlan.workspaceMode,
|
|
7022
|
+
workspaceScope: sessionDescriptor.policy.workspaceScope,
|
|
7023
|
+
sessionScope: sessionDescriptor.policy.sessionScope,
|
|
7024
|
+
slotKey: executionPlan.slotKey,
|
|
7025
|
+
slotId: executionPlan.slotId,
|
|
7026
|
+
sessionKey: executionPlan.sessionKey,
|
|
7027
|
+
piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
|
|
7028
|
+
workspaceId: executionPlan.workspaceId
|
|
7029
|
+
}, "agent-daemon.task_execution_policy");
|
|
7030
|
+
if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
|
|
7031
|
+
taskId: claimedTask.task.id,
|
|
7032
|
+
attemptN: claimedTask.attemptN,
|
|
7033
|
+
status: "failed",
|
|
7034
|
+
output: null,
|
|
7035
|
+
outputCid: null,
|
|
7036
|
+
usage: {
|
|
7037
|
+
inputTokens: 0,
|
|
7038
|
+
outputTokens: 0
|
|
7039
|
+
},
|
|
7040
|
+
durationMs: 0,
|
|
7041
|
+
error: {
|
|
7042
|
+
code: "unsupported_task_type",
|
|
7043
|
+
message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
|
|
7044
|
+
retryable: true
|
|
7045
|
+
}
|
|
7046
|
+
};
|
|
7047
|
+
if (reporter.cancelSignal.aborted) return {
|
|
7048
|
+
taskId: claimedTask.task.id,
|
|
7049
|
+
attemptN: claimedTask.attemptN,
|
|
7050
|
+
status: "cancelled",
|
|
7051
|
+
output: null,
|
|
7052
|
+
outputCid: null,
|
|
7053
|
+
usage: {
|
|
7054
|
+
inputTokens: 0,
|
|
7055
|
+
outputTokens: 0
|
|
7056
|
+
},
|
|
7057
|
+
durationMs: 0,
|
|
7058
|
+
error: {
|
|
7059
|
+
code: "task_cancelled",
|
|
7060
|
+
message: reporter.cancelReason ?? "Task cancelled before executor started.",
|
|
7061
|
+
retryable: false
|
|
7062
|
+
}
|
|
7063
|
+
};
|
|
7064
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
7065
|
+
...slotIdentity,
|
|
7066
|
+
runtimeProfileId: profile.id,
|
|
7067
|
+
provider: profile.provider,
|
|
7068
|
+
model: profile.model,
|
|
7069
|
+
teamId: claimedTask.task.teamId,
|
|
7070
|
+
slotKey: executionPlan.slotKey,
|
|
7071
|
+
taskType: claimedTask.task.taskType,
|
|
7072
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
7073
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
7074
|
+
workspaceId: executionPlan.workspaceId,
|
|
7075
|
+
worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
7076
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
7077
|
+
workspaceKind: executionPlan.workspaceKind,
|
|
7078
|
+
lastTaskId: claimedTask.task.id,
|
|
7079
|
+
lastAttemptN: claimedTask.attemptN
|
|
7080
|
+
});
|
|
7081
|
+
const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
|
|
7082
|
+
agentName: common.agent,
|
|
7083
|
+
moltnetAgent: ctx.agent,
|
|
7084
|
+
agentIdentity,
|
|
7085
|
+
hostCapabilitySigner,
|
|
7086
|
+
hostCapabilityLogger: taskLogger,
|
|
7087
|
+
agentRootDir: ctx.agentRootDir,
|
|
7088
|
+
mountPath: sandbox.rootDir,
|
|
7089
|
+
provider: profile.provider,
|
|
7090
|
+
model: profile.model,
|
|
7091
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7092
|
+
temperature: profile.temperature,
|
|
7093
|
+
topP: profile.topP,
|
|
7094
|
+
topK: profile.topK,
|
|
7095
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7096
|
+
sandboxConfig: sandbox.config,
|
|
7097
|
+
forwardEnv: profile.requiredEnv,
|
|
7098
|
+
onVmDiagnostic: (diagnostic) => {
|
|
7099
|
+
const fields = {
|
|
7100
|
+
event: diagnostic.event,
|
|
7101
|
+
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
7102
|
+
};
|
|
7103
|
+
if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
|
|
7104
|
+
else taskLogger.info(fields, diagnostic.message);
|
|
7105
|
+
},
|
|
7106
|
+
runtimeProfileContext: profile.context,
|
|
7107
|
+
runtimeProfileId: profile.id,
|
|
7108
|
+
toolEnforcement: profile.toolEnforcement,
|
|
7109
|
+
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
7110
|
+
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
7111
|
+
toolPolicyLogger: taskLogger,
|
|
7112
|
+
maxTurns: common.maxTurns,
|
|
7113
|
+
maxBashTimeouts: common.maxBashTimeouts
|
|
7114
|
+
});
|
|
7115
|
+
try {
|
|
7116
|
+
active = {
|
|
7117
|
+
taskId: claimedTask.task.id,
|
|
7118
|
+
attemptN: claimedTask.attemptN
|
|
7119
|
+
};
|
|
7120
|
+
return await runWithDaemonRuntimeContext({
|
|
7121
|
+
profileId: profile.id,
|
|
7122
|
+
profileName: profile.name,
|
|
7123
|
+
provider: profile.provider,
|
|
7124
|
+
model: profile.model,
|
|
7125
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7126
|
+
temperature: profile.temperature,
|
|
7127
|
+
topP: profile.topP,
|
|
7128
|
+
topK: profile.topK,
|
|
7129
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
7130
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
7131
|
+
} finally {
|
|
7132
|
+
active = null;
|
|
7133
|
+
executionPlans.delete(claimedTask);
|
|
7134
|
+
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7282
7137
|
});
|
|
7138
|
+
const drained = await runtime.start();
|
|
7139
|
+
outputs.push(...drained);
|
|
7140
|
+
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
7141
|
+
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
7142
|
+
} finally {
|
|
7143
|
+
clearInterval(reaperTimer);
|
|
7144
|
+
signalHandlers.dispose();
|
|
7145
|
+
await slotRegistry.close();
|
|
7146
|
+
await otelShutdown();
|
|
7147
|
+
await shutdownLogger();
|
|
7283
7148
|
}
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
return
|
|
7292
|
-
}
|
|
7293
|
-
|
|
7294
|
-
return readJson(this.agentPath(alias));
|
|
7295
|
-
}
|
|
7296
|
-
writeAgentConfig(alias, config) {
|
|
7297
|
-
mkdirSync(this.identityDir(alias), {
|
|
7298
|
-
recursive: true,
|
|
7299
|
-
mode: 448
|
|
7300
|
-
});
|
|
7301
|
-
writeJsonAtomic(this.agentPath(alias), config);
|
|
7302
|
-
if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
|
|
7149
|
+
}
|
|
7150
|
+
function resolveRecordedWorkspacePath$1(stateRootDir, mountPath, executionPlan) {
|
|
7151
|
+
if (!executionPlan.workspaceId) return null;
|
|
7152
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
7153
|
+
}
|
|
7154
|
+
function resolveMainWorktree$1(mountPath) {
|
|
7155
|
+
try {
|
|
7156
|
+
return findMainWorktree(mountPath);
|
|
7157
|
+
} catch {
|
|
7158
|
+
return null;
|
|
7303
7159
|
}
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7160
|
+
}
|
|
7161
|
+
function runtimeForClaimedTask(runtimes, claimedTask) {
|
|
7162
|
+
if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
|
|
7163
|
+
return requireRuntime(runtimes, claimedTask.profileId);
|
|
7164
|
+
}
|
|
7165
|
+
function requireRuntime(runtimes, profileId) {
|
|
7166
|
+
const runtime = runtimes.get(profileId);
|
|
7167
|
+
if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
|
|
7168
|
+
return runtime;
|
|
7169
|
+
}
|
|
7170
|
+
function parseProfileValues(raw) {
|
|
7171
|
+
return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7172
|
+
}
|
|
7173
|
+
function parseCsv(raw) {
|
|
7174
|
+
return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7175
|
+
}
|
|
7176
|
+
function optionalPositiveInt(raw, name, defaultValue) {
|
|
7177
|
+
if (raw === void 0) return defaultValue;
|
|
7178
|
+
const v = Number(raw);
|
|
7179
|
+
if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
|
|
7180
|
+
return v;
|
|
7181
|
+
}
|
|
7182
|
+
function optionalNonNegativeInt(raw, name, defaultValue) {
|
|
7183
|
+
if (raw === void 0) return defaultValue;
|
|
7184
|
+
const v = Number(raw);
|
|
7185
|
+
if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
|
|
7186
|
+
return v;
|
|
7187
|
+
}
|
|
7188
|
+
//#endregion
|
|
7189
|
+
//#region src/cli/drain.ts
|
|
7190
|
+
function runDrain(argv, runtimeAdapter) {
|
|
7191
|
+
return runPolling({
|
|
7192
|
+
argv,
|
|
7193
|
+
serviceName: "moltnet.agent-daemon.drain",
|
|
7194
|
+
stopWhenEmpty: true,
|
|
7195
|
+
modeLabel: "drain",
|
|
7196
|
+
helpText: DRAIN_HELP,
|
|
7197
|
+
runtimeAdapter
|
|
7198
|
+
});
|
|
7199
|
+
}
|
|
7200
|
+
//#endregion
|
|
7201
|
+
//#region src/cli/once.ts
|
|
7202
|
+
async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
7203
|
+
if (isHelpFlag(argv)) {
|
|
7204
|
+
console.log(ONCE_HELP);
|
|
7205
|
+
return 0;
|
|
7307
7206
|
}
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7207
|
+
const { values } = parseArgs({
|
|
7208
|
+
args: argv,
|
|
7209
|
+
options: {
|
|
7210
|
+
...commonOptionDefs(),
|
|
7211
|
+
"task-id": {
|
|
7212
|
+
type: "string",
|
|
7213
|
+
short: "t"
|
|
7214
|
+
},
|
|
7215
|
+
team: { type: "string" },
|
|
7216
|
+
sandbox: { type: "string" },
|
|
7217
|
+
profile: { type: "string" }
|
|
7315
7218
|
}
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
return
|
|
7321
|
-
}
|
|
7322
|
-
hasPendingRegistration(alias) {
|
|
7323
|
-
return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
|
|
7324
|
-
}
|
|
7325
|
-
reserveRegistration(alias, apiUrl) {
|
|
7326
|
-
const name = assertStoreName("agent name", alias);
|
|
7327
|
-
const state = this.readAgentServerState();
|
|
7328
|
-
if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
|
|
7329
|
-
state.pendingRegistrations[name] = {
|
|
7330
|
-
apiUrl,
|
|
7331
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7332
|
-
};
|
|
7333
|
-
this.writeAgentServerState(state);
|
|
7334
|
-
}
|
|
7335
|
-
clearPendingRegistration(alias) {
|
|
7336
|
-
const name = assertStoreName("agent name", alias);
|
|
7337
|
-
const state = this.readAgentServerState();
|
|
7338
|
-
delete state.pendingRegistrations[name];
|
|
7339
|
-
this.writeAgentServerState(state);
|
|
7340
|
-
}
|
|
7341
|
-
writeActivation(activation) {
|
|
7342
|
-
const alias = assertStoreName("agent name", activation.alias);
|
|
7343
|
-
validateActivation(alias, activation);
|
|
7344
|
-
const state = this.readAgentServerState();
|
|
7345
|
-
state.activations[alias] = activation;
|
|
7346
|
-
if (activation.source === "managed") delete state.pendingRegistrations[alias];
|
|
7347
|
-
this.writeAgentServerState(state);
|
|
7348
|
-
}
|
|
7349
|
-
listActivations() {
|
|
7350
|
-
return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
|
|
7351
|
-
}
|
|
7352
|
-
get providersPath() {
|
|
7353
|
-
return join(this.root, "providers.json");
|
|
7354
|
-
}
|
|
7355
|
-
readProviders() {
|
|
7356
|
-
const state = readJson(this.providersPath) ?? {};
|
|
7357
|
-
this.validateProviders(state);
|
|
7358
|
-
return state;
|
|
7219
|
+
});
|
|
7220
|
+
if (!values["task-id"]) {
|
|
7221
|
+
console.error("Missing required flag: --task-id\n");
|
|
7222
|
+
console.error(ONCE_HELP);
|
|
7223
|
+
return 1;
|
|
7359
7224
|
}
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7225
|
+
const taskId = values["task-id"];
|
|
7226
|
+
if (!values.profile) {
|
|
7227
|
+
console.error("Missing required flag: --profile\n");
|
|
7228
|
+
console.error(ONCE_HELP);
|
|
7229
|
+
return 1;
|
|
7363
7230
|
}
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7231
|
+
let opts;
|
|
7232
|
+
try {
|
|
7233
|
+
opts = parseCommonOptions(values);
|
|
7234
|
+
} catch (err) {
|
|
7235
|
+
if (err instanceof MissingRequiredOptionError) {
|
|
7236
|
+
console.error(`${err.message}\n`);
|
|
7237
|
+
console.error(ONCE_HELP);
|
|
7238
|
+
return 1;
|
|
7368
7239
|
}
|
|
7240
|
+
throw err;
|
|
7369
7241
|
}
|
|
7370
|
-
|
|
7371
|
-
|
|
7242
|
+
if (values.sandbox) {
|
|
7243
|
+
console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
|
|
7244
|
+
return 1;
|
|
7372
7245
|
}
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7246
|
+
const cfg = loadConfig();
|
|
7247
|
+
const credentialSources = {
|
|
7248
|
+
profileRequirements: cfg.profileCredentialRequirements,
|
|
7249
|
+
bindings: cfg.credentialBindings
|
|
7250
|
+
};
|
|
7251
|
+
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
7252
|
+
const initialOpts = opts;
|
|
7253
|
+
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
7254
|
+
const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
7255
|
+
let gate = "resolve_agent_context";
|
|
7376
7256
|
try {
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7257
|
+
const resolvedContext = await resolveAgentContext(initialOpts.agent, {
|
|
7258
|
+
agentRootDir: explicitAgentRootDir,
|
|
7259
|
+
credentialSource: cfg.credentialSource,
|
|
7260
|
+
envApiUrl: cfg.apiUrl
|
|
7261
|
+
});
|
|
7262
|
+
gate = "authenticate_and_bind";
|
|
7263
|
+
const whoami = await validateStartupBinding({
|
|
7264
|
+
agent: resolvedContext.agent,
|
|
7265
|
+
teamId: values.team,
|
|
7266
|
+
expectedAgent: cfg.expectedAgent
|
|
7267
|
+
});
|
|
7268
|
+
gate = "resolve_signing_material";
|
|
7269
|
+
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
7270
|
+
credentialSource: cfg.credentialSource,
|
|
7271
|
+
agentDir: resolvedContext.agentDir,
|
|
7272
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
7273
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
7274
|
+
});
|
|
7275
|
+
gate = "validate_scopes";
|
|
7276
|
+
validateDaemonScopes(whoami);
|
|
7277
|
+
gate = "validate_signing_identity";
|
|
7278
|
+
await validateExecutorSigningIdentity({
|
|
7279
|
+
whoami,
|
|
7280
|
+
signingPrivateKey: privateKey
|
|
7281
|
+
});
|
|
7282
|
+
gate = "resolve_agent_identity";
|
|
7283
|
+
const agentIdentity = await resolveDaemonAgentIdentity({
|
|
7284
|
+
agentName: initialOpts.agent,
|
|
7285
|
+
whoami,
|
|
7286
|
+
credentialSource: cfg.credentialSource,
|
|
7287
|
+
agentDir: resolvedContext.agentDir,
|
|
7288
|
+
gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
|
|
7289
|
+
});
|
|
7290
|
+
return {
|
|
7291
|
+
ctx: resolvedContext,
|
|
7292
|
+
signingPrivateKey: privateKey,
|
|
7293
|
+
agentIdentity,
|
|
7294
|
+
hostCapabilitySigner: createLocalSeedSigner({
|
|
7295
|
+
privateKeySeed: privateKey,
|
|
7296
|
+
agent: resolvedContext.agent,
|
|
7297
|
+
identity: agentIdentity
|
|
7298
|
+
})
|
|
7299
|
+
};
|
|
7300
|
+
} catch (error) {
|
|
7301
|
+
await logDaemonStartupFailure({
|
|
7302
|
+
serviceName: "agent-daemon.once",
|
|
7303
|
+
level: cfg.logLevel || (initialOpts.debug ? "debug" : "info"),
|
|
7304
|
+
gate,
|
|
7305
|
+
agent: initialOpts.agent,
|
|
7306
|
+
credentialSource: cfg.credentialSource,
|
|
7307
|
+
error
|
|
7308
|
+
});
|
|
7309
|
+
throw error;
|
|
7381
7310
|
}
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7311
|
+
})();
|
|
7312
|
+
const daemonRootDir = explicitAgentRootDir ?? process.cwd();
|
|
7313
|
+
const profile = await resolveRuntimeProfile({
|
|
7314
|
+
agent: ctx.agent,
|
|
7315
|
+
profile: values.profile,
|
|
7316
|
+
teamId: values.team,
|
|
7317
|
+
cwd: daemonRootDir
|
|
7318
|
+
});
|
|
7319
|
+
assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
|
|
7320
|
+
const preparedRuntime = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
|
|
7321
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
|
|
7322
|
+
tools: preparedRuntime.tools,
|
|
7323
|
+
executables: preparedRuntime.executables
|
|
7324
|
+
});
|
|
7325
|
+
assertGuestEnvironmentBoundary({
|
|
7326
|
+
forwardEnv: profile.requiredEnv,
|
|
7327
|
+
sandboxEnv: profile.sandboxConfig.env
|
|
7328
|
+
});
|
|
7329
|
+
await ctx.agent.tasks.registerExecutorManifest(await preparedRuntime.attestor.registration());
|
|
7330
|
+
opts = parseCommonOptions(values, { runtimeDefaults: {
|
|
7331
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
7332
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
7333
|
+
maxBatchSize: profile.maxBatchSize,
|
|
7334
|
+
maxTurns: profile.maxTurns,
|
|
7335
|
+
maxBashTimeouts: profile.maxBashTimeouts,
|
|
7336
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
7337
|
+
} });
|
|
7338
|
+
const sandbox = {
|
|
7339
|
+
config: profile.sandboxConfig,
|
|
7340
|
+
rootDir: profile.mountPath,
|
|
7341
|
+
path: profile.source
|
|
7342
|
+
};
|
|
7343
|
+
const piAgentDir = await resolvePiAgentDir(cfg, sandbox.rootDir, [profile]);
|
|
7344
|
+
process.once("exit", piAgentDir.cleanup);
|
|
7345
|
+
activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
|
|
7346
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
7347
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
7348
|
+
const runtimeSessionStore = createApiRuntimeSessionStore({
|
|
7349
|
+
agent: ctx.agent,
|
|
7350
|
+
logger: { warn: (context, message) => rootLogger.warn(context, message) }
|
|
7351
|
+
});
|
|
7352
|
+
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
7353
|
+
const runtimeInstanceId = createRuntimeInstanceId();
|
|
7354
|
+
const slotIdentity = {
|
|
7355
|
+
agentName: opts.agent,
|
|
7356
|
+
runtimeProfileId: profile.id,
|
|
7357
|
+
runtimeInstanceId
|
|
7358
|
+
};
|
|
7359
|
+
const executionPlans = createExecutionPlanCache({
|
|
7360
|
+
stateDirs,
|
|
7361
|
+
slotIdentity,
|
|
7362
|
+
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
7363
|
+
workspacePolicy: {
|
|
7364
|
+
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
7365
|
+
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
7366
|
+
},
|
|
7367
|
+
slotRegistry,
|
|
7368
|
+
runtimeSessionStore,
|
|
7369
|
+
sourceAttemptResolver
|
|
7370
|
+
});
|
|
7371
|
+
const otelShutdown = await initWorkerOtel({
|
|
7372
|
+
serviceName: "moltnet.agent-daemon.once",
|
|
7373
|
+
agent: ctx.agent,
|
|
7374
|
+
endpoint: cfg.otelEndpoint,
|
|
7375
|
+
resourceAttributes: {
|
|
7376
|
+
"moltnet.task.id": taskId,
|
|
7377
|
+
"moltnet.agent.name": opts.agent,
|
|
7378
|
+
"moltnet.credential.source": ctx.credentialSource,
|
|
7379
|
+
"moltnet.llm.provider": profile.provider,
|
|
7380
|
+
"moltnet.llm.model": profile.model,
|
|
7381
|
+
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
7382
|
+
...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
|
|
7383
|
+
...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
|
|
7384
|
+
...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
|
|
7385
|
+
...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
|
|
7386
|
+
"moltnet.runtime_profile.id": profile.id
|
|
7391
7387
|
}
|
|
7392
|
-
|
|
7388
|
+
});
|
|
7389
|
+
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
7390
|
+
name: "agent-daemon.once",
|
|
7391
|
+
level: cfg.logLevel || (opts.debug ? "debug" : "info")
|
|
7392
|
+
});
|
|
7393
|
+
const rootLogger = logger.child({
|
|
7394
|
+
mode: "once",
|
|
7395
|
+
agent: opts.agent,
|
|
7396
|
+
provider: profile.provider,
|
|
7397
|
+
model: profile.model,
|
|
7398
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7399
|
+
temperature: profile.temperature,
|
|
7400
|
+
topP: profile.topP,
|
|
7401
|
+
topK: profile.topK,
|
|
7402
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7403
|
+
runtimeProfileId: profile.id,
|
|
7404
|
+
runtimeProfileName: profile.name
|
|
7405
|
+
});
|
|
7406
|
+
rootLogger.info({
|
|
7407
|
+
sandbox: sandbox.path,
|
|
7408
|
+
taskId,
|
|
7409
|
+
leaseTtlSec: opts.leaseTtlSec,
|
|
7410
|
+
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
7411
|
+
maxTurns: opts.maxTurns,
|
|
7412
|
+
maxBashTimeouts: opts.maxBashTimeouts,
|
|
7413
|
+
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
7414
|
+
profileId: profile.id,
|
|
7415
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
7416
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
7417
|
+
piAgentDir: piAgentDir.path,
|
|
7418
|
+
piAgentDirSource: piAgentDir.source
|
|
7419
|
+
}, "agent-daemon.starting");
|
|
7420
|
+
try {
|
|
7421
|
+
const reaped = await reapRuntimeSlotResources({
|
|
7422
|
+
onIssue: (issue) => {
|
|
7423
|
+
rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
|
|
7424
|
+
},
|
|
7425
|
+
runtimeSlotStore: slotRegistry,
|
|
7426
|
+
taskReader: ctx.agent.tasks
|
|
7427
|
+
}, {
|
|
7428
|
+
agentName: opts.agent,
|
|
7429
|
+
mainWorktree: resolveMainWorktree(sandbox.rootDir),
|
|
7430
|
+
runtimeInstanceId,
|
|
7431
|
+
runtimeProfileId: profile.id,
|
|
7432
|
+
sessionRootDir: stateDirs.piSessionsDir,
|
|
7433
|
+
scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
|
|
7434
|
+
teamId: profile.teamId
|
|
7435
|
+
});
|
|
7436
|
+
if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
|
|
7437
|
+
} catch (err) {
|
|
7438
|
+
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
7393
7439
|
}
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7440
|
+
let runtime = null;
|
|
7441
|
+
let activeAttemptN = null;
|
|
7442
|
+
const signalHandlers = installShutdownSignalHandlers({
|
|
7443
|
+
logDrain: (signal) => {
|
|
7444
|
+
rootLogger.warn({
|
|
7445
|
+
signal,
|
|
7446
|
+
taskId
|
|
7447
|
+
}, "agent-daemon.draining");
|
|
7448
|
+
},
|
|
7449
|
+
drain: (signal) => {
|
|
7450
|
+
runtime?.stop(`agent-daemon received ${signal}`);
|
|
7451
|
+
abortActiveAttemptOnSignal({
|
|
7452
|
+
active: activeAttemptN === null ? null : {
|
|
7453
|
+
taskId,
|
|
7454
|
+
attemptN: activeAttemptN
|
|
7455
|
+
},
|
|
7456
|
+
signal,
|
|
7457
|
+
abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
|
|
7458
|
+
logFailure: (err, current) => {
|
|
7459
|
+
try {
|
|
7460
|
+
rootLogger.warn({
|
|
7461
|
+
err: err instanceof Error ? err.message : String(err),
|
|
7462
|
+
taskId: current.taskId,
|
|
7463
|
+
attemptN: current.attemptN
|
|
7464
|
+
}, "agent-daemon.abort_on_signal_failed");
|
|
7465
|
+
} catch (logErr) {
|
|
7466
|
+
process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
});
|
|
7470
|
+
}
|
|
7471
|
+
});
|
|
7472
|
+
try {
|
|
7473
|
+
const rawExecuteTask = preparedRuntime.createTaskExecutor({
|
|
7474
|
+
agentName: opts.agent,
|
|
7475
|
+
moltnetAgent: ctx.agent,
|
|
7476
|
+
agentIdentity,
|
|
7477
|
+
hostCapabilitySigner,
|
|
7478
|
+
hostCapabilityLogger: rootLogger,
|
|
7479
|
+
agentRootDir: ctx.agentRootDir,
|
|
7480
|
+
mountPath: sandbox.rootDir,
|
|
7481
|
+
provider: profile.provider,
|
|
7482
|
+
model: profile.model,
|
|
7483
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7484
|
+
temperature: profile.temperature,
|
|
7485
|
+
topP: profile.topP,
|
|
7486
|
+
topK: profile.topK,
|
|
7487
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7488
|
+
sandboxConfig: sandbox.config,
|
|
7489
|
+
forwardEnv: profile.requiredEnv,
|
|
7490
|
+
onVmDiagnostic: (diagnostic) => {
|
|
7491
|
+
const fields = {
|
|
7492
|
+
event: diagnostic.event,
|
|
7493
|
+
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
7494
|
+
};
|
|
7495
|
+
if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
|
|
7496
|
+
else rootLogger.info(fields, diagnostic.message);
|
|
7497
|
+
},
|
|
7498
|
+
runtimeProfileContext: profile.context,
|
|
7499
|
+
runtimeProfileId: profile.id,
|
|
7500
|
+
toolEnforcement: profile.toolEnforcement,
|
|
7501
|
+
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
7502
|
+
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
7503
|
+
toolPolicyLogger: rootLogger,
|
|
7504
|
+
maxTurns: opts.maxTurns,
|
|
7505
|
+
maxBashTimeouts: opts.maxBashTimeouts
|
|
7400
7506
|
});
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7507
|
+
const executeTask = async (claimedTask, reporter) => {
|
|
7508
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
7509
|
+
config: runtimeCredentialConfig,
|
|
7510
|
+
profile,
|
|
7511
|
+
offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
|
|
7512
|
+
registry: createNodeSecretProviderRegistry(),
|
|
7513
|
+
executorFingerprint: preparedRuntime.attestor.fingerprint,
|
|
7514
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
7515
|
+
taskId: claimedTask.task.id,
|
|
7516
|
+
attemptN: claimedTask.attemptN,
|
|
7517
|
+
logger: rootLogger
|
|
7518
|
+
});
|
|
7519
|
+
let executionPlan;
|
|
7520
|
+
try {
|
|
7521
|
+
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
7522
|
+
} catch (err) {
|
|
7523
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7524
|
+
rootLogger.warn({
|
|
7525
|
+
taskId: claimedTask.task.id,
|
|
7526
|
+
attemptN: claimedTask.attemptN,
|
|
7527
|
+
err: message
|
|
7528
|
+
}, "agent-daemon.execution_plan_failed");
|
|
7529
|
+
return {
|
|
7530
|
+
taskId: claimedTask.task.id,
|
|
7531
|
+
attemptN: claimedTask.attemptN,
|
|
7532
|
+
status: "failed",
|
|
7533
|
+
output: null,
|
|
7534
|
+
outputCid: null,
|
|
7535
|
+
usage: {
|
|
7536
|
+
inputTokens: 0,
|
|
7537
|
+
outputTokens: 0
|
|
7538
|
+
},
|
|
7539
|
+
durationMs: 0,
|
|
7540
|
+
error: {
|
|
7541
|
+
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
7542
|
+
message,
|
|
7543
|
+
retryable: false
|
|
7544
|
+
}
|
|
7545
|
+
};
|
|
7546
|
+
}
|
|
7547
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
7548
|
+
...slotIdentity,
|
|
7549
|
+
runtimeProfileId: profile.id,
|
|
7550
|
+
provider: profile.provider,
|
|
7551
|
+
model: profile.model,
|
|
7552
|
+
teamId: claimedTask.task.teamId,
|
|
7553
|
+
slotKey: executionPlan.slotKey,
|
|
7554
|
+
taskType: claimedTask.task.taskType,
|
|
7555
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
7556
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
7557
|
+
workspaceId: executionPlan.workspaceId,
|
|
7558
|
+
worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
7559
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
7560
|
+
workspaceKind: executionPlan.workspaceKind,
|
|
7561
|
+
lastTaskId: claimedTask.task.id,
|
|
7562
|
+
lastAttemptN: claimedTask.attemptN
|
|
7563
|
+
});
|
|
7564
|
+
activeAttemptN = claimedTask.attemptN;
|
|
7565
|
+
try {
|
|
7566
|
+
return await runWithDaemonRuntimeContext({
|
|
7567
|
+
profileId: profile.id,
|
|
7568
|
+
profileName: profile.name,
|
|
7569
|
+
provider: profile.provider,
|
|
7570
|
+
model: profile.model,
|
|
7571
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7572
|
+
temperature: profile.temperature,
|
|
7573
|
+
topP: profile.topP,
|
|
7574
|
+
topK: profile.topK,
|
|
7575
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
7576
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
7577
|
+
} finally {
|
|
7578
|
+
activeAttemptN = null;
|
|
7579
|
+
executionPlans.delete(claimedTask);
|
|
7580
|
+
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
7581
|
+
}
|
|
7405
7582
|
};
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7583
|
+
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
7584
|
+
gh: createGhCliClient(),
|
|
7585
|
+
logger: rootLogger
|
|
7586
|
+
});
|
|
7587
|
+
runtime = new AgentRuntime({
|
|
7588
|
+
logger: rootLogger,
|
|
7589
|
+
source: new ApiTaskSource({
|
|
7590
|
+
agent: ctx.agent,
|
|
7591
|
+
taskId,
|
|
7592
|
+
teamId: profile.teamId,
|
|
7593
|
+
leaseTtlSec: opts.leaseTtlSec,
|
|
7594
|
+
profileId: profile.id,
|
|
7595
|
+
executorFingerprint: preparedRuntime.attestor.fingerprint
|
|
7596
|
+
}),
|
|
7597
|
+
makeReporter: () => new ApiTaskReporter({
|
|
7598
|
+
tasks: ctx.agent.tasks,
|
|
7599
|
+
teamId: profile.teamId,
|
|
7600
|
+
leaseTtlSec: opts.leaseTtlSec,
|
|
7601
|
+
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
7602
|
+
maxBatchSize: opts.maxBatchSize,
|
|
7603
|
+
flushIntervalMs: opts.flushIntervalMs
|
|
7604
|
+
}),
|
|
7605
|
+
onTaskFinished: async (output, claimedTask) => {
|
|
7606
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
7607
|
+
let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
7608
|
+
if (resolved?.session?.sessionDir) try {
|
|
7609
|
+
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
7610
|
+
await runtimeSessionStore.uploadAttemptFinal({
|
|
7611
|
+
attemptN: claimedTask.attemptN,
|
|
7612
|
+
parentSessionId: parentSession?.id ?? null,
|
|
7613
|
+
sessionDir: resolved.session.sessionDir,
|
|
7614
|
+
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
7615
|
+
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
7616
|
+
sourceSlotId: resolved.slot.id,
|
|
7617
|
+
taskId: claimedTask.task.id,
|
|
7618
|
+
teamId: claimedTask.task.teamId
|
|
7619
|
+
});
|
|
7620
|
+
} catch (err) {
|
|
7621
|
+
rootLogger.error({
|
|
7622
|
+
err,
|
|
7623
|
+
taskId: claimedTask.task.id,
|
|
7624
|
+
attemptN: claimedTask.attemptN
|
|
7625
|
+
}, "agent-daemon.runtime_session_upload_failed");
|
|
7626
|
+
terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
|
|
7627
|
+
}
|
|
7628
|
+
return finalizeTask(ctx.agent, terminalOutput, {
|
|
7629
|
+
task: claimedTask.task,
|
|
7630
|
+
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
7631
|
+
retryTriage: createRuntimeProfileRetryTriage({
|
|
7632
|
+
runtimeProfile: profile,
|
|
7633
|
+
piAgentDir: piAgentDir.path,
|
|
7634
|
+
cwd: ctx.agentRootDir
|
|
7635
|
+
}),
|
|
7636
|
+
executorAttestor: preparedRuntime.attestor,
|
|
7637
|
+
writeCorrelationAnchors,
|
|
7638
|
+
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
7447
7639
|
});
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7640
|
+
},
|
|
7641
|
+
executeTask
|
|
7642
|
+
});
|
|
7643
|
+
const [output] = await runtime.start();
|
|
7644
|
+
if (!output) {
|
|
7645
|
+
rootLogger.error({}, "agent-daemon.no_output");
|
|
7646
|
+
return 1;
|
|
7453
7647
|
}
|
|
7454
|
-
|
|
7648
|
+
console.log("\n[done] TaskOutput:");
|
|
7649
|
+
console.log(JSON.stringify(output, null, 2));
|
|
7650
|
+
return output.status === "completed" ? 0 : 1;
|
|
7651
|
+
} finally {
|
|
7652
|
+
signalHandlers.dispose();
|
|
7653
|
+
await slotRegistry.close();
|
|
7654
|
+
await otelShutdown();
|
|
7655
|
+
await shutdownLogger();
|
|
7455
7656
|
}
|
|
7456
|
-
}
|
|
7457
|
-
function
|
|
7458
|
-
|
|
7657
|
+
}
|
|
7658
|
+
function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
|
|
7659
|
+
if (!executionPlan.workspaceId) return null;
|
|
7660
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
7661
|
+
}
|
|
7662
|
+
function resolveMainWorktree(mountPath) {
|
|
7459
7663
|
try {
|
|
7460
|
-
|
|
7664
|
+
return findMainWorktree(mountPath);
|
|
7461
7665
|
} catch {
|
|
7462
|
-
return
|
|
7666
|
+
return null;
|
|
7463
7667
|
}
|
|
7464
|
-
if (info.isSymbolicLink()) return 0;
|
|
7465
|
-
if (!info.isDirectory()) return info.size;
|
|
7466
|
-
let total = 0;
|
|
7467
|
-
for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
|
|
7468
|
-
return total;
|
|
7469
|
-
}
|
|
7470
|
-
function isRecord$1(value) {
|
|
7471
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7472
|
-
}
|
|
7473
|
-
function storeChildPath(root, kind, value, suffix = "") {
|
|
7474
|
-
const name = assertStoreName(kind, value);
|
|
7475
|
-
const normalizedRoot = resolve(root);
|
|
7476
|
-
const candidate = resolve(normalizedRoot, `${name}${suffix}`);
|
|
7477
|
-
if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
|
|
7478
|
-
return candidate;
|
|
7479
|
-
}
|
|
7480
|
-
function isStrictDescendant(root, candidate) {
|
|
7481
|
-
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
7482
|
-
return candidate !== root && candidate.startsWith(rootPrefix);
|
|
7483
7668
|
}
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
"createdAt"
|
|
7496
|
-
].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
|
|
7497
|
-
if (activation.source === "managed") {
|
|
7498
|
-
if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
|
|
7499
|
-
return;
|
|
7500
|
-
}
|
|
7501
|
-
if (activation.source !== "external" || typeof activation.configPath !== "string" || activation.configPath.length === 0 || typeof activation.configApiUrl !== "string" || activation.configApiUrl.length === 0 || activation.apiUrl !== void 0 && typeof activation.apiUrl !== "string") invalid();
|
|
7669
|
+
//#endregion
|
|
7670
|
+
//#region src/cli/poll.ts
|
|
7671
|
+
function runPoll(argv, runtimeAdapter) {
|
|
7672
|
+
return runPolling({
|
|
7673
|
+
argv,
|
|
7674
|
+
serviceName: "moltnet.agent-daemon.poll",
|
|
7675
|
+
stopWhenEmpty: false,
|
|
7676
|
+
modeLabel: "poll",
|
|
7677
|
+
helpText: POLL_HELP,
|
|
7678
|
+
runtimeAdapter
|
|
7679
|
+
});
|
|
7502
7680
|
}
|
|
7503
7681
|
//#endregion
|
|
7504
7682
|
//#region src/lib/provider-lock.ts
|
|
@@ -9189,7 +9367,6 @@ function snapshot(login) {
|
|
|
9189
9367
|
* the current config and verifies it with authenticated `whoami` before use.
|
|
9190
9368
|
*/
|
|
9191
9369
|
var MAX_CONFIG_BYTES = 64 * 1024;
|
|
9192
|
-
var IDENTITY_OPERATION_TIMEOUT_MS = 15e3;
|
|
9193
9370
|
var pendingAliases = /* @__PURE__ */ new WeakMap();
|
|
9194
9371
|
var AgentServerIdentityError = class extends Error {
|
|
9195
9372
|
name = "AgentServerIdentityError";
|
|
@@ -9212,7 +9389,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9212
9389
|
const alias = assertStoreName("agent name", input.name);
|
|
9213
9390
|
if (!input.enrollmentToken.trim()) throw new AgentServerIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
|
|
9214
9391
|
const releaseAlias = reserveAlias(store, alias);
|
|
9215
|
-
let registered
|
|
9392
|
+
let registered;
|
|
9216
9393
|
try {
|
|
9217
9394
|
let apiUrl;
|
|
9218
9395
|
try {
|
|
@@ -9221,61 +9398,40 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9221
9398
|
throw new AgentServerIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
|
|
9222
9399
|
}
|
|
9223
9400
|
store.reserveRegistration(alias, apiUrl);
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
subject_type: "agent",
|
|
9245
|
-
registered_at: now,
|
|
9246
|
-
agent_key_ref: agentKeyReference,
|
|
9247
|
-
keys: {
|
|
9248
|
-
public_key: publicKey,
|
|
9249
|
-
fingerprint,
|
|
9250
|
-
private_key_ref: seedReference
|
|
9251
|
-
},
|
|
9252
|
-
endpoints: {
|
|
9253
|
-
api: result.apiUrl,
|
|
9254
|
-
mcp: deriveMcpUrl(result.apiUrl)
|
|
9401
|
+
try {
|
|
9402
|
+
registered = await register({
|
|
9403
|
+
name: alias,
|
|
9404
|
+
apiUrl,
|
|
9405
|
+
credentialType: "agent_key",
|
|
9406
|
+
enrollmentToken: input.enrollmentToken,
|
|
9407
|
+
secretProvider: secrets,
|
|
9408
|
+
configDir: store.identityDir(alias),
|
|
9409
|
+
publishAlias: false,
|
|
9410
|
+
signal: input.signal,
|
|
9411
|
+
connectAgent
|
|
9412
|
+
});
|
|
9413
|
+
} catch (cause) {
|
|
9414
|
+
if (cause instanceof RegisterIdentityError) {
|
|
9415
|
+
if (cause.nothingRegistered) {
|
|
9416
|
+
store.clearPendingRegistration(alias);
|
|
9417
|
+
throw new AgentServerIdentityError(cause.code === "alias_exists" ? "agent_exists" : "registration_failed", cause.message, { cause });
|
|
9418
|
+
}
|
|
9419
|
+
if (cause.code === "unsupported_credential") throw new AgentServerIdentityError("unsupported_credential", `${cause.message}; agent server manages agent-key credentials only`, { cause });
|
|
9420
|
+
if (cause.code === "identity_mismatch") throw new AgentServerIdentityError("verification_failed", cause.message, { cause });
|
|
9255
9421
|
}
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
await secrets.write(seedReference.key, privateKey);
|
|
9260
|
-
const whoami = await callWhoami(connectAgent, {
|
|
9261
|
-
agentKey: result.credentials.secret,
|
|
9262
|
-
apiUrl: result.apiUrl
|
|
9263
|
-
}, store.agentPath(alias), input.signal);
|
|
9264
|
-
assertIdentityMatches(whoami, {
|
|
9265
|
-
publicKey,
|
|
9266
|
-
fingerprint
|
|
9267
|
-
}, "authenticated whoami", `new managed agent "${alias}"`);
|
|
9268
|
-
assertSubjectMatches(whoami, config, "authenticated whoami", `managed config ${store.agentPath(alias)}`);
|
|
9422
|
+
throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, cause instanceof RegisterIdentityError ? cause : {}), { cause });
|
|
9423
|
+
}
|
|
9424
|
+
const { config, whoami, identity } = registered;
|
|
9269
9425
|
const boundTeamId = boundTeamIdFromWhoami(whoami);
|
|
9270
9426
|
const activation = {
|
|
9271
9427
|
alias,
|
|
9272
9428
|
source: "managed",
|
|
9273
9429
|
subjectId: whoami.subjectId,
|
|
9274
|
-
publicKey,
|
|
9275
|
-
fingerprint,
|
|
9430
|
+
publicKey: identity.publicKey,
|
|
9431
|
+
fingerprint: identity.fingerprint,
|
|
9276
9432
|
...boundTeamId ? { boundTeamId } : {},
|
|
9277
|
-
createdAt:
|
|
9278
|
-
apiUrl:
|
|
9433
|
+
createdAt: config.registered_at,
|
|
9434
|
+
apiUrl: config.endpoints.api
|
|
9279
9435
|
};
|
|
9280
9436
|
store.writeActivation(activation);
|
|
9281
9437
|
return {
|
|
@@ -9284,18 +9440,27 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9284
9440
|
...boundTeamId ? { boundTeamId } : {}
|
|
9285
9441
|
};
|
|
9286
9442
|
} catch (cause) {
|
|
9287
|
-
if (
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9443
|
+
if (cause instanceof AgentServerIdentityError) throw cause;
|
|
9444
|
+
if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, registered ? {
|
|
9445
|
+
...registered.identity,
|
|
9446
|
+
configPath: registered.configPath
|
|
9447
|
+
} : {}), { cause });
|
|
9292
9448
|
throw cause;
|
|
9293
9449
|
} finally {
|
|
9294
9450
|
releaseAlias();
|
|
9295
9451
|
}
|
|
9296
9452
|
}
|
|
9297
|
-
|
|
9298
|
-
|
|
9453
|
+
/**
|
|
9454
|
+
* Guidance for a registration that may have committed. The HTTP response
|
|
9455
|
+
* carries only this message, so it names the reconcile call and, when the
|
|
9456
|
+
* server outcome is unknown, the fingerprint to look up first. Resume needs
|
|
9457
|
+
* the written config, so without one only abandon is offered.
|
|
9458
|
+
*/
|
|
9459
|
+
function incompleteRegistrationMessage(alias, known) {
|
|
9460
|
+
const endpoint = `POST /v1/agents/${alias}/reconcile`;
|
|
9461
|
+
const reconcile = known.configPath ? `Finish it with ${endpoint} and {"action":"resume"}, or discard the local record with {"action":"abandon"}.` : `No local config was written, so it cannot be resumed; discard the local record with ${endpoint} and {"action":"abandon"}.`;
|
|
9462
|
+
if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}`;
|
|
9463
|
+
return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}`;
|
|
9299
9464
|
}
|
|
9300
9465
|
/** Resume a fully persisted registration or explicitly abandon local recovery. */
|
|
9301
9466
|
async function reconcileManagedRegistration(store, secrets, aliasInput, action, connectAgent = connect, signal) {
|
|
@@ -9518,10 +9683,6 @@ async function callWhoami(connectAgent, options, source, signal) {
|
|
|
9518
9683
|
throw verificationError(`could not authenticate ${source} against the API`, cause);
|
|
9519
9684
|
}
|
|
9520
9685
|
}
|
|
9521
|
-
function boundedIdentitySignal(signal) {
|
|
9522
|
-
const timeout = AbortSignal.timeout(IDENTITY_OPERATION_TIMEOUT_MS);
|
|
9523
|
-
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
9524
|
-
}
|
|
9525
9686
|
function identityFromConfig(config) {
|
|
9526
9687
|
const publicKey = config?.keys?.public_key?.trim();
|
|
9527
9688
|
const fingerprint = config?.keys?.fingerprint?.trim();
|
|
@@ -9766,20 +9927,8 @@ var RunManager = class {
|
|
|
9766
9927
|
recursive: true,
|
|
9767
9928
|
mode: 448
|
|
9768
9929
|
});
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
|
|
9772
|
-
api: provider.api,
|
|
9773
|
-
...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
|
|
9774
|
-
baseUrl: provider.baseUrl,
|
|
9775
|
-
models: provider.models
|
|
9776
|
-
}]))
|
|
9777
|
-
});
|
|
9778
|
-
try {
|
|
9779
|
-
(this.options.symlinkImpl ?? symlinkSync)(this.store.piAuthJsonPath, join(piDir, "auth.json"));
|
|
9780
|
-
} catch (cause) {
|
|
9781
|
-
throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
|
|
9782
|
-
}
|
|
9930
|
+
writeStorePiConfig(piDir, providers);
|
|
9931
|
+
linkPiAuth(this.store.piAuthJsonPath, piDir, this.options.symlinkImpl ?? symlinkSync);
|
|
9783
9932
|
const entry = this.entrypoint();
|
|
9784
9933
|
logStream = createWriteStream(logPath, {
|
|
9785
9934
|
fd: openSync(logPath, "a", 384),
|
|
@@ -11153,7 +11302,7 @@ function normalizeAgentServerError(error) {
|
|
|
11153
11302
|
message: error.message
|
|
11154
11303
|
};
|
|
11155
11304
|
if (error instanceof AgentServerIdentityError) return {
|
|
11156
|
-
statusCode: error.code === "agent_exists" ? 409 : 400,
|
|
11305
|
+
statusCode: error.code === "agent_exists" || error.code === "registration_incomplete" ? 409 : 400,
|
|
11157
11306
|
code: error.code,
|
|
11158
11307
|
message: error.message
|
|
11159
11308
|
};
|
|
@@ -11854,7 +12003,7 @@ async function writeCache(cache) {
|
|
|
11854
12003
|
}
|
|
11855
12004
|
//#endregion
|
|
11856
12005
|
//#region src/version.ts
|
|
11857
|
-
var DAEMON_VERSION = "0.
|
|
12006
|
+
var DAEMON_VERSION = "0.57.0";
|
|
11858
12007
|
//#endregion
|
|
11859
12008
|
//#region src/cli.ts
|
|
11860
12009
|
async function runAgentDaemonCli(options) {
|