@themoltnet/agent-daemon 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/main.js +908 -243
- package/package.json +9 -8
package/dist/main.js
CHANGED
|
@@ -9,10 +9,10 @@ import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
|
|
|
9
9
|
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
10
10
|
import crypto$1, { createHash } from "crypto";
|
|
11
11
|
import { createHash as createHash$1 } from "node:crypto";
|
|
12
|
-
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
|
+
import path, { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { parseArgs, parseEnv, promisify } from "node:util";
|
|
14
14
|
import { execFile, execFileSync } from "node:child_process";
|
|
15
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
15
|
+
import { accessSync, constants, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
16
16
|
import { DatabaseSync } from "node:sqlite";
|
|
17
17
|
import { ROOT_CONTEXT, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
18
18
|
import { pino, transport } from "pino";
|
|
@@ -836,8 +836,8 @@ Reset();
|
|
|
836
836
|
*
|
|
837
837
|
* Idempotent: registration is guarded by `Format.Has(...)`.
|
|
838
838
|
*/
|
|
839
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
840
|
-
if (!Has("uuid")) Set$1("uuid", (v) => UUID_RE.test(v));
|
|
839
|
+
var UUID_RE$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
840
|
+
if (!Has("uuid")) Set$1("uuid", (v) => UUID_RE$1.test(v));
|
|
841
841
|
if (!Has("date-time")) Set$1("date-time", (v) => !Number.isNaN(Date.parse(v)));
|
|
842
842
|
//#endregion
|
|
843
843
|
//#region ../../node_modules/.pnpm/typebox@1.2.8/node_modules/typebox/build/system/memory/metrics.mjs
|
|
@@ -4273,18 +4273,120 @@ var TaskContext = _Array_(_Object_({
|
|
|
4273
4273
|
maxItems: 5
|
|
4274
4274
|
});
|
|
4275
4275
|
//#endregion
|
|
4276
|
-
//#region ../../libs/tasks/src/
|
|
4277
|
-
|
|
4276
|
+
//#region ../../libs/tasks/src/rubric.ts
|
|
4277
|
+
/**
|
|
4278
|
+
* Rubric — structured acceptance criteria used by judgment tasks.
|
|
4279
|
+
*
|
|
4280
|
+
* Phase 1 (this PR): rubrics are embedded in task inputs. Their integrity
|
|
4281
|
+
* is pinned via the task's `input_cid` (which covers the whole input,
|
|
4282
|
+
* including the inline rubric). No separate storage, no CRUD.
|
|
4283
|
+
*
|
|
4284
|
+
* Phase 2 (see #881): rubrics become a first-class resource with their
|
|
4285
|
+
* own signed rows and CIDv1 lookup. The schema below is designed to
|
|
4286
|
+
* carry forward unchanged — only storage and addressing differ.
|
|
4287
|
+
*
|
|
4288
|
+
* Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
|
|
4289
|
+
* informational fields the author fills in; no uniqueness is enforced.
|
|
4290
|
+
* `contentHash` is optional in Phase 1 because the *task*'s input_cid
|
|
4291
|
+
* is the authoritative commitment.
|
|
4292
|
+
*/
|
|
4293
|
+
/**
|
|
4294
|
+
* How a judge must score a single criterion.
|
|
4295
|
+
*
|
|
4296
|
+
* - `llm_score`: 0..1 continuous, `rationale` required. Smooths failures
|
|
4297
|
+
* into the gradient — use `llm_checklist` instead for properties where
|
|
4298
|
+
* a single failure is a real failure (grounding, faithfulness).
|
|
4299
|
+
* - `llm_checklist`: judge enumerates per-claim assertions with
|
|
4300
|
+
* `{passed, evidence}`. The criterion's numeric `score` is derived:
|
|
4301
|
+
* `1` iff every assertion passes, else `0`. Per-claim evidence is the
|
|
4302
|
+
* dataset for cluster-analysis of failure modes. See #999.
|
|
4303
|
+
* - `boolean`: 0 or 1, `rationale` optional.
|
|
4304
|
+
* - `deterministic_signature_check`: judge runs a signature check;
|
|
4305
|
+
* result is 0 or 1. No LLM discretion.
|
|
4306
|
+
* - `deterministic_coverage_check`: every referenced source entry
|
|
4307
|
+
* appears in the rendered output; 0 or 1.
|
|
4308
|
+
*/
|
|
4309
|
+
var RubricScoringMode = Union([
|
|
4310
|
+
Literal("llm_score"),
|
|
4311
|
+
Literal("llm_checklist"),
|
|
4312
|
+
Literal("boolean"),
|
|
4313
|
+
Literal("deterministic_signature_check"),
|
|
4314
|
+
Literal("deterministic_coverage_check")
|
|
4315
|
+
], { $id: "RubricScoringMode" });
|
|
4316
|
+
/**
|
|
4317
|
+
* One binary check produced by an `llm_checklist`-mode criterion.
|
|
4318
|
+
*
|
|
4319
|
+
* `evidence` is REQUIRED for both PASS and FAIL — agentskills.io grading
|
|
4320
|
+
* principle: \"Don't give the benefit of the doubt.\" A PASS without
|
|
4321
|
+
* concrete evidence (a quoted span, an entry id, a source location)
|
|
4322
|
+
* cannot be audited. A FAIL without evidence cannot be clustered into
|
|
4323
|
+
* structural fixes. The same shape is reused by `judge-eval-variant`
|
|
4324
|
+
* (#943) so tooling, dashboards, and analysis stay uniform.
|
|
4325
|
+
*/
|
|
4326
|
+
var AssertionResult = _Object_({
|
|
4327
|
+
id: String$1({ minLength: 1 }),
|
|
4328
|
+
text: String$1({ minLength: 1 }),
|
|
4329
|
+
passed: Boolean$1(),
|
|
4330
|
+
evidence: String$1({ minLength: 1 })
|
|
4331
|
+
}, {
|
|
4332
|
+
$id: "AssertionResult",
|
|
4333
|
+
additionalProperties: false
|
|
4334
|
+
});
|
|
4335
|
+
var RubricCriterion = _Object_({
|
|
4336
|
+
id: String$1({ minLength: 1 }),
|
|
4337
|
+
description: String$1({ minLength: 1 }),
|
|
4338
|
+
weight: Number$1({
|
|
4339
|
+
minimum: 0,
|
|
4340
|
+
maximum: 1
|
|
4341
|
+
}),
|
|
4342
|
+
scoring: RubricScoringMode
|
|
4343
|
+
}, {
|
|
4344
|
+
$id: "RubricCriterion",
|
|
4345
|
+
additionalProperties: false
|
|
4346
|
+
});
|
|
4347
|
+
/**
|
|
4348
|
+
* A complete rubric. Same shape used in Phase 1 (inline) and Phase 2
|
|
4349
|
+
* (stored row `body`); only the addressing mechanism differs.
|
|
4350
|
+
*/
|
|
4351
|
+
var Rubric = _Object_({
|
|
4352
|
+
rubricId: String$1({ minLength: 1 }),
|
|
4353
|
+
version: String$1({ minLength: 1 }),
|
|
4354
|
+
preamble: Optional(String$1()),
|
|
4355
|
+
criteria: _Array_(RubricCriterion, { minItems: 1 }),
|
|
4356
|
+
scope: Optional(String$1()),
|
|
4357
|
+
contentHash: Optional(String$1())
|
|
4358
|
+
}, {
|
|
4359
|
+
$id: "Rubric",
|
|
4360
|
+
additionalProperties: false
|
|
4361
|
+
});
|
|
4362
|
+
/**
|
|
4363
|
+
* Verify rubric criteria weights sum to 1.0 within floating-point tolerance.
|
|
4364
|
+
* The schema constrains each weight to [0,1] but can't express a cross-field
|
|
4365
|
+
* sum constraint, so this is enforced programmatically by callers that
|
|
4366
|
+
* accept rubrics (task input validators, server-side task creation).
|
|
4367
|
+
*
|
|
4368
|
+
* Returns null when valid; otherwise an error message suitable for surfacing
|
|
4369
|
+
* to the caller. Tolerance is 1e-6 to accommodate JSON round-tripping of
|
|
4370
|
+
* decimal fractions (e.g. 0.1 + 0.2 + 0.3 + 0.4 ≠ 1.0 exactly).
|
|
4371
|
+
*/
|
|
4372
|
+
function validateRubricWeights(rubric) {
|
|
4373
|
+
const sum = rubric.criteria.reduce((acc, c) => acc + c.weight, 0);
|
|
4374
|
+
if (Math.abs(sum - 1) > 1e-6) return `Rubric weights must sum to 1.0 (got ${sum.toFixed(6)})`;
|
|
4375
|
+
return null;
|
|
4376
|
+
}
|
|
4377
|
+
//#endregion
|
|
4378
|
+
//#region ../../libs/tasks/src/runtime-profiles.ts
|
|
4379
|
+
var RuntimeProfileName = String$1({
|
|
4278
4380
|
minLength: 1,
|
|
4279
4381
|
maxLength: 100,
|
|
4280
4382
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,99}$"
|
|
4281
4383
|
});
|
|
4282
|
-
var
|
|
4384
|
+
var RuntimeProfileEnvName = String$1({
|
|
4283
4385
|
minLength: 1,
|
|
4284
4386
|
maxLength: 128,
|
|
4285
4387
|
pattern: "^[A-Z_][A-Z0-9_]*$"
|
|
4286
4388
|
});
|
|
4287
|
-
var
|
|
4389
|
+
var RuntimeProfileToolName = String$1({
|
|
4288
4390
|
minLength: 1,
|
|
4289
4391
|
maxLength: 128,
|
|
4290
4392
|
pattern: "^[a-zA-Z0-9._/-]+$"
|
|
@@ -4297,7 +4399,7 @@ var SandboxResumeCommandWhenSchema = _Object_({ workspaceMode: Optional(_Array_(
|
|
|
4297
4399
|
minItems: 1,
|
|
4298
4400
|
maxItems: 3
|
|
4299
4401
|
})) }, { additionalProperties: false });
|
|
4300
|
-
var
|
|
4402
|
+
var RuntimeProfileSandboxResumeCommand = Union([String$1({
|
|
4301
4403
|
minLength: 1,
|
|
4302
4404
|
maxLength: 4096
|
|
4303
4405
|
}), _Object_({
|
|
@@ -4315,7 +4417,7 @@ var DaemonProfileSandboxResumeCommand = Union([String$1({
|
|
|
4315
4417
|
maximum: 6e4
|
|
4316
4418
|
}))
|
|
4317
4419
|
}, { additionalProperties: false })]);
|
|
4318
|
-
var
|
|
4420
|
+
var RuntimeProfileSandbox = _Object_({
|
|
4319
4421
|
snapshot: Optional(_Object_({
|
|
4320
4422
|
setupCommands: Optional(_Array_(String$1({
|
|
4321
4423
|
minLength: 1,
|
|
@@ -4331,7 +4433,7 @@ var DaemonProfileSandbox = _Object_({
|
|
|
4331
4433
|
pattern: "^[0-9]+[KMGTP]?$"
|
|
4332
4434
|
}))
|
|
4333
4435
|
}, { additionalProperties: false })),
|
|
4334
|
-
resumeCommands: Optional(_Array_(
|
|
4436
|
+
resumeCommands: Optional(_Array_(RuntimeProfileSandboxResumeCommand, { maxItems: 30 })),
|
|
4335
4437
|
vfs: Optional(_Object_({
|
|
4336
4438
|
shadow: Optional(_Array_(String$1({
|
|
4337
4439
|
minLength: 1,
|
|
@@ -4339,7 +4441,7 @@ var DaemonProfileSandbox = _Object_({
|
|
|
4339
4441
|
}), { maxItems: 100 })),
|
|
4340
4442
|
shadowMode: Optional(Union([Literal("deny"), Literal("tmpfs")]))
|
|
4341
4443
|
}, { additionalProperties: false })),
|
|
4342
|
-
env: Optional(Record(
|
|
4444
|
+
env: Optional(Record(RuntimeProfileEnvName, String$1({ maxLength: 4096 }))),
|
|
4343
4445
|
hostExec: Optional(_Object_({ autoApprove: Optional(Literal(false)) }, { additionalProperties: false })),
|
|
4344
4446
|
resources: Optional(_Object_({
|
|
4345
4447
|
memory: Optional(String$1({
|
|
@@ -4353,10 +4455,10 @@ var DaemonProfileSandbox = _Object_({
|
|
|
4353
4455
|
}))
|
|
4354
4456
|
}, { additionalProperties: false }))
|
|
4355
4457
|
}, {
|
|
4356
|
-
$id: "
|
|
4458
|
+
$id: "RuntimeProfileSandbox",
|
|
4357
4459
|
additionalProperties: false
|
|
4358
4460
|
});
|
|
4359
|
-
var
|
|
4461
|
+
var RuntimeProfileContext = _Object_({
|
|
4360
4462
|
slug: String$1({
|
|
4361
4463
|
minLength: 1,
|
|
4362
4464
|
maxLength: 64,
|
|
@@ -4373,17 +4475,29 @@ var DaemonProfileContext = _Object_({
|
|
|
4373
4475
|
maxLength: 65536
|
|
4374
4476
|
})
|
|
4375
4477
|
}, {
|
|
4376
|
-
$id: "
|
|
4478
|
+
$id: "RuntimeProfileContext",
|
|
4377
4479
|
additionalProperties: false
|
|
4378
4480
|
});
|
|
4379
|
-
var
|
|
4380
|
-
$id: "
|
|
4481
|
+
var RuntimeProfileRef = _Object_({ profileId: String$1({ format: "uuid" }) }, {
|
|
4482
|
+
$id: "RuntimeProfileRef",
|
|
4381
4483
|
additionalProperties: false
|
|
4382
4484
|
});
|
|
4485
|
+
var RuntimeProfileLeaseTtlSec = Integer({
|
|
4486
|
+
minimum: 1,
|
|
4487
|
+
maximum: 86400
|
|
4488
|
+
});
|
|
4489
|
+
var RuntimeProfileHeartbeatIntervalMs = Integer({
|
|
4490
|
+
minimum: 0,
|
|
4491
|
+
maximum: 36e5
|
|
4492
|
+
});
|
|
4493
|
+
var RuntimeProfileMaxBatchSize = Integer({
|
|
4494
|
+
minimum: 1,
|
|
4495
|
+
maximum: 1e3
|
|
4496
|
+
});
|
|
4383
4497
|
_Object_({
|
|
4384
4498
|
id: String$1({ format: "uuid" }),
|
|
4385
4499
|
teamId: String$1({ format: "uuid" }),
|
|
4386
|
-
name:
|
|
4500
|
+
name: RuntimeProfileName,
|
|
4387
4501
|
description: Union([String$1({ maxLength: 4096 }), Null()]),
|
|
4388
4502
|
provider: String$1({
|
|
4389
4503
|
minLength: 1,
|
|
@@ -4394,7 +4508,7 @@ _Object_({
|
|
|
4394
4508
|
maxLength: 200
|
|
4395
4509
|
}),
|
|
4396
4510
|
runtimeKind: Literal("gondolin_pi"),
|
|
4397
|
-
sandbox:
|
|
4511
|
+
sandbox: RuntimeProfileSandbox,
|
|
4398
4512
|
sessionStorageMode: Literal("local"),
|
|
4399
4513
|
workspaceStorageMode: Literal("local"),
|
|
4400
4514
|
sessionTtlSec: Integer({
|
|
@@ -4405,9 +4519,12 @@ _Object_({
|
|
|
4405
4519
|
minimum: 1,
|
|
4406
4520
|
maximum: 86400
|
|
4407
4521
|
}),
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4522
|
+
leaseTtlSec: RuntimeProfileLeaseTtlSec,
|
|
4523
|
+
heartbeatIntervalMs: RuntimeProfileHeartbeatIntervalMs,
|
|
4524
|
+
maxBatchSize: RuntimeProfileMaxBatchSize,
|
|
4525
|
+
requiredEnv: _Array_(RuntimeProfileEnvName, { maxItems: 100 }),
|
|
4526
|
+
requiredTools: _Array_(RuntimeProfileToolName, { maxItems: 100 }),
|
|
4527
|
+
context: _Array_(RuntimeProfileContext, { maxItems: 5 }),
|
|
4411
4528
|
revision: Integer({ minimum: 1 }),
|
|
4412
4529
|
definitionCid: String$1({
|
|
4413
4530
|
minLength: 1,
|
|
@@ -4418,112 +4535,10 @@ _Object_({
|
|
|
4418
4535
|
createdAt: String$1({ format: "date-time" }),
|
|
4419
4536
|
updatedAt: String$1({ format: "date-time" })
|
|
4420
4537
|
}, {
|
|
4421
|
-
$id: "
|
|
4538
|
+
$id: "RuntimeProfile",
|
|
4422
4539
|
additionalProperties: false
|
|
4423
4540
|
});
|
|
4424
4541
|
//#endregion
|
|
4425
|
-
//#region ../../libs/tasks/src/rubric.ts
|
|
4426
|
-
/**
|
|
4427
|
-
* Rubric — structured acceptance criteria used by judgment tasks.
|
|
4428
|
-
*
|
|
4429
|
-
* Phase 1 (this PR): rubrics are embedded in task inputs. Their integrity
|
|
4430
|
-
* is pinned via the task's `input_cid` (which covers the whole input,
|
|
4431
|
-
* including the inline rubric). No separate storage, no CRUD.
|
|
4432
|
-
*
|
|
4433
|
-
* Phase 2 (see #881): rubrics become a first-class resource with their
|
|
4434
|
-
* own signed rows and CIDv1 lookup. The schema below is designed to
|
|
4435
|
-
* carry forward unchanged — only storage and addressing differ.
|
|
4436
|
-
*
|
|
4437
|
-
* Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
|
|
4438
|
-
* informational fields the author fills in; no uniqueness is enforced.
|
|
4439
|
-
* `contentHash` is optional in Phase 1 because the *task*'s input_cid
|
|
4440
|
-
* is the authoritative commitment.
|
|
4441
|
-
*/
|
|
4442
|
-
/**
|
|
4443
|
-
* How a judge must score a single criterion.
|
|
4444
|
-
*
|
|
4445
|
-
* - `llm_score`: 0..1 continuous, `rationale` required. Smooths failures
|
|
4446
|
-
* into the gradient — use `llm_checklist` instead for properties where
|
|
4447
|
-
* a single failure is a real failure (grounding, faithfulness).
|
|
4448
|
-
* - `llm_checklist`: judge enumerates per-claim assertions with
|
|
4449
|
-
* `{passed, evidence}`. The criterion's numeric `score` is derived:
|
|
4450
|
-
* `1` iff every assertion passes, else `0`. Per-claim evidence is the
|
|
4451
|
-
* dataset for cluster-analysis of failure modes. See #999.
|
|
4452
|
-
* - `boolean`: 0 or 1, `rationale` optional.
|
|
4453
|
-
* - `deterministic_signature_check`: judge runs a signature check;
|
|
4454
|
-
* result is 0 or 1. No LLM discretion.
|
|
4455
|
-
* - `deterministic_coverage_check`: every referenced source entry
|
|
4456
|
-
* appears in the rendered output; 0 or 1.
|
|
4457
|
-
*/
|
|
4458
|
-
var RubricScoringMode = Union([
|
|
4459
|
-
Literal("llm_score"),
|
|
4460
|
-
Literal("llm_checklist"),
|
|
4461
|
-
Literal("boolean"),
|
|
4462
|
-
Literal("deterministic_signature_check"),
|
|
4463
|
-
Literal("deterministic_coverage_check")
|
|
4464
|
-
], { $id: "RubricScoringMode" });
|
|
4465
|
-
/**
|
|
4466
|
-
* One binary check produced by an `llm_checklist`-mode criterion.
|
|
4467
|
-
*
|
|
4468
|
-
* `evidence` is REQUIRED for both PASS and FAIL — agentskills.io grading
|
|
4469
|
-
* principle: \"Don't give the benefit of the doubt.\" A PASS without
|
|
4470
|
-
* concrete evidence (a quoted span, an entry id, a source location)
|
|
4471
|
-
* cannot be audited. A FAIL without evidence cannot be clustered into
|
|
4472
|
-
* structural fixes. The same shape is reused by `judge-eval-variant`
|
|
4473
|
-
* (#943) so tooling, dashboards, and analysis stay uniform.
|
|
4474
|
-
*/
|
|
4475
|
-
var AssertionResult = _Object_({
|
|
4476
|
-
id: String$1({ minLength: 1 }),
|
|
4477
|
-
text: String$1({ minLength: 1 }),
|
|
4478
|
-
passed: Boolean$1(),
|
|
4479
|
-
evidence: String$1({ minLength: 1 })
|
|
4480
|
-
}, {
|
|
4481
|
-
$id: "AssertionResult",
|
|
4482
|
-
additionalProperties: false
|
|
4483
|
-
});
|
|
4484
|
-
var RubricCriterion = _Object_({
|
|
4485
|
-
id: String$1({ minLength: 1 }),
|
|
4486
|
-
description: String$1({ minLength: 1 }),
|
|
4487
|
-
weight: Number$1({
|
|
4488
|
-
minimum: 0,
|
|
4489
|
-
maximum: 1
|
|
4490
|
-
}),
|
|
4491
|
-
scoring: RubricScoringMode
|
|
4492
|
-
}, {
|
|
4493
|
-
$id: "RubricCriterion",
|
|
4494
|
-
additionalProperties: false
|
|
4495
|
-
});
|
|
4496
|
-
/**
|
|
4497
|
-
* A complete rubric. Same shape used in Phase 1 (inline) and Phase 2
|
|
4498
|
-
* (stored row `body`); only the addressing mechanism differs.
|
|
4499
|
-
*/
|
|
4500
|
-
var Rubric = _Object_({
|
|
4501
|
-
rubricId: String$1({ minLength: 1 }),
|
|
4502
|
-
version: String$1({ minLength: 1 }),
|
|
4503
|
-
preamble: Optional(String$1()),
|
|
4504
|
-
criteria: _Array_(RubricCriterion, { minItems: 1 }),
|
|
4505
|
-
scope: Optional(String$1()),
|
|
4506
|
-
contentHash: Optional(String$1())
|
|
4507
|
-
}, {
|
|
4508
|
-
$id: "Rubric",
|
|
4509
|
-
additionalProperties: false
|
|
4510
|
-
});
|
|
4511
|
-
/**
|
|
4512
|
-
* Verify rubric criteria weights sum to 1.0 within floating-point tolerance.
|
|
4513
|
-
* The schema constrains each weight to [0,1] but can't express a cross-field
|
|
4514
|
-
* sum constraint, so this is enforced programmatically by callers that
|
|
4515
|
-
* accept rubrics (task input validators, server-side task creation).
|
|
4516
|
-
*
|
|
4517
|
-
* Returns null when valid; otherwise an error message suitable for surfacing
|
|
4518
|
-
* to the caller. Tolerance is 1e-6 to accommodate JSON round-tripping of
|
|
4519
|
-
* decimal fractions (e.g. 0.1 + 0.2 + 0.3 + 0.4 ≠ 1.0 exactly).
|
|
4520
|
-
*/
|
|
4521
|
-
function validateRubricWeights(rubric) {
|
|
4522
|
-
const sum = rubric.criteria.reduce((acc, c) => acc + c.weight, 0);
|
|
4523
|
-
if (Math.abs(sum - 1) > 1e-6) return `Rubric weights must sum to 1.0 (got ${sum.toFixed(6)})`;
|
|
4524
|
-
return null;
|
|
4525
|
-
}
|
|
4526
|
-
//#endregion
|
|
4527
4542
|
//#region ../../libs/tasks/src/success-criteria.ts
|
|
4528
4543
|
/**
|
|
4529
4544
|
* SuccessCriteria — proposer-stated acceptance criteria, evaluated in two
|
|
@@ -9116,6 +9131,7 @@ var TaskAttemptStatus = Union([
|
|
|
9116
9131
|
Literal("completed"),
|
|
9117
9132
|
Literal("failed"),
|
|
9118
9133
|
Literal("cancelled"),
|
|
9134
|
+
Literal("aborted"),
|
|
9119
9135
|
Literal("timed_out")
|
|
9120
9136
|
], { $id: "TaskAttemptStatus" });
|
|
9121
9137
|
var ExecutorTrustLevel = Union([
|
|
@@ -9268,7 +9284,7 @@ _Object_({
|
|
|
9268
9284
|
acceptedAttemptN: Union([Number$1(), Null()]),
|
|
9269
9285
|
claimCondition: Union([Unsafe(Ref$2("ClaimCondition")), Null()]),
|
|
9270
9286
|
requiredExecutorTrustLevel: ExecutorTrustLevel,
|
|
9271
|
-
allowedProfiles: _Array_(
|
|
9287
|
+
allowedProfiles: _Array_(RuntimeProfileRef, { maxItems: 16 }),
|
|
9272
9288
|
status: TaskStatus,
|
|
9273
9289
|
queuedAt: IsoTimestamp,
|
|
9274
9290
|
completedAt: Union([IsoTimestamp, Null()]),
|
|
@@ -9364,15 +9380,23 @@ _Object_({
|
|
|
9364
9380
|
//#region src/lib/help.ts
|
|
9365
9381
|
var COMMON_REQUIRED_FLAGS = `\
|
|
9366
9382
|
-a, --agent <name> MoltNet agent identity. Reads credentials
|
|
9367
|
-
from <repo-root>/.moltnet/<name>/moltnet.json
|
|
9383
|
+
from <repo-root>/.moltnet/<name>/moltnet.json.`;
|
|
9384
|
+
var COMMON_MODEL_FLAGS = `\
|
|
9368
9385
|
-p, --provider <id> LLM provider id (e.g. anthropic, openai-codex).
|
|
9369
9386
|
-m, --model <id> LLM model id for the provider (e.g.
|
|
9370
|
-
claude-sonnet-4-5, gpt-5.3-codex)
|
|
9387
|
+
claude-sonnet-4-5, gpt-5.3-codex). Required
|
|
9388
|
+
unless --profile is set.`;
|
|
9371
9389
|
var COMMON_OPTIONAL_FLAGS = `\
|
|
9372
9390
|
--sandbox <path> Path to sandbox.json. Default: search up from
|
|
9373
9391
|
the daemon's CWD until found. The directory
|
|
9374
9392
|
containing sandbox.json is also used as the
|
|
9375
|
-
VM mountPath.
|
|
9393
|
+
VM mountPath. Cannot be used with --profile.
|
|
9394
|
+
--profile <uuid|name> Remote runtime profile. When set, provider,
|
|
9395
|
+
model, and sandbox policy come from the
|
|
9396
|
+
profile; task listing/claiming is restricted
|
|
9397
|
+
to unrestricted tasks plus tasks allowing this
|
|
9398
|
+
profile. requiredEnv/requiredTools are checked
|
|
9399
|
+
before claiming. Name lookup is team-scoped.
|
|
9376
9400
|
--lease-ttl-sec <n> Sliding liveness window. Silence longer than
|
|
9377
9401
|
this ends the attempt with lease_expired.
|
|
9378
9402
|
Default: 300.
|
|
@@ -9390,7 +9414,8 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
9390
9414
|
--warm-session-ttl-sec <n> Retain resumable daemon slots (Pi sessions +
|
|
9391
9415
|
reusable worktrees) in local daemon state for
|
|
9392
9416
|
this many seconds after use. 0 = disable reuse.
|
|
9393
|
-
Default: 1800
|
|
9417
|
+
Default: 1800, or min(profile session/workspace
|
|
9418
|
+
TTL) when --profile is set.
|
|
9394
9419
|
--debug Verbose logging: also log successful list/claim
|
|
9395
9420
|
outcomes (candidate counts, claim attempts).`;
|
|
9396
9421
|
var REGISTERED_TASK_TYPES = Object.keys(BUILT_IN_TASK_TYPES).sort();
|
|
@@ -9413,9 +9438,10 @@ Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
|
9413
9438
|
|
|
9414
9439
|
Prerequisites (all subcommands):
|
|
9415
9440
|
- <repo-root>/.moltnet/<agent>/moltnet.json — credentials (see --agent)
|
|
9416
|
-
- sandbox.json —
|
|
9417
|
-
from CWD, or pass --sandbox <path>.
|
|
9418
|
-
|
|
9441
|
+
- sandbox.json or --profile — local sandbox config is resolved by
|
|
9442
|
+
searching up from CWD, or pass --sandbox <path>. With --profile, the
|
|
9443
|
+
remote runtime profile supplies provider/model/sandbox policy and CWD
|
|
9444
|
+
is used as the VM mountPath.
|
|
9419
9445
|
|
|
9420
9446
|
Registered task types: ${knownTaskTypesList()}`;
|
|
9421
9447
|
var POLL_HELP = `\
|
|
@@ -9428,6 +9454,7 @@ Required:
|
|
|
9428
9454
|
--team <uuid> Team whose queue to serve. The daemon must be
|
|
9429
9455
|
a member of this team (canAccessTeam permit).
|
|
9430
9456
|
${COMMON_REQUIRED_FLAGS}
|
|
9457
|
+
${COMMON_MODEL_FLAGS}
|
|
9431
9458
|
|
|
9432
9459
|
Optional:
|
|
9433
9460
|
--task-types <csv> Whitelist of task types to claim. Default:
|
|
@@ -9458,6 +9485,7 @@ Required:
|
|
|
9458
9485
|
-t, --task-id <uuid> Task to claim and execute. Must already be
|
|
9459
9486
|
in 'queued' status.
|
|
9460
9487
|
${COMMON_REQUIRED_FLAGS}
|
|
9488
|
+
${COMMON_MODEL_FLAGS}
|
|
9461
9489
|
|
|
9462
9490
|
Optional:
|
|
9463
9491
|
${COMMON_OPTIONAL_FLAGS}
|
|
@@ -9483,6 +9511,7 @@ sleeps and retries forever).
|
|
|
9483
9511
|
Required:
|
|
9484
9512
|
--team <uuid> Team whose queue to drain.
|
|
9485
9513
|
${COMMON_REQUIRED_FLAGS}
|
|
9514
|
+
${COMMON_MODEL_FLAGS}
|
|
9486
9515
|
|
|
9487
9516
|
Optional:
|
|
9488
9517
|
--task-types <csv> Whitelist. Known types: ${knownTaskTypesList()}
|
|
@@ -24459,6 +24488,9 @@ var ApiTaskReporter = class {
|
|
|
24459
24488
|
get cancelReason() {
|
|
24460
24489
|
return this.observedCancelReason;
|
|
24461
24490
|
}
|
|
24491
|
+
requestCancel(reason) {
|
|
24492
|
+
this.abortForCancel(reason);
|
|
24493
|
+
}
|
|
24462
24494
|
async open(ctx) {
|
|
24463
24495
|
if (this.heartbeatTimer) {
|
|
24464
24496
|
clearInterval(this.heartbeatTimer);
|
|
@@ -24641,13 +24673,15 @@ var ApiTaskReporter = class {
|
|
|
24641
24673
|
async sendHeartbeat() {
|
|
24642
24674
|
const body = this.opts.leaseTtlSec ? { leaseTtlSec: this.opts.leaseTtlSec } : {};
|
|
24643
24675
|
const response = await this.opts.tasks.heartbeat(this.taskId, this.attemptN, body);
|
|
24644
|
-
if (response?.cancelled
|
|
24645
|
-
|
|
24646
|
-
|
|
24647
|
-
|
|
24648
|
-
|
|
24649
|
-
|
|
24650
|
-
|
|
24676
|
+
if (response?.cancelled) this.abortForCancel(response.cancelReason ?? null);
|
|
24677
|
+
}
|
|
24678
|
+
abortForCancel(reason) {
|
|
24679
|
+
if (this.cancelController.signal.aborted) return;
|
|
24680
|
+
this.observedCancelReason = reason;
|
|
24681
|
+
this.cancelController.abort(/* @__PURE__ */ new Error(`Task cancelled${this.observedCancelReason ? `: ${this.observedCancelReason}` : ""}`));
|
|
24682
|
+
if (this.heartbeatTimer) {
|
|
24683
|
+
clearInterval(this.heartbeatTimer);
|
|
24684
|
+
this.heartbeatTimer = null;
|
|
24651
24685
|
}
|
|
24652
24686
|
}
|
|
24653
24687
|
};
|
|
@@ -24660,6 +24694,7 @@ var AgentRuntime = class {
|
|
|
24660
24694
|
currentTaskId: null
|
|
24661
24695
|
};
|
|
24662
24696
|
stopRequested = false;
|
|
24697
|
+
currentReporter = null;
|
|
24663
24698
|
logger;
|
|
24664
24699
|
constructor(opts) {
|
|
24665
24700
|
this.opts = opts;
|
|
@@ -24688,6 +24723,7 @@ var AgentRuntime = class {
|
|
|
24688
24723
|
});
|
|
24689
24724
|
taskLogger.info({}, "agent-runtime.task_claimed");
|
|
24690
24725
|
const reporter = this.opts.makeReporter(claimedTask);
|
|
24726
|
+
this.currentReporter = reporter;
|
|
24691
24727
|
const taskCtx = Object.keys(claimedTask.traceHeaders).length ? propagation.extract(ROOT_CONTEXT, claimedTask.traceHeaders) : context.active();
|
|
24692
24728
|
const taskStart = Date.now();
|
|
24693
24729
|
let output;
|
|
@@ -24745,16 +24781,19 @@ var AgentRuntime = class {
|
|
|
24745
24781
|
}
|
|
24746
24782
|
this.status.tasksProcessed += 1;
|
|
24747
24783
|
this.status.currentTaskId = null;
|
|
24784
|
+
this.currentReporter = null;
|
|
24748
24785
|
}
|
|
24749
24786
|
} finally {
|
|
24787
|
+
this.currentReporter = null;
|
|
24750
24788
|
await this.opts.source.close();
|
|
24751
24789
|
this.status.state = "stopped";
|
|
24752
24790
|
}
|
|
24753
24791
|
return outputs;
|
|
24754
24792
|
}
|
|
24755
24793
|
/** Request cooperative shutdown. Safe from signal handlers. */
|
|
24756
|
-
stop() {
|
|
24794
|
+
stop(reason) {
|
|
24757
24795
|
this.stopRequested = true;
|
|
24796
|
+
if (reason !== void 0) this.currentReporter?.requestCancel?.(reason);
|
|
24758
24797
|
}
|
|
24759
24798
|
};
|
|
24760
24799
|
//#endregion
|
|
@@ -24766,8 +24805,11 @@ var ApiTaskSource = class {
|
|
|
24766
24805
|
}
|
|
24767
24806
|
async claim() {
|
|
24768
24807
|
if (this.claimed) return null;
|
|
24769
|
-
const { agent, taskId, leaseTtlSec } = this.opts;
|
|
24770
|
-
const result = await agent.tasks.claim(taskId,
|
|
24808
|
+
const { agent, taskId, leaseTtlSec, profileId } = this.opts;
|
|
24809
|
+
const result = await agent.tasks.claim(taskId, {
|
|
24810
|
+
...leaseTtlSec ? { leaseTtlSec } : {},
|
|
24811
|
+
...profileId ? { profileId } : {}
|
|
24812
|
+
});
|
|
24771
24813
|
this.claimed = true;
|
|
24772
24814
|
return {
|
|
24773
24815
|
task: result.task,
|
|
@@ -26412,6 +26454,124 @@ var updateRenderedPack = (options) => (options.client ?? client).patch({
|
|
|
26412
26454
|
}
|
|
26413
26455
|
});
|
|
26414
26456
|
/**
|
|
26457
|
+
* List runtime profiles for the active team context.
|
|
26458
|
+
*/
|
|
26459
|
+
var listRuntimeProfiles = (options) => (options?.client ?? client).get({
|
|
26460
|
+
security: [
|
|
26461
|
+
{
|
|
26462
|
+
scheme: "bearer",
|
|
26463
|
+
type: "http"
|
|
26464
|
+
},
|
|
26465
|
+
{
|
|
26466
|
+
name: "X-Moltnet-Session-Token",
|
|
26467
|
+
type: "apiKey"
|
|
26468
|
+
},
|
|
26469
|
+
{
|
|
26470
|
+
in: "cookie",
|
|
26471
|
+
name: "ory_kratos_session",
|
|
26472
|
+
type: "apiKey"
|
|
26473
|
+
}
|
|
26474
|
+
],
|
|
26475
|
+
url: "/runtime-profiles",
|
|
26476
|
+
...options
|
|
26477
|
+
});
|
|
26478
|
+
/**
|
|
26479
|
+
* Create a runtime profile for the active team context.
|
|
26480
|
+
*/
|
|
26481
|
+
var createRuntimeProfile = (options) => (options?.client ?? client).post({
|
|
26482
|
+
security: [
|
|
26483
|
+
{
|
|
26484
|
+
scheme: "bearer",
|
|
26485
|
+
type: "http"
|
|
26486
|
+
},
|
|
26487
|
+
{
|
|
26488
|
+
name: "X-Moltnet-Session-Token",
|
|
26489
|
+
type: "apiKey"
|
|
26490
|
+
},
|
|
26491
|
+
{
|
|
26492
|
+
in: "cookie",
|
|
26493
|
+
name: "ory_kratos_session",
|
|
26494
|
+
type: "apiKey"
|
|
26495
|
+
}
|
|
26496
|
+
],
|
|
26497
|
+
url: "/runtime-profiles",
|
|
26498
|
+
...options,
|
|
26499
|
+
headers: {
|
|
26500
|
+
"Content-Type": "application/json",
|
|
26501
|
+
...options?.headers
|
|
26502
|
+
}
|
|
26503
|
+
});
|
|
26504
|
+
/**
|
|
26505
|
+
* Delete one runtime profile.
|
|
26506
|
+
*/
|
|
26507
|
+
var deleteRuntimeProfile = (options) => (options.client ?? client).delete({
|
|
26508
|
+
security: [
|
|
26509
|
+
{
|
|
26510
|
+
scheme: "bearer",
|
|
26511
|
+
type: "http"
|
|
26512
|
+
},
|
|
26513
|
+
{
|
|
26514
|
+
name: "X-Moltnet-Session-Token",
|
|
26515
|
+
type: "apiKey"
|
|
26516
|
+
},
|
|
26517
|
+
{
|
|
26518
|
+
in: "cookie",
|
|
26519
|
+
name: "ory_kratos_session",
|
|
26520
|
+
type: "apiKey"
|
|
26521
|
+
}
|
|
26522
|
+
],
|
|
26523
|
+
url: "/runtime-profiles/{profileId}",
|
|
26524
|
+
...options
|
|
26525
|
+
});
|
|
26526
|
+
/**
|
|
26527
|
+
* Get one runtime profile.
|
|
26528
|
+
*/
|
|
26529
|
+
var getRuntimeProfile = (options) => (options.client ?? client).get({
|
|
26530
|
+
security: [
|
|
26531
|
+
{
|
|
26532
|
+
scheme: "bearer",
|
|
26533
|
+
type: "http"
|
|
26534
|
+
},
|
|
26535
|
+
{
|
|
26536
|
+
name: "X-Moltnet-Session-Token",
|
|
26537
|
+
type: "apiKey"
|
|
26538
|
+
},
|
|
26539
|
+
{
|
|
26540
|
+
in: "cookie",
|
|
26541
|
+
name: "ory_kratos_session",
|
|
26542
|
+
type: "apiKey"
|
|
26543
|
+
}
|
|
26544
|
+
],
|
|
26545
|
+
url: "/runtime-profiles/{profileId}",
|
|
26546
|
+
...options
|
|
26547
|
+
});
|
|
26548
|
+
/**
|
|
26549
|
+
* Update one runtime profile.
|
|
26550
|
+
*/
|
|
26551
|
+
var updateRuntimeProfile = (options) => (options.client ?? client).patch({
|
|
26552
|
+
security: [
|
|
26553
|
+
{
|
|
26554
|
+
scheme: "bearer",
|
|
26555
|
+
type: "http"
|
|
26556
|
+
},
|
|
26557
|
+
{
|
|
26558
|
+
name: "X-Moltnet-Session-Token",
|
|
26559
|
+
type: "apiKey"
|
|
26560
|
+
},
|
|
26561
|
+
{
|
|
26562
|
+
in: "cookie",
|
|
26563
|
+
name: "ory_kratos_session",
|
|
26564
|
+
type: "apiKey"
|
|
26565
|
+
}
|
|
26566
|
+
],
|
|
26567
|
+
url: "/runtime-profiles/{profileId}",
|
|
26568
|
+
...options,
|
|
26569
|
+
headers: {
|
|
26570
|
+
"Content-Type": "application/json",
|
|
26571
|
+
...options.headers
|
|
26572
|
+
}
|
|
26573
|
+
});
|
|
26574
|
+
/**
|
|
26415
26575
|
* List tasks for a team with optional filters.
|
|
26416
26576
|
*/
|
|
26417
26577
|
var listTasks = (options) => (options.client ?? client).get({
|
|
@@ -26526,6 +26686,32 @@ var listTaskAttempts = (options) => (options.client ?? client).get({
|
|
|
26526
26686
|
...options
|
|
26527
26687
|
});
|
|
26528
26688
|
/**
|
|
26689
|
+
* Claimant intentionally abandons this attempt (e.g. daemon shutdown). The attempt becomes aborted and the task requeues for another claim (or fails when retries are exhausted). Does NOT cancel the task.
|
|
26690
|
+
*/
|
|
26691
|
+
var abortTaskAttempt = (options) => (options.client ?? client).post({
|
|
26692
|
+
security: [
|
|
26693
|
+
{
|
|
26694
|
+
scheme: "bearer",
|
|
26695
|
+
type: "http"
|
|
26696
|
+
},
|
|
26697
|
+
{
|
|
26698
|
+
name: "X-Moltnet-Session-Token",
|
|
26699
|
+
type: "apiKey"
|
|
26700
|
+
},
|
|
26701
|
+
{
|
|
26702
|
+
in: "cookie",
|
|
26703
|
+
name: "ory_kratos_session",
|
|
26704
|
+
type: "apiKey"
|
|
26705
|
+
}
|
|
26706
|
+
],
|
|
26707
|
+
url: "/tasks/{id}/attempts/{n}/abort",
|
|
26708
|
+
...options,
|
|
26709
|
+
headers: {
|
|
26710
|
+
"Content-Type": "application/json",
|
|
26711
|
+
...options.headers
|
|
26712
|
+
}
|
|
26713
|
+
});
|
|
26714
|
+
/**
|
|
26529
26715
|
* Mark an attempt as completed with output.
|
|
26530
26716
|
*/
|
|
26531
26717
|
var completeTask = (options) => (options.client ?? client).post({
|
|
@@ -28658,6 +28844,54 @@ function createRecoveryNamespace(context) {
|
|
|
28658
28844
|
};
|
|
28659
28845
|
}
|
|
28660
28846
|
//#endregion
|
|
28847
|
+
//#region ../../libs/sdk/src/namespaces/runtime-profiles.ts
|
|
28848
|
+
function createRuntimeProfilesNamespace(context) {
|
|
28849
|
+
const { client, auth } = context;
|
|
28850
|
+
return {
|
|
28851
|
+
async list(options) {
|
|
28852
|
+
return unwrapResult(await listRuntimeProfiles({
|
|
28853
|
+
client,
|
|
28854
|
+
auth,
|
|
28855
|
+
headers: teamHeaders(options)
|
|
28856
|
+
}));
|
|
28857
|
+
},
|
|
28858
|
+
async create(body, options) {
|
|
28859
|
+
return unwrapResult(await createRuntimeProfile({
|
|
28860
|
+
client,
|
|
28861
|
+
auth,
|
|
28862
|
+
headers: teamHeaders(options),
|
|
28863
|
+
body
|
|
28864
|
+
}));
|
|
28865
|
+
},
|
|
28866
|
+
async get(profileId) {
|
|
28867
|
+
return unwrapResult(await getRuntimeProfile({
|
|
28868
|
+
client,
|
|
28869
|
+
auth,
|
|
28870
|
+
path: { profileId }
|
|
28871
|
+
}));
|
|
28872
|
+
},
|
|
28873
|
+
async update(profileId, body) {
|
|
28874
|
+
return unwrapResult(await updateRuntimeProfile({
|
|
28875
|
+
client,
|
|
28876
|
+
auth,
|
|
28877
|
+
path: { profileId },
|
|
28878
|
+
body
|
|
28879
|
+
}));
|
|
28880
|
+
},
|
|
28881
|
+
async delete(profileId) {
|
|
28882
|
+
const result = await deleteRuntimeProfile({
|
|
28883
|
+
client,
|
|
28884
|
+
auth,
|
|
28885
|
+
path: { profileId }
|
|
28886
|
+
});
|
|
28887
|
+
if (result.error) unwrapResult(result);
|
|
28888
|
+
}
|
|
28889
|
+
};
|
|
28890
|
+
}
|
|
28891
|
+
function teamHeaders(options) {
|
|
28892
|
+
return options?.teamId ? { "x-moltnet-team-id": options.teamId } : void 0;
|
|
28893
|
+
}
|
|
28894
|
+
//#endregion
|
|
28661
28895
|
//#region ../../libs/sdk/src/namespaces/signing-requests.ts
|
|
28662
28896
|
function createSigningRequestsNamespace(context) {
|
|
28663
28897
|
const { client, auth } = context;
|
|
@@ -28778,6 +29012,17 @@ function createTasksNamespace(context) {
|
|
|
28778
29012
|
body
|
|
28779
29013
|
}));
|
|
28780
29014
|
},
|
|
29015
|
+
async abortAttempt(id, n, body) {
|
|
29016
|
+
return unwrapResult(await abortTaskAttempt({
|
|
29017
|
+
client,
|
|
29018
|
+
auth,
|
|
29019
|
+
path: {
|
|
29020
|
+
id,
|
|
29021
|
+
n
|
|
29022
|
+
},
|
|
29023
|
+
body
|
|
29024
|
+
}));
|
|
29025
|
+
},
|
|
28781
29026
|
async cancel(id, body) {
|
|
28782
29027
|
return unwrapResult(await cancelTask({
|
|
28783
29028
|
client,
|
|
@@ -28961,6 +29206,7 @@ function createAgent(options) {
|
|
|
28961
29206
|
legreffier: createLegreffierNamespace(context),
|
|
28962
29207
|
problems: createProblemsNamespace(context),
|
|
28963
29208
|
teams: createTeamsNamespace(context),
|
|
29209
|
+
runtimeProfiles: createRuntimeProfilesNamespace(context),
|
|
28964
29210
|
tasks: createTasksNamespace(context),
|
|
28965
29211
|
client,
|
|
28966
29212
|
getToken: () => tokenManager.getToken()
|
|
@@ -31012,12 +31258,21 @@ if (!etc.sha512Sync) etc.sha512Sync = (...m) => {
|
|
|
31012
31258
|
*/
|
|
31013
31259
|
async function isContinuationClaimableByThisDaemon(task, slotRegistry) {
|
|
31014
31260
|
const cf = task.input?.continueFrom;
|
|
31015
|
-
if (!cf) return true;
|
|
31261
|
+
if (!cf) return { claimable: true };
|
|
31016
31262
|
const slot = await slotRegistry.findLatestProducerSlotByTaskAttempt(cf.taskId, cf.attemptN);
|
|
31017
|
-
if (!slot) return
|
|
31263
|
+
if (!slot) return {
|
|
31264
|
+
claimable: false,
|
|
31265
|
+
reason: "missing_producer_slot",
|
|
31266
|
+
continueFrom: cf
|
|
31267
|
+
};
|
|
31018
31268
|
const sessionDir = slot.session?.sessionDir;
|
|
31019
|
-
if (!sessionDir || !existsSync(sessionDir)) return
|
|
31020
|
-
|
|
31269
|
+
if (!sessionDir || !existsSync(sessionDir)) return {
|
|
31270
|
+
claimable: false,
|
|
31271
|
+
reason: "missing_session_dir",
|
|
31272
|
+
continueFrom: cf,
|
|
31273
|
+
sessionDir
|
|
31274
|
+
};
|
|
31275
|
+
return { claimable: true };
|
|
31021
31276
|
}
|
|
31022
31277
|
var DEFAULT_LIST_LIMIT = 10;
|
|
31023
31278
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
@@ -31105,7 +31360,19 @@ var PollingApiTaskSource = class {
|
|
|
31105
31360
|
if (seen.has(item.id)) continue;
|
|
31106
31361
|
if (this.opts.taskTypes && this.opts.taskTypes.length > 0 && !this.opts.taskTypes.includes(item.taskType)) continue;
|
|
31107
31362
|
if (this.opts.diaryIds && this.opts.diaryIds.length > 0 && (item.diaryId === null || !this.opts.diaryIds.includes(item.diaryId))) continue;
|
|
31108
|
-
if (this.opts.slotRegistry
|
|
31363
|
+
if (this.opts.slotRegistry) {
|
|
31364
|
+
const affinity = await isContinuationClaimableByThisDaemon(item, this.opts.slotRegistry);
|
|
31365
|
+
if (!affinity.claimable) {
|
|
31366
|
+
this.logger.debug({
|
|
31367
|
+
taskId: item.id,
|
|
31368
|
+
taskType: item.taskType,
|
|
31369
|
+
reason: affinity.reason,
|
|
31370
|
+
continueFrom: affinity.continueFrom,
|
|
31371
|
+
sessionDir: affinity.sessionDir
|
|
31372
|
+
}, "polling-api.continuation_skipped");
|
|
31373
|
+
continue;
|
|
31374
|
+
}
|
|
31375
|
+
}
|
|
31109
31376
|
if (this.opts.profileId) {
|
|
31110
31377
|
const allowed = item.allowedProfiles ?? [];
|
|
31111
31378
|
if (allowed.length > 0 && !allowed.some((p) => p.profileId === this.opts.profileId)) continue;
|
|
@@ -32268,6 +32535,63 @@ function pruneOldSnapshots(maxCached, currentDir) {
|
|
|
32268
32535
|
});
|
|
32269
32536
|
}
|
|
32270
32537
|
//#endregion
|
|
32538
|
+
//#region ../../libs/pi-extension/src/abort-utils.ts
|
|
32539
|
+
function throwIfAborted(signal, label) {
|
|
32540
|
+
if (!signal?.aborted) return;
|
|
32541
|
+
throw abortError(label, signal);
|
|
32542
|
+
}
|
|
32543
|
+
function abortError(label, signal) {
|
|
32544
|
+
const reason = signal.reason;
|
|
32545
|
+
const suffix = reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason);
|
|
32546
|
+
const err = /* @__PURE__ */ new Error(`${label} aborted: ${suffix}`);
|
|
32547
|
+
err.name = "AbortError";
|
|
32548
|
+
return err;
|
|
32549
|
+
}
|
|
32550
|
+
function cleanupLateResource(resourcePromise, opts) {
|
|
32551
|
+
resourcePromise.then(async (resource) => {
|
|
32552
|
+
try {
|
|
32553
|
+
await opts.cleanup(resource);
|
|
32554
|
+
} catch (err) {
|
|
32555
|
+
opts.onCleanupError?.(err);
|
|
32556
|
+
}
|
|
32557
|
+
}, () => {});
|
|
32558
|
+
}
|
|
32559
|
+
async function abortableResource(opts) {
|
|
32560
|
+
const { signal } = opts;
|
|
32561
|
+
if (!signal) return opts.promise;
|
|
32562
|
+
throwIfAborted(signal, opts.label);
|
|
32563
|
+
const resourcePromise = Promise.resolve(opts.promise);
|
|
32564
|
+
const abortPromise = new Promise((_, reject) => {
|
|
32565
|
+
const abort = () => {
|
|
32566
|
+
cleanupLateResource(resourcePromise, opts);
|
|
32567
|
+
reject(abortError(opts.label, signal));
|
|
32568
|
+
};
|
|
32569
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
32570
|
+
resourcePromise.then(() => signal.removeEventListener("abort", abort), () => signal.removeEventListener("abort", abort));
|
|
32571
|
+
});
|
|
32572
|
+
return Promise.race([resourcePromise, abortPromise]);
|
|
32573
|
+
}
|
|
32574
|
+
async function delay(ms, signal, label) {
|
|
32575
|
+
if (!signal) {
|
|
32576
|
+
await new Promise((resolve) => {
|
|
32577
|
+
setTimeout(resolve, ms);
|
|
32578
|
+
});
|
|
32579
|
+
return;
|
|
32580
|
+
}
|
|
32581
|
+
throwIfAborted(signal, label);
|
|
32582
|
+
await new Promise((resolve, reject) => {
|
|
32583
|
+
const listener = () => {
|
|
32584
|
+
clearTimeout(timeout);
|
|
32585
|
+
reject(abortError(label, signal));
|
|
32586
|
+
};
|
|
32587
|
+
const timeout = setTimeout(() => {
|
|
32588
|
+
signal.removeEventListener("abort", listener);
|
|
32589
|
+
resolve();
|
|
32590
|
+
}, ms);
|
|
32591
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
32592
|
+
});
|
|
32593
|
+
}
|
|
32594
|
+
//#endregion
|
|
32271
32595
|
//#region ../../libs/pi-extension/src/vm-manager.ts
|
|
32272
32596
|
/**
|
|
32273
32597
|
* Memory-backed VFS mount used by the daemon to inject task-context
|
|
@@ -32384,23 +32708,33 @@ var BASE_ALLOWED_HOSTS = [
|
|
|
32384
32708
|
* surface immediately rather than fall through to cryptic agent
|
|
32385
32709
|
* errors later.
|
|
32386
32710
|
*/
|
|
32387
|
-
async function vmRun(vm, label, command) {
|
|
32711
|
+
async function vmRun(vm, label, command, signal) {
|
|
32388
32712
|
const wrapped = `set -eu\nset -o pipefail\n${command}`;
|
|
32713
|
+
throwIfAborted(signal, `resume step "${label}"`);
|
|
32389
32714
|
const r = await vm.exec([
|
|
32390
32715
|
"sh",
|
|
32391
32716
|
"-c",
|
|
32392
32717
|
wrapped
|
|
32393
|
-
]);
|
|
32718
|
+
], { signal });
|
|
32394
32719
|
if (r.exitCode !== 0) {
|
|
32395
32720
|
const tail = [r.stderr, r.stdout].filter(Boolean).join("\n").slice(-800);
|
|
32396
32721
|
throw new Error(`resume step "${label}" failed (exit ${r.exitCode}):\n${tail}`);
|
|
32397
32722
|
}
|
|
32398
32723
|
}
|
|
32724
|
+
function nonErrorMessage(err) {
|
|
32725
|
+
if (typeof err === "string") return err;
|
|
32726
|
+
try {
|
|
32727
|
+
return JSON.stringify(err) ?? "unknown error";
|
|
32728
|
+
} catch {
|
|
32729
|
+
return "unknown error";
|
|
32730
|
+
}
|
|
32731
|
+
}
|
|
32399
32732
|
/**
|
|
32400
32733
|
* Resume a VM from a checkpoint, inject credentials, configure egress +
|
|
32401
32734
|
* TLS. Returns the managed VM handle.
|
|
32402
32735
|
*/
|
|
32403
32736
|
async function resumeVm(config) {
|
|
32737
|
+
throwIfAborted(config.signal, "VM resume");
|
|
32404
32738
|
const mainRepo = findMainWorktree();
|
|
32405
32739
|
const agentDir = path.join(mainRepo, ".moltnet", config.agentName);
|
|
32406
32740
|
const guestWorkspace = path.resolve(config.mountPath);
|
|
@@ -32444,24 +32778,33 @@ async function resumeVm(config) {
|
|
|
32444
32778
|
};
|
|
32445
32779
|
const resources = config.sandboxConfig?.resources;
|
|
32446
32780
|
const workspaceMode = config.workspaceMode ?? "shared_mount";
|
|
32447
|
-
const vm = await
|
|
32448
|
-
|
|
32449
|
-
|
|
32450
|
-
|
|
32451
|
-
|
|
32452
|
-
|
|
32453
|
-
|
|
32454
|
-
|
|
32455
|
-
|
|
32781
|
+
const vm = await abortableResource({
|
|
32782
|
+
promise: VmCheckpoint.load(config.checkpointPath).resume({
|
|
32783
|
+
httpHooks,
|
|
32784
|
+
env: vmEnv,
|
|
32785
|
+
...resources?.memory && { memory: resources.memory },
|
|
32786
|
+
...resources?.cpus && { cpus: resources.cpus },
|
|
32787
|
+
vfs: { mounts: {
|
|
32788
|
+
[guestWorkspace]: workspaceProvider,
|
|
32789
|
+
[GUEST_TASK_SKILLS_MOUNT]: new MemoryProvider()
|
|
32790
|
+
} }
|
|
32791
|
+
}),
|
|
32792
|
+
signal: config.signal,
|
|
32793
|
+
label: "VM resume",
|
|
32794
|
+
cleanup: (resumedVm) => resumedVm.close(),
|
|
32795
|
+
onCleanupError: (err) => {
|
|
32796
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32797
|
+
process.stderr.write(`[vm] aborted resume late vm.close() failed: ${message}\n`);
|
|
32798
|
+
}
|
|
32456
32799
|
});
|
|
32457
32800
|
try {
|
|
32458
|
-
await vm
|
|
32801
|
+
await vmRun(vm, "TLS certificates", `
|
|
32459
32802
|
cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
|
|
32460
32803
|
update-ca-certificates 2>/dev/null
|
|
32461
32804
|
cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
|
|
32462
|
-
|
|
32463
|
-
await vmRun(vm, "DNS resolvers", `printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' > /etc/resolv.conf
|
|
32464
|
-
await vmRun(vm, "git safe.directory", `git config --system --add safe.directory '*'
|
|
32805
|
+
`, config.signal);
|
|
32806
|
+
await vmRun(vm, "DNS resolvers", `printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' > /etc/resolv.conf`, config.signal);
|
|
32807
|
+
await vmRun(vm, "git safe.directory", `git config --system --add safe.directory '*'`, config.signal);
|
|
32465
32808
|
for (const [i, entry] of (config.sandboxConfig?.resumeCommands ?? []).entries()) {
|
|
32466
32809
|
if (!shouldRunResumeCommand(entry, { workspaceMode })) continue;
|
|
32467
32810
|
const { run, retries, backoffMs } = typeof entry === "string" ? {
|
|
@@ -32476,34 +32819,67 @@ async function resumeVm(config) {
|
|
|
32476
32819
|
const label = `resumeCommands[${i}]`;
|
|
32477
32820
|
let lastErr;
|
|
32478
32821
|
for (let attempt = 0; attempt <= retries; attempt++) try {
|
|
32479
|
-
await vmRun(vm, label, run);
|
|
32822
|
+
await vmRun(vm, label, run, config.signal);
|
|
32480
32823
|
lastErr = void 0;
|
|
32481
32824
|
break;
|
|
32482
32825
|
} catch (err) {
|
|
32483
32826
|
lastErr = err;
|
|
32484
32827
|
if (attempt === retries) break;
|
|
32485
|
-
await
|
|
32486
|
-
setTimeout(resolve, (attempt + 1) * backoffMs);
|
|
32487
|
-
});
|
|
32828
|
+
await delay((attempt + 1) * backoffMs, config.signal, label);
|
|
32488
32829
|
}
|
|
32489
|
-
if (lastErr) throw lastErr instanceof Error ? lastErr : new Error(
|
|
32830
|
+
if (lastErr) throw lastErr instanceof Error ? lastErr : new Error(nonErrorMessage(lastErr));
|
|
32490
32831
|
}
|
|
32491
32832
|
const vmSshDir = `${vmAgentDir}/ssh`;
|
|
32492
|
-
await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent
|
|
32493
|
-
if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, {
|
|
32833
|
+
await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent`, { signal: config.signal });
|
|
32834
|
+
if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, {
|
|
32835
|
+
mode: 384,
|
|
32836
|
+
signal: config.signal
|
|
32837
|
+
});
|
|
32494
32838
|
const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
|
|
32495
|
-
await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, {
|
|
32496
|
-
|
|
32839
|
+
await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, {
|
|
32840
|
+
mode: 384,
|
|
32841
|
+
signal: config.signal
|
|
32842
|
+
});
|
|
32843
|
+
await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, {
|
|
32844
|
+
mode: 384,
|
|
32845
|
+
signal: config.signal
|
|
32846
|
+
});
|
|
32497
32847
|
if (creds.gitconfig) {
|
|
32498
32848
|
const vmSigningKey = `${vmSshDir}/id_ed25519`;
|
|
32499
32849
|
const vmGitconfig = creds.gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSigningKey}`);
|
|
32500
|
-
await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
|
|
32850
|
+
await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
|
|
32851
|
+
mode: 420,
|
|
32852
|
+
signal: config.signal
|
|
32853
|
+
});
|
|
32501
32854
|
}
|
|
32502
|
-
if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, {
|
|
32503
|
-
|
|
32504
|
-
|
|
32505
|
-
|
|
32506
|
-
await vm.
|
|
32855
|
+
if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, {
|
|
32856
|
+
mode: 384,
|
|
32857
|
+
signal: config.signal
|
|
32858
|
+
});
|
|
32859
|
+
if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, {
|
|
32860
|
+
mode: 420,
|
|
32861
|
+
signal: config.signal
|
|
32862
|
+
});
|
|
32863
|
+
if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, {
|
|
32864
|
+
mode: 420,
|
|
32865
|
+
signal: config.signal
|
|
32866
|
+
});
|
|
32867
|
+
if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, {
|
|
32868
|
+
mode: 384,
|
|
32869
|
+
signal: config.signal
|
|
32870
|
+
});
|
|
32871
|
+
await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet", { signal: config.signal });
|
|
32872
|
+
const gitCredHelperPath = `${vmSshDir}/git-credential-moltnet`;
|
|
32873
|
+
const credHelperScript = `#!/bin/sh
|
|
32874
|
+
echo "username=x-access-token"
|
|
32875
|
+
echo "password=$(moltnet github token --credentials ${vmSshDir}/moltnet.json)"
|
|
32876
|
+
`;
|
|
32877
|
+
await vm.fs.writeFile(gitCredHelperPath, credHelperScript, {
|
|
32878
|
+
mode: 493,
|
|
32879
|
+
signal: config.signal
|
|
32880
|
+
});
|
|
32881
|
+
await vmRun(vm, "git credential helper", `git config --global credential.helper ${gitCredHelperPath} && \
|
|
32882
|
+
git config --global url."https://github.com/".insteadOf "git@github.com:"`, config.signal);
|
|
32507
32883
|
return {
|
|
32508
32884
|
vm,
|
|
32509
32885
|
credentials: creds,
|
|
@@ -33756,6 +34132,20 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
33756
34132
|
retryable: false
|
|
33757
34133
|
}
|
|
33758
34134
|
});
|
|
34135
|
+
const makeCancelledOutput = (message) => ({
|
|
34136
|
+
taskId: task.id,
|
|
34137
|
+
attemptN,
|
|
34138
|
+
status: "cancelled",
|
|
34139
|
+
output: null,
|
|
34140
|
+
outputCid: null,
|
|
34141
|
+
usage: finalUsage,
|
|
34142
|
+
durationMs: Date.now() - startTime,
|
|
34143
|
+
error: {
|
|
34144
|
+
code: "task_cancelled",
|
|
34145
|
+
message,
|
|
34146
|
+
retryable: false
|
|
34147
|
+
}
|
|
34148
|
+
});
|
|
33759
34149
|
let onTurnEvent;
|
|
33760
34150
|
if (opts.makeOnTurnEvent) try {
|
|
33761
34151
|
onTurnEvent = opts.makeOnTurnEvent(claimedTask);
|
|
@@ -33818,10 +34208,15 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
33818
34208
|
mountPath,
|
|
33819
34209
|
workspaceMode: workspace.mode,
|
|
33820
34210
|
extraAllowedHosts: opts.extraAllowedHosts,
|
|
33821
|
-
sandboxConfig
|
|
34211
|
+
sandboxConfig,
|
|
34212
|
+
signal: reporter.cancelSignal
|
|
33822
34213
|
});
|
|
33823
34214
|
} catch (err) {
|
|
33824
34215
|
const message = err instanceof Error ? err.message : String(err);
|
|
34216
|
+
if (reporter.cancelSignal.aborted) {
|
|
34217
|
+
await emitError("vm_resume", message, { cancelled: true });
|
|
34218
|
+
return makeCancelledOutput(reporter.cancelReason ?? "Task cancelled during VM resume.");
|
|
34219
|
+
}
|
|
33825
34220
|
await emitError("vm_resume", message);
|
|
33826
34221
|
return makeFailedOutput("vm_resume_failed", message);
|
|
33827
34222
|
}
|
|
@@ -34409,9 +34804,15 @@ function loadConfig() {
|
|
|
34409
34804
|
return {
|
|
34410
34805
|
agentDaemonStateDatabaseUrl: process.env["MOLTNET_AGENT_DAEMON_STATE_DATABASE_URL"] ?? "",
|
|
34411
34806
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
34412
|
-
logLevel: process.env["LOG_LEVEL"] ?? ""
|
|
34807
|
+
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
34808
|
+
profilePrerequisiteEnv: process.env,
|
|
34809
|
+
profilePrerequisitePath: process.env.PATH ?? "",
|
|
34810
|
+
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? ""
|
|
34413
34811
|
};
|
|
34414
34812
|
}
|
|
34813
|
+
function activatePiCodingAgentDir(path) {
|
|
34814
|
+
process.env["PI_CODING_AGENT_DIR"] = path;
|
|
34815
|
+
}
|
|
34415
34816
|
//#endregion
|
|
34416
34817
|
//#region src/lib/agent-context.ts
|
|
34417
34818
|
/**
|
|
@@ -34676,6 +35077,7 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
34676
35077
|
return {
|
|
34677
35078
|
...basePlan,
|
|
34678
35079
|
workspaceMode: "dedicated_worktree",
|
|
35080
|
+
workspaceId: resolution.producerSlot.workspace?.workspaceId ?? null,
|
|
34679
35081
|
worktreeBranch: resolution.producerSlot.workspace?.worktreeBranch ?? null,
|
|
34680
35082
|
sessionPersistence: {
|
|
34681
35083
|
sessionDir: `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`,
|
|
@@ -34837,13 +35239,27 @@ function createRootLogger(options) {
|
|
|
34837
35239
|
target: "pino-pretty",
|
|
34838
35240
|
options: { colorize: true }
|
|
34839
35241
|
}) : null;
|
|
35242
|
+
let transportClosed = prettyTransport === null;
|
|
35243
|
+
let shutdownStarted = false;
|
|
35244
|
+
prettyTransport?.on("close", () => {
|
|
35245
|
+
transportClosed = true;
|
|
35246
|
+
});
|
|
35247
|
+
prettyTransport?.on("error", (err) => {
|
|
35248
|
+
transportClosed = true;
|
|
35249
|
+
process.stderr.write(`[pino] transport error: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
35250
|
+
});
|
|
34840
35251
|
const logger = pino(options, prettyTransport ?? void 0);
|
|
34841
35252
|
const shutdown = async () => {
|
|
34842
|
-
if (!prettyTransport) return;
|
|
34843
|
-
|
|
35253
|
+
if (!prettyTransport || shutdownStarted || transportClosed) return;
|
|
35254
|
+
shutdownStarted = true;
|
|
35255
|
+
try {
|
|
35256
|
+
logger.flush();
|
|
35257
|
+
} catch (err) {
|
|
35258
|
+
process.stderr.write(`[pino] logger flush failed: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
35259
|
+
}
|
|
34844
35260
|
try {
|
|
34845
35261
|
prettyTransport.end();
|
|
34846
|
-
await once(prettyTransport, "close");
|
|
35262
|
+
if (!transportClosed) await once(prettyTransport, "close");
|
|
34847
35263
|
} catch (err) {
|
|
34848
35264
|
process.stderr.write(`[pino] transport teardown failed: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
34849
35265
|
}
|
|
@@ -34871,22 +35287,29 @@ var MissingRequiredOptionError = class extends Error {
|
|
|
34871
35287
|
this.name = "MissingRequiredOptionError";
|
|
34872
35288
|
}
|
|
34873
35289
|
};
|
|
34874
|
-
function parseCommonOptions(args) {
|
|
35290
|
+
function parseCommonOptions(args, options = {}) {
|
|
35291
|
+
const requireProviderModel = options.requireProviderModel ?? true;
|
|
35292
|
+
const runtimeDefaults = {
|
|
35293
|
+
leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
|
|
35294
|
+
heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
|
|
35295
|
+
maxBatchSize: options.runtimeDefaults?.maxBatchSize ?? DEFAULTS.maxBatchSize,
|
|
35296
|
+
warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
|
|
35297
|
+
};
|
|
34875
35298
|
if (!args.agent) throw new MissingRequiredOptionError("agent");
|
|
34876
|
-
if (!args.provider) throw new MissingRequiredOptionError("provider");
|
|
34877
|
-
if (!args.model) throw new MissingRequiredOptionError("model");
|
|
35299
|
+
if (requireProviderModel && !args.provider) throw new MissingRequiredOptionError("provider");
|
|
35300
|
+
if (requireProviderModel && !args.model) throw new MissingRequiredOptionError("model");
|
|
34878
35301
|
if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
34879
35302
|
return {
|
|
34880
35303
|
agent: args.agent,
|
|
34881
|
-
provider: args.provider,
|
|
34882
|
-
model: args.model,
|
|
34883
|
-
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec",
|
|
34884
|
-
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms",
|
|
34885
|
-
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size",
|
|
35304
|
+
...args.provider ? { provider: args.provider } : {},
|
|
35305
|
+
...args.model ? { model: args.model } : {},
|
|
35306
|
+
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
|
|
35307
|
+
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
|
|
35308
|
+
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
|
|
34886
35309
|
flushIntervalMs: parseNonNegativeInt(args["flush-interval-ms"], "flush-interval-ms", DEFAULTS.flushIntervalMs),
|
|
34887
35310
|
maxTurns: parseNonNegativeInt(args["max-turns"], "max-turns", DEFAULTS.maxTurns),
|
|
34888
35311
|
maxBashTimeouts: parseNonNegativeInt(args["max-bash-timeouts"], "max-bash-timeouts", DEFAULTS.maxBashTimeouts),
|
|
34889
|
-
warmSessionTtlSec: parseNonNegativeInt(args["warm-session-ttl-sec"], "warm-session-ttl-sec",
|
|
35312
|
+
warmSessionTtlSec: parseNonNegativeInt(args["warm-session-ttl-sec"], "warm-session-ttl-sec", runtimeDefaults.warmSessionTtlSec),
|
|
34890
35313
|
debug: args.debug === true
|
|
34891
35314
|
};
|
|
34892
35315
|
}
|
|
@@ -34977,6 +35400,88 @@ async function initWorkerOtel(options) {
|
|
|
34977
35400
|
};
|
|
34978
35401
|
}
|
|
34979
35402
|
//#endregion
|
|
35403
|
+
//#region src/lib/pi-agent-dir.ts
|
|
35404
|
+
function ensurePiAgentDir(repoRoot, explicitPath) {
|
|
35405
|
+
if (explicitPath) {
|
|
35406
|
+
mkdirSync(explicitPath, { recursive: true });
|
|
35407
|
+
return {
|
|
35408
|
+
path: explicitPath,
|
|
35409
|
+
source: "env"
|
|
35410
|
+
};
|
|
35411
|
+
}
|
|
35412
|
+
const path = join(repoRoot, ".pi");
|
|
35413
|
+
mkdirSync(path, { recursive: true });
|
|
35414
|
+
return {
|
|
35415
|
+
path,
|
|
35416
|
+
source: "repo"
|
|
35417
|
+
};
|
|
35418
|
+
}
|
|
35419
|
+
//#endregion
|
|
35420
|
+
//#region src/lib/runtime-profile.ts
|
|
35421
|
+
var RuntimeProfilePrerequisiteError = class extends Error {
|
|
35422
|
+
constructor(profileName, missingEnv, missingTools) {
|
|
35423
|
+
const parts = [missingEnv.length > 0 ? `missing env: ${missingEnv.join(", ")}` : null, missingTools.length > 0 ? `missing tools: ${missingTools.join(", ")}` : null].filter(Boolean);
|
|
35424
|
+
super(`Runtime profile "${profileName}" prerequisites are not satisfied: ${parts.join("; ")}`);
|
|
35425
|
+
this.profileName = profileName;
|
|
35426
|
+
this.missingEnv = missingEnv;
|
|
35427
|
+
this.missingTools = missingTools;
|
|
35428
|
+
this.name = "RuntimeProfilePrerequisiteError";
|
|
35429
|
+
}
|
|
35430
|
+
};
|
|
35431
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35432
|
+
async function resolveRuntimeProfile(options) {
|
|
35433
|
+
const profile = UUID_RE.test(options.profile) ? await options.agent.runtimeProfiles.get(options.profile) : await resolveProfileByName(options);
|
|
35434
|
+
if (options.teamId && profile.teamId !== options.teamId) throw new Error(`Runtime profile "${options.profile}" belongs to team ${profile.teamId}, not ${options.teamId}.`);
|
|
35435
|
+
return {
|
|
35436
|
+
id: profile.id,
|
|
35437
|
+
name: profile.name,
|
|
35438
|
+
teamId: profile.teamId,
|
|
35439
|
+
provider: profile.provider,
|
|
35440
|
+
model: profile.model,
|
|
35441
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
35442
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
35443
|
+
maxBatchSize: profile.maxBatchSize,
|
|
35444
|
+
sessionTtlSec: profile.sessionTtlSec,
|
|
35445
|
+
workspaceTtlSec: profile.workspaceTtlSec,
|
|
35446
|
+
requiredEnv: profile.requiredEnv,
|
|
35447
|
+
requiredTools: profile.requiredTools,
|
|
35448
|
+
sandboxConfig: profile.sandbox,
|
|
35449
|
+
mountPath: resolve(options.cwd),
|
|
35450
|
+
source: `runtime-profile:${profile.id}`
|
|
35451
|
+
};
|
|
35452
|
+
}
|
|
35453
|
+
function validateRuntimeProfilePrerequisites(profile, env, pathValue) {
|
|
35454
|
+
const missingEnv = profile.requiredEnv.filter((name) => !env[name]);
|
|
35455
|
+
const missingTools = profile.requiredTools.filter((tool) => !isExecutableOnPath(tool, pathValue));
|
|
35456
|
+
if (missingEnv.length > 0 || missingTools.length > 0) throw new RuntimeProfilePrerequisiteError(profile.name, missingEnv, missingTools);
|
|
35457
|
+
}
|
|
35458
|
+
function resolveProfileWarmSessionTtlSec(profile) {
|
|
35459
|
+
return Math.min(profile.sessionTtlSec, profile.workspaceTtlSec);
|
|
35460
|
+
}
|
|
35461
|
+
async function resolveProfileByName(options) {
|
|
35462
|
+
if (!options.teamId) throw new Error(`Runtime profile name "${options.profile}" requires --team. Use a profile UUID when running without a team-scoped list.`);
|
|
35463
|
+
const matches = (await options.agent.runtimeProfiles.list({ teamId: options.teamId })).items.filter((item) => item.name === options.profile);
|
|
35464
|
+
if (matches.length === 0) throw new Error(`Runtime profile "${options.profile}" was not found in team ${options.teamId}.`);
|
|
35465
|
+
if (matches.length > 1) throw new Error(`Runtime profile name "${options.profile}" is ambiguous in team ${options.teamId}. Use the profile UUID instead.`);
|
|
35466
|
+
return matches[0];
|
|
35467
|
+
}
|
|
35468
|
+
function isExecutableOnPath(tool, pathValue) {
|
|
35469
|
+
if (tool.includes("/")) return isExecutable(isAbsolute(tool) ? tool : resolve(process.cwd(), tool));
|
|
35470
|
+
for (const dir of (pathValue ?? "").split(delimiter)) {
|
|
35471
|
+
if (!dir) continue;
|
|
35472
|
+
if (isExecutable(resolve(dir, tool))) return true;
|
|
35473
|
+
}
|
|
35474
|
+
return false;
|
|
35475
|
+
}
|
|
35476
|
+
function isExecutable(path) {
|
|
35477
|
+
try {
|
|
35478
|
+
accessSync(path, constants.X_OK);
|
|
35479
|
+
return true;
|
|
35480
|
+
} catch {
|
|
35481
|
+
return false;
|
|
35482
|
+
}
|
|
35483
|
+
}
|
|
35484
|
+
//#endregion
|
|
34980
35485
|
//#region src/lib/sandbox.ts
|
|
34981
35486
|
function resolveSandbox(startDir, explicitPath) {
|
|
34982
35487
|
const path = explicitPath ? isAbsolute(explicitPath) ? explicitPath : resolve(startDir, explicitPath) : findUp(startDir, "sandbox.json");
|
|
@@ -35005,6 +35510,38 @@ function findUp(startDir, filename) {
|
|
|
35005
35510
|
}
|
|
35006
35511
|
}
|
|
35007
35512
|
//#endregion
|
|
35513
|
+
//#region src/lib/shutdown-signal.ts
|
|
35514
|
+
function installShutdownSignalHandlers(opts) {
|
|
35515
|
+
const proc = opts.proc ?? process;
|
|
35516
|
+
let drainingSignal = null;
|
|
35517
|
+
const onSignal = (signal) => {
|
|
35518
|
+
if (drainingSignal) {
|
|
35519
|
+
proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
|
|
35520
|
+
proc.exitCode = signalExitCode(signal);
|
|
35521
|
+
return;
|
|
35522
|
+
}
|
|
35523
|
+
drainingSignal = signal;
|
|
35524
|
+
proc.exitCode = signalExitCode(signal);
|
|
35525
|
+
try {
|
|
35526
|
+
opts.logDrain(signal);
|
|
35527
|
+
} catch (err) {
|
|
35528
|
+
proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
35529
|
+
}
|
|
35530
|
+
opts.drain(signal);
|
|
35531
|
+
};
|
|
35532
|
+
const handleSigint = () => onSignal("SIGINT");
|
|
35533
|
+
const handleSigterm = () => onSignal("SIGTERM");
|
|
35534
|
+
proc.on("SIGINT", handleSigint);
|
|
35535
|
+
proc.on("SIGTERM", handleSigterm);
|
|
35536
|
+
return { dispose: () => {
|
|
35537
|
+
proc.off("SIGINT", handleSigint);
|
|
35538
|
+
proc.off("SIGTERM", handleSigterm);
|
|
35539
|
+
} };
|
|
35540
|
+
}
|
|
35541
|
+
function signalExitCode(signal) {
|
|
35542
|
+
return signal === "SIGINT" ? 130 : 143;
|
|
35543
|
+
}
|
|
35544
|
+
//#endregion
|
|
35008
35545
|
//#region src/lib/state-dir.ts
|
|
35009
35546
|
function ensureDaemonStateDirs(mountPath) {
|
|
35010
35547
|
const rootDir = join(mountPath, ".moltnet", "d");
|
|
@@ -35054,7 +35591,8 @@ async function runPolling(opts) {
|
|
|
35054
35591
|
"poll-interval-ms": { type: "string" },
|
|
35055
35592
|
"max-poll-interval-ms": { type: "string" },
|
|
35056
35593
|
"list-limit": { type: "string" },
|
|
35057
|
-
sandbox: { type: "string" }
|
|
35594
|
+
sandbox: { type: "string" },
|
|
35595
|
+
profile: { type: "string" }
|
|
35058
35596
|
}
|
|
35059
35597
|
});
|
|
35060
35598
|
if (!values.team) {
|
|
@@ -35074,7 +35612,7 @@ async function runPolling(opts) {
|
|
|
35074
35612
|
const diaryIds = parseCsv(values["diary-ids"]);
|
|
35075
35613
|
let common;
|
|
35076
35614
|
try {
|
|
35077
|
-
common = parseCommonOptions(values);
|
|
35615
|
+
common = parseCommonOptions(values, { requireProviderModel: !values.profile });
|
|
35078
35616
|
} catch (err) {
|
|
35079
35617
|
if (err instanceof MissingRequiredOptionError) {
|
|
35080
35618
|
console.error(`${err.message}\n`);
|
|
@@ -35086,15 +35624,47 @@ async function runPolling(opts) {
|
|
|
35086
35624
|
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
35087
35625
|
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
35088
35626
|
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
35627
|
+
if (values.profile && values.sandbox) {
|
|
35628
|
+
console.error(`[${opts.modeLabel}] Cannot use --sandbox with --profile. Remote runtime profiles define sandbox policy.`);
|
|
35629
|
+
return 1;
|
|
35630
|
+
}
|
|
35089
35631
|
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).`);
|
|
35090
|
-
const sandbox = resolveSandbox(process.cwd(), values.sandbox);
|
|
35091
35632
|
const cfg = loadConfig();
|
|
35633
|
+
const ctx = await resolveAgentContext(common.agent);
|
|
35634
|
+
const profile = values.profile ? await resolveRuntimeProfile({
|
|
35635
|
+
agent: ctx.agent,
|
|
35636
|
+
profile: values.profile,
|
|
35637
|
+
teamId,
|
|
35638
|
+
cwd: process.cwd()
|
|
35639
|
+
}) : null;
|
|
35640
|
+
if (profile) {
|
|
35641
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
35642
|
+
common = parseCommonOptions(values, {
|
|
35643
|
+
requireProviderModel: false,
|
|
35644
|
+
runtimeDefaults: {
|
|
35645
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
35646
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
35647
|
+
maxBatchSize: profile.maxBatchSize,
|
|
35648
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
35649
|
+
}
|
|
35650
|
+
});
|
|
35651
|
+
}
|
|
35652
|
+
const provider = profile?.provider ?? common.provider;
|
|
35653
|
+
const model = profile?.model ?? common.model;
|
|
35654
|
+
if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
|
|
35655
|
+
const sandbox = profile ? {
|
|
35656
|
+
config: profile.sandboxConfig,
|
|
35657
|
+
rootDir: profile.mountPath,
|
|
35658
|
+
path: profile.source
|
|
35659
|
+
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
35660
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
35661
|
+
activatePiCodingAgentDir(piAgentDir.path);
|
|
35092
35662
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
35093
35663
|
const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
|
|
35094
35664
|
const slotIdentity = {
|
|
35095
35665
|
agentName: common.agent,
|
|
35096
|
-
provider
|
|
35097
|
-
model
|
|
35666
|
+
provider,
|
|
35667
|
+
model
|
|
35098
35668
|
};
|
|
35099
35669
|
const mainRepo = findMainWorktree();
|
|
35100
35670
|
const executionPlans = createExecutionPlanCache({
|
|
@@ -35103,7 +35673,6 @@ async function runPolling(opts) {
|
|
|
35103
35673
|
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
35104
35674
|
slotRegistry
|
|
35105
35675
|
});
|
|
35106
|
-
const ctx = await resolveAgentContext(common.agent);
|
|
35107
35676
|
const otelShutdown = await initWorkerOtel({
|
|
35108
35677
|
serviceName: opts.serviceName,
|
|
35109
35678
|
agentDir: ctx.agentDir,
|
|
@@ -35111,8 +35680,9 @@ async function runPolling(opts) {
|
|
|
35111
35680
|
resourceAttributes: {
|
|
35112
35681
|
"moltnet.team.id": teamId,
|
|
35113
35682
|
"moltnet.agent.name": common.agent,
|
|
35114
|
-
"moltnet.llm.provider":
|
|
35115
|
-
"moltnet.llm.model":
|
|
35683
|
+
"moltnet.llm.provider": provider,
|
|
35684
|
+
"moltnet.llm.model": model,
|
|
35685
|
+
...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
|
|
35116
35686
|
}
|
|
35117
35687
|
});
|
|
35118
35688
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -35123,46 +35693,84 @@ async function runPolling(opts) {
|
|
|
35123
35693
|
mode: opts.modeLabel,
|
|
35124
35694
|
agent: common.agent,
|
|
35125
35695
|
teamId,
|
|
35126
|
-
provider
|
|
35127
|
-
model
|
|
35696
|
+
provider,
|
|
35697
|
+
model,
|
|
35698
|
+
...profile ? {
|
|
35699
|
+
daemonProfileId: profile.id,
|
|
35700
|
+
daemonProfileName: profile.name
|
|
35701
|
+
} : {}
|
|
35128
35702
|
});
|
|
35129
35703
|
const abort = new AbortController();
|
|
35130
35704
|
let runtime = null;
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35705
|
+
let active = null;
|
|
35706
|
+
const signalHandlers = installShutdownSignalHandlers({
|
|
35707
|
+
logDrain: (signal) => {
|
|
35708
|
+
rootLogger.warn({ signal }, "agent-daemon.draining");
|
|
35709
|
+
},
|
|
35710
|
+
drain: (signal) => {
|
|
35711
|
+
abort.abort();
|
|
35712
|
+
runtime?.stop(`agent-daemon received ${signal}`);
|
|
35713
|
+
if (active === null) return;
|
|
35714
|
+
const { taskId, attemptN } = active;
|
|
35715
|
+
ctx.agent.tasks.abortAttempt(taskId, attemptN, { reason: `runner_${signal.toLowerCase()}` }).catch((err) => {
|
|
35716
|
+
try {
|
|
35717
|
+
rootLogger.warn({
|
|
35718
|
+
err: err instanceof Error ? err.message : String(err),
|
|
35719
|
+
taskId,
|
|
35720
|
+
attemptN
|
|
35721
|
+
}, "agent-daemon.abort_on_signal_failed");
|
|
35722
|
+
} catch {}
|
|
35723
|
+
});
|
|
35724
|
+
}
|
|
35725
|
+
});
|
|
35138
35726
|
rootLogger.info({
|
|
35139
35727
|
sandbox: sandbox.path,
|
|
35140
35728
|
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
35141
35729
|
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
35142
35730
|
leaseTtlSec: common.leaseTtlSec,
|
|
35143
35731
|
heartbeatIntervalMs: common.heartbeatIntervalMs,
|
|
35732
|
+
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
35144
35733
|
pollIntervalMs,
|
|
35145
|
-
maxPollIntervalMs
|
|
35734
|
+
maxPollIntervalMs,
|
|
35735
|
+
...profile ? {
|
|
35736
|
+
profileId: profile.id,
|
|
35737
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
35738
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
35739
|
+
} : {},
|
|
35740
|
+
piAgentDir: piAgentDir.path,
|
|
35741
|
+
piAgentDirSource: piAgentDir.source
|
|
35146
35742
|
}, "agent-daemon.starting");
|
|
35147
35743
|
const outputs = [];
|
|
35148
35744
|
try {
|
|
35149
|
-
const
|
|
35745
|
+
const rawExecuteTask = createPiTaskExecutor({
|
|
35150
35746
|
agentName: common.agent,
|
|
35151
35747
|
mountPath: sandbox.rootDir,
|
|
35152
|
-
provider
|
|
35153
|
-
model
|
|
35748
|
+
provider,
|
|
35749
|
+
model,
|
|
35154
35750
|
sandboxConfig: sandbox.config,
|
|
35155
35751
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
35156
35752
|
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
|
|
35157
35753
|
maxTurns: common.maxTurns,
|
|
35158
35754
|
maxBashTimeouts: common.maxBashTimeouts
|
|
35159
35755
|
});
|
|
35756
|
+
const executeTask = async (claimedTask, reporter) => {
|
|
35757
|
+
active = {
|
|
35758
|
+
taskId: claimedTask.task.id,
|
|
35759
|
+
attemptN: claimedTask.attemptN
|
|
35760
|
+
};
|
|
35761
|
+
try {
|
|
35762
|
+
return await rawExecuteTask(claimedTask, reporter);
|
|
35763
|
+
} finally {
|
|
35764
|
+
active = null;
|
|
35765
|
+
}
|
|
35766
|
+
};
|
|
35160
35767
|
runtime = new AgentRuntime({
|
|
35161
35768
|
logger: rootLogger,
|
|
35162
35769
|
source: new PollingApiTaskSource({
|
|
35163
35770
|
agent: ctx.agent,
|
|
35164
35771
|
teamId,
|
|
35165
35772
|
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
35773
|
+
...profile ? { profileId: profile.id } : {},
|
|
35166
35774
|
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
35167
35775
|
leaseTtlSec: common.leaseTtlSec,
|
|
35168
35776
|
listLimit,
|
|
@@ -35309,6 +35917,7 @@ async function runPolling(opts) {
|
|
|
35309
35917
|
rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
|
|
35310
35918
|
return drained.some((o) => o.status !== "completed") ? 1 : 0;
|
|
35311
35919
|
} finally {
|
|
35920
|
+
signalHandlers.dispose();
|
|
35312
35921
|
await slotRegistry.close();
|
|
35313
35922
|
await otelShutdown();
|
|
35314
35923
|
await shutdownLogger();
|
|
@@ -35353,7 +35962,8 @@ async function runOnce(argv) {
|
|
|
35353
35962
|
type: "string",
|
|
35354
35963
|
short: "t"
|
|
35355
35964
|
},
|
|
35356
|
-
sandbox: { type: "string" }
|
|
35965
|
+
sandbox: { type: "string" },
|
|
35966
|
+
profile: { type: "string" }
|
|
35357
35967
|
}
|
|
35358
35968
|
});
|
|
35359
35969
|
if (!values["task-id"]) {
|
|
@@ -35364,7 +35974,7 @@ async function runOnce(argv) {
|
|
|
35364
35974
|
const taskId = values["task-id"];
|
|
35365
35975
|
let opts;
|
|
35366
35976
|
try {
|
|
35367
|
-
opts = parseCommonOptions(values);
|
|
35977
|
+
opts = parseCommonOptions(values, { requireProviderModel: !values.profile });
|
|
35368
35978
|
} catch (err) {
|
|
35369
35979
|
if (err instanceof MissingRequiredOptionError) {
|
|
35370
35980
|
console.error(`${err.message}\n`);
|
|
@@ -35373,14 +35983,45 @@ async function runOnce(argv) {
|
|
|
35373
35983
|
}
|
|
35374
35984
|
throw err;
|
|
35375
35985
|
}
|
|
35376
|
-
|
|
35986
|
+
if (values.profile && values.sandbox) {
|
|
35987
|
+
console.error("Cannot use --sandbox with --profile. Remote runtime profiles define sandbox policy.");
|
|
35988
|
+
return 1;
|
|
35989
|
+
}
|
|
35377
35990
|
const cfg = loadConfig();
|
|
35991
|
+
const ctx = await resolveAgentContext(opts.agent);
|
|
35992
|
+
const profile = values.profile ? await resolveRuntimeProfile({
|
|
35993
|
+
agent: ctx.agent,
|
|
35994
|
+
profile: values.profile,
|
|
35995
|
+
cwd: process.cwd()
|
|
35996
|
+
}) : null;
|
|
35997
|
+
if (profile) {
|
|
35998
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
35999
|
+
opts = parseCommonOptions(values, {
|
|
36000
|
+
requireProviderModel: false,
|
|
36001
|
+
runtimeDefaults: {
|
|
36002
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
36003
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
36004
|
+
maxBatchSize: profile.maxBatchSize,
|
|
36005
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
36006
|
+
}
|
|
36007
|
+
});
|
|
36008
|
+
}
|
|
36009
|
+
const provider = profile?.provider ?? opts.provider;
|
|
36010
|
+
const model = profile?.model ?? opts.model;
|
|
36011
|
+
if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
|
|
36012
|
+
const sandbox = profile ? {
|
|
36013
|
+
config: profile.sandboxConfig,
|
|
36014
|
+
rootDir: profile.mountPath,
|
|
36015
|
+
path: profile.source
|
|
36016
|
+
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
36017
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
36018
|
+
activatePiCodingAgentDir(piAgentDir.path);
|
|
35378
36019
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
35379
36020
|
const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
|
|
35380
36021
|
const slotIdentity = {
|
|
35381
36022
|
agentName: opts.agent,
|
|
35382
|
-
provider
|
|
35383
|
-
model
|
|
36023
|
+
provider,
|
|
36024
|
+
model
|
|
35384
36025
|
};
|
|
35385
36026
|
const mainRepo = findMainWorktree();
|
|
35386
36027
|
const executionPlans = createExecutionPlanCache({
|
|
@@ -35389,7 +36030,6 @@ async function runOnce(argv) {
|
|
|
35389
36030
|
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
35390
36031
|
slotRegistry
|
|
35391
36032
|
});
|
|
35392
|
-
const ctx = await resolveAgentContext(opts.agent);
|
|
35393
36033
|
const otelShutdown = await initWorkerOtel({
|
|
35394
36034
|
serviceName: "moltnet.agent-daemon.once",
|
|
35395
36035
|
agentDir: ctx.agentDir,
|
|
@@ -35397,8 +36037,9 @@ async function runOnce(argv) {
|
|
|
35397
36037
|
resourceAttributes: {
|
|
35398
36038
|
"moltnet.task.id": taskId,
|
|
35399
36039
|
"moltnet.agent.name": opts.agent,
|
|
35400
|
-
"moltnet.llm.provider":
|
|
35401
|
-
"moltnet.llm.model":
|
|
36040
|
+
"moltnet.llm.provider": provider,
|
|
36041
|
+
"moltnet.llm.model": model,
|
|
36042
|
+
...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
|
|
35402
36043
|
}
|
|
35403
36044
|
});
|
|
35404
36045
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -35408,39 +36049,59 @@ async function runOnce(argv) {
|
|
|
35408
36049
|
const rootLogger = logger.child({
|
|
35409
36050
|
mode: "once",
|
|
35410
36051
|
agent: opts.agent,
|
|
35411
|
-
provider
|
|
35412
|
-
model
|
|
36052
|
+
provider,
|
|
36053
|
+
model,
|
|
36054
|
+
...profile ? {
|
|
36055
|
+
daemonProfileId: profile.id,
|
|
36056
|
+
daemonProfileName: profile.name
|
|
36057
|
+
} : {}
|
|
35413
36058
|
});
|
|
35414
36059
|
rootLogger.info({
|
|
35415
36060
|
sandbox: sandbox.path,
|
|
35416
|
-
taskId
|
|
36061
|
+
taskId,
|
|
36062
|
+
leaseTtlSec: opts.leaseTtlSec,
|
|
36063
|
+
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
36064
|
+
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
36065
|
+
...profile ? {
|
|
36066
|
+
profileId: profile.id,
|
|
36067
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
36068
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
36069
|
+
} : {},
|
|
36070
|
+
piAgentDir: piAgentDir.path,
|
|
36071
|
+
piAgentDirSource: piAgentDir.source
|
|
35417
36072
|
}, "agent-daemon.starting");
|
|
35418
36073
|
let runtime = null;
|
|
35419
|
-
|
|
35420
|
-
|
|
35421
|
-
|
|
35422
|
-
taskId
|
|
35423
|
-
}, "agent-daemon.draining");
|
|
35424
|
-
runtime?.stop();
|
|
35425
|
-
ctx.agent.tasks.cancel(taskId, { reason: `runner_${sig.toLowerCase()}` }).catch((err) => {
|
|
36074
|
+
let activeAttemptN = null;
|
|
36075
|
+
const signalHandlers = installShutdownSignalHandlers({
|
|
36076
|
+
logDrain: (signal) => {
|
|
35426
36077
|
rootLogger.warn({
|
|
35427
|
-
|
|
36078
|
+
signal,
|
|
35428
36079
|
taskId
|
|
35429
|
-
}, "agent-daemon.
|
|
35430
|
-
}
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
|
|
36080
|
+
}, "agent-daemon.draining");
|
|
36081
|
+
},
|
|
36082
|
+
drain: (signal) => {
|
|
36083
|
+
runtime?.stop(`agent-daemon received ${signal}`);
|
|
36084
|
+
if (activeAttemptN === null) return;
|
|
36085
|
+
const attemptN = activeAttemptN;
|
|
36086
|
+
ctx.agent.tasks.abortAttempt(taskId, attemptN, { reason: `runner_${signal.toLowerCase()}` }).catch((err) => {
|
|
36087
|
+
try {
|
|
36088
|
+
rootLogger.warn({
|
|
36089
|
+
err: err instanceof Error ? err.message : String(err),
|
|
36090
|
+
taskId,
|
|
36091
|
+
attemptN
|
|
36092
|
+
}, "agent-daemon.abort_on_signal_failed");
|
|
36093
|
+
} catch (logErr) {
|
|
36094
|
+
process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
|
|
36095
|
+
}
|
|
36096
|
+
});
|
|
36097
|
+
}
|
|
35437
36098
|
});
|
|
35438
36099
|
try {
|
|
35439
36100
|
const rawExecuteTask = createPiTaskExecutor({
|
|
35440
36101
|
agentName: opts.agent,
|
|
35441
36102
|
mountPath: sandbox.rootDir,
|
|
35442
|
-
provider
|
|
35443
|
-
model
|
|
36103
|
+
provider,
|
|
36104
|
+
model,
|
|
35444
36105
|
sandboxConfig: sandbox.config,
|
|
35445
36106
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
35446
36107
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
@@ -35501,9 +36162,11 @@ async function runOnce(argv) {
|
|
|
35501
36162
|
lastAttemptN: claimedTask.attemptN,
|
|
35502
36163
|
ttlSec: opts.warmSessionTtlSec
|
|
35503
36164
|
});
|
|
36165
|
+
activeAttemptN = claimedTask.attemptN;
|
|
35504
36166
|
try {
|
|
35505
36167
|
return await rawExecuteTask(claimedTask, reporter);
|
|
35506
36168
|
} finally {
|
|
36169
|
+
activeAttemptN = null;
|
|
35507
36170
|
executionPlans.delete(claimedTask);
|
|
35508
36171
|
if (executionPlan.slotKey) await slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey, opts.warmSessionTtlSec, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
35509
36172
|
}
|
|
@@ -35517,7 +36180,8 @@ async function runOnce(argv) {
|
|
|
35517
36180
|
source: new ApiTaskSource({
|
|
35518
36181
|
agent: ctx.agent,
|
|
35519
36182
|
taskId,
|
|
35520
|
-
leaseTtlSec: opts.leaseTtlSec
|
|
36183
|
+
leaseTtlSec: opts.leaseTtlSec,
|
|
36184
|
+
...profile ? { profileId: profile.id } : {}
|
|
35521
36185
|
}),
|
|
35522
36186
|
makeReporter: () => new ApiTaskReporter({
|
|
35523
36187
|
tasks: ctx.agent.tasks,
|
|
@@ -35546,6 +36210,7 @@ async function runOnce(argv) {
|
|
|
35546
36210
|
console.log(JSON.stringify(output, null, 2));
|
|
35547
36211
|
return output.status === "completed" ? 0 : 1;
|
|
35548
36212
|
} finally {
|
|
36213
|
+
signalHandlers.dispose();
|
|
35549
36214
|
await slotRegistry.close();
|
|
35550
36215
|
await otelShutdown();
|
|
35551
36216
|
await shutdownLogger();
|