@themoltnet/agent-daemon 0.56.2 → 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -39
- package/dist/cli.js +2127 -2049
- package/package.json +10 -9
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
|
|
3
2
|
import { assertRuntimeAdapterSupportsProfile } from "./runtime.js";
|
|
3
|
+
import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { pino, transport } from "pino";
|
|
6
6
|
import { Type } from "typebox";
|
|
@@ -10,11 +10,11 @@ import "multiformats/hashes/sha2";
|
|
|
10
10
|
import "typebox/value";
|
|
11
11
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
12
12
|
import { parseArgs, promisify } from "node:util";
|
|
13
|
-
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity,
|
|
13
|
+
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, 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
|
|
@@ -1612,6 +1612,55 @@ Type.Object({
|
|
|
1612
1612
|
edges: Type.Array(ProvenanceGraphEdgeSchema)
|
|
1613
1613
|
}, { $id: "ProvenanceGraph" });
|
|
1614
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
|
|
1615
1664
|
//#region ../../libs/models/src/signer-constraint.ts
|
|
1616
1665
|
var SIGNER_CONSTRAINT_TYPE = {
|
|
1617
1666
|
Human: "human",
|
|
@@ -1888,18 +1937,6 @@ var RuntimeProfileRef = Type.Object({ profileId: Type.String({ format: "uuid" })
|
|
|
1888
1937
|
$id: "RuntimeProfileRef",
|
|
1889
1938
|
additionalProperties: false
|
|
1890
1939
|
});
|
|
1891
|
-
var RuntimeProfileLeaseTtlSec = Type.Integer({
|
|
1892
|
-
minimum: 1,
|
|
1893
|
-
maximum: 86400
|
|
1894
|
-
});
|
|
1895
|
-
var RuntimeProfileHeartbeatIntervalMs = Type.Integer({
|
|
1896
|
-
minimum: 0,
|
|
1897
|
-
maximum: 36e5
|
|
1898
|
-
});
|
|
1899
|
-
var RuntimeProfileMaxBatchSize = Type.Integer({
|
|
1900
|
-
minimum: 1,
|
|
1901
|
-
maximum: 1e3
|
|
1902
|
-
});
|
|
1903
1940
|
var RuntimeProfileMaxTurns = Type.Integer({
|
|
1904
1941
|
minimum: 0,
|
|
1905
1942
|
maximum: 1e4
|
|
@@ -1928,21 +1965,8 @@ Type.Object({
|
|
|
1928
1965
|
maxOutputTokens: RuntimeProfileNullableMaxOutputTokens,
|
|
1929
1966
|
runtimeKind: RuntimeProfileRuntimeKind,
|
|
1930
1967
|
sandbox: RuntimeProfileSandbox,
|
|
1931
|
-
sessionStorageMode: Type.Literal("local"),
|
|
1932
|
-
workspaceStorageMode: Type.Literal("local"),
|
|
1933
1968
|
defaultWorkspaceMode: Type.Union([RuntimeProfileWorkspaceMode, Type.Null()]),
|
|
1934
1969
|
allowedWorkspaceModes: RuntimeProfileAllowedWorkspaceModes,
|
|
1935
|
-
sessionTtlSec: Type.Integer({
|
|
1936
|
-
minimum: 1,
|
|
1937
|
-
maximum: 86400
|
|
1938
|
-
}),
|
|
1939
|
-
workspaceTtlSec: Type.Integer({
|
|
1940
|
-
minimum: 1,
|
|
1941
|
-
maximum: 86400
|
|
1942
|
-
}),
|
|
1943
|
-
leaseTtlSec: RuntimeProfileLeaseTtlSec,
|
|
1944
|
-
heartbeatIntervalMs: RuntimeProfileHeartbeatIntervalMs,
|
|
1945
|
-
maxBatchSize: RuntimeProfileMaxBatchSize,
|
|
1946
1970
|
maxTurns: RuntimeProfileMaxTurns,
|
|
1947
1971
|
maxBashTimeouts: RuntimeProfileMaxBashTimeouts,
|
|
1948
1972
|
toolEnforcement: RuntimeProfileToolEnforcement,
|
|
@@ -2075,6 +2099,7 @@ var ResolvedRuntimeSlot = Type.Object({
|
|
|
2075
2099
|
workspace: Type.Union([RuntimeWorkspace, Type.Null()])
|
|
2076
2100
|
}, { $id: "ResolvedRuntimeSlot" });
|
|
2077
2101
|
Type.Object({ items: Type.Array(ResolvedRuntimeSlot) }, { $id: "RuntimeSlotListResponse" });
|
|
2102
|
+
var MAX_RUNTIME_WARM_RETENTION_SEC = 86400;
|
|
2078
2103
|
Type.Object({
|
|
2079
2104
|
agentName: Type.String({
|
|
2080
2105
|
minLength: 1,
|
|
@@ -2101,7 +2126,11 @@ Type.Object({
|
|
|
2101
2126
|
worktreeBranch: Type.Optional(Type.String({ minLength: 1 })),
|
|
2102
2127
|
workspaceKind: Type.Optional(RuntimeWorkspaceKind),
|
|
2103
2128
|
lastTaskId: Type.String({ format: "uuid" }),
|
|
2104
|
-
lastAttemptN: Type.Integer({ minimum: 1 })
|
|
2129
|
+
lastAttemptN: Type.Integer({ minimum: 1 }),
|
|
2130
|
+
warmRetentionSec: Type.Integer({
|
|
2131
|
+
minimum: 0,
|
|
2132
|
+
maximum: MAX_RUNTIME_WARM_RETENTION_SEC
|
|
2133
|
+
})
|
|
2105
2134
|
}, {
|
|
2106
2135
|
$id: "BeginRuntimeSlotBody",
|
|
2107
2136
|
additionalProperties: false
|
|
@@ -2123,7 +2152,11 @@ Type.Object({
|
|
|
2123
2152
|
slotKey: Type.String({ minLength: 1 }),
|
|
2124
2153
|
taskId: Type.String({ format: "uuid" }),
|
|
2125
2154
|
attemptN: Type.Integer({ minimum: 1 }),
|
|
2126
|
-
sessionPath: Type.Optional(Type.String({ minLength: 1 }))
|
|
2155
|
+
sessionPath: Type.Optional(Type.String({ minLength: 1 })),
|
|
2156
|
+
warmRetentionSec: Type.Integer({
|
|
2157
|
+
minimum: 0,
|
|
2158
|
+
maximum: MAX_RUNTIME_WARM_RETENTION_SEC
|
|
2159
|
+
})
|
|
2127
2160
|
}, {
|
|
2128
2161
|
$id: "FinishRuntimeSlotBody",
|
|
2129
2162
|
additionalProperties: false
|
|
@@ -2719,7 +2752,7 @@ var RenderPackInput = Type.Object({
|
|
|
2719
2752
|
var RenderPackOutput = Type.Object({
|
|
2720
2753
|
renderedPackId: Type.Union([Type.String({ format: "uuid" }), Type.Null()]),
|
|
2721
2754
|
renderedCid: Type.String({ minLength: 1 }),
|
|
2722
|
-
renderMethod:
|
|
2755
|
+
renderMethod: RenderMethodSchema,
|
|
2723
2756
|
byteSize: Type.Number({ minimum: 0 }),
|
|
2724
2757
|
entriesRendered: Type.Number({ minimum: 0 }),
|
|
2725
2758
|
summary: Type.String({ minLength: 1 }),
|
|
@@ -3384,8 +3417,7 @@ Type.Object({
|
|
|
3384
3417
|
outputCid: Type.Union([Cid, Type.Null()]),
|
|
3385
3418
|
usage: TaskUsage,
|
|
3386
3419
|
durationMs: Type.Number({ minimum: 0 }),
|
|
3387
|
-
error: Type.Optional(TaskError)
|
|
3388
|
-
contentSignature: Type.Optional(Type.String())
|
|
3420
|
+
error: Type.Optional(TaskError)
|
|
3389
3421
|
}, {
|
|
3390
3422
|
$id: "TaskOutput",
|
|
3391
3423
|
additionalProperties: false
|
|
@@ -3413,7 +3445,7 @@ var COMMON_REQUIRED_FLAGS = `\
|
|
|
3413
3445
|
--profile <uuid|name> Remote runtime profile. Repeat for poll/drain
|
|
3414
3446
|
to declare priority order. Provider, model,
|
|
3415
3447
|
sandbox policy, prerequisites, and runtime
|
|
3416
|
-
|
|
3448
|
+
execution policy come from the selected profile.`;
|
|
3417
3449
|
var COMMON_OPTIONAL_FLAGS = `\
|
|
3418
3450
|
--sandbox <path> Deprecated. Remote runtime profiles define
|
|
3419
3451
|
sandbox policy.
|
|
@@ -3425,24 +3457,10 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
3425
3457
|
host git config. Configless agent-key runs must
|
|
3426
3458
|
provide this flag or MOLTNET_GIT_AUTHOR.
|
|
3427
3459
|
Env: MOLTNET_GIT_AUTHOR.
|
|
3428
|
-
--lease-ttl-sec <n> Sliding liveness window. Silence longer than
|
|
3429
|
-
this ends the attempt with lease_expired.
|
|
3430
|
-
Default: 300.
|
|
3431
3460
|
--heartbeat-interval-ms <n> Reporter heartbeat cadence. Default: 60000.
|
|
3432
|
-
--
|
|
3433
|
-
--flush-interval-ms <n> Reporter flush window. Default: 200.
|
|
3434
|
-
--max-turns <n> Cap on tool-use turns per attempt; over this,
|
|
3435
|
-
the pi session aborts and the attempt fails
|
|
3436
|
-
with code 'max_turns_exceeded'. 0 = disabled.
|
|
3437
|
-
Default: 0. Recommended ~30 for fulfill_brief.
|
|
3438
|
-
--max-bash-timeouts <n> Cap on bash-tool timeouts per attempt; over
|
|
3439
|
-
this, the pi session aborts and the attempt
|
|
3440
|
-
fails with code 'max_bash_timeouts_exceeded'.
|
|
3441
|
-
0 = disabled. Default: 3.
|
|
3442
|
-
--warm-session-ttl-sec <n> Resumability window for runtime slots
|
|
3461
|
+
--warm-retention-sec <n> Resumability window for runtime slots
|
|
3443
3462
|
(Pi sessions + reusable worktrees) after use.
|
|
3444
|
-
Default: 1800
|
|
3445
|
-
TTL) when --profile is set.
|
|
3463
|
+
Default: 1800.
|
|
3446
3464
|
--debug Verbose logging: also log successful list/claim
|
|
3447
3465
|
outcomes (candidate counts, claim attempts).`;
|
|
3448
3466
|
var REGISTERED_TASK_TYPES = Object.keys(BUILT_IN_TASK_TYPES).sort();
|
|
@@ -3633,6 +3651,8 @@ Options:
|
|
|
3633
3651
|
(or MOLTNET_AGENT_SERVER_ROOT).
|
|
3634
3652
|
--api-url <url> Default MoltNet API for new managed agents.
|
|
3635
3653
|
Default: https://api.themolt.net.
|
|
3654
|
+
--heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
|
|
3655
|
+
--warm-retention-sec <n> Child session/workspace retention. Default: 1800.
|
|
3636
3656
|
|
|
3637
3657
|
On macOS, the first interactive run asks to trust a per-user local CA in the
|
|
3638
3658
|
login keychain and serves HTTPS. Run \`agent-daemon server trust --remove\` to
|
|
@@ -3893,6 +3913,7 @@ function loadConfig() {
|
|
|
3893
3913
|
profilePrerequisiteEnv: process.env,
|
|
3894
3914
|
profilePrerequisitePath: process.env.PATH ?? "",
|
|
3895
3915
|
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
|
|
3916
|
+
agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
|
|
3896
3917
|
credentialSource: detectCredentialSource(process.env),
|
|
3897
3918
|
apiUrl: process.env["MOLTNET_API_URL"] ?? "",
|
|
3898
3919
|
signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
|
|
@@ -3936,8 +3957,9 @@ function readBoolean(name, value) {
|
|
|
3936
3957
|
if (value === "false") return false;
|
|
3937
3958
|
throw new Error(`${name} must be either true or false`);
|
|
3938
3959
|
}
|
|
3939
|
-
function activatePiCodingAgentDir(path) {
|
|
3960
|
+
function activatePiCodingAgentDir(path, env = {}) {
|
|
3940
3961
|
process.env["PI_CODING_AGENT_DIR"] = path;
|
|
3962
|
+
Object.assign(process.env, env);
|
|
3941
3963
|
}
|
|
3942
3964
|
function loadAgentServerEnvConfig() {
|
|
3943
3965
|
return {
|
|
@@ -4139,10 +4161,10 @@ function slugifySessionComponent(input) {
|
|
|
4139
4161
|
}
|
|
4140
4162
|
//#endregion
|
|
4141
4163
|
//#region src/lib/task-execution-plan.ts
|
|
4142
|
-
function buildDaemonTaskExecutionPlan(task, stateDirs, identity,
|
|
4164
|
+
function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmRetentionSec, runtimeProfileWorkspacePolicy = {}, attemptN) {
|
|
4143
4165
|
const descriptor = deriveTaskSessionDescriptor(task);
|
|
4144
4166
|
const workspaceMode = resolveTaskWorkspaceMode(task, descriptor.policy, runtimeProfileWorkspacePolicy);
|
|
4145
|
-
const slotKey =
|
|
4167
|
+
const slotKey = warmRetentionSec > 0 && descriptor.sessionKey ? buildRuntimeSlotKey(descriptor.sessionKey, identity.runtimeInstanceId) : null;
|
|
4146
4168
|
const workspaceScope = slotKey !== null ? descriptor.policy.workspaceScope : "attempt";
|
|
4147
4169
|
const slotId = slotKey ? buildDaemonSlotId(identity, slotKey) : null;
|
|
4148
4170
|
const sessionDir = slotId ? `${stateDirs.piSessionsDir}/${boundedKeyDirComponent(slotId)}` : null;
|
|
@@ -4281,7 +4303,7 @@ function createExecutionPlanCache(args) {
|
|
|
4281
4303
|
const key = buildClaimedTaskKey(claimedTask);
|
|
4282
4304
|
const existing = cache.get(key);
|
|
4283
4305
|
if (existing) return existing;
|
|
4284
|
-
const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.
|
|
4306
|
+
const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmRetentionSec, args.workspacePolicy, claimedTask.attemptN), args.stateDirs, args.slotRegistry, runtimeSessionStore, sourceAttemptResolver);
|
|
4285
4307
|
assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy, args.slotIdentity.runtimeProfileId);
|
|
4286
4308
|
cache.set(key, plan);
|
|
4287
4309
|
return plan;
|
|
@@ -4803,7 +4825,6 @@ async function finalizeTask(agent, output, ctx = {}) {
|
|
|
4803
4825
|
output: output.output,
|
|
4804
4826
|
outputCid: output.outputCid,
|
|
4805
4827
|
usage: output.usage,
|
|
4806
|
-
...output.contentSignature ? { contentSignature: output.contentSignature } : {},
|
|
4807
4828
|
...daemonState ? { daemonState } : {},
|
|
4808
4829
|
...attestation
|
|
4809
4830
|
});
|
|
@@ -4973,7 +4994,8 @@ function ownBinding(bindings, name) {
|
|
|
4973
4994
|
}
|
|
4974
4995
|
//#endregion
|
|
4975
4996
|
//#region ../../libs/execution-integrations/src/runtime-profile.ts
|
|
4976
|
-
var CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION = "effective-policy:
|
|
4997
|
+
var CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION = "effective-policy:v2";
|
|
4998
|
+
var DEFAULT_EXECUTION_LEASE_TTL_SEC = 300;
|
|
4977
4999
|
/**
|
|
4978
5000
|
* Maps resolved product authority into portable intent. Policy composition is
|
|
4979
5001
|
* deliberately upstream: this boundary accepts no policy IDs and performs no
|
|
@@ -4997,7 +5019,7 @@ function executionIntentFromRuntimeProfile(input) {
|
|
|
4997
5019
|
credentialRequirements: structuredClone(input.credentialRequirements),
|
|
4998
5020
|
requiredCapabilities: [...input.requiredCapabilities ?? []],
|
|
4999
5021
|
lease: {
|
|
5000
|
-
ttlSec:
|
|
5022
|
+
ttlSec: DEFAULT_EXECUTION_LEASE_TTL_SEC,
|
|
5001
5023
|
requiredControls: [...input.requiredLeaseControls ?? []]
|
|
5002
5024
|
},
|
|
5003
5025
|
network: {
|
|
@@ -5247,14 +5269,9 @@ async function logDaemonStartupFailure(input) {
|
|
|
5247
5269
|
}
|
|
5248
5270
|
//#endregion
|
|
5249
5271
|
//#region src/lib/options.ts
|
|
5250
|
-
var
|
|
5251
|
-
leaseTtlSec: 300,
|
|
5272
|
+
var DEFAULT_LOCAL_OPERATIONAL_SETTINGS = {
|
|
5252
5273
|
heartbeatIntervalMs: 6e4,
|
|
5253
|
-
|
|
5254
|
-
flushIntervalMs: 200,
|
|
5255
|
-
maxTurns: 0,
|
|
5256
|
-
maxBashTimeouts: 3,
|
|
5257
|
-
warmSessionTtlSec: 1800
|
|
5274
|
+
warmRetentionSec: 1800
|
|
5258
5275
|
};
|
|
5259
5276
|
var MissingRequiredOptionError = class extends Error {
|
|
5260
5277
|
constructor(flag) {
|
|
@@ -5263,42 +5280,33 @@ var MissingRequiredOptionError = class extends Error {
|
|
|
5263
5280
|
this.name = "MissingRequiredOptionError";
|
|
5264
5281
|
}
|
|
5265
5282
|
};
|
|
5266
|
-
function
|
|
5267
|
-
const runtimeDefaults = {
|
|
5268
|
-
leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
|
|
5269
|
-
heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
|
|
5270
|
-
maxBatchSize: options.runtimeDefaults?.maxBatchSize ?? DEFAULTS.maxBatchSize,
|
|
5271
|
-
maxTurns: options.runtimeDefaults?.maxTurns ?? DEFAULTS.maxTurns,
|
|
5272
|
-
maxBashTimeouts: options.runtimeDefaults?.maxBashTimeouts ?? DEFAULTS.maxBashTimeouts,
|
|
5273
|
-
warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
|
|
5274
|
-
};
|
|
5283
|
+
function parseIdentityProcessOptions(args) {
|
|
5275
5284
|
if (!args.agent) throw new MissingRequiredOptionError("agent");
|
|
5276
5285
|
if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
5277
5286
|
return {
|
|
5278
5287
|
agent: args.agent,
|
|
5279
|
-
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
|
|
5280
|
-
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
|
|
5281
|
-
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
|
|
5282
|
-
flushIntervalMs: parseNonNegativeInt(args["flush-interval-ms"], "flush-interval-ms", DEFAULTS.flushIntervalMs),
|
|
5283
|
-
maxTurns: parseNonNegativeInt(args["max-turns"], "max-turns", runtimeDefaults.maxTurns),
|
|
5284
|
-
maxBashTimeouts: parseNonNegativeInt(args["max-bash-timeouts"], "max-bash-timeouts", runtimeDefaults.maxBashTimeouts),
|
|
5285
|
-
warmSessionTtlSec: parseNonNegativeInt(args["warm-session-ttl-sec"], "warm-session-ttl-sec", runtimeDefaults.warmSessionTtlSec),
|
|
5286
5288
|
debug: args.debug === true
|
|
5287
5289
|
};
|
|
5288
5290
|
}
|
|
5289
|
-
function
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5291
|
+
function parseLocalOperationalSettings(args) {
|
|
5292
|
+
return {
|
|
5293
|
+
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", DEFAULT_LOCAL_OPERATIONAL_SETTINGS.heartbeatIntervalMs),
|
|
5294
|
+
warmRetentionSec: parseNonNegativeInt(args["warm-retention-sec"], "warm-retention-sec", DEFAULT_LOCAL_OPERATIONAL_SETTINGS.warmRetentionSec, MAX_RUNTIME_WARM_RETENTION_SEC)
|
|
5295
|
+
};
|
|
5296
|
+
}
|
|
5297
|
+
function parseRuntimeCommandOptions(args) {
|
|
5298
|
+
return {
|
|
5299
|
+
identity: parseIdentityProcessOptions(args),
|
|
5300
|
+
operations: parseLocalOperationalSettings(args)
|
|
5301
|
+
};
|
|
5294
5302
|
}
|
|
5295
|
-
function parseNonNegativeInt(raw, name, defaultValue) {
|
|
5303
|
+
function parseNonNegativeInt(raw, name, defaultValue, maximum = Number.MAX_SAFE_INTEGER) {
|
|
5296
5304
|
if (raw === void 0) return defaultValue;
|
|
5297
|
-
const
|
|
5298
|
-
if (!Number.isInteger(
|
|
5299
|
-
return
|
|
5305
|
+
const value = Number(raw);
|
|
5306
|
+
if (!Number.isInteger(value) || value < 0 || value > maximum) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer no greater than ${maximum}`);
|
|
5307
|
+
return value;
|
|
5300
5308
|
}
|
|
5301
|
-
function
|
|
5309
|
+
function identityOptionDefs() {
|
|
5302
5310
|
return {
|
|
5303
5311
|
agent: {
|
|
5304
5312
|
type: "string",
|
|
@@ -5306,18 +5314,18 @@ function commonOptionDefs() {
|
|
|
5306
5314
|
},
|
|
5307
5315
|
"agent-root": { type: "string" },
|
|
5308
5316
|
"git-author": { type: "string" },
|
|
5309
|
-
"lease-ttl-sec": { type: "string" },
|
|
5310
|
-
"heartbeat-interval-ms": { type: "string" },
|
|
5311
|
-
"max-batch-size": { type: "string" },
|
|
5312
|
-
"flush-interval-ms": { type: "string" },
|
|
5313
|
-
"max-turns": { type: "string" },
|
|
5314
|
-
"max-bash-timeouts": { type: "string" },
|
|
5315
|
-
"warm-session-ttl-sec": { type: "string" },
|
|
5316
5317
|
debug: { type: "boolean" }
|
|
5317
5318
|
};
|
|
5318
5319
|
}
|
|
5320
|
+
function runtimeCommandOptionDefs() {
|
|
5321
|
+
return {
|
|
5322
|
+
...identityOptionDefs(),
|
|
5323
|
+
"heartbeat-interval-ms": { type: "string" },
|
|
5324
|
+
"warm-retention-sec": { type: "string" }
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5319
5327
|
function validateTaskTypes(types) {
|
|
5320
|
-
const unknown = types.filter((
|
|
5328
|
+
const unknown = types.filter((taskType) => !Object.prototype.hasOwnProperty.call(BUILT_IN_TASK_TYPES, taskType));
|
|
5321
5329
|
if (unknown.length > 0) throw new Error(`Unknown task type(s): ${unknown.join(", ")}. Known types: ${knownTaskTypesList()}.`);
|
|
5322
5330
|
return [...types];
|
|
5323
5331
|
}
|
|
@@ -5381,569 +5389,510 @@ function initWorkerOtel(options) {
|
|
|
5381
5389
|
})]);
|
|
5382
5390
|
});
|
|
5383
5391
|
}
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
function
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
source: "env"
|
|
5392
|
-
};
|
|
5392
|
+
var NAME_RE = IDENTITY_ALIAS_PATTERN;
|
|
5393
|
+
var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
5394
|
+
function assertStoreName(kind, value) {
|
|
5395
|
+
try {
|
|
5396
|
+
return assertIdentityAlias(value);
|
|
5397
|
+
} catch {
|
|
5398
|
+
throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
|
|
5393
5399
|
}
|
|
5394
|
-
const path = join(repoRoot, ".pi");
|
|
5395
|
-
mkdirSync(path, { recursive: true });
|
|
5396
|
-
return {
|
|
5397
|
-
path,
|
|
5398
|
-
source: "repo"
|
|
5399
|
-
};
|
|
5400
5400
|
}
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
function runWithDaemonRuntimeContext(context, callback) {
|
|
5405
|
-
return storage.run(context, callback);
|
|
5406
|
-
}
|
|
5407
|
-
//#endregion
|
|
5408
|
-
//#region src/lib/runtime-profile-retry-triage.ts
|
|
5409
|
-
function createRuntimeProfileRetryTriage(options) {
|
|
5410
|
-
return async (input) => {
|
|
5411
|
-
const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
|
|
5412
|
-
return createPiRetryTriage({
|
|
5413
|
-
model: modelHandle,
|
|
5414
|
-
modelRuntime,
|
|
5415
|
-
thinkingLevel: options.runtimeProfile.thinkingLevel,
|
|
5416
|
-
piAgentDir: options.piAgentDir,
|
|
5417
|
-
timeoutMs: options.timeoutMs,
|
|
5418
|
-
cwd: options.cwd
|
|
5419
|
-
})(input);
|
|
5420
|
-
};
|
|
5401
|
+
function assertProviderId(value) {
|
|
5402
|
+
if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
|
|
5403
|
+
return value;
|
|
5421
5404
|
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
"cancelled",
|
|
5430
|
-
"aborted",
|
|
5431
|
-
"timed_out"
|
|
5432
|
-
]);
|
|
5405
|
+
var AgentServerStoreError = class extends Error {
|
|
5406
|
+
name = "AgentServerStoreError";
|
|
5407
|
+
constructor(code, message, options) {
|
|
5408
|
+
super(message, options);
|
|
5409
|
+
this.code = code;
|
|
5410
|
+
}
|
|
5411
|
+
};
|
|
5433
5412
|
/**
|
|
5434
|
-
*
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5437
|
-
*
|
|
5413
|
+
* `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
|
|
5414
|
+
*
|
|
5415
|
+
* Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
|
|
5416
|
+
* and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
|
|
5417
|
+
* so honouring XDG here gave one application two config roots: on a machine
|
|
5418
|
+
* with the variable set, the daemon wrote identities the CLI and SDK could not
|
|
5419
|
+
* read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
|
|
5420
|
+
* genuinely custom location.
|
|
5438
5421
|
*/
|
|
5439
|
-
|
|
5440
|
-
const
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
const idle = idlePage.items;
|
|
5444
|
-
const slots = [...active, ...idle];
|
|
5445
|
-
const result = {
|
|
5446
|
-
failed: 0,
|
|
5447
|
-
failures: [],
|
|
5448
|
-
removedSessions: 0,
|
|
5449
|
-
removedWorkspaces: 0,
|
|
5450
|
-
scanned: slots.length,
|
|
5451
|
-
truncated: activePage.truncated || idlePage.truncated,
|
|
5452
|
-
unsafePathDetails: [],
|
|
5453
|
-
unsafePaths: 0
|
|
5454
|
-
};
|
|
5455
|
-
const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
|
|
5456
|
-
for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
|
|
5457
|
-
const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
|
|
5458
|
-
const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
|
|
5459
|
-
const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
|
|
5460
|
-
kind: "ready",
|
|
5461
|
-
paths: /* @__PURE__ */ new Set()
|
|
5462
|
-
};
|
|
5463
|
-
for (const item of slots) {
|
|
5464
|
-
if (!reaped.has(item.slot.id)) continue;
|
|
5465
|
-
if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
|
|
5466
|
-
const sessionDir = item.session?.sessionDir;
|
|
5467
|
-
if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
|
|
5468
|
-
else try {
|
|
5469
|
-
rmSync(sessionDir, {
|
|
5470
|
-
force: true,
|
|
5471
|
-
recursive: true
|
|
5472
|
-
});
|
|
5473
|
-
result.removedSessions++;
|
|
5474
|
-
} catch (err) {
|
|
5475
|
-
recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
|
|
5476
|
-
}
|
|
5477
|
-
const workspacePath = item.workspace?.worktreePath;
|
|
5478
|
-
if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
|
|
5479
|
-
const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
|
|
5480
|
-
if (!workspaceRoot) {
|
|
5481
|
-
recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
|
|
5482
|
-
continue;
|
|
5483
|
-
}
|
|
5484
|
-
await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
|
|
5485
|
-
}
|
|
5486
|
-
if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
|
|
5487
|
-
return result;
|
|
5422
|
+
function resolveAgentServerRoot(input) {
|
|
5423
|
+
const override = input.root?.trim();
|
|
5424
|
+
if (override) return override;
|
|
5425
|
+
return getConfigDir();
|
|
5488
5426
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
agentName: input.agentName,
|
|
5492
|
-
limit: RUNTIME_SLOT_PAGE_LIMIT,
|
|
5493
|
-
runtimeProfileId: input.runtimeProfileId,
|
|
5494
|
-
state,
|
|
5495
|
-
teamId: input.teamId
|
|
5496
|
-
});
|
|
5497
|
-
return {
|
|
5498
|
-
items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
|
|
5499
|
-
truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
|
|
5500
|
-
};
|
|
5427
|
+
function providerEnvName(providerId) {
|
|
5428
|
+
return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
|
|
5501
5429
|
}
|
|
5502
|
-
|
|
5503
|
-
const
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
taskSlots.push(item);
|
|
5507
|
-
byTask.set(item.slot.lastTaskId, taskSlots);
|
|
5508
|
-
}
|
|
5509
|
-
const terminal = /* @__PURE__ */ new Set();
|
|
5510
|
-
await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
|
|
5511
|
-
let attempts;
|
|
5512
|
-
try {
|
|
5513
|
-
attempts = await deps.taskReader.listAttempts(taskId);
|
|
5514
|
-
} catch (err) {
|
|
5515
|
-
recordFailure(deps, result, null, `task:${taskId}`, null, err);
|
|
5516
|
-
return;
|
|
5517
|
-
}
|
|
5518
|
-
for (const item of taskSlots) {
|
|
5519
|
-
const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
|
|
5520
|
-
if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
|
|
5521
|
-
}
|
|
5522
|
-
});
|
|
5523
|
-
return terminal;
|
|
5430
|
+
function assertProviderEnvName(providerId, value) {
|
|
5431
|
+
const expected = providerEnvName(providerId);
|
|
5432
|
+
if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
|
|
5433
|
+
return value;
|
|
5524
5434
|
}
|
|
5525
|
-
|
|
5526
|
-
let
|
|
5435
|
+
function readJson(path) {
|
|
5436
|
+
let raw;
|
|
5527
5437
|
try {
|
|
5528
|
-
|
|
5529
|
-
} catch (
|
|
5530
|
-
|
|
5531
|
-
|
|
5438
|
+
raw = readFileSync(path, "utf8");
|
|
5439
|
+
} catch (cause) {
|
|
5440
|
+
if (cause.code === "ENOENT") return null;
|
|
5441
|
+
throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
|
|
5532
5442
|
}
|
|
5533
|
-
const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
|
|
5534
|
-
if (!current) return false;
|
|
5535
|
-
if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
|
|
5536
|
-
return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
|
|
5537
|
-
}
|
|
5538
|
-
async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
|
|
5539
|
-
if (!input.mainWorktree) return;
|
|
5540
|
-
const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
|
|
5541
|
-
if (!existsSync(workspaceRoot)) return;
|
|
5542
|
-
let entries;
|
|
5543
5443
|
try {
|
|
5544
|
-
|
|
5545
|
-
} catch (
|
|
5546
|
-
|
|
5547
|
-
return;
|
|
5548
|
-
}
|
|
5549
|
-
for (const entry of entries) {
|
|
5550
|
-
if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
|
|
5551
|
-
const workspacePath = resolve(workspaceRoot, entry.name);
|
|
5552
|
-
if (retainedWorkspacePaths.has(workspacePath)) continue;
|
|
5553
|
-
const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
|
|
5554
|
-
if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
|
|
5555
|
-
await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
|
|
5444
|
+
return JSON.parse(raw);
|
|
5445
|
+
} catch (cause) {
|
|
5446
|
+
throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
|
|
5556
5447
|
}
|
|
5557
5448
|
}
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
}
|
|
5564
|
-
if (scratch || !mainWorktree) {
|
|
5449
|
+
function writeJsonAtomic(path, value) {
|
|
5450
|
+
const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
5451
|
+
try {
|
|
5452
|
+
writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
5453
|
+
renameSync(temp, path);
|
|
5454
|
+
} catch (cause) {
|
|
5565
5455
|
try {
|
|
5566
|
-
rmSync(
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
});
|
|
5570
|
-
result.removedWorkspaces++;
|
|
5571
|
-
} catch (err) {
|
|
5572
|
-
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
5573
|
-
}
|
|
5574
|
-
return;
|
|
5456
|
+
rmSync(temp, { force: true });
|
|
5457
|
+
} catch {}
|
|
5458
|
+
throw cause;
|
|
5575
5459
|
}
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5460
|
+
}
|
|
5461
|
+
var AgentServerStore = class {
|
|
5462
|
+
root;
|
|
5463
|
+
identitiesDir;
|
|
5464
|
+
runsDir;
|
|
5465
|
+
secretsDir;
|
|
5466
|
+
/** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
|
|
5467
|
+
piDir;
|
|
5468
|
+
constructor(root) {
|
|
5469
|
+
this.root = root;
|
|
5470
|
+
this.identitiesDir = join(root, "identities");
|
|
5471
|
+
this.runsDir = join(root, "runs");
|
|
5472
|
+
this.secretsDir = join(root, "secrets");
|
|
5473
|
+
this.piDir = join(root, "pi");
|
|
5579
5474
|
}
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
force: true,
|
|
5594
|
-
recursive: true
|
|
5475
|
+
get piAuthJsonPath() {
|
|
5476
|
+
return join(this.piDir, "auth.json");
|
|
5477
|
+
}
|
|
5478
|
+
/** Create the directory layout (0700) if missing. Idempotent. */
|
|
5479
|
+
ensure() {
|
|
5480
|
+
for (const dir of [
|
|
5481
|
+
this.root,
|
|
5482
|
+
this.identitiesDir,
|
|
5483
|
+
this.runsDir,
|
|
5484
|
+
this.secretsDir
|
|
5485
|
+
]) mkdirSync(dir, {
|
|
5486
|
+
recursive: true,
|
|
5487
|
+
mode: 448
|
|
5595
5488
|
});
|
|
5596
|
-
|
|
5597
|
-
} catch (err) {
|
|
5598
|
-
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
5489
|
+
return this;
|
|
5599
5490
|
}
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
]);
|
|
5610
|
-
return {
|
|
5611
|
-
kind: "ready",
|
|
5612
|
-
paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
|
|
5491
|
+
get statePath() {
|
|
5492
|
+
return join(this.root, "agent-server.json");
|
|
5493
|
+
}
|
|
5494
|
+
readAgentServerState() {
|
|
5495
|
+
const state = readJson(this.statePath);
|
|
5496
|
+
if (!state) return {
|
|
5497
|
+
version: 2,
|
|
5498
|
+
pendingRegistrations: {},
|
|
5499
|
+
activations: {}
|
|
5613
5500
|
};
|
|
5614
|
-
|
|
5501
|
+
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`);
|
|
5502
|
+
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");
|
|
5503
|
+
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");
|
|
5504
|
+
for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
|
|
5505
|
+
for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
|
|
5506
|
+
assertStoreName("agent name", alias);
|
|
5507
|
+
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`);
|
|
5508
|
+
}
|
|
5615
5509
|
return {
|
|
5616
|
-
|
|
5617
|
-
|
|
5510
|
+
version: 2,
|
|
5511
|
+
pendingRegistrations: state.pendingRegistrations,
|
|
5512
|
+
activations: state.activations
|
|
5618
5513
|
};
|
|
5619
5514
|
}
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
try {
|
|
5623
|
-
return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
|
|
5624
|
-
} catch {
|
|
5625
|
-
return false;
|
|
5515
|
+
writeAgentServerState(state) {
|
|
5516
|
+
writeJsonAtomic(this.statePath, state);
|
|
5626
5517
|
}
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
try {
|
|
5630
|
-
return realpathSync(path);
|
|
5631
|
-
} catch {
|
|
5632
|
-
return resolve(path);
|
|
5518
|
+
agentPath(name) {
|
|
5519
|
+
return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
|
|
5633
5520
|
}
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
root,
|
|
5653
|
-
slotId
|
|
5654
|
-
};
|
|
5655
|
-
result.failed++;
|
|
5656
|
-
result.failures.push(issue);
|
|
5657
|
-
emitIssue(deps, issue);
|
|
5658
|
-
}
|
|
5659
|
-
function emitIssue(deps, issue) {
|
|
5660
|
-
try {
|
|
5661
|
-
deps.onIssue?.(issue);
|
|
5662
|
-
} catch {}
|
|
5663
|
-
}
|
|
5664
|
-
function errorMessage(err) {
|
|
5665
|
-
return err instanceof Error ? err.message : String(err);
|
|
5666
|
-
}
|
|
5667
|
-
async function execFileText(file, args) {
|
|
5668
|
-
return new Promise((resolvePromise, reject) => {
|
|
5669
|
-
execFile(file, args, {
|
|
5670
|
-
encoding: "utf8",
|
|
5671
|
-
maxBuffer: 10 * 1024 * 1024
|
|
5672
|
-
}, (err, stdout) => {
|
|
5673
|
-
if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
|
|
5674
|
-
else resolvePromise(stdout);
|
|
5521
|
+
/** Central identity directory, shared with the Go CLI layout. */
|
|
5522
|
+
identityDir(name) {
|
|
5523
|
+
return storeChildPath(this.identitiesDir, "identity alias", name);
|
|
5524
|
+
}
|
|
5525
|
+
get identitySelectorPath() {
|
|
5526
|
+
return join(this.root, "identity-selector.json");
|
|
5527
|
+
}
|
|
5528
|
+
readIdentitySelector() {
|
|
5529
|
+
const selector = readJson(this.identitySelectorPath);
|
|
5530
|
+
if (!selector) return null;
|
|
5531
|
+
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");
|
|
5532
|
+
if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
|
|
5533
|
+
return selector;
|
|
5534
|
+
}
|
|
5535
|
+
writeIdentitySelector(alias) {
|
|
5536
|
+
writeJsonAtomic(this.identitySelectorPath, {
|
|
5537
|
+
version: 1,
|
|
5538
|
+
default_identity: assertStoreName("identity alias", alias)
|
|
5675
5539
|
});
|
|
5676
|
-
}
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
}
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
});
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5540
|
+
}
|
|
5541
|
+
/** Identity aliases available in the shared user-level store. */
|
|
5542
|
+
listIdentityAliases() {
|
|
5543
|
+
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));
|
|
5544
|
+
}
|
|
5545
|
+
resolveIdentityAlias(explicit, active) {
|
|
5546
|
+
const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
|
|
5547
|
+
if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
|
|
5548
|
+
return assertStoreName("identity alias", alias);
|
|
5549
|
+
}
|
|
5550
|
+
readAgentConfig(alias) {
|
|
5551
|
+
return readJson(this.agentPath(alias));
|
|
5552
|
+
}
|
|
5553
|
+
writeAgentConfig(alias, config) {
|
|
5554
|
+
mkdirSync(this.identityDir(alias), {
|
|
5555
|
+
recursive: true,
|
|
5556
|
+
mode: 448
|
|
5557
|
+
});
|
|
5558
|
+
writeJsonAtomic(this.agentPath(alias), config);
|
|
5559
|
+
if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
|
|
5560
|
+
}
|
|
5561
|
+
removeAgentConfig(alias) {
|
|
5562
|
+
rmSync(this.agentPath(alias), { force: true });
|
|
5563
|
+
this.clearIdentitySelectorIfDefault(alias);
|
|
5564
|
+
}
|
|
5565
|
+
/** Clears the persisted default when it names `alias`. */
|
|
5566
|
+
clearIdentitySelectorIfDefault(alias) {
|
|
5567
|
+
let selector = null;
|
|
5568
|
+
try {
|
|
5569
|
+
selector = this.readIdentitySelector();
|
|
5570
|
+
} catch {
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
if (!selector || selector.default_identity !== alias) return;
|
|
5574
|
+
writeJsonAtomic(this.identitySelectorPath, { version: 1 });
|
|
5575
|
+
}
|
|
5576
|
+
readActivation(alias) {
|
|
5577
|
+
return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
|
|
5578
|
+
}
|
|
5579
|
+
hasPendingRegistration(alias) {
|
|
5580
|
+
return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
|
|
5581
|
+
}
|
|
5582
|
+
reserveRegistration(alias, apiUrl) {
|
|
5583
|
+
const name = assertStoreName("agent name", alias);
|
|
5584
|
+
const state = this.readAgentServerState();
|
|
5585
|
+
if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
|
|
5586
|
+
state.pendingRegistrations[name] = {
|
|
5587
|
+
apiUrl,
|
|
5588
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5589
|
+
};
|
|
5590
|
+
this.writeAgentServerState(state);
|
|
5591
|
+
}
|
|
5592
|
+
clearPendingRegistration(alias) {
|
|
5593
|
+
const name = assertStoreName("agent name", alias);
|
|
5594
|
+
const state = this.readAgentServerState();
|
|
5595
|
+
delete state.pendingRegistrations[name];
|
|
5596
|
+
this.writeAgentServerState(state);
|
|
5597
|
+
}
|
|
5598
|
+
writeActivation(activation) {
|
|
5599
|
+
const alias = assertStoreName("agent name", activation.alias);
|
|
5600
|
+
validateActivation(alias, activation);
|
|
5601
|
+
const state = this.readAgentServerState();
|
|
5602
|
+
state.activations[alias] = activation;
|
|
5603
|
+
if (activation.source === "managed") delete state.pendingRegistrations[alias];
|
|
5604
|
+
this.writeAgentServerState(state);
|
|
5605
|
+
}
|
|
5606
|
+
listActivations() {
|
|
5607
|
+
return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
|
|
5608
|
+
}
|
|
5609
|
+
get providersPath() {
|
|
5610
|
+
return join(this.root, "providers.json");
|
|
5611
|
+
}
|
|
5612
|
+
readProviders() {
|
|
5613
|
+
const state = readJson(this.providersPath) ?? {};
|
|
5614
|
+
this.validateProviders(state);
|
|
5615
|
+
return state;
|
|
5616
|
+
}
|
|
5617
|
+
writeProviders(state) {
|
|
5618
|
+
this.validateProviders(state);
|
|
5619
|
+
writeJsonAtomic(this.providersPath, state);
|
|
5620
|
+
}
|
|
5621
|
+
validateProviders(state) {
|
|
5622
|
+
for (const [id, provider] of Object.entries(state)) {
|
|
5623
|
+
assertProviderId(id);
|
|
5624
|
+
assertProviderEnvName(id, provider.envName);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
runDir(id) {
|
|
5628
|
+
return storeChildPath(this.runsDir, "run id", id);
|
|
5629
|
+
}
|
|
5630
|
+
resolveRunLogPath(id) {
|
|
5631
|
+
let root;
|
|
5632
|
+
let runDir;
|
|
5633
|
+
try {
|
|
5634
|
+
root = realpathSync(this.runsDir);
|
|
5635
|
+
runDir = realpathSync(this.runDir(id));
|
|
5636
|
+
} catch (cause) {
|
|
5637
|
+
throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
|
|
5638
|
+
}
|
|
5639
|
+
if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
|
|
5640
|
+
const logPath = join(runDir, "daemon.log");
|
|
5641
|
+
try {
|
|
5642
|
+
if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
|
|
5643
|
+
const resolvedLog = realpathSync(logPath);
|
|
5644
|
+
if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
|
|
5645
|
+
} catch (cause) {
|
|
5646
|
+
if (cause instanceof AgentServerStoreError) throw cause;
|
|
5647
|
+
if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
|
|
5648
|
+
}
|
|
5649
|
+
return logPath;
|
|
5650
|
+
}
|
|
5651
|
+
createRunDir(id) {
|
|
5652
|
+
const dir = this.runDir(id);
|
|
5653
|
+
const piDir = join(dir, "pi");
|
|
5654
|
+
mkdirSync(piDir, {
|
|
5655
|
+
recursive: true,
|
|
5656
|
+
mode: 448
|
|
5657
|
+
});
|
|
5658
|
+
return {
|
|
5659
|
+
dir,
|
|
5660
|
+
piDir,
|
|
5661
|
+
logPath: join(dir, "daemon.log")
|
|
5662
|
+
};
|
|
5663
|
+
}
|
|
5664
|
+
readRun(id) {
|
|
5665
|
+
return readJson(join(this.runDir(id), "run.json"));
|
|
5666
|
+
}
|
|
5667
|
+
writeRun(record) {
|
|
5668
|
+
writeJsonAtomic(join(this.runDir(record.id), "run.json"), record);
|
|
5669
|
+
}
|
|
5670
|
+
listRuns(limit = Number.POSITIVE_INFINITY) {
|
|
5671
|
+
let ids;
|
|
5672
|
+
try {
|
|
5673
|
+
ids = readdirSync(this.runsDir);
|
|
5674
|
+
} catch {
|
|
5675
|
+
return [];
|
|
5676
|
+
}
|
|
5677
|
+
const sortedIds = ids.filter((id) => NAME_RE.test(id)).sort().reverse();
|
|
5678
|
+
const selectedIds = Number.isFinite(limit) ? sortedIds.slice(0, Math.max(0, limit)) : sortedIds;
|
|
5679
|
+
const records = [];
|
|
5680
|
+
for (const id of selectedIds) {
|
|
5681
|
+
const record = this.readRun(id);
|
|
5682
|
+
if (record) records.push(record);
|
|
5683
|
+
}
|
|
5684
|
+
return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
5685
|
+
}
|
|
5686
|
+
/** Remove only completed run directories outside the configured budget. */
|
|
5687
|
+
pruneCompletedRuns(options) {
|
|
5688
|
+
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5689
|
+
let retainedBytes = 0;
|
|
5690
|
+
let retainedCount = 0;
|
|
5691
|
+
const removed = [];
|
|
5692
|
+
for (const record of this.listRuns()) {
|
|
5693
|
+
if (record.status === "running") continue;
|
|
5694
|
+
const dir = this.runDir(record.id);
|
|
5695
|
+
const bytes = directoryBytes(dir);
|
|
5696
|
+
const endedAt = Date.parse(record.endedAt ?? record.startedAt);
|
|
5697
|
+
const expired = !Number.isFinite(endedAt) || now - endedAt > options.maxAgeMs;
|
|
5698
|
+
const overCount = retainedCount >= options.maxCount;
|
|
5699
|
+
const overBytes = retainedBytes + bytes > options.maxBytes;
|
|
5700
|
+
if (expired || overCount || overBytes) {
|
|
5701
|
+
rmSync(dir, {
|
|
5702
|
+
recursive: true,
|
|
5703
|
+
force: true
|
|
5704
|
+
});
|
|
5705
|
+
removed.push(record.id);
|
|
5706
|
+
continue;
|
|
5770
5707
|
}
|
|
5708
|
+
retainedCount += 1;
|
|
5709
|
+
retainedBytes += bytes;
|
|
5771
5710
|
}
|
|
5711
|
+
return removed;
|
|
5712
|
+
}
|
|
5713
|
+
};
|
|
5714
|
+
function directoryBytes(path) {
|
|
5715
|
+
let info;
|
|
5716
|
+
try {
|
|
5717
|
+
info = lstatSync(path);
|
|
5718
|
+
} catch {
|
|
5719
|
+
return 0;
|
|
5720
|
+
}
|
|
5721
|
+
if (info.isSymbolicLink()) return 0;
|
|
5722
|
+
if (!info.isDirectory()) return info.size;
|
|
5723
|
+
let total = 0;
|
|
5724
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
|
|
5725
|
+
return total;
|
|
5726
|
+
}
|
|
5727
|
+
function isRecord$1(value) {
|
|
5728
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5729
|
+
}
|
|
5730
|
+
function storeChildPath(root, kind, value, suffix = "") {
|
|
5731
|
+
const name = assertStoreName(kind, value);
|
|
5732
|
+
const normalizedRoot = resolve(root);
|
|
5733
|
+
const candidate = resolve(normalizedRoot, `${name}${suffix}`);
|
|
5734
|
+
if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
|
|
5735
|
+
return candidate;
|
|
5736
|
+
}
|
|
5737
|
+
function isStrictDescendant(root, candidate) {
|
|
5738
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
5739
|
+
return candidate !== root && candidate.startsWith(rootPrefix);
|
|
5740
|
+
}
|
|
5741
|
+
function validateActivation(alias, value) {
|
|
5742
|
+
const invalid = () => {
|
|
5743
|
+
throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
|
|
5772
5744
|
};
|
|
5745
|
+
if (!isRecord$1(value)) invalid();
|
|
5746
|
+
const activation = value;
|
|
5747
|
+
if (activation.alias !== alias) invalid();
|
|
5748
|
+
if (![
|
|
5749
|
+
"subjectId",
|
|
5750
|
+
"publicKey",
|
|
5751
|
+
"fingerprint",
|
|
5752
|
+
"createdAt"
|
|
5753
|
+
].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
|
|
5754
|
+
if (activation.source === "managed") {
|
|
5755
|
+
if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
|
|
5756
|
+
return;
|
|
5757
|
+
}
|
|
5758
|
+
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();
|
|
5773
5759
|
}
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5760
|
+
//#endregion
|
|
5761
|
+
//#region src/lib/agent-server/pi-store-config.ts
|
|
5762
|
+
/**
|
|
5763
|
+
* Materialise the Agent Server provider store as a Pi agent dir. Shared by
|
|
5764
|
+
* supervised runs (`runs.ts`) and direct `once`/`poll`/`drain` runs
|
|
5765
|
+
* (`pi-agent-dir.ts`) so both generate the same `models.json`.
|
|
5766
|
+
*/
|
|
5767
|
+
/**
|
|
5768
|
+
* Write `models.json` + `settings.json` for the stored providers. API keys
|
|
5769
|
+
* appear only as `$MOLTNET_PROVIDER_<ID>_API_KEY` placeholders.
|
|
5770
|
+
*/
|
|
5771
|
+
function writeStorePiConfig(piDir, providers) {
|
|
5772
|
+
writePiConfig({
|
|
5773
|
+
piDir,
|
|
5774
|
+
providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
|
|
5775
|
+
api: provider.api,
|
|
5776
|
+
...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
|
|
5777
|
+
baseUrl: provider.baseUrl,
|
|
5778
|
+
models: provider.models
|
|
5779
|
+
}]))
|
|
5780
|
+
});
|
|
5777
5781
|
}
|
|
5778
|
-
|
|
5779
|
-
|
|
5782
|
+
/**
|
|
5783
|
+
* Link a shared Pi `auth.json` into `piDir`. Pi lockfiles the target and
|
|
5784
|
+
* rotates tokens in place, so a link (never a copy) keeps one credential
|
|
5785
|
+
* document. A dangling link is fine — Pi treats a missing auth.json as "no
|
|
5786
|
+
* subscription auth". Failing to create the link is not: the run would start
|
|
5787
|
+
* with a connected subscription silently unavailable.
|
|
5788
|
+
*/
|
|
5789
|
+
function linkPiAuth(authPath, piDir, symlinkImpl = symlinkSync) {
|
|
5790
|
+
try {
|
|
5791
|
+
symlinkImpl(authPath, join(piDir, "auth.json"));
|
|
5792
|
+
} catch (cause) {
|
|
5793
|
+
throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
|
|
5794
|
+
}
|
|
5780
5795
|
}
|
|
5781
5796
|
//#endregion
|
|
5782
|
-
//#region src/lib/
|
|
5783
|
-
function createApiRuntimeSlotStore(args) {
|
|
5784
|
-
const { agent } = args;
|
|
5785
|
-
return {
|
|
5786
|
-
async beginSlot(input) {
|
|
5787
|
-
await agent.runtimeSlots.begin({
|
|
5788
|
-
agentName: input.agentName,
|
|
5789
|
-
runtimeProfileId: input.runtimeProfileId,
|
|
5790
|
-
lastAttemptN: input.lastAttemptN,
|
|
5791
|
-
lastTaskId: input.lastTaskId,
|
|
5792
|
-
model: input.model,
|
|
5793
|
-
provider: input.provider,
|
|
5794
|
-
sessionDir: input.sessionDir ?? void 0,
|
|
5795
|
-
sessionPath: input.sessionPath ?? void 0,
|
|
5796
|
-
slotKey: input.slotKey,
|
|
5797
|
-
taskType: input.taskType,
|
|
5798
|
-
workspaceId: input.workspaceId ?? void 0,
|
|
5799
|
-
workspaceKind: input.workspaceKind,
|
|
5800
|
-
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
5801
|
-
worktreePath: input.worktreePath ?? void 0
|
|
5802
|
-
}, { teamId: input.teamId });
|
|
5803
|
-
},
|
|
5804
|
-
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
5805
|
-
await agent.runtimeSlots.finish({
|
|
5806
|
-
agentName: identity.agentName,
|
|
5807
|
-
attemptN,
|
|
5808
|
-
runtimeProfileId: identity.runtimeProfileId,
|
|
5809
|
-
model,
|
|
5810
|
-
provider,
|
|
5811
|
-
sessionPath: sessionPath ?? void 0,
|
|
5812
|
-
slotKey,
|
|
5813
|
-
taskId
|
|
5814
|
-
}, { teamId });
|
|
5815
|
-
},
|
|
5816
|
-
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
5817
|
-
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
5818
|
-
attemptN,
|
|
5819
|
-
taskId
|
|
5820
|
-
}, { teamId });
|
|
5821
|
-
if (!resolved) return null;
|
|
5822
|
-
return {
|
|
5823
|
-
slot: {
|
|
5824
|
-
expiresAtMs: resolved.slot.expiresAtMs,
|
|
5825
|
-
id: resolved.slot.id,
|
|
5826
|
-
runtimeProfileId: resolved.slot.runtimeProfileId
|
|
5827
|
-
},
|
|
5828
|
-
session: resolved.slot.sessionDir ? {
|
|
5829
|
-
sessionDir: resolved.slot.sessionDir,
|
|
5830
|
-
sessionPath: resolved.slot.sessionPath
|
|
5831
|
-
} : null,
|
|
5832
|
-
workspace: resolved.workspace ? {
|
|
5833
|
-
kind: resolved.workspace.kind,
|
|
5834
|
-
workspaceId: resolved.workspace.workspaceId,
|
|
5835
|
-
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
5836
|
-
worktreePath: resolved.workspace.worktreePath
|
|
5837
|
-
} : null
|
|
5838
|
-
};
|
|
5839
|
-
},
|
|
5840
|
-
async listSlots(input) {
|
|
5841
|
-
const query = {
|
|
5842
|
-
agentName: input.agentName,
|
|
5843
|
-
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
5844
|
-
...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
|
|
5845
|
-
...input.state === void 0 ? {} : { state: input.state }
|
|
5846
|
-
};
|
|
5847
|
-
return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
|
|
5848
|
-
slot: {
|
|
5849
|
-
expiresAtMs: resolved.slot.expiresAtMs,
|
|
5850
|
-
id: resolved.slot.id,
|
|
5851
|
-
lastAttemptN: resolved.slot.lastAttemptN,
|
|
5852
|
-
lastTaskId: resolved.slot.lastTaskId,
|
|
5853
|
-
runtimeProfileId: resolved.slot.runtimeProfileId,
|
|
5854
|
-
slotKey: resolved.slot.slotKey,
|
|
5855
|
-
state: resolved.slot.state,
|
|
5856
|
-
taskType: resolved.slot.taskType
|
|
5857
|
-
},
|
|
5858
|
-
session: resolved.slot.sessionDir ? {
|
|
5859
|
-
sessionDir: resolved.slot.sessionDir,
|
|
5860
|
-
sessionPath: resolved.slot.sessionPath
|
|
5861
|
-
} : null,
|
|
5862
|
-
workspace: resolved.workspace ? {
|
|
5863
|
-
kind: resolved.workspace.kind,
|
|
5864
|
-
workspaceId: resolved.workspace.workspaceId,
|
|
5865
|
-
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
5866
|
-
worktreePath: resolved.workspace.worktreePath
|
|
5867
|
-
} : null
|
|
5868
|
-
}));
|
|
5869
|
-
},
|
|
5870
|
-
async close() {}
|
|
5871
|
-
};
|
|
5872
|
-
}
|
|
5873
|
-
//#endregion
|
|
5874
|
-
//#region src/lib/secret-redaction.ts
|
|
5875
|
-
var REDACTED = "[REDACTED]";
|
|
5876
|
-
var MIN_SECRET_LENGTH = 8;
|
|
5797
|
+
//#region src/lib/pi-agent-dir.ts
|
|
5877
5798
|
/**
|
|
5878
|
-
*
|
|
5879
|
-
*
|
|
5880
|
-
*
|
|
5881
|
-
*
|
|
5799
|
+
* Pick the Pi agent dir for direct `once`/`poll`/`drain` runs:
|
|
5800
|
+
*
|
|
5801
|
+
* 1. `PI_CODING_AGENT_DIR`, unchanged (`env`).
|
|
5802
|
+
* 2. When the Agent Server store has providers or a subscription login, a
|
|
5803
|
+
* private dir built like an Agent Server run, with `<agentRoot>/.pi`
|
|
5804
|
+
* config the store does not define layered in (`store`).
|
|
5805
|
+
* 3. `<agentRoot>/.pi` (`repo`).
|
|
5882
5806
|
*/
|
|
5883
|
-
function
|
|
5884
|
-
const
|
|
5885
|
-
if (
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
}
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
const
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5807
|
+
async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
|
|
5808
|
+
const noop = () => void 0;
|
|
5809
|
+
if (cfg.piCodingAgentDir) {
|
|
5810
|
+
mkdirSync(cfg.piCodingAgentDir, { recursive: true });
|
|
5811
|
+
return {
|
|
5812
|
+
path: cfg.piCodingAgentDir,
|
|
5813
|
+
source: "env",
|
|
5814
|
+
env: {},
|
|
5815
|
+
cleanup: noop
|
|
5816
|
+
};
|
|
5817
|
+
}
|
|
5818
|
+
const repoPiDir = join(agentRoot, ".pi");
|
|
5819
|
+
const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
|
|
5820
|
+
const providers = store.readProviders();
|
|
5821
|
+
const storeHasAuth = existsSync(store.piAuthJsonPath);
|
|
5822
|
+
if (Object.keys(providers).length === 0 && !storeHasAuth) {
|
|
5823
|
+
mkdirSync(repoPiDir, { recursive: true });
|
|
5824
|
+
return {
|
|
5825
|
+
path: repoPiDir,
|
|
5826
|
+
source: "repo",
|
|
5827
|
+
env: {},
|
|
5828
|
+
cleanup: noop
|
|
5829
|
+
};
|
|
5830
|
+
}
|
|
5831
|
+
const path = mkdtempSync(join(options.tempRoot ?? tmpdir(), "moltnet-pi-"));
|
|
5832
|
+
const cleanup = () => rmSync(path, {
|
|
5833
|
+
recursive: true,
|
|
5834
|
+
force: true
|
|
5835
|
+
});
|
|
5836
|
+
try {
|
|
5837
|
+
writeStorePiConfig(path, providers);
|
|
5838
|
+
const repoModelsPath = join(repoPiDir, "models.json");
|
|
5839
|
+
if (existsSync(repoModelsPath)) {
|
|
5840
|
+
const modelsPath = join(path, "models.json");
|
|
5841
|
+
const merged = mergePiModels(readPiModels(modelsPath), readPiModels(repoModelsPath));
|
|
5842
|
+
writeFileSync(modelsPath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
|
|
5843
|
+
}
|
|
5844
|
+
const repoSettingsPath = join(repoPiDir, "settings.json");
|
|
5845
|
+
if (existsSync(repoSettingsPath)) copyFileSync(repoSettingsPath, join(path, "settings.json"));
|
|
5846
|
+
const repoAuthPath = join(repoPiDir, "auth.json");
|
|
5847
|
+
linkPiAuth(storeHasAuth || !existsSync(repoAuthPath) ? store.piAuthJsonPath : repoAuthPath, path);
|
|
5848
|
+
const secretProviders = options.secretProviders ?? createNodeSecretProviderRegistry().register(new FileSecretProvider({ root: store.secretsDir }));
|
|
5849
|
+
const env = {};
|
|
5850
|
+
for (const providerId of new Set(profiles.map((p) => p.provider))) {
|
|
5851
|
+
const provider = providers[providerId];
|
|
5852
|
+
if (!provider?.apiKeyRef) continue;
|
|
5853
|
+
if (cfg.profilePrerequisiteEnv[provider.envName]) continue;
|
|
5854
|
+
try {
|
|
5855
|
+
env[provider.envName] = await secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
|
|
5856
|
+
} catch {
|
|
5857
|
+
throw new Error(`provider "${providerId}" API key could not be resolved from the Agent Server store`);
|
|
5858
|
+
}
|
|
5911
5859
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
}
|
|
5860
|
+
return {
|
|
5861
|
+
path,
|
|
5862
|
+
source: "store",
|
|
5863
|
+
env,
|
|
5864
|
+
cleanup
|
|
5865
|
+
};
|
|
5866
|
+
} catch (error) {
|
|
5867
|
+
cleanup();
|
|
5868
|
+
throw error;
|
|
5869
|
+
}
|
|
5922
5870
|
}
|
|
5923
|
-
function
|
|
5924
|
-
|
|
5871
|
+
function readPiModels(path) {
|
|
5872
|
+
try {
|
|
5873
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
5874
|
+
if (typeof parsed?.providers === "object" && parsed.providers !== null) return parsed;
|
|
5875
|
+
} catch {}
|
|
5876
|
+
throw new Error(`${path} is not a valid Pi models document`);
|
|
5925
5877
|
}
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
return resolveOutputBranch(attempt.output);
|
|
5935
|
-
},
|
|
5936
|
-
async findInputRevision(input) {
|
|
5937
|
-
const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
|
|
5938
|
-
if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
|
|
5939
|
-
return resolveTaskWorkspaceRevision(task.input);
|
|
5940
|
-
}
|
|
5878
|
+
/**
|
|
5879
|
+
* Store entries win per provider id. Repo models the store lacks are appended
|
|
5880
|
+
* with their metadata; repo-only providers are added unchanged.
|
|
5881
|
+
*/
|
|
5882
|
+
function mergePiModels(store, repo) {
|
|
5883
|
+
const providers = {
|
|
5884
|
+
...repo.providers,
|
|
5885
|
+
...store.providers
|
|
5941
5886
|
};
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5887
|
+
for (const [id, storeProvider] of Object.entries(store.providers)) {
|
|
5888
|
+
const repoModels = repo.providers[id]?.models ?? [];
|
|
5889
|
+
const known = new Set((storeProvider.models ?? []).map((m) => m.id));
|
|
5890
|
+
providers[id] = {
|
|
5891
|
+
...storeProvider,
|
|
5892
|
+
models: [...storeProvider.models ?? [], ...repoModels.filter((m) => !known.has(m.id))]
|
|
5893
|
+
};
|
|
5894
|
+
}
|
|
5895
|
+
return { providers };
|
|
5947
5896
|
}
|
|
5948
5897
|
//#endregion
|
|
5949
5898
|
//#region src/lib/state-dir.ts
|
|
@@ -5957,769 +5906,706 @@ function ensureDaemonStateDirs(mountPath) {
|
|
|
5957
5906
|
};
|
|
5958
5907
|
}
|
|
5959
5908
|
//#endregion
|
|
5960
|
-
//#region src/lib/
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5909
|
+
//#region src/lib/prepare-runtime-profile.ts
|
|
5910
|
+
/** Validate and prepare a profile through the shared daemon execution path. */
|
|
5911
|
+
async function prepareRuntimeProfile(input) {
|
|
5912
|
+
const { profile } = input;
|
|
5913
|
+
assertRuntimeAdapterSupportsProfile(input.runtimeAdapter, profile);
|
|
5914
|
+
const preparedRuntime = attestPreparedRuntime(await input.runtimeAdapter.prepare({ profile }), input.signingPrivateKey);
|
|
5915
|
+
validateRuntimeProfilePrerequisites(profile, input.prerequisiteEnv, {
|
|
5916
|
+
tools: preparedRuntime.tools,
|
|
5917
|
+
executables: preparedRuntime.executables
|
|
5965
5918
|
});
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5919
|
+
assertGuestEnvironmentBoundary({
|
|
5920
|
+
forwardEnv: profile.requiredEnv,
|
|
5921
|
+
sandboxEnv: profile.sandboxConfig.env
|
|
5922
|
+
});
|
|
5923
|
+
await input.agent.tasks.registerExecutorManifest(await preparedRuntime.attestor.registration());
|
|
5924
|
+
const sandbox = {
|
|
5925
|
+
config: profile.sandboxConfig,
|
|
5926
|
+
rootDir: profile.mountPath,
|
|
5927
|
+
path: profile.source
|
|
5928
|
+
};
|
|
5929
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
5930
|
+
const slotIdentity = {
|
|
5931
|
+
agentName: input.agentName,
|
|
5932
|
+
runtimeProfileId: profile.id,
|
|
5933
|
+
runtimeInstanceId: input.runtimeInstanceId
|
|
5934
|
+
};
|
|
5935
|
+
return {
|
|
5936
|
+
profile,
|
|
5937
|
+
preparedRuntime,
|
|
5938
|
+
sandbox,
|
|
5939
|
+
stateDirs,
|
|
5940
|
+
slotIdentity,
|
|
5941
|
+
executionPlans: createExecutionPlanCache({
|
|
5942
|
+
stateDirs,
|
|
5943
|
+
slotIdentity,
|
|
5944
|
+
warmRetentionSec: input.warmRetentionSec,
|
|
5945
|
+
workspacePolicy: {
|
|
5946
|
+
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
5947
|
+
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
5948
|
+
},
|
|
5949
|
+
slotRegistry: input.slotRegistry,
|
|
5950
|
+
runtimeSessionStore: input.runtimeSessionStore,
|
|
5951
|
+
sourceAttemptResolver: input.sourceAttemptResolver
|
|
5952
|
+
})
|
|
5972
5953
|
};
|
|
5973
5954
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5955
|
+
//#endregion
|
|
5956
|
+
//#region src/lib/runtime-context.ts
|
|
5957
|
+
var storage = new AsyncLocalStorage();
|
|
5958
|
+
function runWithDaemonRuntimeContext(context, callback) {
|
|
5959
|
+
return storage.run(context, callback);
|
|
5979
5960
|
}
|
|
5980
5961
|
//#endregion
|
|
5981
|
-
//#region src/
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
return
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
5962
|
+
//#region src/lib/runtime-profile-retry-triage.ts
|
|
5963
|
+
function createRuntimeProfileRetryTriage(options) {
|
|
5964
|
+
return async (input) => {
|
|
5965
|
+
const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
|
|
5966
|
+
return createPiRetryTriage({
|
|
5967
|
+
model: modelHandle,
|
|
5968
|
+
modelRuntime,
|
|
5969
|
+
thinkingLevel: options.runtimeProfile.thinkingLevel,
|
|
5970
|
+
piAgentDir: options.piAgentDir,
|
|
5971
|
+
timeoutMs: options.timeoutMs,
|
|
5972
|
+
cwd: options.cwd
|
|
5973
|
+
})(input);
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
5976
|
+
//#endregion
|
|
5977
|
+
//#region src/lib/runtime-resource-reaper.ts
|
|
5978
|
+
var RUNTIME_SLOT_PAGE_LIMIT = 200;
|
|
5979
|
+
var ATTEMPT_LOOKUP_CONCURRENCY = 8;
|
|
5980
|
+
var TERMINAL_ATTEMPT_STATUSES = new Set([
|
|
5981
|
+
"completed",
|
|
5982
|
+
"failed",
|
|
5983
|
+
"cancelled",
|
|
5984
|
+
"aborted",
|
|
5985
|
+
"timed_out"
|
|
5986
|
+
]);
|
|
5987
|
+
/**
|
|
5988
|
+
* Reap daemon-owned local resources whose runtime slot is no longer usable.
|
|
5989
|
+
* The destructive path is deliberately conservative: uncertainty about slot
|
|
5990
|
+
* ownership, attempt state, listing completeness, or real filesystem
|
|
5991
|
+
* containment retains resources rather than risking another daemon's data.
|
|
5992
|
+
*/
|
|
5993
|
+
async function reapRuntimeSlotResources(deps, input) {
|
|
5994
|
+
const now = input.now ?? Date.now();
|
|
5995
|
+
const [activePage, idlePage] = await Promise.all([listOwnedSlots(deps.runtimeSlotStore, input, "active"), listOwnedSlots(deps.runtimeSlotStore, input, "idle")]);
|
|
5996
|
+
const active = activePage.items;
|
|
5997
|
+
const idle = idlePage.items;
|
|
5998
|
+
const slots = [...active, ...idle];
|
|
5999
|
+
const result = {
|
|
6000
|
+
failed: 0,
|
|
6001
|
+
failures: [],
|
|
6002
|
+
removedSessions: 0,
|
|
6003
|
+
removedWorkspaces: 0,
|
|
6004
|
+
scanned: slots.length,
|
|
6005
|
+
truncated: activePage.truncated || idlePage.truncated,
|
|
6006
|
+
unsafePathDetails: [],
|
|
6007
|
+
unsafePaths: 0
|
|
6008
|
+
};
|
|
6009
|
+
const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
|
|
6010
|
+
for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
|
|
6011
|
+
const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
|
|
6012
|
+
const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
|
|
6013
|
+
const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
|
|
6014
|
+
kind: "ready",
|
|
6015
|
+
paths: /* @__PURE__ */ new Set()
|
|
6016
|
+
};
|
|
6017
|
+
for (const item of slots) {
|
|
6018
|
+
if (!reaped.has(item.slot.id)) continue;
|
|
6019
|
+
if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
|
|
6020
|
+
const sessionDir = item.session?.sessionDir;
|
|
6021
|
+
if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
|
|
6022
|
+
else try {
|
|
6023
|
+
rmSync(sessionDir, {
|
|
6024
|
+
force: true,
|
|
6025
|
+
recursive: true
|
|
6026
|
+
});
|
|
6027
|
+
result.removedSessions++;
|
|
6028
|
+
} catch (err) {
|
|
6029
|
+
recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
|
|
6030
|
+
}
|
|
6031
|
+
const workspacePath = item.workspace?.worktreePath;
|
|
6032
|
+
if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
|
|
6033
|
+
const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
|
|
6034
|
+
if (!workspaceRoot) {
|
|
6035
|
+
recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
|
|
6036
|
+
continue;
|
|
6037
|
+
}
|
|
6038
|
+
await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
|
|
6011
6039
|
}
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6040
|
+
if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
|
|
6041
|
+
return result;
|
|
6042
|
+
}
|
|
6043
|
+
async function listOwnedSlots(store, input, state) {
|
|
6044
|
+
const listed = await store.listSlots({
|
|
6045
|
+
agentName: input.agentName,
|
|
6046
|
+
limit: RUNTIME_SLOT_PAGE_LIMIT,
|
|
6047
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
6048
|
+
state,
|
|
6049
|
+
teamId: input.teamId
|
|
6050
|
+
});
|
|
6051
|
+
return {
|
|
6052
|
+
items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
|
|
6053
|
+
truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
async function findTerminalActiveSlotIds(deps, active, result) {
|
|
6057
|
+
const byTask = /* @__PURE__ */ new Map();
|
|
6058
|
+
for (const item of active) {
|
|
6059
|
+
const taskSlots = byTask.get(item.slot.lastTaskId) ?? [];
|
|
6060
|
+
taskSlots.push(item);
|
|
6061
|
+
byTask.set(item.slot.lastTaskId, taskSlots);
|
|
6018
6062
|
}
|
|
6019
|
-
|
|
6063
|
+
const terminal = /* @__PURE__ */ new Set();
|
|
6064
|
+
await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
|
|
6065
|
+
let attempts;
|
|
6066
|
+
try {
|
|
6067
|
+
attempts = await deps.taskReader.listAttempts(taskId);
|
|
6068
|
+
} catch (err) {
|
|
6069
|
+
recordFailure(deps, result, null, `task:${taskId}`, null, err);
|
|
6070
|
+
return;
|
|
6071
|
+
}
|
|
6072
|
+
for (const item of taskSlots) {
|
|
6073
|
+
const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
|
|
6074
|
+
if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
return terminal;
|
|
6078
|
+
}
|
|
6079
|
+
async function slotStillQualifiesForReaping(deps, input, original, now, result) {
|
|
6080
|
+
let page;
|
|
6020
6081
|
try {
|
|
6021
|
-
|
|
6082
|
+
page = await listOwnedSlots(deps.runtimeSlotStore, input, original.slot.state);
|
|
6022
6083
|
} catch (err) {
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
return 1;
|
|
6084
|
+
recordFailure(deps, result, original.slot.id, `slot:${original.slot.id}`, null, err);
|
|
6085
|
+
return false;
|
|
6026
6086
|
}
|
|
6027
|
-
const
|
|
6028
|
-
|
|
6087
|
+
const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
|
|
6088
|
+
if (!current) return false;
|
|
6089
|
+
if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
|
|
6090
|
+
return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
|
|
6091
|
+
}
|
|
6092
|
+
async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
|
|
6093
|
+
if (!input.mainWorktree) return;
|
|
6094
|
+
const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
|
|
6095
|
+
if (!existsSync(workspaceRoot)) return;
|
|
6096
|
+
let entries;
|
|
6029
6097
|
try {
|
|
6030
|
-
|
|
6098
|
+
entries = readdirSync(workspaceRoot, { withFileTypes: true });
|
|
6031
6099
|
} catch (err) {
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
console.error(opts.helpText);
|
|
6035
|
-
return 1;
|
|
6036
|
-
}
|
|
6037
|
-
throw err;
|
|
6100
|
+
recordFailure(deps, result, null, workspaceRoot, workspaceRoot, err);
|
|
6101
|
+
return;
|
|
6038
6102
|
}
|
|
6039
|
-
const
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
return 1;
|
|
6103
|
+
for (const entry of entries) {
|
|
6104
|
+
if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
|
|
6105
|
+
const workspacePath = resolve(workspaceRoot, entry.name);
|
|
6106
|
+
if (retainedWorkspacePaths.has(workspacePath)) continue;
|
|
6107
|
+
const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
|
|
6108
|
+
if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
|
|
6109
|
+
await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
|
|
6047
6110
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6111
|
+
}
|
|
6112
|
+
async function removeWorkspace(deps, result, worktrees, mainWorktree, slotId, workspacePath, workspaceRoot, scratch) {
|
|
6113
|
+
if (!existsSync(workspacePath)) return;
|
|
6114
|
+
if (!isRealPathInsideRoot(workspacePath, workspaceRoot)) {
|
|
6115
|
+
recordUnsafePath(deps, result, slotId, workspacePath, workspaceRoot);
|
|
6116
|
+
return;
|
|
6051
6117
|
}
|
|
6052
|
-
if (
|
|
6053
|
-
const cfg = loadConfig();
|
|
6054
|
-
const credentialSources = {
|
|
6055
|
-
profileRequirements: cfg.profileCredentialRequirements,
|
|
6056
|
-
bindings: cfg.credentialBindings
|
|
6057
|
-
};
|
|
6058
|
-
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
6059
|
-
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
6060
|
-
const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
6061
|
-
let gate = "resolve_agent_context";
|
|
6118
|
+
if (scratch || !mainWorktree) {
|
|
6062
6119
|
try {
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
envApiUrl: cfg.apiUrl
|
|
6067
|
-
});
|
|
6068
|
-
gate = "authenticate_and_bind";
|
|
6069
|
-
const whoami = await validateStartupBinding({
|
|
6070
|
-
agent: resolvedContext.agent,
|
|
6071
|
-
teamId,
|
|
6072
|
-
expectedAgent: cfg.expectedAgent
|
|
6073
|
-
});
|
|
6074
|
-
gate = "resolve_signing_material";
|
|
6075
|
-
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
6076
|
-
credentialSource: cfg.credentialSource,
|
|
6077
|
-
agentDir: resolvedContext.agentDir,
|
|
6078
|
-
configuredPrivateKey: cfg.signingPrivateKey,
|
|
6079
|
-
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
6080
|
-
});
|
|
6081
|
-
gate = "validate_scopes";
|
|
6082
|
-
validateDaemonScopes(whoami);
|
|
6083
|
-
gate = "validate_signing_identity";
|
|
6084
|
-
await validateExecutorSigningIdentity({
|
|
6085
|
-
whoami,
|
|
6086
|
-
signingPrivateKey: privateKey
|
|
6087
|
-
});
|
|
6088
|
-
gate = "resolve_agent_identity";
|
|
6089
|
-
const agentIdentity = await resolveDaemonAgentIdentity({
|
|
6090
|
-
agentName: baseCommon.agent,
|
|
6091
|
-
whoami,
|
|
6092
|
-
credentialSource: cfg.credentialSource,
|
|
6093
|
-
agentDir: resolvedContext.agentDir,
|
|
6094
|
-
gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
|
|
6095
|
-
});
|
|
6096
|
-
return {
|
|
6097
|
-
ctx: resolvedContext,
|
|
6098
|
-
signingPrivateKey: privateKey,
|
|
6099
|
-
startupWhoami: whoami,
|
|
6100
|
-
agentIdentity,
|
|
6101
|
-
hostCapabilitySigner: createLocalSeedSigner({
|
|
6102
|
-
privateKeySeed: privateKey,
|
|
6103
|
-
agent: resolvedContext.agent,
|
|
6104
|
-
identity: agentIdentity
|
|
6105
|
-
})
|
|
6106
|
-
};
|
|
6107
|
-
} catch (error) {
|
|
6108
|
-
await logDaemonStartupFailure({
|
|
6109
|
-
serviceName: `agent-daemon.${opts.modeLabel}`,
|
|
6110
|
-
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info"),
|
|
6111
|
-
gate,
|
|
6112
|
-
agent: baseCommon.agent,
|
|
6113
|
-
credentialSource: cfg.credentialSource,
|
|
6114
|
-
error
|
|
6120
|
+
rmSync(workspacePath, {
|
|
6121
|
+
force: true,
|
|
6122
|
+
recursive: true
|
|
6115
6123
|
});
|
|
6116
|
-
|
|
6124
|
+
result.removedWorkspaces++;
|
|
6125
|
+
} catch (err) {
|
|
6126
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
6117
6127
|
}
|
|
6118
|
-
|
|
6119
|
-
const daemonRootDir = explicitAgentRootDir ?? process.cwd();
|
|
6120
|
-
const resolvedProfiles = await resolveRuntimeProfiles({
|
|
6121
|
-
agent: ctx.agent,
|
|
6122
|
-
profiles: profileValues,
|
|
6123
|
-
teamId,
|
|
6124
|
-
cwd: daemonRootDir
|
|
6125
|
-
});
|
|
6126
|
-
const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
|
|
6127
|
-
const preparedRuntimes = /* @__PURE__ */ new Map();
|
|
6128
|
-
const profiles = [];
|
|
6129
|
-
const skippedProfileBoundaries = [];
|
|
6130
|
-
for (const profile of resolvedProfiles) try {
|
|
6131
|
-
assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
|
|
6132
|
-
const prepared = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
|
|
6133
|
-
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
|
|
6134
|
-
tools: prepared.tools,
|
|
6135
|
-
executables: prepared.executables
|
|
6136
|
-
});
|
|
6137
|
-
assertGuestEnvironmentBoundary({
|
|
6138
|
-
forwardEnv: profile.requiredEnv,
|
|
6139
|
-
sandboxEnv: profile.sandboxConfig.env
|
|
6140
|
-
});
|
|
6141
|
-
await ctx.agent.tasks.registerExecutorManifest(await prepared.attestor.registration());
|
|
6142
|
-
preparedRuntimes.set(profile.id, prepared);
|
|
6143
|
-
profiles.push(profile);
|
|
6144
|
-
} catch (error) {
|
|
6145
|
-
if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
|
|
6146
|
-
skippedProfileBoundaries.push({
|
|
6147
|
-
profile,
|
|
6148
|
-
error
|
|
6149
|
-
});
|
|
6128
|
+
return;
|
|
6150
6129
|
}
|
|
6151
|
-
if (
|
|
6152
|
-
|
|
6153
|
-
|
|
6130
|
+
if (worktrees.kind === "unavailable") {
|
|
6131
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
|
|
6132
|
+
return;
|
|
6154
6133
|
}
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6134
|
+
try {
|
|
6135
|
+
const canonical = realpathSync(workspacePath);
|
|
6136
|
+
if (worktrees.paths.has(canonical)) {
|
|
6137
|
+
await execFileText("git", [
|
|
6138
|
+
"-C",
|
|
6139
|
+
mainWorktree,
|
|
6140
|
+
"worktree",
|
|
6141
|
+
"remove",
|
|
6142
|
+
"--force",
|
|
6143
|
+
workspacePath
|
|
6144
|
+
]);
|
|
6145
|
+
worktrees.paths.delete(canonical);
|
|
6146
|
+
} else rmSync(workspacePath, {
|
|
6147
|
+
force: true,
|
|
6148
|
+
recursive: true
|
|
6149
|
+
});
|
|
6150
|
+
result.removedWorkspaces++;
|
|
6151
|
+
} catch (err) {
|
|
6152
|
+
recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
async function listRegisteredWorktrees(mainWorktree) {
|
|
6156
|
+
try {
|
|
6157
|
+
const list = await execFileText("git", [
|
|
6158
|
+
"-C",
|
|
6159
|
+
mainWorktree,
|
|
6160
|
+
"worktree",
|
|
6161
|
+
"list",
|
|
6162
|
+
"--porcelain"
|
|
6163
|
+
]);
|
|
6164
|
+
return {
|
|
6165
|
+
kind: "ready",
|
|
6166
|
+
paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
|
|
6176
6167
|
};
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
runtimeProfileId: profile.id,
|
|
6182
|
-
runtimeInstanceId
|
|
6168
|
+
} catch (err) {
|
|
6169
|
+
return {
|
|
6170
|
+
kind: "unavailable",
|
|
6171
|
+
reason: errorMessage(err)
|
|
6183
6172
|
};
|
|
6184
|
-
const executionPlans = createExecutionPlanCache({
|
|
6185
|
-
stateDirs,
|
|
6186
|
-
slotIdentity,
|
|
6187
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
6188
|
-
workspacePolicy: {
|
|
6189
|
-
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6190
|
-
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6191
|
-
},
|
|
6192
|
-
slotRegistry,
|
|
6193
|
-
runtimeSessionStore,
|
|
6194
|
-
sourceAttemptResolver
|
|
6195
|
-
});
|
|
6196
|
-
runtimes.set(profile.id, {
|
|
6197
|
-
common,
|
|
6198
|
-
profile,
|
|
6199
|
-
sandbox,
|
|
6200
|
-
stateDirs,
|
|
6201
|
-
piAgentDir,
|
|
6202
|
-
slotIdentity,
|
|
6203
|
-
executionPlans,
|
|
6204
|
-
preparedRuntime: preparedRuntimes.get(profile.id)
|
|
6205
|
-
});
|
|
6206
6173
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
const
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
agent: baseCommon.agent,
|
|
6230
|
-
teamId,
|
|
6231
|
-
runtimeProfileIds: profiles.map((p) => p.id),
|
|
6232
|
-
runtimeProfileNames: profiles.map((p) => p.name)
|
|
6233
|
-
});
|
|
6234
|
-
for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
|
|
6235
|
-
runtimeProfileId: profile.id,
|
|
6236
|
-
runtimeProfileName: profile.name,
|
|
6237
|
-
refusedEnvironmentNames: error.refusedNames
|
|
6238
|
-
}, "agent-daemon.runtime_profile_skipped_credential_boundary");
|
|
6239
|
-
const abort = new AbortController();
|
|
6240
|
-
let runtime = null;
|
|
6241
|
-
let active = null;
|
|
6242
|
-
const signalHandlers = installShutdownSignalHandlers({
|
|
6243
|
-
logDrain: (signal) => {
|
|
6244
|
-
rootLogger.warn({ signal }, "agent-daemon.draining");
|
|
6245
|
-
},
|
|
6246
|
-
drain: (signal) => {
|
|
6247
|
-
abort.abort();
|
|
6248
|
-
runtime?.stop(`agent-daemon received ${signal}`);
|
|
6249
|
-
abortActiveAttemptOnSignal({
|
|
6250
|
-
active,
|
|
6251
|
-
signal,
|
|
6252
|
-
abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
|
|
6253
|
-
logFailure: (err, current) => {
|
|
6254
|
-
try {
|
|
6255
|
-
rootLogger.warn({
|
|
6256
|
-
err: err instanceof Error ? err.message : String(err),
|
|
6257
|
-
taskId: current.taskId,
|
|
6258
|
-
attemptN: current.attemptN
|
|
6259
|
-
}, "agent-daemon.abort_on_signal_failed");
|
|
6260
|
-
} catch {}
|
|
6261
|
-
}
|
|
6262
|
-
});
|
|
6263
|
-
}
|
|
6264
|
-
});
|
|
6265
|
-
rootLogger.info({
|
|
6266
|
-
credentialSource: cfg.credentialSource,
|
|
6267
|
-
subjectType: startupWhoami.subjectType,
|
|
6268
|
-
bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
|
|
6269
|
-
credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
|
|
6270
|
-
boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
|
|
6271
|
-
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
6272
|
-
correlationId: values["correlation-id"] ?? null,
|
|
6273
|
-
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
6274
|
-
pollIntervalMs,
|
|
6275
|
-
maxPollIntervalMs,
|
|
6276
|
-
profiles: profiles.map((profile) => {
|
|
6277
|
-
const runtime = requireRuntime(runtimes, profile.id);
|
|
6278
|
-
return {
|
|
6279
|
-
id: profile.id,
|
|
6280
|
-
name: profile.name,
|
|
6281
|
-
provider: profile.provider,
|
|
6282
|
-
model: profile.model,
|
|
6283
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6284
|
-
temperature: profile.temperature,
|
|
6285
|
-
topP: profile.topP,
|
|
6286
|
-
topK: profile.topK,
|
|
6287
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6288
|
-
sandbox: runtime.sandbox.path,
|
|
6289
|
-
leaseTtlSec: runtime.common.leaseTtlSec,
|
|
6290
|
-
heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
|
|
6291
|
-
maxTurns: runtime.common.maxTurns,
|
|
6292
|
-
maxBashTimeouts: runtime.common.maxBashTimeouts,
|
|
6293
|
-
warmSessionTtlSec: runtime.common.warmSessionTtlSec,
|
|
6294
|
-
profileSessionTtlSec: profile.sessionTtlSec,
|
|
6295
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
6296
|
-
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6297
|
-
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6298
|
-
};
|
|
6299
|
-
}),
|
|
6300
|
-
piAgentDir: piAgentDir.path,
|
|
6301
|
-
piAgentDirSource: piAgentDir.source
|
|
6302
|
-
}, "agent-daemon.starting");
|
|
6303
|
-
let reaperRunning = false;
|
|
6304
|
-
const reapRuntimeResources = async () => {
|
|
6305
|
-
if (reaperRunning) return;
|
|
6306
|
-
reaperRunning = true;
|
|
6307
|
-
try {
|
|
6308
|
-
for (const profile of profiles) {
|
|
6309
|
-
const selected = requireRuntime(runtimes, profile.id);
|
|
6310
|
-
const result = await reapRuntimeSlotResources({
|
|
6311
|
-
onIssue: (issue) => {
|
|
6312
|
-
rootLogger.warn({
|
|
6313
|
-
...issue,
|
|
6314
|
-
runtimeProfileId: profile.id
|
|
6315
|
-
}, "agent-daemon.runtime_resource_reap_issue");
|
|
6316
|
-
},
|
|
6317
|
-
runtimeSlotStore: slotRegistry,
|
|
6318
|
-
taskReader: ctx.agent.tasks
|
|
6319
|
-
}, {
|
|
6320
|
-
agentName: selected.common.agent,
|
|
6321
|
-
mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
|
|
6322
|
-
runtimeInstanceId,
|
|
6323
|
-
runtimeProfileId: profile.id,
|
|
6324
|
-
sessionRootDir: selected.stateDirs.piSessionsDir,
|
|
6325
|
-
scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
|
|
6326
|
-
teamId
|
|
6327
|
-
});
|
|
6328
|
-
if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
|
|
6329
|
-
...result,
|
|
6330
|
-
runtimeProfileId: profile.id
|
|
6331
|
-
}, "agent-daemon.runtime_resources_reaped");
|
|
6332
|
-
}
|
|
6333
|
-
} catch (err) {
|
|
6334
|
-
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
6335
|
-
} finally {
|
|
6336
|
-
reaperRunning = false;
|
|
6337
|
-
}
|
|
6174
|
+
}
|
|
6175
|
+
function isRealPathInsideRoot(path, root) {
|
|
6176
|
+
try {
|
|
6177
|
+
return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
|
|
6178
|
+
} catch {
|
|
6179
|
+
return false;
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
function canonicalPath(path) {
|
|
6183
|
+
try {
|
|
6184
|
+
return realpathSync(path);
|
|
6185
|
+
} catch {
|
|
6186
|
+
return resolve(path);
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
function recordUnsafePath(deps, result, slotId, path, root) {
|
|
6190
|
+
const issue = {
|
|
6191
|
+
kind: "unsafe_path",
|
|
6192
|
+
path,
|
|
6193
|
+
reason: root === null ? "No daemon-owned root was available for this workspace." : "Candidate or root could not be resolved inside the daemon-owned root.",
|
|
6194
|
+
root,
|
|
6195
|
+
slotId
|
|
6338
6196
|
};
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
const
|
|
6197
|
+
result.unsafePaths++;
|
|
6198
|
+
result.unsafePathDetails.push(issue);
|
|
6199
|
+
emitIssue(deps, issue);
|
|
6200
|
+
}
|
|
6201
|
+
function recordFailure(deps, result, slotId, path, root, err) {
|
|
6202
|
+
const issue = {
|
|
6203
|
+
kind: "failure",
|
|
6204
|
+
path,
|
|
6205
|
+
reason: errorMessage(err),
|
|
6206
|
+
root,
|
|
6207
|
+
slotId
|
|
6208
|
+
};
|
|
6209
|
+
result.failed++;
|
|
6210
|
+
result.failures.push(issue);
|
|
6211
|
+
emitIssue(deps, issue);
|
|
6212
|
+
}
|
|
6213
|
+
function emitIssue(deps, issue) {
|
|
6345
6214
|
try {
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
pollIntervalMs,
|
|
6361
|
-
maxPollIntervalMs,
|
|
6362
|
-
signal: abort.signal,
|
|
6363
|
-
stopWhenEmpty: opts.stopWhenEmpty,
|
|
6364
|
-
waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
|
|
6365
|
-
waitAfterTaskMs: waitAfterTaskSec * 1e3,
|
|
6366
|
-
debug: baseCommon.debug,
|
|
6367
|
-
traceIdlePolling: cfg.traceIdlePolling,
|
|
6368
|
-
logger: rootLogger,
|
|
6369
|
-
executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
|
|
6370
|
-
slotRegistry,
|
|
6371
|
-
sessionRegistry: runtimeSessionStore,
|
|
6372
|
-
sourceAttemptResolver
|
|
6373
|
-
}),
|
|
6374
|
-
makeReporter: (claimedTask) => {
|
|
6375
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6376
|
-
return new ApiTaskReporter({
|
|
6377
|
-
tasks: ctx.agent.tasks,
|
|
6378
|
-
teamId: claimedTask.task.teamId,
|
|
6379
|
-
leaseTtlSec: selected.common.leaseTtlSec,
|
|
6380
|
-
heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
|
|
6381
|
-
maxBatchSize: selected.common.maxBatchSize,
|
|
6382
|
-
flushIntervalMs: selected.common.flushIntervalMs
|
|
6383
|
-
});
|
|
6384
|
-
},
|
|
6385
|
-
onTaskFinished: async (output, claimedTask) => {
|
|
6386
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6387
|
-
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
6388
|
-
let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
6389
|
-
if (resolved?.session?.sessionDir) try {
|
|
6390
|
-
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
6391
|
-
await runtimeSessionStore.uploadAttemptFinal({
|
|
6392
|
-
attemptN: claimedTask.attemptN,
|
|
6393
|
-
parentSessionId: parentSession?.id ?? null,
|
|
6394
|
-
sessionDir: resolved.session.sessionDir,
|
|
6395
|
-
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
6396
|
-
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
6397
|
-
sourceSlotId: resolved.slot.id,
|
|
6398
|
-
taskId: claimedTask.task.id,
|
|
6399
|
-
teamId: claimedTask.task.teamId
|
|
6400
|
-
});
|
|
6401
|
-
} catch (err) {
|
|
6402
|
-
rootLogger.error({
|
|
6403
|
-
err,
|
|
6404
|
-
taskId: claimedTask.task.id,
|
|
6405
|
-
attemptN: claimedTask.attemptN
|
|
6406
|
-
}, "agent-daemon.runtime_session_upload_failed");
|
|
6407
|
-
terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
|
|
6408
|
-
}
|
|
6409
|
-
return finalizeTask(ctx.agent, terminalOutput, {
|
|
6410
|
-
task: claimedTask.task,
|
|
6411
|
-
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
6412
|
-
retryTriage: createRuntimeProfileRetryTriage({
|
|
6413
|
-
runtimeProfile: selected.profile,
|
|
6414
|
-
piAgentDir: selected.piAgentDir.path,
|
|
6415
|
-
cwd: ctx.agentRootDir
|
|
6416
|
-
}),
|
|
6417
|
-
executorAttestor: selected.preparedRuntime.attestor,
|
|
6418
|
-
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
6419
|
-
gh: createGhCliClient(),
|
|
6420
|
-
logger: rootLogger.child({
|
|
6421
|
-
runtimeProfileId: selected.profile.id,
|
|
6422
|
-
runtimeProfileName: selected.profile.name
|
|
6423
|
-
})
|
|
6424
|
-
}),
|
|
6425
|
-
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
6426
|
-
});
|
|
6427
|
-
},
|
|
6428
|
-
executeTask: async (claimedTask, reporter) => {
|
|
6429
|
-
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6430
|
-
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
|
|
6431
|
-
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6432
|
-
config: runtimeCredentialConfig,
|
|
6433
|
-
profile,
|
|
6434
|
-
offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
|
|
6435
|
-
registry: createNodeSecretProviderRegistry(),
|
|
6436
|
-
executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
|
|
6437
|
-
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6438
|
-
taskId: claimedTask.task.id,
|
|
6439
|
-
attemptN: claimedTask.attemptN,
|
|
6440
|
-
logger: rootLogger
|
|
6441
|
-
});
|
|
6442
|
-
const taskLogger = rootLogger.child({
|
|
6443
|
-
runtimeProfileId: profile.id,
|
|
6444
|
-
runtimeProfileName: profile.name,
|
|
6445
|
-
provider: profile.provider,
|
|
6446
|
-
model: profile.model,
|
|
6447
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6448
|
-
temperature: profile.temperature,
|
|
6449
|
-
topP: profile.topP,
|
|
6450
|
-
topK: profile.topK,
|
|
6451
|
-
maxOutputTokens: profile.maxOutputTokens
|
|
6452
|
-
});
|
|
6453
|
-
let executionPlan;
|
|
6454
|
-
try {
|
|
6455
|
-
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
6456
|
-
} catch (err) {
|
|
6457
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6458
|
-
taskLogger.warn({
|
|
6459
|
-
taskId: claimedTask.task.id,
|
|
6460
|
-
attemptN: claimedTask.attemptN,
|
|
6461
|
-
err: message
|
|
6462
|
-
}, "agent-daemon.execution_plan_failed");
|
|
6463
|
-
return {
|
|
6464
|
-
taskId: claimedTask.task.id,
|
|
6465
|
-
attemptN: claimedTask.attemptN,
|
|
6466
|
-
status: "failed",
|
|
6467
|
-
output: null,
|
|
6468
|
-
outputCid: null,
|
|
6469
|
-
usage: {
|
|
6470
|
-
inputTokens: 0,
|
|
6471
|
-
outputTokens: 0
|
|
6472
|
-
},
|
|
6473
|
-
durationMs: 0,
|
|
6474
|
-
error: {
|
|
6475
|
-
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
6476
|
-
message,
|
|
6477
|
-
retryable: false
|
|
6478
|
-
}
|
|
6479
|
-
};
|
|
6480
|
-
}
|
|
6481
|
-
const sessionDescriptor = executionPlan.descriptor;
|
|
6482
|
-
taskLogger.debug({
|
|
6483
|
-
taskId: claimedTask.task.id,
|
|
6484
|
-
taskType: claimedTask.task.taskType,
|
|
6485
|
-
resumable: sessionDescriptor.policy.resumable,
|
|
6486
|
-
workspaceMode: executionPlan.workspaceMode,
|
|
6487
|
-
workspaceScope: sessionDescriptor.policy.workspaceScope,
|
|
6488
|
-
sessionScope: sessionDescriptor.policy.sessionScope,
|
|
6489
|
-
slotKey: executionPlan.slotKey,
|
|
6490
|
-
slotId: executionPlan.slotId,
|
|
6491
|
-
sessionKey: executionPlan.sessionKey,
|
|
6492
|
-
piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
|
|
6493
|
-
workspaceId: executionPlan.workspaceId
|
|
6494
|
-
}, "agent-daemon.task_execution_policy");
|
|
6495
|
-
if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
|
|
6496
|
-
taskId: claimedTask.task.id,
|
|
6497
|
-
attemptN: claimedTask.attemptN,
|
|
6498
|
-
status: "failed",
|
|
6499
|
-
output: null,
|
|
6500
|
-
outputCid: null,
|
|
6501
|
-
usage: {
|
|
6502
|
-
inputTokens: 0,
|
|
6503
|
-
outputTokens: 0
|
|
6504
|
-
},
|
|
6505
|
-
durationMs: 0,
|
|
6506
|
-
error: {
|
|
6507
|
-
code: "unsupported_task_type",
|
|
6508
|
-
message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
|
|
6509
|
-
retryable: true
|
|
6510
|
-
}
|
|
6511
|
-
};
|
|
6512
|
-
if (reporter.cancelSignal.aborted) return {
|
|
6513
|
-
taskId: claimedTask.task.id,
|
|
6514
|
-
attemptN: claimedTask.attemptN,
|
|
6515
|
-
status: "cancelled",
|
|
6516
|
-
output: null,
|
|
6517
|
-
outputCid: null,
|
|
6518
|
-
usage: {
|
|
6519
|
-
inputTokens: 0,
|
|
6520
|
-
outputTokens: 0
|
|
6521
|
-
},
|
|
6522
|
-
durationMs: 0,
|
|
6523
|
-
error: {
|
|
6524
|
-
code: "task_cancelled",
|
|
6525
|
-
message: reporter.cancelReason ?? "Task cancelled before executor started.",
|
|
6526
|
-
retryable: false
|
|
6527
|
-
}
|
|
6528
|
-
};
|
|
6529
|
-
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
6530
|
-
...slotIdentity,
|
|
6531
|
-
runtimeProfileId: profile.id,
|
|
6532
|
-
provider: profile.provider,
|
|
6533
|
-
model: profile.model,
|
|
6534
|
-
teamId: claimedTask.task.teamId,
|
|
6535
|
-
slotKey: executionPlan.slotKey,
|
|
6536
|
-
taskType: claimedTask.task.taskType,
|
|
6537
|
-
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
6538
|
-
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
6539
|
-
workspaceId: executionPlan.workspaceId,
|
|
6540
|
-
worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
6541
|
-
worktreeBranch: executionPlan.worktreeBranch,
|
|
6542
|
-
workspaceKind: executionPlan.workspaceKind,
|
|
6543
|
-
lastTaskId: claimedTask.task.id,
|
|
6544
|
-
lastAttemptN: claimedTask.attemptN
|
|
6545
|
-
});
|
|
6546
|
-
const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
|
|
6547
|
-
agentName: common.agent,
|
|
6548
|
-
moltnetAgent: ctx.agent,
|
|
6549
|
-
agentIdentity,
|
|
6550
|
-
hostCapabilitySigner,
|
|
6551
|
-
hostCapabilityLogger: taskLogger,
|
|
6552
|
-
agentRootDir: ctx.agentRootDir,
|
|
6553
|
-
mountPath: sandbox.rootDir,
|
|
6554
|
-
provider: profile.provider,
|
|
6555
|
-
model: profile.model,
|
|
6556
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6557
|
-
temperature: profile.temperature,
|
|
6558
|
-
topP: profile.topP,
|
|
6559
|
-
topK: profile.topK,
|
|
6560
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6561
|
-
sandboxConfig: sandbox.config,
|
|
6562
|
-
forwardEnv: profile.requiredEnv,
|
|
6563
|
-
onVmDiagnostic: (diagnostic) => {
|
|
6564
|
-
const fields = {
|
|
6565
|
-
event: diagnostic.event,
|
|
6566
|
-
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
6567
|
-
};
|
|
6568
|
-
if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
|
|
6569
|
-
else taskLogger.info(fields, diagnostic.message);
|
|
6570
|
-
},
|
|
6571
|
-
runtimeProfileContext: profile.context,
|
|
6572
|
-
runtimeProfileId: profile.id,
|
|
6573
|
-
toolEnforcement: profile.toolEnforcement,
|
|
6574
|
-
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
6575
|
-
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
6576
|
-
toolPolicyLogger: taskLogger,
|
|
6577
|
-
maxTurns: common.maxTurns,
|
|
6578
|
-
maxBashTimeouts: common.maxBashTimeouts
|
|
6579
|
-
});
|
|
6580
|
-
try {
|
|
6581
|
-
active = {
|
|
6582
|
-
taskId: claimedTask.task.id,
|
|
6583
|
-
attemptN: claimedTask.attemptN
|
|
6584
|
-
};
|
|
6585
|
-
return await runWithDaemonRuntimeContext({
|
|
6586
|
-
profileId: profile.id,
|
|
6587
|
-
profileName: profile.name,
|
|
6588
|
-
provider: profile.provider,
|
|
6589
|
-
model: profile.model,
|
|
6590
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6591
|
-
temperature: profile.temperature,
|
|
6592
|
-
topP: profile.topP,
|
|
6593
|
-
topK: profile.topK,
|
|
6594
|
-
maxOutputTokens: profile.maxOutputTokens
|
|
6595
|
-
}, () => rawExecuteTask(claimedTask, reporter));
|
|
6596
|
-
} finally {
|
|
6597
|
-
active = null;
|
|
6598
|
-
executionPlans.delete(claimedTask);
|
|
6599
|
-
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);
|
|
6600
|
-
}
|
|
6601
|
-
}
|
|
6215
|
+
deps.onIssue?.(issue);
|
|
6216
|
+
} catch {}
|
|
6217
|
+
}
|
|
6218
|
+
function errorMessage(err) {
|
|
6219
|
+
return err instanceof Error ? err.message : String(err);
|
|
6220
|
+
}
|
|
6221
|
+
async function execFileText(file, args) {
|
|
6222
|
+
return new Promise((resolvePromise, reject) => {
|
|
6223
|
+
execFile(file, args, {
|
|
6224
|
+
encoding: "utf8",
|
|
6225
|
+
maxBuffer: 10 * 1024 * 1024
|
|
6226
|
+
}, (err, stdout) => {
|
|
6227
|
+
if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
|
|
6228
|
+
else resolvePromise(stdout);
|
|
6602
6229
|
});
|
|
6603
|
-
|
|
6604
|
-
outputs.push(...drained);
|
|
6605
|
-
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
6606
|
-
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
6607
|
-
} finally {
|
|
6608
|
-
clearInterval(reaperTimer);
|
|
6609
|
-
signalHandlers.dispose();
|
|
6610
|
-
await slotRegistry.close();
|
|
6611
|
-
await otelShutdown();
|
|
6612
|
-
await shutdownLogger();
|
|
6613
|
-
}
|
|
6230
|
+
});
|
|
6614
6231
|
}
|
|
6615
|
-
function
|
|
6616
|
-
|
|
6617
|
-
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
6232
|
+
async function mapWithConcurrency(items, concurrency, worker) {
|
|
6233
|
+
for (let index = 0; index < items.length; index += concurrency) await Promise.all(items.slice(index, index + concurrency).map(worker));
|
|
6618
6234
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6235
|
+
//#endregion
|
|
6236
|
+
//#region src/lib/runtime-sessions.ts
|
|
6237
|
+
function resolveRuntimeSessionKind(claimedTask) {
|
|
6238
|
+
const continueFrom = resolveContinueFrom(claimedTask);
|
|
6239
|
+
if (!continueFrom) return "root";
|
|
6240
|
+
return continueFrom.mode === "fork" ? "fork" : "extend";
|
|
6625
6241
|
}
|
|
6626
|
-
function
|
|
6627
|
-
|
|
6628
|
-
|
|
6242
|
+
async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
|
|
6243
|
+
const continueFrom = resolveContinueFrom(claimedTask);
|
|
6244
|
+
if (!continueFrom) return null;
|
|
6245
|
+
return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
|
|
6629
6246
|
}
|
|
6630
|
-
function
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6247
|
+
function applyRuntimeSessionUploadFailure(output, err) {
|
|
6248
|
+
if (output.status !== "completed") return output;
|
|
6249
|
+
return {
|
|
6250
|
+
...output,
|
|
6251
|
+
error: {
|
|
6252
|
+
code: "runtime_session_upload_failed",
|
|
6253
|
+
message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
|
|
6254
|
+
retryable: isTransientUploadError(err)
|
|
6255
|
+
},
|
|
6256
|
+
output: null,
|
|
6257
|
+
outputCid: null,
|
|
6258
|
+
status: "failed"
|
|
6259
|
+
};
|
|
6634
6260
|
}
|
|
6635
|
-
|
|
6636
|
-
|
|
6261
|
+
/**
|
|
6262
|
+
* Transient faults worth retrying in-attempt: network-level errors
|
|
6263
|
+
* (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
|
|
6264
|
+
* validation, not-found) will not heal on retry.
|
|
6265
|
+
*/
|
|
6266
|
+
function isTransientUploadError(error) {
|
|
6267
|
+
if (!(error instanceof Error)) return false;
|
|
6268
|
+
const statusCode = error.statusCode;
|
|
6269
|
+
if (typeof statusCode !== "number") return true;
|
|
6270
|
+
return statusCode >= 500 || statusCode === 429;
|
|
6271
|
+
}
|
|
6272
|
+
var defaultSleep = (ms) => new Promise((resolve) => {
|
|
6273
|
+
setTimeout(resolve, ms);
|
|
6274
|
+
});
|
|
6275
|
+
function createApiRuntimeSessionStore(args) {
|
|
6276
|
+
const { agent, logger, uploadRetry } = args;
|
|
6277
|
+
return {
|
|
6278
|
+
async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
|
|
6279
|
+
return agent.runtimeSessions.getForAttempt({
|
|
6280
|
+
attemptN,
|
|
6281
|
+
taskId
|
|
6282
|
+
}, { teamId });
|
|
6283
|
+
},
|
|
6284
|
+
async hydrateSession(input) {
|
|
6285
|
+
const downloaded = await agent.runtimeSessions.download({
|
|
6286
|
+
attemptN: input.attemptN,
|
|
6287
|
+
taskId: input.taskId
|
|
6288
|
+
}, { teamId: input.teamId });
|
|
6289
|
+
await mkdir(input.destinationDir, { recursive: true });
|
|
6290
|
+
const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
|
|
6291
|
+
await pipeline(downloaded, createWriteStream(sessionPath));
|
|
6292
|
+
return sessionPath;
|
|
6293
|
+
},
|
|
6294
|
+
async uploadAttemptFinal(input) {
|
|
6295
|
+
const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
|
|
6296
|
+
if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
|
|
6297
|
+
const maxTries = uploadRetry?.maxTries ?? 3;
|
|
6298
|
+
const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
|
|
6299
|
+
const sleep = uploadRetry?.sleep ?? defaultSleep;
|
|
6300
|
+
for (let tryN = 1;; tryN += 1) try {
|
|
6301
|
+
await agent.runtimeSessions.upload({
|
|
6302
|
+
attemptN: input.attemptN,
|
|
6303
|
+
taskId: input.taskId
|
|
6304
|
+
}, createReadStream(sessionPath), {
|
|
6305
|
+
parentSessionId: input.parentSessionId ?? void 0,
|
|
6306
|
+
sessionKind: input.sessionKind,
|
|
6307
|
+
sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
|
|
6308
|
+
sourceSlotId: input.sourceSlotId ?? void 0
|
|
6309
|
+
}, { teamId: input.teamId });
|
|
6310
|
+
return;
|
|
6311
|
+
} catch (error) {
|
|
6312
|
+
if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
|
|
6313
|
+
const delayMs = baseDelayMs * tryN;
|
|
6314
|
+
logger?.warn({
|
|
6315
|
+
event: "agent-daemon.runtime_session_upload_retry",
|
|
6316
|
+
attemptN: input.attemptN,
|
|
6317
|
+
delayMs,
|
|
6318
|
+
statusCode: uploadStatusCode(error),
|
|
6319
|
+
taskId: input.taskId,
|
|
6320
|
+
tryN
|
|
6321
|
+
}, "Retrying durable runtime session upload");
|
|
6322
|
+
await sleep(delayMs);
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
};
|
|
6326
|
+
}
|
|
6327
|
+
function uploadStatusCode(error) {
|
|
6328
|
+
const statusCode = error?.statusCode;
|
|
6329
|
+
return typeof statusCode === "number" ? statusCode : void 0;
|
|
6330
|
+
}
|
|
6331
|
+
function resolveContinueFrom(claimedTask) {
|
|
6332
|
+
return claimedTask.task.input.continueFrom;
|
|
6333
|
+
}
|
|
6334
|
+
//#endregion
|
|
6335
|
+
//#region src/lib/runtime-slots.ts
|
|
6336
|
+
function createApiRuntimeSlotStore(args) {
|
|
6337
|
+
const { agent } = args;
|
|
6338
|
+
return {
|
|
6339
|
+
async beginSlot(input) {
|
|
6340
|
+
await agent.runtimeSlots.begin({
|
|
6341
|
+
agentName: input.agentName,
|
|
6342
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
6343
|
+
lastAttemptN: input.lastAttemptN,
|
|
6344
|
+
lastTaskId: input.lastTaskId,
|
|
6345
|
+
model: input.model,
|
|
6346
|
+
provider: input.provider,
|
|
6347
|
+
sessionDir: input.sessionDir ?? void 0,
|
|
6348
|
+
sessionPath: input.sessionPath ?? void 0,
|
|
6349
|
+
slotKey: input.slotKey,
|
|
6350
|
+
taskType: input.taskType,
|
|
6351
|
+
warmRetentionSec: input.warmRetentionSec,
|
|
6352
|
+
workspaceId: input.workspaceId ?? void 0,
|
|
6353
|
+
workspaceKind: input.workspaceKind,
|
|
6354
|
+
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
6355
|
+
worktreePath: input.worktreePath ?? void 0
|
|
6356
|
+
}, { teamId: input.teamId });
|
|
6357
|
+
},
|
|
6358
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath, warmRetentionSec) {
|
|
6359
|
+
await agent.runtimeSlots.finish({
|
|
6360
|
+
agentName: identity.agentName,
|
|
6361
|
+
attemptN,
|
|
6362
|
+
runtimeProfileId: identity.runtimeProfileId,
|
|
6363
|
+
model,
|
|
6364
|
+
provider,
|
|
6365
|
+
sessionPath: sessionPath ?? void 0,
|
|
6366
|
+
slotKey,
|
|
6367
|
+
taskId,
|
|
6368
|
+
warmRetentionSec
|
|
6369
|
+
}, { teamId });
|
|
6370
|
+
},
|
|
6371
|
+
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
6372
|
+
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
6373
|
+
attemptN,
|
|
6374
|
+
taskId
|
|
6375
|
+
}, { teamId });
|
|
6376
|
+
if (!resolved) return null;
|
|
6377
|
+
return {
|
|
6378
|
+
slot: {
|
|
6379
|
+
expiresAtMs: resolved.slot.expiresAtMs,
|
|
6380
|
+
id: resolved.slot.id,
|
|
6381
|
+
runtimeProfileId: resolved.slot.runtimeProfileId
|
|
6382
|
+
},
|
|
6383
|
+
session: resolved.slot.sessionDir ? {
|
|
6384
|
+
sessionDir: resolved.slot.sessionDir,
|
|
6385
|
+
sessionPath: resolved.slot.sessionPath
|
|
6386
|
+
} : null,
|
|
6387
|
+
workspace: resolved.workspace ? {
|
|
6388
|
+
kind: resolved.workspace.kind,
|
|
6389
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
6390
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
6391
|
+
worktreePath: resolved.workspace.worktreePath
|
|
6392
|
+
} : null
|
|
6393
|
+
};
|
|
6394
|
+
},
|
|
6395
|
+
async listSlots(input) {
|
|
6396
|
+
const query = {
|
|
6397
|
+
agentName: input.agentName,
|
|
6398
|
+
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
6399
|
+
...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
|
|
6400
|
+
...input.state === void 0 ? {} : { state: input.state }
|
|
6401
|
+
};
|
|
6402
|
+
return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
|
|
6403
|
+
slot: {
|
|
6404
|
+
expiresAtMs: resolved.slot.expiresAtMs,
|
|
6405
|
+
id: resolved.slot.id,
|
|
6406
|
+
lastAttemptN: resolved.slot.lastAttemptN,
|
|
6407
|
+
lastTaskId: resolved.slot.lastTaskId,
|
|
6408
|
+
runtimeProfileId: resolved.slot.runtimeProfileId,
|
|
6409
|
+
slotKey: resolved.slot.slotKey,
|
|
6410
|
+
state: resolved.slot.state,
|
|
6411
|
+
taskType: resolved.slot.taskType
|
|
6412
|
+
},
|
|
6413
|
+
session: resolved.slot.sessionDir ? {
|
|
6414
|
+
sessionDir: resolved.slot.sessionDir,
|
|
6415
|
+
sessionPath: resolved.slot.sessionPath
|
|
6416
|
+
} : null,
|
|
6417
|
+
workspace: resolved.workspace ? {
|
|
6418
|
+
kind: resolved.workspace.kind,
|
|
6419
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
6420
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
6421
|
+
worktreePath: resolved.workspace.worktreePath
|
|
6422
|
+
} : null
|
|
6423
|
+
}));
|
|
6424
|
+
},
|
|
6425
|
+
async close() {}
|
|
6426
|
+
};
|
|
6427
|
+
}
|
|
6428
|
+
//#endregion
|
|
6429
|
+
//#region src/lib/secret-redaction.ts
|
|
6430
|
+
var REDACTED = "[REDACTED]";
|
|
6431
|
+
var MIN_SECRET_LENGTH = 8;
|
|
6432
|
+
/**
|
|
6433
|
+
* Redact required runtime-profile environment values before task output leaves
|
|
6434
|
+
* the daemon. This is defense in depth for shell-capable profiles: a model may
|
|
6435
|
+
* read a forwarded provider key, but it must not be able to publish that exact
|
|
6436
|
+
* value through a task result.
|
|
6437
|
+
*/
|
|
6438
|
+
function redactRequiredEnvValues(value, requiredEnv, env) {
|
|
6439
|
+
const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
|
|
6440
|
+
if (secrets.length === 0) return value;
|
|
6441
|
+
const redact = (candidate) => {
|
|
6442
|
+
if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
|
|
6443
|
+
if (Array.isArray(candidate)) return candidate.map(redact);
|
|
6444
|
+
if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
|
|
6445
|
+
return candidate;
|
|
6446
|
+
};
|
|
6447
|
+
return redact(value);
|
|
6448
|
+
}
|
|
6449
|
+
//#endregion
|
|
6450
|
+
//#region src/lib/shutdown-signal.ts
|
|
6451
|
+
function installShutdownSignalHandlers(opts) {
|
|
6452
|
+
const proc = opts.proc ?? process;
|
|
6453
|
+
let drainingSignal = null;
|
|
6454
|
+
const onSignal = (signal) => {
|
|
6455
|
+
if (drainingSignal) {
|
|
6456
|
+
proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
|
|
6457
|
+
proc.exitCode = signalExitCode(signal);
|
|
6458
|
+
return;
|
|
6459
|
+
}
|
|
6460
|
+
drainingSignal = signal;
|
|
6461
|
+
proc.exitCode = signalExitCode(signal);
|
|
6462
|
+
try {
|
|
6463
|
+
opts.logDrain(signal);
|
|
6464
|
+
} catch (err) {
|
|
6465
|
+
proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
6466
|
+
}
|
|
6467
|
+
opts.drain(signal);
|
|
6468
|
+
};
|
|
6469
|
+
const handleSigint = () => onSignal("SIGINT");
|
|
6470
|
+
const handleSigterm = () => onSignal("SIGTERM");
|
|
6471
|
+
proc.on("SIGINT", handleSigint);
|
|
6472
|
+
proc.on("SIGTERM", handleSigterm);
|
|
6473
|
+
return { dispose: () => {
|
|
6474
|
+
proc.off("SIGINT", handleSigint);
|
|
6475
|
+
proc.off("SIGTERM", handleSigterm);
|
|
6476
|
+
} };
|
|
6637
6477
|
}
|
|
6638
|
-
function
|
|
6639
|
-
return
|
|
6478
|
+
function signalExitCode(signal) {
|
|
6479
|
+
return signal === "SIGINT" ? 130 : 143;
|
|
6640
6480
|
}
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
return
|
|
6481
|
+
//#endregion
|
|
6482
|
+
//#region src/lib/source-attempts.ts
|
|
6483
|
+
function createApiSourceAttemptResolver(args) {
|
|
6484
|
+
const { agent } = args;
|
|
6485
|
+
return {
|
|
6486
|
+
async findOutputBranch(input) {
|
|
6487
|
+
const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
|
|
6488
|
+
if (!attempt || attempt.status !== "completed") return null;
|
|
6489
|
+
return resolveOutputBranch(attempt.output);
|
|
6490
|
+
},
|
|
6491
|
+
async findInputRevision(input) {
|
|
6492
|
+
const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
|
|
6493
|
+
if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
|
|
6494
|
+
return resolveTaskWorkspaceRevision(task.input);
|
|
6495
|
+
}
|
|
6496
|
+
};
|
|
6646
6497
|
}
|
|
6647
|
-
function
|
|
6648
|
-
if (
|
|
6649
|
-
const
|
|
6650
|
-
|
|
6651
|
-
return v;
|
|
6498
|
+
function resolveOutputBranch(output) {
|
|
6499
|
+
if (!output || typeof output !== "object") return null;
|
|
6500
|
+
const branch = output.branch;
|
|
6501
|
+
return typeof branch === "string" && branch.length > 0 ? branch : null;
|
|
6652
6502
|
}
|
|
6653
6503
|
//#endregion
|
|
6654
|
-
//#region src/
|
|
6655
|
-
function
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6504
|
+
//#region src/lib/turn-event-logger.ts
|
|
6505
|
+
function makeTurnEventHandler(base, context = {}) {
|
|
6506
|
+
const log = base.child({
|
|
6507
|
+
name: "agent-daemon.turn",
|
|
6508
|
+
...context
|
|
6509
|
+
});
|
|
6510
|
+
return (event, summary) => {
|
|
6511
|
+
if (event === "text_delta") return;
|
|
6512
|
+
log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
|
|
6513
|
+
event,
|
|
6514
|
+
...summary
|
|
6515
|
+
}, `turn.${event}`);
|
|
6516
|
+
};
|
|
6517
|
+
}
|
|
6518
|
+
function makeTurnEventHandlerFactory(base) {
|
|
6519
|
+
return (claimedTask) => makeTurnEventHandler(base, {
|
|
6520
|
+
taskId: claimedTask.task.id,
|
|
6521
|
+
attemptN: claimedTask.attemptN
|
|
6663
6522
|
});
|
|
6664
6523
|
}
|
|
6665
6524
|
//#endregion
|
|
6666
|
-
//#region src/cli/
|
|
6667
|
-
async function
|
|
6668
|
-
if (isHelpFlag(argv)) {
|
|
6669
|
-
console.log(
|
|
6525
|
+
//#region src/cli/poll-shared.ts
|
|
6526
|
+
async function runPolling(opts) {
|
|
6527
|
+
if (isHelpFlag(opts.argv)) {
|
|
6528
|
+
console.log(opts.helpText);
|
|
6670
6529
|
return 0;
|
|
6671
6530
|
}
|
|
6672
6531
|
const { values } = parseArgs({
|
|
6673
|
-
args: argv,
|
|
6532
|
+
args: opts.argv,
|
|
6674
6533
|
options: {
|
|
6675
|
-
...
|
|
6676
|
-
"task-id": {
|
|
6677
|
-
type: "string",
|
|
6678
|
-
short: "t"
|
|
6679
|
-
},
|
|
6534
|
+
...runtimeCommandOptionDefs(),
|
|
6680
6535
|
team: { type: "string" },
|
|
6536
|
+
"task-types": { type: "string" },
|
|
6537
|
+
"correlation-id": { type: "string" },
|
|
6538
|
+
"diary-ids": { type: "string" },
|
|
6539
|
+
"wait-for-first-task-sec": { type: "string" },
|
|
6540
|
+
"wait-after-task-sec": { type: "string" },
|
|
6541
|
+
"poll-interval-ms": { type: "string" },
|
|
6542
|
+
"max-poll-interval-ms": { type: "string" },
|
|
6543
|
+
"list-limit": { type: "string" },
|
|
6681
6544
|
sandbox: { type: "string" },
|
|
6682
|
-
profile: {
|
|
6545
|
+
profile: {
|
|
6546
|
+
type: "string",
|
|
6547
|
+
multiple: true
|
|
6548
|
+
}
|
|
6683
6549
|
}
|
|
6684
6550
|
});
|
|
6685
|
-
if (!values
|
|
6686
|
-
console.error("Missing required flag: --
|
|
6687
|
-
console.error(
|
|
6551
|
+
if (!values.team) {
|
|
6552
|
+
console.error("Missing required flag: --team\n");
|
|
6553
|
+
console.error(opts.helpText);
|
|
6688
6554
|
return 1;
|
|
6689
6555
|
}
|
|
6690
|
-
const
|
|
6691
|
-
|
|
6556
|
+
const teamId = values.team;
|
|
6557
|
+
const profileValues = parseProfileValues(values.profile);
|
|
6558
|
+
if (profileValues.length === 0) {
|
|
6692
6559
|
console.error("Missing required flag: --profile\n");
|
|
6693
|
-
console.error(
|
|
6560
|
+
console.error(opts.helpText);
|
|
6561
|
+
return 1;
|
|
6562
|
+
}
|
|
6563
|
+
let taskTypes;
|
|
6564
|
+
try {
|
|
6565
|
+
taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
|
|
6566
|
+
} catch (err) {
|
|
6567
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6568
|
+
console.error(message);
|
|
6694
6569
|
return 1;
|
|
6695
6570
|
}
|
|
6696
|
-
|
|
6571
|
+
const diaryIds = parseCsv(values["diary-ids"]);
|
|
6572
|
+
let commandOptions;
|
|
6697
6573
|
try {
|
|
6698
|
-
|
|
6574
|
+
commandOptions = parseRuntimeCommandOptions(values);
|
|
6699
6575
|
} catch (err) {
|
|
6700
6576
|
if (err instanceof MissingRequiredOptionError) {
|
|
6701
6577
|
console.error(`${err.message}\n`);
|
|
6702
|
-
console.error(
|
|
6578
|
+
console.error(opts.helpText);
|
|
6703
6579
|
return 1;
|
|
6704
6580
|
}
|
|
6705
6581
|
throw err;
|
|
6706
6582
|
}
|
|
6583
|
+
const { identity, operations } = commandOptions;
|
|
6584
|
+
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
6585
|
+
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
6586
|
+
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
6587
|
+
const waitForFirstTaskSec = optionalNonNegativeInt(values["wait-for-first-task-sec"], "wait-for-first-task-sec", 0);
|
|
6588
|
+
const waitAfterTaskSec = optionalNonNegativeInt(values["wait-after-task-sec"], "wait-after-task-sec", 0);
|
|
6589
|
+
if (!opts.stopWhenEmpty && (waitForFirstTaskSec > 0 || waitAfterTaskSec > 0)) {
|
|
6590
|
+
console.error(`[${opts.modeLabel}] --wait-for-first-task-sec and --wait-after-task-sec are only valid with drain.`);
|
|
6591
|
+
return 1;
|
|
6592
|
+
}
|
|
6707
6593
|
if (values.sandbox) {
|
|
6708
|
-
console.error(
|
|
6594
|
+
console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
|
|
6709
6595
|
return 1;
|
|
6710
6596
|
}
|
|
6597
|
+
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).`);
|
|
6711
6598
|
const cfg = loadConfig();
|
|
6712
6599
|
const credentialSources = {
|
|
6713
6600
|
profileRequirements: cfg.profileCredentialRequirements,
|
|
6714
6601
|
bindings: cfg.credentialBindings
|
|
6715
6602
|
};
|
|
6716
6603
|
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
6717
|
-
const initialOpts = opts;
|
|
6718
6604
|
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
6719
|
-
const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
6605
|
+
const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
6720
6606
|
let gate = "resolve_agent_context";
|
|
6721
6607
|
try {
|
|
6722
|
-
const resolvedContext = await resolveAgentContext(
|
|
6608
|
+
const resolvedContext = await resolveAgentContext(identity.agent, {
|
|
6723
6609
|
agentRootDir: explicitAgentRootDir,
|
|
6724
6610
|
credentialSource: cfg.credentialSource,
|
|
6725
6611
|
envApiUrl: cfg.apiUrl
|
|
@@ -6727,7 +6613,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6727
6613
|
gate = "authenticate_and_bind";
|
|
6728
6614
|
const whoami = await validateStartupBinding({
|
|
6729
6615
|
agent: resolvedContext.agent,
|
|
6730
|
-
teamId
|
|
6616
|
+
teamId,
|
|
6731
6617
|
expectedAgent: cfg.expectedAgent
|
|
6732
6618
|
});
|
|
6733
6619
|
gate = "resolve_signing_material";
|
|
@@ -6746,7 +6632,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6746
6632
|
});
|
|
6747
6633
|
gate = "resolve_agent_identity";
|
|
6748
6634
|
const agentIdentity = await resolveDaemonAgentIdentity({
|
|
6749
|
-
agentName:
|
|
6635
|
+
agentName: identity.agent,
|
|
6750
6636
|
whoami,
|
|
6751
6637
|
credentialSource: cfg.credentialSource,
|
|
6752
6638
|
agentDir: resolvedContext.agentDir,
|
|
@@ -6755,6 +6641,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6755
6641
|
return {
|
|
6756
6642
|
ctx: resolvedContext,
|
|
6757
6643
|
signingPrivateKey: privateKey,
|
|
6644
|
+
startupWhoami: whoami,
|
|
6758
6645
|
agentIdentity,
|
|
6759
6646
|
hostCapabilitySigner: createLocalSeedSigner({
|
|
6760
6647
|
privateKeySeed: privateKey,
|
|
@@ -6764,10 +6651,10 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6764
6651
|
};
|
|
6765
6652
|
} catch (error) {
|
|
6766
6653
|
await logDaemonStartupFailure({
|
|
6767
|
-
serviceName:
|
|
6768
|
-
level: cfg.logLevel || (
|
|
6654
|
+
serviceName: `agent-daemon.${opts.modeLabel}`,
|
|
6655
|
+
level: cfg.logLevel || (identity.debug ? "debug" : "info"),
|
|
6769
6656
|
gate,
|
|
6770
|
-
agent:
|
|
6657
|
+
agent: identity.agent,
|
|
6771
6658
|
credentialSource: cfg.credentialSource,
|
|
6772
6659
|
error
|
|
6773
6660
|
});
|
|
@@ -6775,39 +6662,27 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6775
6662
|
}
|
|
6776
6663
|
})();
|
|
6777
6664
|
const daemonRootDir = explicitAgentRootDir ?? process.cwd();
|
|
6778
|
-
const
|
|
6665
|
+
const resolvedProfiles = await resolveRuntimeProfiles({
|
|
6779
6666
|
agent: ctx.agent,
|
|
6780
|
-
|
|
6781
|
-
teamId
|
|
6667
|
+
profiles: profileValues,
|
|
6668
|
+
teamId,
|
|
6782
6669
|
cwd: daemonRootDir
|
|
6783
6670
|
});
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
tools: preparedRuntime.tools,
|
|
6788
|
-
executables: preparedRuntime.executables
|
|
6671
|
+
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
6672
|
+
name: `agent-daemon.${opts.modeLabel}`,
|
|
6673
|
+
level: cfg.logLevel || (identity.debug ? "debug" : "info")
|
|
6789
6674
|
});
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6675
|
+
const rootLogger = logger.child({
|
|
6676
|
+
mode: opts.modeLabel,
|
|
6677
|
+
agent: identity.agent,
|
|
6678
|
+
teamId,
|
|
6679
|
+
runtimeProfileIds: resolvedProfiles.map((profile) => profile.id),
|
|
6680
|
+
runtimeProfileNames: resolvedProfiles.map((profile) => profile.name)
|
|
6793
6681
|
});
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
maxBatchSize: profile.maxBatchSize,
|
|
6799
|
-
maxTurns: profile.maxTurns,
|
|
6800
|
-
maxBashTimeouts: profile.maxBashTimeouts,
|
|
6801
|
-
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
6802
|
-
} });
|
|
6803
|
-
const sandbox = {
|
|
6804
|
-
config: profile.sandboxConfig,
|
|
6805
|
-
rootDir: profile.mountPath,
|
|
6806
|
-
path: profile.source
|
|
6807
|
-
};
|
|
6808
|
-
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
6809
|
-
activatePiCodingAgentDir(piAgentDir.path);
|
|
6810
|
-
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
6682
|
+
const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
|
|
6683
|
+
const profiles = [];
|
|
6684
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
6685
|
+
const skippedProfileBoundaries = [];
|
|
6811
6686
|
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
6812
6687
|
const runtimeSessionStore = createApiRuntimeSessionStore({
|
|
6813
6688
|
agent: ctx.agent,
|
|
@@ -6815,110 +6690,68 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6815
6690
|
});
|
|
6816
6691
|
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
6817
6692
|
const runtimeInstanceId = createRuntimeInstanceId();
|
|
6818
|
-
const
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
})
|
|
6693
|
+
for (const profile of resolvedProfiles) try {
|
|
6694
|
+
const prepared = await prepareRuntimeProfile({
|
|
6695
|
+
agent: ctx.agent,
|
|
6696
|
+
agentName: identity.agent,
|
|
6697
|
+
profile,
|
|
6698
|
+
prerequisiteEnv: cfg.profilePrerequisiteEnv,
|
|
6699
|
+
runtimeAdapter,
|
|
6700
|
+
runtimeInstanceId,
|
|
6701
|
+
signingPrivateKey,
|
|
6702
|
+
slotRegistry,
|
|
6703
|
+
runtimeSessionStore,
|
|
6704
|
+
sourceAttemptResolver,
|
|
6705
|
+
warmRetentionSec: operations.warmRetentionSec
|
|
6706
|
+
});
|
|
6707
|
+
runtimes.set(profile.id, prepared);
|
|
6708
|
+
profiles.push(profile);
|
|
6709
|
+
} catch (error) {
|
|
6710
|
+
if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
|
|
6711
|
+
skippedProfileBoundaries.push({
|
|
6712
|
+
profile,
|
|
6713
|
+
error
|
|
6714
|
+
});
|
|
6715
|
+
}
|
|
6716
|
+
if (profiles.length === 0) {
|
|
6717
|
+
const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
|
|
6718
|
+
throw new Error(`No safe runtime profiles remain. ${details}`);
|
|
6719
|
+
}
|
|
6720
|
+
const piAgentDir = await resolvePiAgentDir(cfg, profiles[0].mountPath, profiles);
|
|
6721
|
+
process.once("exit", piAgentDir.cleanup);
|
|
6722
|
+
activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
|
|
6723
|
+
if (!runtimes.has(profiles[0].id)) throw new Error("No runtime profiles resolved");
|
|
6835
6724
|
const otelShutdown = await initWorkerOtel({
|
|
6836
|
-
serviceName:
|
|
6725
|
+
serviceName: opts.serviceName,
|
|
6837
6726
|
agent: ctx.agent,
|
|
6838
6727
|
endpoint: cfg.otelEndpoint,
|
|
6839
6728
|
resourceAttributes: {
|
|
6840
|
-
"moltnet.
|
|
6841
|
-
"moltnet.agent.name":
|
|
6729
|
+
"moltnet.team.id": teamId,
|
|
6730
|
+
"moltnet.agent.name": identity.agent,
|
|
6842
6731
|
"moltnet.credential.source": ctx.credentialSource,
|
|
6843
|
-
"moltnet.
|
|
6844
|
-
"moltnet.
|
|
6845
|
-
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
6846
|
-
...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
|
|
6847
|
-
...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
|
|
6848
|
-
...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
|
|
6849
|
-
...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
|
|
6850
|
-
"moltnet.runtime_profile.id": profile.id
|
|
6732
|
+
"moltnet.runtime_profile.count": String(profiles.length),
|
|
6733
|
+
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
6851
6734
|
}
|
|
6852
6735
|
});
|
|
6853
|
-
const {
|
|
6854
|
-
name: "agent-daemon.once",
|
|
6855
|
-
level: cfg.logLevel || (opts.debug ? "debug" : "info")
|
|
6856
|
-
});
|
|
6857
|
-
const rootLogger = logger.child({
|
|
6858
|
-
mode: "once",
|
|
6859
|
-
agent: opts.agent,
|
|
6860
|
-
provider: profile.provider,
|
|
6861
|
-
model: profile.model,
|
|
6862
|
-
thinkingLevel: profile.thinkingLevel,
|
|
6863
|
-
temperature: profile.temperature,
|
|
6864
|
-
topP: profile.topP,
|
|
6865
|
-
topK: profile.topK,
|
|
6866
|
-
maxOutputTokens: profile.maxOutputTokens,
|
|
6736
|
+
for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
|
|
6867
6737
|
runtimeProfileId: profile.id,
|
|
6868
|
-
runtimeProfileName: profile.name
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
taskId,
|
|
6873
|
-
leaseTtlSec: opts.leaseTtlSec,
|
|
6874
|
-
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
6875
|
-
maxTurns: opts.maxTurns,
|
|
6876
|
-
maxBashTimeouts: opts.maxBashTimeouts,
|
|
6877
|
-
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
6878
|
-
profileId: profile.id,
|
|
6879
|
-
profileSessionTtlSec: profile.sessionTtlSec,
|
|
6880
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
6881
|
-
piAgentDir: piAgentDir.path,
|
|
6882
|
-
piAgentDirSource: piAgentDir.source
|
|
6883
|
-
}, "agent-daemon.starting");
|
|
6884
|
-
try {
|
|
6885
|
-
const reaped = await reapRuntimeSlotResources({
|
|
6886
|
-
onIssue: (issue) => {
|
|
6887
|
-
rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
|
|
6888
|
-
},
|
|
6889
|
-
runtimeSlotStore: slotRegistry,
|
|
6890
|
-
taskReader: ctx.agent.tasks
|
|
6891
|
-
}, {
|
|
6892
|
-
agentName: opts.agent,
|
|
6893
|
-
mainWorktree: resolveMainWorktree(sandbox.rootDir),
|
|
6894
|
-
runtimeInstanceId,
|
|
6895
|
-
runtimeProfileId: profile.id,
|
|
6896
|
-
sessionRootDir: stateDirs.piSessionsDir,
|
|
6897
|
-
scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
|
|
6898
|
-
teamId: profile.teamId
|
|
6899
|
-
});
|
|
6900
|
-
if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
|
|
6901
|
-
} catch (err) {
|
|
6902
|
-
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
6903
|
-
}
|
|
6738
|
+
runtimeProfileName: profile.name,
|
|
6739
|
+
refusedEnvironmentNames: error.refusedNames
|
|
6740
|
+
}, "agent-daemon.runtime_profile_skipped_credential_boundary");
|
|
6741
|
+
const abort = new AbortController();
|
|
6904
6742
|
let runtime = null;
|
|
6905
|
-
let
|
|
6743
|
+
let active = null;
|
|
6906
6744
|
const signalHandlers = installShutdownSignalHandlers({
|
|
6907
6745
|
logDrain: (signal) => {
|
|
6908
|
-
rootLogger.warn({
|
|
6909
|
-
signal,
|
|
6910
|
-
taskId
|
|
6911
|
-
}, "agent-daemon.draining");
|
|
6746
|
+
rootLogger.warn({ signal }, "agent-daemon.draining");
|
|
6912
6747
|
},
|
|
6913
6748
|
drain: (signal) => {
|
|
6749
|
+
abort.abort();
|
|
6914
6750
|
runtime?.stop(`agent-daemon received ${signal}`);
|
|
6915
6751
|
abortActiveAttemptOnSignal({
|
|
6916
|
-
active
|
|
6917
|
-
taskId,
|
|
6918
|
-
attemptN: activeAttemptN
|
|
6919
|
-
},
|
|
6752
|
+
active,
|
|
6920
6753
|
signal,
|
|
6921
|
-
abortAttempt: (
|
|
6754
|
+
abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
|
|
6922
6755
|
logFailure: (err, current) => {
|
|
6923
6756
|
try {
|
|
6924
6757
|
rootLogger.warn({
|
|
@@ -6926,71 +6759,232 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6926
6759
|
taskId: current.taskId,
|
|
6927
6760
|
attemptN: current.attemptN
|
|
6928
6761
|
}, "agent-daemon.abort_on_signal_failed");
|
|
6929
|
-
} catch
|
|
6930
|
-
process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
|
|
6931
|
-
}
|
|
6762
|
+
} catch {}
|
|
6932
6763
|
}
|
|
6933
6764
|
});
|
|
6934
6765
|
}
|
|
6935
6766
|
});
|
|
6767
|
+
rootLogger.info({
|
|
6768
|
+
credentialSource: cfg.credentialSource,
|
|
6769
|
+
subjectType: startupWhoami.subjectType,
|
|
6770
|
+
bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
|
|
6771
|
+
credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
|
|
6772
|
+
boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
|
|
6773
|
+
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
6774
|
+
correlationId: values["correlation-id"] ?? null,
|
|
6775
|
+
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
6776
|
+
pollIntervalMs,
|
|
6777
|
+
maxPollIntervalMs,
|
|
6778
|
+
profiles: profiles.map((profile) => {
|
|
6779
|
+
const runtime = requireRuntime(runtimes, profile.id);
|
|
6780
|
+
return {
|
|
6781
|
+
id: profile.id,
|
|
6782
|
+
name: profile.name,
|
|
6783
|
+
provider: profile.provider,
|
|
6784
|
+
model: profile.model,
|
|
6785
|
+
thinkingLevel: profile.thinkingLevel,
|
|
6786
|
+
temperature: profile.temperature,
|
|
6787
|
+
topP: profile.topP,
|
|
6788
|
+
topK: profile.topK,
|
|
6789
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
6790
|
+
sandbox: runtime.sandbox.path,
|
|
6791
|
+
heartbeatIntervalMs: operations.heartbeatIntervalMs,
|
|
6792
|
+
maxTurns: profile.maxTurns,
|
|
6793
|
+
maxBashTimeouts: profile.maxBashTimeouts,
|
|
6794
|
+
warmRetentionSec: operations.warmRetentionSec,
|
|
6795
|
+
defaultWorkspaceMode: profile.defaultWorkspaceMode,
|
|
6796
|
+
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
6797
|
+
};
|
|
6798
|
+
}),
|
|
6799
|
+
piAgentDir: piAgentDir.path,
|
|
6800
|
+
piAgentDirSource: piAgentDir.source
|
|
6801
|
+
}, "agent-daemon.starting");
|
|
6802
|
+
let reaperRunning = false;
|
|
6803
|
+
const reapRuntimeResources = async () => {
|
|
6804
|
+
if (reaperRunning) return;
|
|
6805
|
+
reaperRunning = true;
|
|
6806
|
+
try {
|
|
6807
|
+
for (const profile of profiles) {
|
|
6808
|
+
const selected = requireRuntime(runtimes, profile.id);
|
|
6809
|
+
const result = await reapRuntimeSlotResources({
|
|
6810
|
+
onIssue: (issue) => {
|
|
6811
|
+
rootLogger.warn({
|
|
6812
|
+
...issue,
|
|
6813
|
+
runtimeProfileId: profile.id
|
|
6814
|
+
}, "agent-daemon.runtime_resource_reap_issue");
|
|
6815
|
+
},
|
|
6816
|
+
runtimeSlotStore: slotRegistry,
|
|
6817
|
+
taskReader: ctx.agent.tasks
|
|
6818
|
+
}, {
|
|
6819
|
+
agentName: identity.agent,
|
|
6820
|
+
mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
|
|
6821
|
+
runtimeInstanceId,
|
|
6822
|
+
runtimeProfileId: profile.id,
|
|
6823
|
+
sessionRootDir: selected.stateDirs.piSessionsDir,
|
|
6824
|
+
scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
|
|
6825
|
+
teamId
|
|
6826
|
+
});
|
|
6827
|
+
if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
|
|
6828
|
+
...result,
|
|
6829
|
+
runtimeProfileId: profile.id
|
|
6830
|
+
}, "agent-daemon.runtime_resources_reaped");
|
|
6831
|
+
}
|
|
6832
|
+
} catch (err) {
|
|
6833
|
+
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
6834
|
+
} finally {
|
|
6835
|
+
reaperRunning = false;
|
|
6836
|
+
}
|
|
6837
|
+
};
|
|
6838
|
+
await reapRuntimeResources();
|
|
6839
|
+
const reaperTimer = setInterval(() => {
|
|
6840
|
+
reapRuntimeResources();
|
|
6841
|
+
}, 6e4);
|
|
6842
|
+
reaperTimer.unref();
|
|
6843
|
+
const outputs = [];
|
|
6936
6844
|
try {
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6845
|
+
runtime = new AgentRuntime({
|
|
6846
|
+
logger: rootLogger,
|
|
6847
|
+
source: new PollingApiTaskSource({
|
|
6848
|
+
agent: ctx.agent,
|
|
6849
|
+
teamId,
|
|
6850
|
+
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
6851
|
+
correlationId: values["correlation-id"],
|
|
6852
|
+
profiles: profiles.map((profile) => ({ profileId: profile.id })),
|
|
6853
|
+
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
6854
|
+
listLimit,
|
|
6855
|
+
pollIntervalMs,
|
|
6856
|
+
maxPollIntervalMs,
|
|
6857
|
+
signal: abort.signal,
|
|
6858
|
+
stopWhenEmpty: opts.stopWhenEmpty,
|
|
6859
|
+
waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
|
|
6860
|
+
waitAfterTaskMs: waitAfterTaskSec * 1e3,
|
|
6861
|
+
debug: identity.debug,
|
|
6862
|
+
traceIdlePolling: cfg.traceIdlePolling,
|
|
6863
|
+
logger: rootLogger,
|
|
6864
|
+
executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
|
|
6865
|
+
slotRegistry,
|
|
6866
|
+
sessionRegistry: runtimeSessionStore,
|
|
6867
|
+
sourceAttemptResolver
|
|
6868
|
+
}),
|
|
6869
|
+
makeReporter: (claimedTask) => {
|
|
6870
|
+
return new ApiTaskReporter({
|
|
6871
|
+
tasks: ctx.agent.tasks,
|
|
6872
|
+
teamId: claimedTask.task.teamId,
|
|
6873
|
+
heartbeatIntervalMs: operations.heartbeatIntervalMs,
|
|
6874
|
+
logger: rootLogger
|
|
6875
|
+
});
|
|
6961
6876
|
},
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6877
|
+
onTaskFinished: async (output, claimedTask) => {
|
|
6878
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6879
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
6880
|
+
let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
6881
|
+
if (resolved?.session?.sessionDir) try {
|
|
6882
|
+
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
6883
|
+
await runtimeSessionStore.uploadAttemptFinal({
|
|
6884
|
+
attemptN: claimedTask.attemptN,
|
|
6885
|
+
parentSessionId: parentSession?.id ?? null,
|
|
6886
|
+
sessionDir: resolved.session.sessionDir,
|
|
6887
|
+
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
6888
|
+
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
6889
|
+
sourceSlotId: resolved.slot.id,
|
|
6890
|
+
taskId: claimedTask.task.id,
|
|
6891
|
+
teamId: claimedTask.task.teamId
|
|
6892
|
+
});
|
|
6893
|
+
} catch (err) {
|
|
6894
|
+
rootLogger.error({
|
|
6895
|
+
err,
|
|
6896
|
+
taskId: claimedTask.task.id,
|
|
6897
|
+
attemptN: claimedTask.attemptN
|
|
6898
|
+
}, "agent-daemon.runtime_session_upload_failed");
|
|
6899
|
+
terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
|
|
6900
|
+
}
|
|
6901
|
+
return finalizeTask(ctx.agent, terminalOutput, {
|
|
6902
|
+
task: claimedTask.task,
|
|
6903
|
+
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
6904
|
+
retryTriage: createRuntimeProfileRetryTriage({
|
|
6905
|
+
runtimeProfile: selected.profile,
|
|
6906
|
+
piAgentDir: piAgentDir.path,
|
|
6907
|
+
cwd: ctx.agentRootDir
|
|
6908
|
+
}),
|
|
6909
|
+
executorAttestor: selected.preparedRuntime.attestor,
|
|
6910
|
+
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
6911
|
+
gh: createGhCliClient(),
|
|
6912
|
+
logger: rootLogger.child({
|
|
6913
|
+
runtimeProfileId: selected.profile.id,
|
|
6914
|
+
runtimeProfileName: selected.profile.name
|
|
6915
|
+
})
|
|
6916
|
+
}),
|
|
6917
|
+
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
6918
|
+
});
|
|
6919
|
+
},
|
|
6920
|
+
executeTask: async (claimedTask, reporter) => {
|
|
6921
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6922
|
+
const { executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
|
|
6923
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6924
|
+
config: runtimeCredentialConfig,
|
|
6925
|
+
profile,
|
|
6926
|
+
offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
|
|
6927
|
+
registry: createNodeSecretProviderRegistry(),
|
|
6928
|
+
executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
|
|
6929
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6930
|
+
taskId: claimedTask.task.id,
|
|
6931
|
+
attemptN: claimedTask.attemptN,
|
|
6932
|
+
logger: rootLogger
|
|
6933
|
+
});
|
|
6934
|
+
const taskLogger = rootLogger.child({
|
|
6935
|
+
runtimeProfileId: profile.id,
|
|
6936
|
+
runtimeProfileName: profile.name,
|
|
6937
|
+
provider: profile.provider,
|
|
6938
|
+
model: profile.model,
|
|
6939
|
+
thinkingLevel: profile.thinkingLevel,
|
|
6940
|
+
temperature: profile.temperature,
|
|
6941
|
+
topP: profile.topP,
|
|
6942
|
+
topK: profile.topK,
|
|
6943
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
6944
|
+
});
|
|
6945
|
+
let executionPlan;
|
|
6946
|
+
try {
|
|
6947
|
+
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
6948
|
+
} catch (err) {
|
|
6949
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6950
|
+
taskLogger.warn({
|
|
6951
|
+
taskId: claimedTask.task.id,
|
|
6952
|
+
attemptN: claimedTask.attemptN,
|
|
6953
|
+
err: message
|
|
6954
|
+
}, "agent-daemon.execution_plan_failed");
|
|
6955
|
+
return {
|
|
6956
|
+
taskId: claimedTask.task.id,
|
|
6957
|
+
attemptN: claimedTask.attemptN,
|
|
6958
|
+
status: "failed",
|
|
6959
|
+
output: null,
|
|
6960
|
+
outputCid: null,
|
|
6961
|
+
usage: {
|
|
6962
|
+
inputTokens: 0,
|
|
6963
|
+
outputTokens: 0
|
|
6964
|
+
},
|
|
6965
|
+
durationMs: 0,
|
|
6966
|
+
error: {
|
|
6967
|
+
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
6968
|
+
message,
|
|
6969
|
+
retryable: false
|
|
6970
|
+
}
|
|
6971
|
+
};
|
|
6972
|
+
}
|
|
6973
|
+
const sessionDescriptor = executionPlan.descriptor;
|
|
6974
|
+
taskLogger.debug({
|
|
6989
6975
|
taskId: claimedTask.task.id,
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6976
|
+
taskType: claimedTask.task.taskType,
|
|
6977
|
+
resumable: sessionDescriptor.policy.resumable,
|
|
6978
|
+
workspaceMode: executionPlan.workspaceMode,
|
|
6979
|
+
workspaceScope: sessionDescriptor.policy.workspaceScope,
|
|
6980
|
+
sessionScope: sessionDescriptor.policy.sessionScope,
|
|
6981
|
+
slotKey: executionPlan.slotKey,
|
|
6982
|
+
slotId: executionPlan.slotId,
|
|
6983
|
+
sessionKey: executionPlan.sessionKey,
|
|
6984
|
+
piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
|
|
6985
|
+
workspaceId: executionPlan.workspaceId
|
|
6986
|
+
}, "agent-daemon.task_execution_policy");
|
|
6987
|
+
if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
|
|
6994
6988
|
taskId: claimedTask.task.id,
|
|
6995
6989
|
attemptN: claimedTask.attemptN,
|
|
6996
6990
|
status: "failed",
|
|
@@ -7002,513 +6996,611 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
7002
6996
|
},
|
|
7003
6997
|
durationMs: 0,
|
|
7004
6998
|
error: {
|
|
7005
|
-
code:
|
|
7006
|
-
message,
|
|
6999
|
+
code: "unsupported_task_type",
|
|
7000
|
+
message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
|
|
7001
|
+
retryable: true
|
|
7002
|
+
}
|
|
7003
|
+
};
|
|
7004
|
+
if (reporter.cancelSignal.aborted) return {
|
|
7005
|
+
taskId: claimedTask.task.id,
|
|
7006
|
+
attemptN: claimedTask.attemptN,
|
|
7007
|
+
status: "cancelled",
|
|
7008
|
+
output: null,
|
|
7009
|
+
outputCid: null,
|
|
7010
|
+
usage: {
|
|
7011
|
+
inputTokens: 0,
|
|
7012
|
+
outputTokens: 0
|
|
7013
|
+
},
|
|
7014
|
+
durationMs: 0,
|
|
7015
|
+
error: {
|
|
7016
|
+
code: "task_cancelled",
|
|
7017
|
+
message: reporter.cancelReason ?? "Task cancelled before executor started.",
|
|
7007
7018
|
retryable: false
|
|
7008
7019
|
}
|
|
7009
7020
|
};
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7021
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
7022
|
+
...slotIdentity,
|
|
7023
|
+
runtimeProfileId: profile.id,
|
|
7024
|
+
provider: profile.provider,
|
|
7025
|
+
model: profile.model,
|
|
7026
|
+
teamId: claimedTask.task.teamId,
|
|
7027
|
+
slotKey: executionPlan.slotKey,
|
|
7028
|
+
taskType: claimedTask.task.taskType,
|
|
7029
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
7030
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
7031
|
+
workspaceId: executionPlan.workspaceId,
|
|
7032
|
+
worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
7033
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
7034
|
+
workspaceKind: executionPlan.workspaceKind,
|
|
7035
|
+
lastTaskId: claimedTask.task.id,
|
|
7036
|
+
lastAttemptN: claimedTask.attemptN,
|
|
7037
|
+
warmRetentionSec: operations.warmRetentionSec
|
|
7038
|
+
});
|
|
7039
|
+
const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
|
|
7040
|
+
agentName: identity.agent,
|
|
7041
|
+
moltnetAgent: ctx.agent,
|
|
7042
|
+
agentIdentity,
|
|
7043
|
+
hostCapabilitySigner,
|
|
7044
|
+
hostCapabilityLogger: taskLogger,
|
|
7045
|
+
agentRootDir: ctx.agentRootDir,
|
|
7046
|
+
mountPath: sandbox.rootDir,
|
|
7033
7047
|
provider: profile.provider,
|
|
7034
7048
|
model: profile.model,
|
|
7035
7049
|
thinkingLevel: profile.thinkingLevel,
|
|
7036
7050
|
temperature: profile.temperature,
|
|
7037
7051
|
topP: profile.topP,
|
|
7038
7052
|
topK: profile.topK,
|
|
7039
|
-
maxOutputTokens: profile.maxOutputTokens
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
makeReporter: () => new ApiTaskReporter({
|
|
7062
|
-
tasks: ctx.agent.tasks,
|
|
7063
|
-
teamId: profile.teamId,
|
|
7064
|
-
leaseTtlSec: opts.leaseTtlSec,
|
|
7065
|
-
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
7066
|
-
maxBatchSize: opts.maxBatchSize,
|
|
7067
|
-
flushIntervalMs: opts.flushIntervalMs
|
|
7068
|
-
}),
|
|
7069
|
-
onTaskFinished: async (output, claimedTask) => {
|
|
7070
|
-
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
7071
|
-
let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
7072
|
-
if (resolved?.session?.sessionDir) try {
|
|
7073
|
-
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
7074
|
-
await runtimeSessionStore.uploadAttemptFinal({
|
|
7075
|
-
attemptN: claimedTask.attemptN,
|
|
7076
|
-
parentSessionId: parentSession?.id ?? null,
|
|
7077
|
-
sessionDir: resolved.session.sessionDir,
|
|
7078
|
-
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
7079
|
-
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
7080
|
-
sourceSlotId: resolved.slot.id,
|
|
7081
|
-
taskId: claimedTask.task.id,
|
|
7082
|
-
teamId: claimedTask.task.teamId
|
|
7083
|
-
});
|
|
7084
|
-
} catch (err) {
|
|
7085
|
-
rootLogger.error({
|
|
7086
|
-
err,
|
|
7053
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7054
|
+
sandboxConfig: sandbox.config,
|
|
7055
|
+
forwardEnv: profile.requiredEnv,
|
|
7056
|
+
onVmDiagnostic: (diagnostic) => {
|
|
7057
|
+
const fields = {
|
|
7058
|
+
event: diagnostic.event,
|
|
7059
|
+
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
7060
|
+
};
|
|
7061
|
+
if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
|
|
7062
|
+
else taskLogger.info(fields, diagnostic.message);
|
|
7063
|
+
},
|
|
7064
|
+
runtimeProfileContext: profile.context,
|
|
7065
|
+
runtimeProfileId: profile.id,
|
|
7066
|
+
toolEnforcement: profile.toolEnforcement,
|
|
7067
|
+
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
7068
|
+
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
7069
|
+
toolPolicyLogger: taskLogger,
|
|
7070
|
+
maxTurns: profile.maxTurns,
|
|
7071
|
+
maxBashTimeouts: profile.maxBashTimeouts
|
|
7072
|
+
});
|
|
7073
|
+
try {
|
|
7074
|
+
active = {
|
|
7087
7075
|
taskId: claimedTask.task.id,
|
|
7088
7076
|
attemptN: claimedTask.attemptN
|
|
7089
|
-
}
|
|
7090
|
-
|
|
7077
|
+
};
|
|
7078
|
+
return await runWithDaemonRuntimeContext({
|
|
7079
|
+
profileId: profile.id,
|
|
7080
|
+
profileName: profile.name,
|
|
7081
|
+
provider: profile.provider,
|
|
7082
|
+
model: profile.model,
|
|
7083
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7084
|
+
temperature: profile.temperature,
|
|
7085
|
+
topP: profile.topP,
|
|
7086
|
+
topK: profile.topK,
|
|
7087
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
7088
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
7089
|
+
} finally {
|
|
7090
|
+
active = null;
|
|
7091
|
+
executionPlans.delete(claimedTask);
|
|
7092
|
+
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, operations.warmRetentionSec);
|
|
7091
7093
|
}
|
|
7092
|
-
|
|
7093
|
-
task: claimedTask.task,
|
|
7094
|
-
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
7095
|
-
retryTriage: createRuntimeProfileRetryTriage({
|
|
7096
|
-
runtimeProfile: profile,
|
|
7097
|
-
piAgentDir: piAgentDir.path,
|
|
7098
|
-
cwd: ctx.agentRootDir
|
|
7099
|
-
}),
|
|
7100
|
-
executorAttestor: preparedRuntime.attestor,
|
|
7101
|
-
writeCorrelationAnchors,
|
|
7102
|
-
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
7103
|
-
});
|
|
7104
|
-
},
|
|
7105
|
-
executeTask
|
|
7094
|
+
}
|
|
7106
7095
|
});
|
|
7107
|
-
const
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
}
|
|
7112
|
-
console.log("\n[done] TaskOutput:");
|
|
7113
|
-
console.log(JSON.stringify(output, null, 2));
|
|
7114
|
-
return output.status === "completed" ? 0 : 1;
|
|
7096
|
+
const drained = await runtime.start();
|
|
7097
|
+
outputs.push(...drained);
|
|
7098
|
+
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
7099
|
+
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
7115
7100
|
} finally {
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
await
|
|
7119
|
-
await
|
|
7120
|
-
|
|
7121
|
-
}
|
|
7122
|
-
function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
|
|
7123
|
-
if (!executionPlan.workspaceId) return null;
|
|
7124
|
-
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
7125
|
-
}
|
|
7126
|
-
function resolveMainWorktree(mountPath) {
|
|
7127
|
-
try {
|
|
7128
|
-
return findMainWorktree(mountPath);
|
|
7129
|
-
} catch {
|
|
7130
|
-
return null;
|
|
7131
|
-
}
|
|
7132
|
-
}
|
|
7133
|
-
//#endregion
|
|
7134
|
-
//#region src/cli/poll.ts
|
|
7135
|
-
function runPoll(argv, runtimeAdapter) {
|
|
7136
|
-
return runPolling({
|
|
7137
|
-
argv,
|
|
7138
|
-
serviceName: "moltnet.agent-daemon.poll",
|
|
7139
|
-
stopWhenEmpty: false,
|
|
7140
|
-
modeLabel: "poll",
|
|
7141
|
-
helpText: POLL_HELP,
|
|
7142
|
-
runtimeAdapter
|
|
7143
|
-
});
|
|
7144
|
-
}
|
|
7145
|
-
var NAME_RE = IDENTITY_ALIAS_PATTERN;
|
|
7146
|
-
var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
7147
|
-
function assertStoreName(kind, value) {
|
|
7148
|
-
try {
|
|
7149
|
-
return assertIdentityAlias(value);
|
|
7150
|
-
} catch {
|
|
7151
|
-
throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
|
|
7152
|
-
}
|
|
7153
|
-
}
|
|
7154
|
-
function assertProviderId(value) {
|
|
7155
|
-
if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
|
|
7156
|
-
return value;
|
|
7157
|
-
}
|
|
7158
|
-
var AgentServerStoreError = class extends Error {
|
|
7159
|
-
name = "AgentServerStoreError";
|
|
7160
|
-
constructor(code, message, options) {
|
|
7161
|
-
super(message, options);
|
|
7162
|
-
this.code = code;
|
|
7163
|
-
}
|
|
7164
|
-
};
|
|
7165
|
-
/**
|
|
7166
|
-
* `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
|
|
7167
|
-
*
|
|
7168
|
-
* Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
|
|
7169
|
-
* and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
|
|
7170
|
-
* so honouring XDG here gave one application two config roots: on a machine
|
|
7171
|
-
* with the variable set, the daemon wrote identities the CLI and SDK could not
|
|
7172
|
-
* read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
|
|
7173
|
-
* genuinely custom location.
|
|
7174
|
-
*/
|
|
7175
|
-
function resolveAgentServerRoot(input) {
|
|
7176
|
-
const override = input.root?.trim();
|
|
7177
|
-
if (override) return override;
|
|
7178
|
-
return getConfigDir();
|
|
7179
|
-
}
|
|
7180
|
-
function providerEnvName(providerId) {
|
|
7181
|
-
return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
|
|
7101
|
+
clearInterval(reaperTimer);
|
|
7102
|
+
signalHandlers.dispose();
|
|
7103
|
+
await slotRegistry.close();
|
|
7104
|
+
await otelShutdown();
|
|
7105
|
+
await shutdownLogger();
|
|
7106
|
+
}
|
|
7182
7107
|
}
|
|
7183
|
-
function
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
return value;
|
|
7108
|
+
function resolveRecordedWorkspacePath$1(stateRootDir, mountPath, executionPlan) {
|
|
7109
|
+
if (!executionPlan.workspaceId) return null;
|
|
7110
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
7187
7111
|
}
|
|
7188
|
-
function
|
|
7189
|
-
let raw;
|
|
7190
|
-
try {
|
|
7191
|
-
raw = readFileSync(path, "utf8");
|
|
7192
|
-
} catch (cause) {
|
|
7193
|
-
if (cause.code === "ENOENT") return null;
|
|
7194
|
-
throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
|
|
7195
|
-
}
|
|
7112
|
+
function resolveMainWorktree$1(mountPath) {
|
|
7196
7113
|
try {
|
|
7197
|
-
return
|
|
7198
|
-
} catch
|
|
7199
|
-
|
|
7114
|
+
return findMainWorktree(mountPath);
|
|
7115
|
+
} catch {
|
|
7116
|
+
return null;
|
|
7200
7117
|
}
|
|
7201
7118
|
}
|
|
7202
|
-
function
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
7206
|
-
renameSync(temp, path);
|
|
7207
|
-
} catch (cause) {
|
|
7208
|
-
try {
|
|
7209
|
-
rmSync(temp, { force: true });
|
|
7210
|
-
} catch {}
|
|
7211
|
-
throw cause;
|
|
7212
|
-
}
|
|
7119
|
+
function runtimeForClaimedTask(runtimes, claimedTask) {
|
|
7120
|
+
if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
|
|
7121
|
+
return requireRuntime(runtimes, claimedTask.profileId);
|
|
7213
7122
|
}
|
|
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
|
-
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");
|
|
7256
|
-
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");
|
|
7257
|
-
for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
|
|
7258
|
-
for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
|
|
7259
|
-
assertStoreName("agent name", alias);
|
|
7260
|
-
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`);
|
|
7261
|
-
}
|
|
7262
|
-
return {
|
|
7263
|
-
version: 2,
|
|
7264
|
-
pendingRegistrations: state.pendingRegistrations,
|
|
7265
|
-
activations: state.activations
|
|
7266
|
-
};
|
|
7267
|
-
}
|
|
7268
|
-
writeAgentServerState(state) {
|
|
7269
|
-
writeJsonAtomic(this.statePath, state);
|
|
7270
|
-
}
|
|
7271
|
-
agentPath(name) {
|
|
7272
|
-
return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
|
|
7273
|
-
}
|
|
7274
|
-
/** Central identity directory, shared with the Go CLI layout. */
|
|
7275
|
-
identityDir(name) {
|
|
7276
|
-
return storeChildPath(this.identitiesDir, "identity alias", name);
|
|
7277
|
-
}
|
|
7278
|
-
get identitySelectorPath() {
|
|
7279
|
-
return join(this.root, "identity-selector.json");
|
|
7280
|
-
}
|
|
7281
|
-
readIdentitySelector() {
|
|
7282
|
-
const selector = readJson(this.identitySelectorPath);
|
|
7283
|
-
if (!selector) return null;
|
|
7284
|
-
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");
|
|
7285
|
-
if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
|
|
7286
|
-
return selector;
|
|
7287
|
-
}
|
|
7288
|
-
writeIdentitySelector(alias) {
|
|
7289
|
-
writeJsonAtomic(this.identitySelectorPath, {
|
|
7290
|
-
version: 1,
|
|
7291
|
-
default_identity: assertStoreName("identity alias", alias)
|
|
7292
|
-
});
|
|
7293
|
-
}
|
|
7294
|
-
/** Identity aliases available in the shared user-level store. */
|
|
7295
|
-
listIdentityAliases() {
|
|
7296
|
-
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));
|
|
7297
|
-
}
|
|
7298
|
-
resolveIdentityAlias(explicit, active) {
|
|
7299
|
-
const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
|
|
7300
|
-
if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
|
|
7301
|
-
return assertStoreName("identity alias", alias);
|
|
7302
|
-
}
|
|
7303
|
-
readAgentConfig(alias) {
|
|
7304
|
-
return readJson(this.agentPath(alias));
|
|
7305
|
-
}
|
|
7306
|
-
writeAgentConfig(alias, config) {
|
|
7307
|
-
mkdirSync(this.identityDir(alias), {
|
|
7308
|
-
recursive: true,
|
|
7309
|
-
mode: 448
|
|
7310
|
-
});
|
|
7311
|
-
writeJsonAtomic(this.agentPath(alias), config);
|
|
7312
|
-
if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
|
|
7313
|
-
}
|
|
7314
|
-
removeAgentConfig(alias) {
|
|
7315
|
-
rmSync(this.agentPath(alias), { force: true });
|
|
7316
|
-
this.clearIdentitySelectorIfDefault(alias);
|
|
7123
|
+
function requireRuntime(runtimes, profileId) {
|
|
7124
|
+
const runtime = runtimes.get(profileId);
|
|
7125
|
+
if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
|
|
7126
|
+
return runtime;
|
|
7127
|
+
}
|
|
7128
|
+
function parseProfileValues(raw) {
|
|
7129
|
+
return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7130
|
+
}
|
|
7131
|
+
function parseCsv(raw) {
|
|
7132
|
+
return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7133
|
+
}
|
|
7134
|
+
function optionalPositiveInt(raw, name, defaultValue) {
|
|
7135
|
+
if (raw === void 0) return defaultValue;
|
|
7136
|
+
const v = Number(raw);
|
|
7137
|
+
if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
|
|
7138
|
+
return v;
|
|
7139
|
+
}
|
|
7140
|
+
function optionalNonNegativeInt(raw, name, defaultValue) {
|
|
7141
|
+
if (raw === void 0) return defaultValue;
|
|
7142
|
+
const v = Number(raw);
|
|
7143
|
+
if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
|
|
7144
|
+
return v;
|
|
7145
|
+
}
|
|
7146
|
+
//#endregion
|
|
7147
|
+
//#region src/cli/drain.ts
|
|
7148
|
+
function runDrain(argv, runtimeAdapter) {
|
|
7149
|
+
return runPolling({
|
|
7150
|
+
argv,
|
|
7151
|
+
serviceName: "moltnet.agent-daemon.drain",
|
|
7152
|
+
stopWhenEmpty: true,
|
|
7153
|
+
modeLabel: "drain",
|
|
7154
|
+
helpText: DRAIN_HELP,
|
|
7155
|
+
runtimeAdapter
|
|
7156
|
+
});
|
|
7157
|
+
}
|
|
7158
|
+
//#endregion
|
|
7159
|
+
//#region src/cli/once.ts
|
|
7160
|
+
async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
7161
|
+
if (isHelpFlag(argv)) {
|
|
7162
|
+
console.log(ONCE_HELP);
|
|
7163
|
+
return 0;
|
|
7317
7164
|
}
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7165
|
+
const { values } = parseArgs({
|
|
7166
|
+
args: argv,
|
|
7167
|
+
options: {
|
|
7168
|
+
...runtimeCommandOptionDefs(),
|
|
7169
|
+
"task-id": {
|
|
7170
|
+
type: "string",
|
|
7171
|
+
short: "t"
|
|
7172
|
+
},
|
|
7173
|
+
team: { type: "string" },
|
|
7174
|
+
sandbox: { type: "string" },
|
|
7175
|
+
profile: { type: "string" }
|
|
7325
7176
|
}
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
return
|
|
7331
|
-
}
|
|
7332
|
-
hasPendingRegistration(alias) {
|
|
7333
|
-
return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
|
|
7334
|
-
}
|
|
7335
|
-
reserveRegistration(alias, apiUrl) {
|
|
7336
|
-
const name = assertStoreName("agent name", alias);
|
|
7337
|
-
const state = this.readAgentServerState();
|
|
7338
|
-
if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
|
|
7339
|
-
state.pendingRegistrations[name] = {
|
|
7340
|
-
apiUrl,
|
|
7341
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7342
|
-
};
|
|
7343
|
-
this.writeAgentServerState(state);
|
|
7344
|
-
}
|
|
7345
|
-
clearPendingRegistration(alias) {
|
|
7346
|
-
const name = assertStoreName("agent name", alias);
|
|
7347
|
-
const state = this.readAgentServerState();
|
|
7348
|
-
delete state.pendingRegistrations[name];
|
|
7349
|
-
this.writeAgentServerState(state);
|
|
7350
|
-
}
|
|
7351
|
-
writeActivation(activation) {
|
|
7352
|
-
const alias = assertStoreName("agent name", activation.alias);
|
|
7353
|
-
validateActivation(alias, activation);
|
|
7354
|
-
const state = this.readAgentServerState();
|
|
7355
|
-
state.activations[alias] = activation;
|
|
7356
|
-
if (activation.source === "managed") delete state.pendingRegistrations[alias];
|
|
7357
|
-
this.writeAgentServerState(state);
|
|
7358
|
-
}
|
|
7359
|
-
listActivations() {
|
|
7360
|
-
return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
|
|
7361
|
-
}
|
|
7362
|
-
get providersPath() {
|
|
7363
|
-
return join(this.root, "providers.json");
|
|
7364
|
-
}
|
|
7365
|
-
readProviders() {
|
|
7366
|
-
const state = readJson(this.providersPath) ?? {};
|
|
7367
|
-
this.validateProviders(state);
|
|
7368
|
-
return state;
|
|
7177
|
+
});
|
|
7178
|
+
if (!values["task-id"]) {
|
|
7179
|
+
console.error("Missing required flag: --task-id\n");
|
|
7180
|
+
console.error(ONCE_HELP);
|
|
7181
|
+
return 1;
|
|
7369
7182
|
}
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7183
|
+
const taskId = values["task-id"];
|
|
7184
|
+
if (!values.profile) {
|
|
7185
|
+
console.error("Missing required flag: --profile\n");
|
|
7186
|
+
console.error(ONCE_HELP);
|
|
7187
|
+
return 1;
|
|
7373
7188
|
}
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7189
|
+
let commandOptions;
|
|
7190
|
+
try {
|
|
7191
|
+
commandOptions = parseRuntimeCommandOptions(values);
|
|
7192
|
+
} catch (err) {
|
|
7193
|
+
if (err instanceof MissingRequiredOptionError) {
|
|
7194
|
+
console.error(`${err.message}\n`);
|
|
7195
|
+
console.error(ONCE_HELP);
|
|
7196
|
+
return 1;
|
|
7378
7197
|
}
|
|
7198
|
+
throw err;
|
|
7379
7199
|
}
|
|
7380
|
-
|
|
7381
|
-
|
|
7200
|
+
const { identity, operations } = commandOptions;
|
|
7201
|
+
if (values.sandbox) {
|
|
7202
|
+
console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
|
|
7203
|
+
return 1;
|
|
7382
7204
|
}
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7205
|
+
const cfg = loadConfig();
|
|
7206
|
+
const credentialSources = {
|
|
7207
|
+
profileRequirements: cfg.profileCredentialRequirements,
|
|
7208
|
+
bindings: cfg.credentialBindings
|
|
7209
|
+
};
|
|
7210
|
+
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
7211
|
+
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
7212
|
+
const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
7213
|
+
let gate = "resolve_agent_context";
|
|
7386
7214
|
try {
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7215
|
+
const resolvedContext = await resolveAgentContext(identity.agent, {
|
|
7216
|
+
agentRootDir: explicitAgentRootDir,
|
|
7217
|
+
credentialSource: cfg.credentialSource,
|
|
7218
|
+
envApiUrl: cfg.apiUrl
|
|
7219
|
+
});
|
|
7220
|
+
gate = "authenticate_and_bind";
|
|
7221
|
+
const whoami = await validateStartupBinding({
|
|
7222
|
+
agent: resolvedContext.agent,
|
|
7223
|
+
teamId: values.team,
|
|
7224
|
+
expectedAgent: cfg.expectedAgent
|
|
7225
|
+
});
|
|
7226
|
+
gate = "resolve_signing_material";
|
|
7227
|
+
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
7228
|
+
credentialSource: cfg.credentialSource,
|
|
7229
|
+
agentDir: resolvedContext.agentDir,
|
|
7230
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
7231
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
7232
|
+
});
|
|
7233
|
+
gate = "validate_scopes";
|
|
7234
|
+
validateDaemonScopes(whoami);
|
|
7235
|
+
gate = "validate_signing_identity";
|
|
7236
|
+
await validateExecutorSigningIdentity({
|
|
7237
|
+
whoami,
|
|
7238
|
+
signingPrivateKey: privateKey
|
|
7239
|
+
});
|
|
7240
|
+
gate = "resolve_agent_identity";
|
|
7241
|
+
const agentIdentity = await resolveDaemonAgentIdentity({
|
|
7242
|
+
agentName: identity.agent,
|
|
7243
|
+
whoami,
|
|
7244
|
+
credentialSource: cfg.credentialSource,
|
|
7245
|
+
agentDir: resolvedContext.agentDir,
|
|
7246
|
+
gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
|
|
7247
|
+
});
|
|
7248
|
+
return {
|
|
7249
|
+
ctx: resolvedContext,
|
|
7250
|
+
signingPrivateKey: privateKey,
|
|
7251
|
+
agentIdentity,
|
|
7252
|
+
hostCapabilitySigner: createLocalSeedSigner({
|
|
7253
|
+
privateKeySeed: privateKey,
|
|
7254
|
+
agent: resolvedContext.agent,
|
|
7255
|
+
identity: agentIdentity
|
|
7256
|
+
})
|
|
7257
|
+
};
|
|
7258
|
+
} catch (error) {
|
|
7259
|
+
await logDaemonStartupFailure({
|
|
7260
|
+
serviceName: "agent-daemon.once",
|
|
7261
|
+
level: cfg.logLevel || (identity.debug ? "debug" : "info"),
|
|
7262
|
+
gate,
|
|
7263
|
+
agent: identity.agent,
|
|
7264
|
+
credentialSource: cfg.credentialSource,
|
|
7265
|
+
error
|
|
7266
|
+
});
|
|
7267
|
+
throw error;
|
|
7391
7268
|
}
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7269
|
+
})();
|
|
7270
|
+
const daemonRootDir = explicitAgentRootDir ?? process.cwd();
|
|
7271
|
+
const profile = await resolveRuntimeProfile({
|
|
7272
|
+
agent: ctx.agent,
|
|
7273
|
+
profile: values.profile,
|
|
7274
|
+
teamId: values.team,
|
|
7275
|
+
cwd: daemonRootDir
|
|
7276
|
+
});
|
|
7277
|
+
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
7278
|
+
name: "agent-daemon.once",
|
|
7279
|
+
level: cfg.logLevel || (identity.debug ? "debug" : "info")
|
|
7280
|
+
});
|
|
7281
|
+
const rootLogger = logger.child({
|
|
7282
|
+
mode: "once",
|
|
7283
|
+
agent: identity.agent,
|
|
7284
|
+
provider: profile.provider,
|
|
7285
|
+
model: profile.model,
|
|
7286
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7287
|
+
temperature: profile.temperature,
|
|
7288
|
+
topP: profile.topP,
|
|
7289
|
+
topK: profile.topK,
|
|
7290
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7291
|
+
runtimeProfileId: profile.id,
|
|
7292
|
+
runtimeProfileName: profile.name
|
|
7293
|
+
});
|
|
7294
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
7295
|
+
const runtimeSessionStore = createApiRuntimeSessionStore({
|
|
7296
|
+
agent: ctx.agent,
|
|
7297
|
+
logger: { warn: (context, message) => rootLogger.warn(context, message) }
|
|
7298
|
+
});
|
|
7299
|
+
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
7300
|
+
const runtimeInstanceId = createRuntimeInstanceId();
|
|
7301
|
+
const { executionPlans, preparedRuntime, sandbox, slotIdentity, stateDirs } = await prepareRuntimeProfile({
|
|
7302
|
+
agent: ctx.agent,
|
|
7303
|
+
agentName: identity.agent,
|
|
7304
|
+
profile,
|
|
7305
|
+
prerequisiteEnv: cfg.profilePrerequisiteEnv,
|
|
7306
|
+
runtimeAdapter,
|
|
7307
|
+
runtimeInstanceId,
|
|
7308
|
+
signingPrivateKey,
|
|
7309
|
+
slotRegistry,
|
|
7310
|
+
runtimeSessionStore,
|
|
7311
|
+
sourceAttemptResolver,
|
|
7312
|
+
warmRetentionSec: operations.warmRetentionSec
|
|
7313
|
+
});
|
|
7314
|
+
const piAgentDir = await resolvePiAgentDir(cfg, sandbox.rootDir, [profile]);
|
|
7315
|
+
process.once("exit", piAgentDir.cleanup);
|
|
7316
|
+
activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
|
|
7317
|
+
const otelShutdown = await initWorkerOtel({
|
|
7318
|
+
serviceName: "moltnet.agent-daemon.once",
|
|
7319
|
+
agent: ctx.agent,
|
|
7320
|
+
endpoint: cfg.otelEndpoint,
|
|
7321
|
+
resourceAttributes: {
|
|
7322
|
+
"moltnet.task.id": taskId,
|
|
7323
|
+
"moltnet.agent.name": identity.agent,
|
|
7324
|
+
"moltnet.credential.source": ctx.credentialSource,
|
|
7325
|
+
"moltnet.llm.provider": profile.provider,
|
|
7326
|
+
"moltnet.llm.model": profile.model,
|
|
7327
|
+
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
7328
|
+
...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
|
|
7329
|
+
...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
|
|
7330
|
+
...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
|
|
7331
|
+
...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
|
|
7332
|
+
"moltnet.runtime_profile.id": profile.id
|
|
7401
7333
|
}
|
|
7402
|
-
|
|
7334
|
+
});
|
|
7335
|
+
rootLogger.info({
|
|
7336
|
+
sandbox: sandbox.path,
|
|
7337
|
+
taskId,
|
|
7338
|
+
heartbeatIntervalMs: operations.heartbeatIntervalMs,
|
|
7339
|
+
maxTurns: profile.maxTurns,
|
|
7340
|
+
maxBashTimeouts: profile.maxBashTimeouts,
|
|
7341
|
+
warmRetentionSec: operations.warmRetentionSec,
|
|
7342
|
+
profileId: profile.id,
|
|
7343
|
+
piAgentDir: piAgentDir.path,
|
|
7344
|
+
piAgentDirSource: piAgentDir.source
|
|
7345
|
+
}, "agent-daemon.starting");
|
|
7346
|
+
try {
|
|
7347
|
+
const reaped = await reapRuntimeSlotResources({
|
|
7348
|
+
onIssue: (issue) => {
|
|
7349
|
+
rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
|
|
7350
|
+
},
|
|
7351
|
+
runtimeSlotStore: slotRegistry,
|
|
7352
|
+
taskReader: ctx.agent.tasks
|
|
7353
|
+
}, {
|
|
7354
|
+
agentName: identity.agent,
|
|
7355
|
+
mainWorktree: resolveMainWorktree(sandbox.rootDir),
|
|
7356
|
+
runtimeInstanceId,
|
|
7357
|
+
runtimeProfileId: profile.id,
|
|
7358
|
+
sessionRootDir: stateDirs.piSessionsDir,
|
|
7359
|
+
scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
|
|
7360
|
+
teamId: profile.teamId
|
|
7361
|
+
});
|
|
7362
|
+
if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
|
|
7363
|
+
} catch (err) {
|
|
7364
|
+
rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
|
|
7403
7365
|
}
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7366
|
+
let runtime = null;
|
|
7367
|
+
let activeAttemptN = null;
|
|
7368
|
+
const signalHandlers = installShutdownSignalHandlers({
|
|
7369
|
+
logDrain: (signal) => {
|
|
7370
|
+
rootLogger.warn({
|
|
7371
|
+
signal,
|
|
7372
|
+
taskId
|
|
7373
|
+
}, "agent-daemon.draining");
|
|
7374
|
+
},
|
|
7375
|
+
drain: (signal) => {
|
|
7376
|
+
runtime?.stop(`agent-daemon received ${signal}`);
|
|
7377
|
+
abortActiveAttemptOnSignal({
|
|
7378
|
+
active: activeAttemptN === null ? null : {
|
|
7379
|
+
taskId,
|
|
7380
|
+
attemptN: activeAttemptN
|
|
7381
|
+
},
|
|
7382
|
+
signal,
|
|
7383
|
+
abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
|
|
7384
|
+
logFailure: (err, current) => {
|
|
7385
|
+
try {
|
|
7386
|
+
rootLogger.warn({
|
|
7387
|
+
err: err instanceof Error ? err.message : String(err),
|
|
7388
|
+
taskId: current.taskId,
|
|
7389
|
+
attemptN: current.attemptN
|
|
7390
|
+
}, "agent-daemon.abort_on_signal_failed");
|
|
7391
|
+
} catch (logErr) {
|
|
7392
|
+
process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
});
|
|
7398
|
+
try {
|
|
7399
|
+
const rawExecuteTask = preparedRuntime.createTaskExecutor({
|
|
7400
|
+
agentName: identity.agent,
|
|
7401
|
+
moltnetAgent: ctx.agent,
|
|
7402
|
+
agentIdentity,
|
|
7403
|
+
hostCapabilitySigner,
|
|
7404
|
+
hostCapabilityLogger: rootLogger,
|
|
7405
|
+
agentRootDir: ctx.agentRootDir,
|
|
7406
|
+
mountPath: sandbox.rootDir,
|
|
7407
|
+
provider: profile.provider,
|
|
7408
|
+
model: profile.model,
|
|
7409
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7410
|
+
temperature: profile.temperature,
|
|
7411
|
+
topP: profile.topP,
|
|
7412
|
+
topK: profile.topK,
|
|
7413
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
7414
|
+
sandboxConfig: sandbox.config,
|
|
7415
|
+
forwardEnv: profile.requiredEnv,
|
|
7416
|
+
onVmDiagnostic: (diagnostic) => {
|
|
7417
|
+
const fields = {
|
|
7418
|
+
event: diagnostic.event,
|
|
7419
|
+
...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
|
|
7420
|
+
};
|
|
7421
|
+
if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
|
|
7422
|
+
else rootLogger.info(fields, diagnostic.message);
|
|
7423
|
+
},
|
|
7424
|
+
runtimeProfileContext: profile.context,
|
|
7425
|
+
runtimeProfileId: profile.id,
|
|
7426
|
+
toolEnforcement: profile.toolEnforcement,
|
|
7427
|
+
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
7428
|
+
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
7429
|
+
toolPolicyLogger: rootLogger,
|
|
7430
|
+
maxTurns: profile.maxTurns,
|
|
7431
|
+
maxBashTimeouts: profile.maxBashTimeouts
|
|
7410
7432
|
});
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7433
|
+
const executeTask = async (claimedTask, reporter) => {
|
|
7434
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
7435
|
+
config: runtimeCredentialConfig,
|
|
7436
|
+
profile,
|
|
7437
|
+
offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
|
|
7438
|
+
registry: createNodeSecretProviderRegistry(),
|
|
7439
|
+
executorFingerprint: preparedRuntime.attestor.fingerprint,
|
|
7440
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
7441
|
+
taskId: claimedTask.task.id,
|
|
7442
|
+
attemptN: claimedTask.attemptN,
|
|
7443
|
+
logger: rootLogger
|
|
7444
|
+
});
|
|
7445
|
+
let executionPlan;
|
|
7446
|
+
try {
|
|
7447
|
+
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
7448
|
+
} catch (err) {
|
|
7449
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7450
|
+
rootLogger.warn({
|
|
7451
|
+
taskId: claimedTask.task.id,
|
|
7452
|
+
attemptN: claimedTask.attemptN,
|
|
7453
|
+
err: message
|
|
7454
|
+
}, "agent-daemon.execution_plan_failed");
|
|
7455
|
+
return {
|
|
7456
|
+
taskId: claimedTask.task.id,
|
|
7457
|
+
attemptN: claimedTask.attemptN,
|
|
7458
|
+
status: "failed",
|
|
7459
|
+
output: null,
|
|
7460
|
+
outputCid: null,
|
|
7461
|
+
usage: {
|
|
7462
|
+
inputTokens: 0,
|
|
7463
|
+
outputTokens: 0
|
|
7464
|
+
},
|
|
7465
|
+
durationMs: 0,
|
|
7466
|
+
error: {
|
|
7467
|
+
code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
|
|
7468
|
+
message,
|
|
7469
|
+
retryable: false
|
|
7470
|
+
}
|
|
7471
|
+
};
|
|
7472
|
+
}
|
|
7473
|
+
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
7474
|
+
...slotIdentity,
|
|
7475
|
+
runtimeProfileId: profile.id,
|
|
7476
|
+
provider: profile.provider,
|
|
7477
|
+
model: profile.model,
|
|
7478
|
+
teamId: claimedTask.task.teamId,
|
|
7479
|
+
slotKey: executionPlan.slotKey,
|
|
7480
|
+
taskType: claimedTask.task.taskType,
|
|
7481
|
+
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
7482
|
+
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
7483
|
+
workspaceId: executionPlan.workspaceId,
|
|
7484
|
+
worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, sandbox.rootDir, executionPlan),
|
|
7485
|
+
worktreeBranch: executionPlan.worktreeBranch,
|
|
7486
|
+
workspaceKind: executionPlan.workspaceKind,
|
|
7487
|
+
lastTaskId: claimedTask.task.id,
|
|
7488
|
+
lastAttemptN: claimedTask.attemptN,
|
|
7489
|
+
warmRetentionSec: operations.warmRetentionSec
|
|
7490
|
+
});
|
|
7491
|
+
activeAttemptN = claimedTask.attemptN;
|
|
7492
|
+
try {
|
|
7493
|
+
return await runWithDaemonRuntimeContext({
|
|
7494
|
+
profileId: profile.id,
|
|
7495
|
+
profileName: profile.name,
|
|
7496
|
+
provider: profile.provider,
|
|
7497
|
+
model: profile.model,
|
|
7498
|
+
thinkingLevel: profile.thinkingLevel,
|
|
7499
|
+
temperature: profile.temperature,
|
|
7500
|
+
topP: profile.topP,
|
|
7501
|
+
topK: profile.topK,
|
|
7502
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
7503
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
7504
|
+
} finally {
|
|
7505
|
+
activeAttemptN = null;
|
|
7506
|
+
executionPlans.delete(claimedTask);
|
|
7507
|
+
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, operations.warmRetentionSec);
|
|
7508
|
+
}
|
|
7415
7509
|
};
|
|
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
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7510
|
+
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
7511
|
+
gh: createGhCliClient(),
|
|
7512
|
+
logger: rootLogger
|
|
7513
|
+
});
|
|
7514
|
+
runtime = new AgentRuntime({
|
|
7515
|
+
logger: rootLogger,
|
|
7516
|
+
source: new ApiTaskSource({
|
|
7517
|
+
agent: ctx.agent,
|
|
7518
|
+
taskId,
|
|
7519
|
+
teamId: profile.teamId,
|
|
7520
|
+
profileId: profile.id,
|
|
7521
|
+
executorFingerprint: preparedRuntime.attestor.fingerprint
|
|
7522
|
+
}),
|
|
7523
|
+
makeReporter: () => new ApiTaskReporter({
|
|
7524
|
+
tasks: ctx.agent.tasks,
|
|
7525
|
+
teamId: profile.teamId,
|
|
7526
|
+
heartbeatIntervalMs: operations.heartbeatIntervalMs,
|
|
7527
|
+
logger: rootLogger
|
|
7528
|
+
}),
|
|
7529
|
+
onTaskFinished: async (output, claimedTask) => {
|
|
7530
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
7531
|
+
let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
|
|
7532
|
+
if (resolved?.session?.sessionDir) try {
|
|
7533
|
+
const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
|
|
7534
|
+
await runtimeSessionStore.uploadAttemptFinal({
|
|
7535
|
+
attemptN: claimedTask.attemptN,
|
|
7536
|
+
parentSessionId: parentSession?.id ?? null,
|
|
7537
|
+
sessionDir: resolved.session.sessionDir,
|
|
7538
|
+
sessionKind: resolveRuntimeSessionKind(claimedTask),
|
|
7539
|
+
sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
|
|
7540
|
+
sourceSlotId: resolved.slot.id,
|
|
7541
|
+
taskId: claimedTask.task.id,
|
|
7542
|
+
teamId: claimedTask.task.teamId
|
|
7543
|
+
});
|
|
7544
|
+
} catch (err) {
|
|
7545
|
+
rootLogger.error({
|
|
7546
|
+
err,
|
|
7547
|
+
taskId: claimedTask.task.id,
|
|
7548
|
+
attemptN: claimedTask.attemptN
|
|
7549
|
+
}, "agent-daemon.runtime_session_upload_failed");
|
|
7550
|
+
terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
|
|
7551
|
+
}
|
|
7552
|
+
return finalizeTask(ctx.agent, terminalOutput, {
|
|
7553
|
+
task: claimedTask.task,
|
|
7554
|
+
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
7555
|
+
retryTriage: createRuntimeProfileRetryTriage({
|
|
7556
|
+
runtimeProfile: profile,
|
|
7557
|
+
piAgentDir: piAgentDir.path,
|
|
7558
|
+
cwd: ctx.agentRootDir
|
|
7559
|
+
}),
|
|
7560
|
+
executorAttestor: preparedRuntime.attestor,
|
|
7561
|
+
writeCorrelationAnchors,
|
|
7562
|
+
log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
|
|
7457
7563
|
});
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7564
|
+
},
|
|
7565
|
+
executeTask
|
|
7566
|
+
});
|
|
7567
|
+
const [output] = await runtime.start();
|
|
7568
|
+
if (!output) {
|
|
7569
|
+
rootLogger.error({}, "agent-daemon.no_output");
|
|
7570
|
+
return 1;
|
|
7463
7571
|
}
|
|
7464
|
-
|
|
7572
|
+
console.log("\n[done] TaskOutput:");
|
|
7573
|
+
console.log(JSON.stringify(output, null, 2));
|
|
7574
|
+
return output.status === "completed" ? 0 : 1;
|
|
7575
|
+
} finally {
|
|
7576
|
+
signalHandlers.dispose();
|
|
7577
|
+
await slotRegistry.close();
|
|
7578
|
+
await otelShutdown();
|
|
7579
|
+
await shutdownLogger();
|
|
7465
7580
|
}
|
|
7466
|
-
}
|
|
7467
|
-
function
|
|
7468
|
-
|
|
7581
|
+
}
|
|
7582
|
+
function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
|
|
7583
|
+
if (!executionPlan.workspaceId) return null;
|
|
7584
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
|
|
7585
|
+
}
|
|
7586
|
+
function resolveMainWorktree(mountPath) {
|
|
7469
7587
|
try {
|
|
7470
|
-
|
|
7588
|
+
return findMainWorktree(mountPath);
|
|
7471
7589
|
} catch {
|
|
7472
|
-
return
|
|
7590
|
+
return null;
|
|
7473
7591
|
}
|
|
7474
|
-
if (info.isSymbolicLink()) return 0;
|
|
7475
|
-
if (!info.isDirectory()) return info.size;
|
|
7476
|
-
let total = 0;
|
|
7477
|
-
for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
|
|
7478
|
-
return total;
|
|
7479
|
-
}
|
|
7480
|
-
function isRecord$1(value) {
|
|
7481
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7482
|
-
}
|
|
7483
|
-
function storeChildPath(root, kind, value, suffix = "") {
|
|
7484
|
-
const name = assertStoreName(kind, value);
|
|
7485
|
-
const normalizedRoot = resolve(root);
|
|
7486
|
-
const candidate = resolve(normalizedRoot, `${name}${suffix}`);
|
|
7487
|
-
if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
|
|
7488
|
-
return candidate;
|
|
7489
|
-
}
|
|
7490
|
-
function isStrictDescendant(root, candidate) {
|
|
7491
|
-
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
7492
|
-
return candidate !== root && candidate.startsWith(rootPrefix);
|
|
7493
7592
|
}
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
"createdAt"
|
|
7506
|
-
].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
|
|
7507
|
-
if (activation.source === "managed") {
|
|
7508
|
-
if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
|
|
7509
|
-
return;
|
|
7510
|
-
}
|
|
7511
|
-
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();
|
|
7593
|
+
//#endregion
|
|
7594
|
+
//#region src/cli/poll.ts
|
|
7595
|
+
function runPoll(argv, runtimeAdapter) {
|
|
7596
|
+
return runPolling({
|
|
7597
|
+
argv,
|
|
7598
|
+
serviceName: "moltnet.agent-daemon.poll",
|
|
7599
|
+
stopWhenEmpty: false,
|
|
7600
|
+
modeLabel: "poll",
|
|
7601
|
+
helpText: POLL_HELP,
|
|
7602
|
+
runtimeAdapter
|
|
7603
|
+
});
|
|
7512
7604
|
}
|
|
7513
7605
|
//#endregion
|
|
7514
7606
|
//#region src/lib/provider-lock.ts
|
|
@@ -9199,7 +9291,6 @@ function snapshot(login) {
|
|
|
9199
9291
|
* the current config and verifies it with authenticated `whoami` before use.
|
|
9200
9292
|
*/
|
|
9201
9293
|
var MAX_CONFIG_BYTES = 64 * 1024;
|
|
9202
|
-
var IDENTITY_OPERATION_TIMEOUT_MS = 15e3;
|
|
9203
9294
|
var pendingAliases = /* @__PURE__ */ new WeakMap();
|
|
9204
9295
|
var AgentServerIdentityError = class extends Error {
|
|
9205
9296
|
name = "AgentServerIdentityError";
|
|
@@ -9222,7 +9313,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9222
9313
|
const alias = assertStoreName("agent name", input.name);
|
|
9223
9314
|
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");
|
|
9224
9315
|
const releaseAlias = reserveAlias(store, alias);
|
|
9225
|
-
let registered
|
|
9316
|
+
let registered;
|
|
9226
9317
|
try {
|
|
9227
9318
|
let apiUrl;
|
|
9228
9319
|
try {
|
|
@@ -9231,61 +9322,40 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9231
9322
|
throw new AgentServerIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
|
|
9232
9323
|
}
|
|
9233
9324
|
store.reserveRegistration(alias, apiUrl);
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
subject_type: "agent",
|
|
9255
|
-
registered_at: now,
|
|
9256
|
-
agent_key_ref: agentKeyReference,
|
|
9257
|
-
keys: {
|
|
9258
|
-
public_key: publicKey,
|
|
9259
|
-
fingerprint,
|
|
9260
|
-
private_key_ref: seedReference
|
|
9261
|
-
},
|
|
9262
|
-
endpoints: {
|
|
9263
|
-
api: result.apiUrl,
|
|
9264
|
-
mcp: deriveMcpUrl(result.apiUrl)
|
|
9325
|
+
try {
|
|
9326
|
+
registered = await register({
|
|
9327
|
+
name: alias,
|
|
9328
|
+
apiUrl,
|
|
9329
|
+
credentialType: "agent_key",
|
|
9330
|
+
enrollmentToken: input.enrollmentToken,
|
|
9331
|
+
secretProvider: secrets,
|
|
9332
|
+
configDir: store.identityDir(alias),
|
|
9333
|
+
publishAlias: false,
|
|
9334
|
+
signal: input.signal,
|
|
9335
|
+
connectAgent
|
|
9336
|
+
});
|
|
9337
|
+
} catch (cause) {
|
|
9338
|
+
if (cause instanceof RegisterIdentityError) {
|
|
9339
|
+
if (cause.nothingRegistered) {
|
|
9340
|
+
store.clearPendingRegistration(alias);
|
|
9341
|
+
throw new AgentServerIdentityError(cause.code === "alias_exists" ? "agent_exists" : "registration_failed", cause.message, { cause });
|
|
9342
|
+
}
|
|
9343
|
+
if (cause.code === "unsupported_credential") throw new AgentServerIdentityError("unsupported_credential", `${cause.message}; agent server manages agent-key credentials only`, { cause });
|
|
9344
|
+
if (cause.code === "identity_mismatch") throw new AgentServerIdentityError("verification_failed", cause.message, { cause });
|
|
9265
9345
|
}
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
await secrets.write(seedReference.key, privateKey);
|
|
9270
|
-
const whoami = await callWhoami(connectAgent, {
|
|
9271
|
-
agentKey: result.credentials.secret,
|
|
9272
|
-
apiUrl: result.apiUrl
|
|
9273
|
-
}, store.agentPath(alias), input.signal);
|
|
9274
|
-
assertIdentityMatches(whoami, {
|
|
9275
|
-
publicKey,
|
|
9276
|
-
fingerprint
|
|
9277
|
-
}, "authenticated whoami", `new managed agent "${alias}"`);
|
|
9278
|
-
assertSubjectMatches(whoami, config, "authenticated whoami", `managed config ${store.agentPath(alias)}`);
|
|
9346
|
+
throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, cause instanceof RegisterIdentityError ? cause : {}), { cause });
|
|
9347
|
+
}
|
|
9348
|
+
const { config, whoami, identity } = registered;
|
|
9279
9349
|
const boundTeamId = boundTeamIdFromWhoami(whoami);
|
|
9280
9350
|
const activation = {
|
|
9281
9351
|
alias,
|
|
9282
9352
|
source: "managed",
|
|
9283
9353
|
subjectId: whoami.subjectId,
|
|
9284
|
-
publicKey,
|
|
9285
|
-
fingerprint,
|
|
9354
|
+
publicKey: identity.publicKey,
|
|
9355
|
+
fingerprint: identity.fingerprint,
|
|
9286
9356
|
...boundTeamId ? { boundTeamId } : {},
|
|
9287
|
-
createdAt:
|
|
9288
|
-
apiUrl:
|
|
9357
|
+
createdAt: config.registered_at,
|
|
9358
|
+
apiUrl: config.endpoints.api
|
|
9289
9359
|
};
|
|
9290
9360
|
store.writeActivation(activation);
|
|
9291
9361
|
return {
|
|
@@ -9294,18 +9364,27 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9294
9364
|
...boundTeamId ? { boundTeamId } : {}
|
|
9295
9365
|
};
|
|
9296
9366
|
} catch (cause) {
|
|
9297
|
-
if (
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9367
|
+
if (cause instanceof AgentServerIdentityError) throw cause;
|
|
9368
|
+
if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, registered ? {
|
|
9369
|
+
...registered.identity,
|
|
9370
|
+
configPath: registered.configPath
|
|
9371
|
+
} : {}), { cause });
|
|
9302
9372
|
throw cause;
|
|
9303
9373
|
} finally {
|
|
9304
9374
|
releaseAlias();
|
|
9305
9375
|
}
|
|
9306
9376
|
}
|
|
9307
|
-
|
|
9308
|
-
|
|
9377
|
+
/**
|
|
9378
|
+
* Guidance for a registration that may have committed. The HTTP response
|
|
9379
|
+
* carries only this message, so it names the reconcile call and, when the
|
|
9380
|
+
* server outcome is unknown, the fingerprint to look up first. Resume needs
|
|
9381
|
+
* the written config, so without one only abandon is offered.
|
|
9382
|
+
*/
|
|
9383
|
+
function incompleteRegistrationMessage(alias, known) {
|
|
9384
|
+
const endpoint = `POST /v1/agents/${alias}/reconcile`;
|
|
9385
|
+
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"}.`;
|
|
9386
|
+
if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}`;
|
|
9387
|
+
return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}`;
|
|
9309
9388
|
}
|
|
9310
9389
|
/** Resume a fully persisted registration or explicitly abandon local recovery. */
|
|
9311
9390
|
async function reconcileManagedRegistration(store, secrets, aliasInput, action, connectAgent = connect, signal) {
|
|
@@ -9528,10 +9607,6 @@ async function callWhoami(connectAgent, options, source, signal) {
|
|
|
9528
9607
|
throw verificationError(`could not authenticate ${source} against the API`, cause);
|
|
9529
9608
|
}
|
|
9530
9609
|
}
|
|
9531
|
-
function boundedIdentitySignal(signal) {
|
|
9532
|
-
const timeout = AbortSignal.timeout(IDENTITY_OPERATION_TIMEOUT_MS);
|
|
9533
|
-
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
9534
|
-
}
|
|
9535
9610
|
function identityFromConfig(config) {
|
|
9536
9611
|
const publicKey = config?.keys?.public_key?.trim();
|
|
9537
9612
|
const fingerprint = config?.keys?.fingerprint?.trim();
|
|
@@ -9691,6 +9766,7 @@ var RunManager = class {
|
|
|
9691
9766
|
extraArgs: ["--agent-root", agentRoot]
|
|
9692
9767
|
};
|
|
9693
9768
|
})();
|
|
9769
|
+
const runtimeSettings = this.options.runtimeSettings ?? DEFAULT_LOCAL_OPERATIONAL_SETTINGS;
|
|
9694
9770
|
const args = [
|
|
9695
9771
|
...runtimeModule ? ["--runtime", runtimeModule] : [],
|
|
9696
9772
|
spec.mode,
|
|
@@ -9701,6 +9777,10 @@ var RunManager = class {
|
|
|
9701
9777
|
...spec.profiles.flatMap((profile) => ["--profile", profile]),
|
|
9702
9778
|
"--task-types",
|
|
9703
9779
|
spec.taskTypes.join(","),
|
|
9780
|
+
"--heartbeat-interval-ms",
|
|
9781
|
+
String(runtimeSettings.heartbeatIntervalMs),
|
|
9782
|
+
"--warm-retention-sec",
|
|
9783
|
+
String(runtimeSettings.warmRetentionSec),
|
|
9704
9784
|
...target.extraArgs
|
|
9705
9785
|
];
|
|
9706
9786
|
if (activation.source === "managed") {
|
|
@@ -9776,20 +9856,8 @@ var RunManager = class {
|
|
|
9776
9856
|
recursive: true,
|
|
9777
9857
|
mode: 448
|
|
9778
9858
|
});
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
|
|
9782
|
-
api: provider.api,
|
|
9783
|
-
...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
|
|
9784
|
-
baseUrl: provider.baseUrl,
|
|
9785
|
-
models: provider.models
|
|
9786
|
-
}]))
|
|
9787
|
-
});
|
|
9788
|
-
try {
|
|
9789
|
-
(this.options.symlinkImpl ?? symlinkSync)(this.store.piAuthJsonPath, join(piDir, "auth.json"));
|
|
9790
|
-
} catch (cause) {
|
|
9791
|
-
throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
|
|
9792
|
-
}
|
|
9859
|
+
writeStorePiConfig(piDir, providers);
|
|
9860
|
+
linkPiAuth(this.store.piAuthJsonPath, piDir, this.options.symlinkImpl ?? symlinkSync);
|
|
9793
9861
|
const entry = this.entrypoint();
|
|
9794
9862
|
logStream = createWriteStream(logPath, {
|
|
9795
9863
|
fd: openSync(logPath, "a", 384),
|
|
@@ -10383,7 +10451,11 @@ var AgentServerStatusSchema = Type.Object({
|
|
|
10383
10451
|
identities: Type.Array(schemaRef(AgentServerIdentitySchema)),
|
|
10384
10452
|
selectedIdentity: Type.Optional(Type.String()),
|
|
10385
10453
|
providers: Type.Record(Type.String(), schemaRef(AgentServerProviderSchema)),
|
|
10386
|
-
runs: Type.Array(schemaRef(AgentServerRunSchema))
|
|
10454
|
+
runs: Type.Array(schemaRef(AgentServerRunSchema)),
|
|
10455
|
+
runtimeSettings: Type.Object({
|
|
10456
|
+
heartbeatIntervalMs: Type.Integer({ minimum: 0 }),
|
|
10457
|
+
warmRetentionSec: Type.Integer({ minimum: 0 })
|
|
10458
|
+
})
|
|
10387
10459
|
}, { $id: "AgentServerStatus" });
|
|
10388
10460
|
var PairingStartedSchema = Type.Object({
|
|
10389
10461
|
pairingId: Type.String(),
|
|
@@ -10857,7 +10929,8 @@ function registerStatusRoute(app, options, requirePairedOrigin) {
|
|
|
10857
10929
|
identities: identityViews(store),
|
|
10858
10930
|
...selected ? { selectedIdentity: selected } : {},
|
|
10859
10931
|
providers: options.providers.list(),
|
|
10860
|
-
runs: runViews(runs)
|
|
10932
|
+
runs: runViews(runs),
|
|
10933
|
+
runtimeSettings: options.runtimeSettings ?? DEFAULT_LOCAL_OPERATIONAL_SETTINGS
|
|
10861
10934
|
};
|
|
10862
10935
|
});
|
|
10863
10936
|
}
|
|
@@ -11163,7 +11236,7 @@ function normalizeAgentServerError(error) {
|
|
|
11163
11236
|
message: error.message
|
|
11164
11237
|
};
|
|
11165
11238
|
if (error instanceof AgentServerIdentityError) return {
|
|
11166
|
-
statusCode: error.code === "agent_exists" ? 409 : 400,
|
|
11239
|
+
statusCode: error.code === "agent_exists" || error.code === "registration_incomplete" ? 409 : 400,
|
|
11167
11240
|
code: error.code,
|
|
11168
11241
|
message: error.message
|
|
11169
11242
|
};
|
|
@@ -11357,6 +11430,8 @@ async function runAgentServer(argv) {
|
|
|
11357
11430
|
"allowed-origins": { type: "string" },
|
|
11358
11431
|
root: { type: "string" },
|
|
11359
11432
|
"api-url": { type: "string" },
|
|
11433
|
+
"heartbeat-interval-ms": { type: "string" },
|
|
11434
|
+
"warm-retention-sec": { type: "string" },
|
|
11360
11435
|
remove: { type: "boolean" }
|
|
11361
11436
|
}
|
|
11362
11437
|
});
|
|
@@ -11368,6 +11443,7 @@ async function runAgentServer(argv) {
|
|
|
11368
11443
|
const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
|
|
11369
11444
|
const root = values.root ?? resolveAgentServerRoot({ root: envConfig.root });
|
|
11370
11445
|
const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
|
|
11446
|
+
const runtimeSettings = parseLocalOperationalSettings(values);
|
|
11371
11447
|
const store = new AgentServerStore(root).ensure();
|
|
11372
11448
|
if (trustRequested) return runTrustCommand(commandArgs, root);
|
|
11373
11449
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -11401,7 +11477,8 @@ async function runAgentServer(argv) {
|
|
|
11401
11477
|
externalSecretProviders,
|
|
11402
11478
|
baseEnv: processEnvSnapshot(),
|
|
11403
11479
|
logger,
|
|
11404
|
-
runtimeRegistry: new RuntimeRegistry(store.root)
|
|
11480
|
+
runtimeRegistry: new RuntimeRegistry(store.root),
|
|
11481
|
+
runtimeSettings
|
|
11405
11482
|
});
|
|
11406
11483
|
const tls = isMacos() ? await ensureTrustedLocalTls(root) : void 0;
|
|
11407
11484
|
const app = buildAgentServer({
|
|
@@ -11420,6 +11497,7 @@ async function runAgentServer(argv) {
|
|
|
11420
11497
|
cert: tls.cert
|
|
11421
11498
|
} } : {},
|
|
11422
11499
|
defaultApiUrl,
|
|
11500
|
+
runtimeSettings,
|
|
11423
11501
|
...envConfig.activeIdentity ? { activeIdentity: envConfig.activeIdentity } : {},
|
|
11424
11502
|
version: "dev",
|
|
11425
11503
|
logger,
|
|
@@ -11643,7 +11721,7 @@ async function runSyncSessions(argv) {
|
|
|
11643
11721
|
const { values } = parseArgs({
|
|
11644
11722
|
args: argv,
|
|
11645
11723
|
options: {
|
|
11646
|
-
...
|
|
11724
|
+
...identityOptionDefs(),
|
|
11647
11725
|
team: { type: "string" },
|
|
11648
11726
|
"runtime-profile-id": { type: "string" },
|
|
11649
11727
|
state: { type: "string" },
|
|
@@ -11656,9 +11734,9 @@ async function runSyncSessions(argv) {
|
|
|
11656
11734
|
console.error(SYNC_SESSIONS_HELP);
|
|
11657
11735
|
return 1;
|
|
11658
11736
|
}
|
|
11659
|
-
let
|
|
11737
|
+
let identity;
|
|
11660
11738
|
try {
|
|
11661
|
-
|
|
11739
|
+
identity = parseIdentityProcessOptions(values);
|
|
11662
11740
|
} catch (err) {
|
|
11663
11741
|
if (err instanceof MissingRequiredOptionError) {
|
|
11664
11742
|
console.error(`${err.message}\n`);
|
|
@@ -11672,7 +11750,7 @@ async function runSyncSessions(argv) {
|
|
|
11672
11750
|
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
11673
11751
|
const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
|
|
11674
11752
|
const cfg = loadConfig();
|
|
11675
|
-
const ctx = await resolveAgentContext(
|
|
11753
|
+
const ctx = await resolveAgentContext(identity.agent, {
|
|
11676
11754
|
credentialSource: cfg.credentialSource,
|
|
11677
11755
|
envApiUrl: cfg.apiUrl,
|
|
11678
11756
|
agentRootDir: explicitAgentRootDir
|
|
@@ -11687,7 +11765,7 @@ async function runSyncSessions(argv) {
|
|
|
11687
11765
|
runtimeSlotStore: createApiRuntimeSlotStore({ agent: ctx.agent }),
|
|
11688
11766
|
taskReader: ctx.agent.tasks
|
|
11689
11767
|
}, {
|
|
11690
|
-
agentName:
|
|
11768
|
+
agentName: identity.agent,
|
|
11691
11769
|
dryRun: values["dry-run"] === true,
|
|
11692
11770
|
limit,
|
|
11693
11771
|
runtimeProfileId: values["runtime-profile-id"],
|
|
@@ -11864,7 +11942,7 @@ async function writeCache(cache) {
|
|
|
11864
11942
|
}
|
|
11865
11943
|
//#endregion
|
|
11866
11944
|
//#region src/version.ts
|
|
11867
|
-
var DAEMON_VERSION = "0.
|
|
11945
|
+
var DAEMON_VERSION = "0.57.1";
|
|
11868
11946
|
//#endregion
|
|
11869
11947
|
//#region src/cli.ts
|
|
11870
11948
|
async function runAgentDaemonCli(options) {
|