@themoltnet/agent-daemon 0.7.0 → 0.8.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +210 -162
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -57,7 +57,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
57
57
  # https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/env-api-keys.ts
58
58
  ```
59
59
 
60
- To use an alternate auth-file path: `PI_AUTH_PATH=/abs/path/to/auth.json`.
60
+ To use a non-default auth directory: `PI_CODING_AGENT_DIR=/abs/path/to/.pi/agent`.
61
61
 
62
62
  ### Observability
63
63
 
package/dist/main.js CHANGED
@@ -3045,10 +3045,10 @@ function validateRubricWeights(rubric) {
3045
3045
  * complementary places.
3046
3046
  *
3047
3047
  * Before this envelope existed, criteria were scattered: a vestigial
3048
- * `criteriaCid` column nobody resolved, an `acceptanceCriteria: string[]`
3049
- * field on `fulfill_brief.input` that was "interpreted by the claiming
3050
- * agent," and inline `rubric` / `criteria[]` fields on judgment-task
3051
- * inputs. None of those were machine-verifiable end-to-end.
3048
+ * `criteriaCid` column nobody resolved, free-form prose on
3049
+ * `fulfill_brief.input`, and inline `rubric` / `criteria[]` fields on
3050
+ * judgment-task inputs. None of those were machine-verifiable
3051
+ * end-to-end.
3052
3052
  *
3053
3053
  * This module defines a single, content-addressable envelope an imposer
3054
3054
  * attaches to any task type. It has four orthogonal sections — pick
@@ -4185,7 +4185,6 @@ var FULFILL_BRIEF_TYPE = "fulfill_brief";
4185
4185
  var FulfillBriefInput = Type$2.Object({
4186
4186
  brief: Type$2.String({ minLength: 1 }),
4187
4187
  title: Type$2.Optional(Type$2.String()),
4188
- acceptanceCriteria: Type$2.Optional(Type$2.Array(Type$2.String())),
4189
4188
  successCriteria: Type$2.Optional(SuccessCriteria),
4190
4189
  seedFiles: Type$2.Optional(Type$2.Array(Type$2.String())),
4191
4190
  scopeHint: Type$2.Optional(Type$2.String())
@@ -6169,78 +6168,6 @@ function isHelpFlag(args) {
6169
6168
  return args.includes("--help") || args.includes("-h");
6170
6169
  }
6171
6170
  //#endregion
6172
- //#region ../../libs/agent-runtime/src/subagent-output-contracts.ts
6173
- var REGISTRY = /* @__PURE__ */ new Map();
6174
- /**
6175
- * Register a subagent output contract. Idempotent: re-registering the
6176
- * same name with a different schema throws — contracts are meant to
6177
- * be stable. Re-registering with the identical contract object (same
6178
- * reference) is a no-op for HMR and test convenience.
6179
- *
6180
- * Typically called at module-init time alongside task-type
6181
- * registration. See task-types/index.ts in @moltnet/tasks for the
6182
- * conventional pattern.
6183
- */
6184
- function registerSubagentOutputContract(contract) {
6185
- if (!contract.name || contract.name.trim().length === 0) throw new Error("subagent output contract name is required");
6186
- if (!/^[a-z][a-z0-9_]*$/.test(contract.name)) throw new Error(`subagent output contract name '${contract.name}' must be lower_snake_case (starts with a letter, then [a-z0-9_]+)`);
6187
- const existing = REGISTRY.get(contract.name);
6188
- if (existing && existing !== contract) {
6189
- if (existing.parametersSchema !== contract.parametersSchema) throw new Error(`subagent output contract '${contract.name}' is already registered with a different schema; refusing to override`);
6190
- }
6191
- REGISTRY.set(contract.name, contract);
6192
- }
6193
- /**
6194
- * Resolve a subagent output contract by name. Returns `null` for
6195
- * unknown names — callers (the subagent custom tool) decide whether
6196
- * that's a tool error the parent LLM can recover from or a hard fail.
6197
- */
6198
- function getSubagentOutputContract(name) {
6199
- return REGISTRY.get(name) ?? null;
6200
- }
6201
- /**
6202
- * List all registered contracts. Useful for diagnostics and for the
6203
- * subagent tool's parameter description so a parent LLM can see what
6204
- * contracts are available without enumerating them in its prompt.
6205
- */
6206
- function listSubagentOutputContracts() {
6207
- return [...REGISTRY.values()];
6208
- }
6209
- //#endregion
6210
- //#region ../../libs/agent-runtime/src/built-in-contract-registrations.ts
6211
- /**
6212
- * Built-in subagent output contracts (#1087, #943).
6213
- *
6214
- * Why this is an exported function and not a module-init side
6215
- * effect:
6216
- *
6217
- * - The registry is process-global. Module-init registration
6218
- * fires exactly once per Node process (ESM modules are cached
6219
- * by URL). Tests that call `__resetSubagentOutputContractsForTests()`
6220
- * to start from an empty registry have no way to repopulate
6221
- * the built-ins without re-evaluating the module — which the
6222
- * cache prevents. PR #1101 review M4.
6223
- * - An explicit `registerBuiltInSubagentContracts()` lets the
6224
- * package index call it once at module load AND lets test
6225
- * setup hooks call it again after `__reset...`.
6226
- * - `registerSubagentOutputContract` is itself idempotent for
6227
- * identical re-registrations, so calling this function twice
6228
- * in the same process is safe.
6229
- *
6230
- * Adding a new built-in: extend the body of this function. Do not
6231
- * call `registerSubagentOutputContract` from anywhere else in the
6232
- * package — keeping all built-ins in one function makes the set
6233
- * auditable.
6234
- */
6235
- function registerBuiltInSubagentContracts() {
6236
- registerSubagentOutputContract({
6237
- name: "judge_eval_variant_result",
6238
- description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
6239
- parametersSchema: JudgeEvalVariantResult
6240
- });
6241
- }
6242
- registerBuiltInSubagentContracts();
6243
- //#endregion
6244
6171
  //#region ../../libs/agent-runtime/src/context-bindings.ts
6245
6172
  var PROMPT_SEPARATOR = "\n\n---\n\n";
6246
6173
  /**
@@ -6700,13 +6627,7 @@ function buildCuratePackUserPrompt(input, ctx) {
6700
6627
  * is told to inspect them itself.
6701
6628
  */
6702
6629
  function buildFulfillBriefUserPrompt(input, ctx) {
6703
- const { brief, title, acceptanceCriteria, seedFiles, scopeHint } = input;
6704
- const criteriaSection = acceptanceCriteria?.length ? [
6705
- "### Acceptance criteria",
6706
- "",
6707
- ...acceptanceCriteria.map((c) => `- ${c}`),
6708
- ""
6709
- ].join("\n") : "";
6630
+ const { brief, title, seedFiles, scopeHint } = input;
6710
6631
  const seedSection = seedFiles?.length ? [
6711
6632
  "### Seed files",
6712
6633
  "",
@@ -6754,7 +6675,6 @@ function buildFulfillBriefUserPrompt(input, ctx) {
6754
6675
  "",
6755
6676
  brief,
6756
6677
  "",
6757
- criteriaSection,
6758
6678
  seedSection,
6759
6679
  correlationSection,
6760
6680
  workspaceSection,
@@ -13916,6 +13836,31 @@ function abortableSleep(ms, signal) {
13916
13836
  });
13917
13837
  }
13918
13838
  //#endregion
13839
+ //#region ../../libs/agent-runtime/src/subagent-output-contracts.ts
13840
+ /**
13841
+ * Construct an immutable contract registry from a static list.
13842
+ *
13843
+ * The resulting registry is safe to share across sessions and
13844
+ * invocations — no mutation is possible after construction.
13845
+ */
13846
+ function createSubagentContractRegistry(contracts) {
13847
+ const lookup = /* @__PURE__ */ new Map();
13848
+ for (const c of contracts) {
13849
+ if (!c.name || c.name.trim().length === 0) throw new Error("subagent output contract name is required");
13850
+ if (!/^[a-z][a-z0-9_]*$/.test(c.name)) throw new Error(`subagent output contract name '${c.name}' must be lower_snake_case (starts with a letter, then [a-z0-9_]+)`);
13851
+ if (lookup.has(c.name)) throw new Error(`duplicate subagent output contract name '${c.name}' in constructor args`);
13852
+ lookup.set(c.name, c);
13853
+ }
13854
+ return {
13855
+ get(name) {
13856
+ return lookup.get(name) ?? null;
13857
+ },
13858
+ list() {
13859
+ return [...lookup.values()];
13860
+ }
13861
+ };
13862
+ }
13863
+ //#endregion
13919
13864
  //#region ../../libs/pi-extension/src/moltnet/render-phase6.ts
13920
13865
  function slugToTitle(value) {
13921
13866
  return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
@@ -14892,7 +14837,8 @@ function findMainWorktree() {
14892
14837
  function loadCredentials(agentDir) {
14893
14838
  const moltnetJson = readFileSync(path.join(agentDir, "moltnet.json"), "utf8");
14894
14839
  const agentEnvRaw = readFileSync(path.join(agentDir, "env"), "utf8");
14895
- const piAuthPath = process.env.PI_AUTH_PATH ?? path.join(process.env.HOME ?? "", ".pi", "agent", "auth.json");
14840
+ const piAgentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent");
14841
+ const piAuthPath = path.join(piAgentDir, "auth.json");
14896
14842
  const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
14897
14843
  const gitconfigPath = path.join(agentDir, "gitconfig");
14898
14844
  const gitconfig = existsSync(gitconfigPath) ? readFileSync(gitconfigPath, "utf8") : null;
@@ -15404,7 +15350,7 @@ async function buildAgentSession(args) {
15404
15350
  spanAttributes: args.otelSpanAttrs
15405
15351
  });
15406
15352
  const resourceLoader = new DefaultResourceLoader({
15407
- cwd: args.mountPath,
15353
+ cwd: args.cwdPath,
15408
15354
  agentDir: args.piAuthDir,
15409
15355
  extensionFactories: [piOtelExtension],
15410
15356
  appendSystemPrompt: args.appendSystemPrompt,
@@ -15412,12 +15358,12 @@ async function buildAgentSession(args) {
15412
15358
  });
15413
15359
  await resourceLoader.reload();
15414
15360
  const sessionManager = args.sessionPersistence ? await resolvePersistentSessionManager({
15415
- cwd: args.mountPath,
15361
+ cwd: args.cwdPath,
15416
15362
  sessionDir: args.sessionPersistence.sessionDir
15417
- }) : SessionManager.inMemory(args.mountPath);
15363
+ }) : SessionManager.inMemory(args.cwdPath);
15418
15364
  return (await createAgentSession({
15419
15365
  agentDir: args.piAuthDir,
15420
- cwd: args.mountPath,
15366
+ cwd: args.cwdPath,
15421
15367
  model: args.modelHandle,
15422
15368
  customTools: args.customTools,
15423
15369
  sessionManager,
@@ -15662,6 +15608,7 @@ var DEFAULT_SUBAGENT_TIMEOUT_MS = 300 * 1e3;
15662
15608
  */
15663
15609
  function createSubagentTool(args) {
15664
15610
  const buildSession = args.buildAgentSession ?? buildAgentSession;
15611
+ const { contractRegistry } = args;
15665
15612
  let callCount = 0;
15666
15613
  return {
15667
15614
  tool: defineTool({
@@ -15672,8 +15619,8 @@ function createSubagentTool(args) {
15672
15619
  async execute(_id, params) {
15673
15620
  if (!Check(SubagentToolParameters, params)) return toolError(`subagent: invalid parameters: ${JSON.stringify([...Errors(SubagentToolParameters, params)].slice(0, 3))}`);
15674
15621
  const { task, output_schema } = params;
15675
- const contract = getSubagentOutputContract(output_schema);
15676
- if (!contract) return toolError(`subagent: unknown output_schema "${output_schema}". Registered contracts: [${listSubagentOutputContracts().map((c) => c.name).join(", ")}]`);
15622
+ const contract = contractRegistry.get(output_schema);
15623
+ if (!contract) return toolError(`subagent: unknown output_schema "${output_schema}". Registered contracts: [${contractRegistry.list().map((c) => c.name).join(", ")}]`);
15677
15624
  callCount += 1;
15678
15625
  const callIndex = callCount;
15679
15626
  let captured = null;
@@ -15703,6 +15650,7 @@ function createSubagentTool(args) {
15703
15650
  });
15704
15651
  const session = await buildSession({
15705
15652
  mountPath: args.mountPath,
15653
+ cwdPath: args.cwdPath ?? args.mountPath,
15706
15654
  piAuthDir: args.piAuthDir,
15707
15655
  modelHandle: args.modelHandle,
15708
15656
  agentName: args.agentName,
@@ -16043,6 +15991,7 @@ function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
16043
15991
  const branch = executionPlan?.worktreeBranch ?? null;
16044
15992
  if (!branch) return {
16045
15993
  mountPath: requestedMountPath,
15994
+ cwdPath: requestedMountPath,
16046
15995
  mode: "shared_mount",
16047
15996
  branch: null,
16048
15997
  cleanup: () => {}
@@ -16050,7 +15999,7 @@ function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
16050
15999
  const mainRepo = findMainWorktree();
16051
16000
  const worktreeDir = resolveTaskWorktreePath(mainRepo, executionPlan?.workspaceId ?? `task-${task.id}`);
16052
16001
  const relMount = relative(mainRepo, requestedMountPath);
16053
- const mountPath = relMount === "" || relMount.startsWith("..") ? worktreeDir : join(worktreeDir, relMount);
16002
+ const cwdPath = relMount === "" || relMount.startsWith("..") ? worktreeDir : join(worktreeDir, relMount);
16054
16003
  const keepWorkspace = executionPlan?.workspaceScope === "session" && executionPlan.sessionKey !== null;
16055
16004
  if (keepWorkspace) ensureReusableTaskWorktree(mainRepo, worktreeDir, branch);
16056
16005
  else {
@@ -16058,7 +16007,8 @@ function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
16058
16007
  addTaskWorktree(mainRepo, worktreeDir, branch);
16059
16008
  }
16060
16009
  return {
16061
- mountPath,
16010
+ mountPath: mainRepo,
16011
+ cwdPath,
16062
16012
  mode: "dedicated_worktree",
16063
16013
  branch,
16064
16014
  cleanup: keepWorkspace ? () => {} : () => {
@@ -16171,15 +16121,24 @@ var noopTurnEventHandler = () => {};
16171
16121
  function createPiTaskExecutor(opts) {
16172
16122
  let cachedCheckpoint = opts.checkpointPath ?? null;
16173
16123
  return async (claimedTask, reporter) => {
16174
- if (!cachedCheckpoint) cachedCheckpoint = await ensureSnapshot({
16175
- config: opts.sandboxConfig?.snapshot,
16176
- onProgress: opts.onSnapshotProgress ?? ((m) => {
16177
- process.stderr.write(`[snapshot] ${m}\n`);
16178
- })
16124
+ const reporterWasOpened = !reporter.cancelSignal.aborted;
16125
+ if (reporterWasOpened) await reporter.open({
16126
+ taskId: claimedTask.task.id,
16127
+ attemptN: claimedTask.attemptN
16179
16128
  });
16180
16129
  return executePiTask(claimedTask, reporter, {
16181
16130
  ...opts,
16182
- checkpointPath: cachedCheckpoint
16131
+ checkpointPath: cachedCheckpoint ?? void 0,
16132
+ resolveCheckpointPath: async () => {
16133
+ if (!cachedCheckpoint) cachedCheckpoint = await ensureSnapshot({
16134
+ config: opts.sandboxConfig?.snapshot,
16135
+ onProgress: opts.onSnapshotProgress ?? ((m) => {
16136
+ process.stderr.write(`[snapshot] ${m}\n`);
16137
+ })
16138
+ });
16139
+ return cachedCheckpoint;
16140
+ },
16141
+ reporterAlreadyOpened: reporterWasOpened
16183
16142
  });
16184
16143
  };
16185
16144
  }
@@ -16195,8 +16154,9 @@ async function executePiTask(claimedTask, reporter, opts) {
16195
16154
  const startTime = Date.now();
16196
16155
  const requestedMountPath = opts.mountPath ?? process.cwd();
16197
16156
  const executionPlan = opts.makeExecutionPlan?.(claimedTask) ?? null;
16198
- const workspace = prepareTaskWorkspace(task, requestedMountPath, executionPlan);
16199
- const mountPath = workspace.mountPath;
16157
+ let workspace = null;
16158
+ let mountPath = requestedMountPath;
16159
+ let cwdPath = requestedMountPath;
16200
16160
  if (reporter.cancelSignal.aborted) return {
16201
16161
  taskId: task.id,
16202
16162
  attemptN,
@@ -16211,33 +16171,8 @@ async function executePiTask(claimedTask, reporter, opts) {
16211
16171
  retryable: false
16212
16172
  }
16213
16173
  };
16214
- const checkpointPath = opts.checkpointPath ?? await ensureSnapshot({
16215
- config: opts.sandboxConfig?.snapshot,
16216
- onProgress: opts.onSnapshotProgress ?? ((m) => {
16217
- process.stderr.write(`[snapshot] ${m}\n`);
16218
- })
16219
- });
16220
- const mainRepoForRepair = findMainWorktree();
16221
- try {
16222
- execFileSync("git", [
16223
- "-C",
16224
- mainRepoForRepair,
16225
- "worktree",
16226
- "repair",
16227
- "--relative-paths"
16228
- ], { stdio: "pipe" });
16229
- } catch {}
16174
+ let reporterOpen = opts.reporterAlreadyOpened ?? false;
16230
16175
  let managed = null;
16231
- managed = await resumeVm({
16232
- checkpointPath,
16233
- agentName: opts.agentName,
16234
- mountPath,
16235
- extraAllowedHosts: opts.extraAllowedHosts,
16236
- sandboxConfig: opts.sandboxConfig
16237
- });
16238
- const diaryId = task.diaryId ?? "";
16239
- const taskTeamId = task.teamId ?? "";
16240
- let reporterOpen = false;
16241
16176
  let session = null;
16242
16177
  let subagentHandle = null;
16243
16178
  const finalUsage = emptyUsage(opts.provider, opts.model);
@@ -16256,41 +16191,103 @@ async function executePiTask(claimedTask, reporter, opts) {
16256
16191
  retryable: false
16257
16192
  }
16258
16193
  });
16194
+ let onTurnEvent;
16195
+ if (opts.makeOnTurnEvent) try {
16196
+ onTurnEvent = opts.makeOnTurnEvent(claimedTask);
16197
+ } catch (err) {
16198
+ process.stderr.write(`[emit] makeOnTurnEvent threw: ${err instanceof Error ? err.message : String(err)}\n`);
16199
+ onTurnEvent = noopTurnEventHandler;
16200
+ }
16201
+ else onTurnEvent = opts.onTurnEvent ?? noopTurnEventHandler;
16202
+ const emit = (kind, payload) => {
16203
+ try {
16204
+ onTurnEvent(kind, summarizePayloadForLog(kind, payload));
16205
+ } catch (err) {
16206
+ process.stderr.write(`[emit] onTurnEvent threw for kind="${kind}": ${err instanceof Error ? err.message : String(err)}\n`);
16207
+ }
16208
+ return reporter.record({
16209
+ kind,
16210
+ payload
16211
+ });
16212
+ };
16213
+ const emitError = async (phase, message, extra = {}) => {
16214
+ await emit("error", {
16215
+ phase,
16216
+ message,
16217
+ ...extra
16218
+ });
16219
+ };
16259
16220
  try {
16260
- const mainRepo = findMainWorktree();
16261
- activateAgentEnv(managed.credentials.agentEnv, mainRepo);
16262
- await reporter.open({
16221
+ if (!opts.reporterAlreadyOpened) await reporter.open({
16263
16222
  taskId: task.id,
16264
16223
  attemptN
16265
16224
  });
16266
16225
  reporterOpen = true;
16267
- let onTurnEvent;
16268
- if (opts.makeOnTurnEvent) try {
16269
- onTurnEvent = opts.makeOnTurnEvent(claimedTask);
16226
+ let checkpointPath;
16227
+ try {
16228
+ checkpointPath = opts.checkpointPath ?? (opts.resolveCheckpointPath ? await opts.resolveCheckpointPath() : await ensureSnapshot({
16229
+ config: opts.sandboxConfig?.snapshot,
16230
+ onProgress: opts.onSnapshotProgress ?? ((m) => {
16231
+ process.stderr.write(`[snapshot] ${m}\n`);
16232
+ })
16233
+ }));
16270
16234
  } catch (err) {
16271
- process.stderr.write(`[emit] makeOnTurnEvent threw: ${err instanceof Error ? err.message : String(err)}\n`);
16272
- onTurnEvent = noopTurnEventHandler;
16235
+ const message = err instanceof Error ? err.message : String(err);
16236
+ await emitError("snapshot", message);
16237
+ return makeFailedOutput("snapshot_failed", message);
16273
16238
  }
16274
- else onTurnEvent = opts.onTurnEvent ?? noopTurnEventHandler;
16275
- const emit = (kind, payload) => {
16239
+ try {
16240
+ workspace = prepareTaskWorkspace(task, requestedMountPath, executionPlan);
16241
+ mountPath = workspace.mountPath;
16242
+ cwdPath = workspace.cwdPath;
16243
+ } catch (err) {
16244
+ const message = err instanceof Error ? err.message : String(err);
16245
+ await emitError("worktree_setup", message);
16246
+ return makeFailedOutput("worktree_setup_failed", message);
16247
+ }
16248
+ try {
16249
+ const mainRepoForRepair = findMainWorktree();
16276
16250
  try {
16277
- onTurnEvent(kind, summarizePayloadForLog(kind, payload));
16278
- } catch (err) {
16279
- process.stderr.write(`[emit] onTurnEvent threw for kind="${kind}": ${err instanceof Error ? err.message : String(err)}\n`);
16280
- }
16281
- return reporter.record({
16282
- kind,
16283
- payload
16251
+ execFileSync("git", [
16252
+ "-C",
16253
+ mainRepoForRepair,
16254
+ "worktree",
16255
+ "repair",
16256
+ "--relative-paths"
16257
+ ], { stdio: "pipe" });
16258
+ } catch {}
16259
+ } catch (err) {
16260
+ const message = err instanceof Error ? err.message : String(err);
16261
+ await emitError("worktree_setup", message);
16262
+ return makeFailedOutput("worktree_setup_failed", message);
16263
+ }
16264
+ try {
16265
+ managed = await resumeVm({
16266
+ checkpointPath,
16267
+ agentName: opts.agentName,
16268
+ mountPath,
16269
+ extraAllowedHosts: opts.extraAllowedHosts,
16270
+ sandboxConfig: opts.sandboxConfig
16284
16271
  });
16285
- };
16272
+ } catch (err) {
16273
+ const message = err instanceof Error ? err.message : String(err);
16274
+ await emitError("vm_resume", message);
16275
+ return makeFailedOutput("vm_resume_failed", message);
16276
+ }
16277
+ const diaryId = task.diaryId ?? "";
16278
+ const taskTeamId = task.teamId ?? "";
16279
+ const mainRepo = findMainWorktree();
16280
+ activateAgentEnv(managed.credentials.agentEnv, mainRepo);
16281
+ const activeWorkspace = workspace;
16282
+ if (!activeWorkspace) throw new Error("task workspace not prepared");
16286
16283
  await emit("info", {
16287
16284
  event: "execute_start",
16288
16285
  taskType: task.taskType,
16289
16286
  teamId: task.teamId,
16290
16287
  provider: opts.provider,
16291
16288
  model: opts.model,
16292
- workspaceMode: workspace.mode,
16293
- workspaceBranch: workspace.branch
16289
+ workspaceMode: activeWorkspace.mode,
16290
+ workspaceBranch: activeWorkspace.branch
16294
16291
  });
16295
16292
  let taskPrompt;
16296
16293
  try {
@@ -16298,8 +16295,8 @@ async function executePiTask(claimedTask, reporter, opts) {
16298
16295
  diaryId,
16299
16296
  taskId: task.id,
16300
16297
  workspace: {
16301
- mode: workspace.mode,
16302
- branch: workspace.branch
16298
+ mode: activeWorkspace.mode,
16299
+ branch: activeWorkspace.branch
16303
16300
  },
16304
16301
  extras: opts.promptExtras
16305
16302
  });
@@ -16351,7 +16348,7 @@ async function executePiTask(claimedTask, reporter, opts) {
16351
16348
  getTeamId: () => taskTeamId,
16352
16349
  getSessionErrors: () => [],
16353
16350
  clearSessionErrors: () => {},
16354
- getHostCwd: () => mountPath,
16351
+ getHostCwd: () => cwdPath,
16355
16352
  hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]),
16356
16353
  hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
16357
16354
  getTaskContext: () => ({
@@ -16379,6 +16376,7 @@ async function executePiTask(claimedTask, reporter, opts) {
16379
16376
  if (taskTypeUsesSubagents(task.taskType)) {
16380
16377
  subagentHandle = createSubagentTool({
16381
16378
  mountPath,
16379
+ cwdPath,
16382
16380
  piAuthDir,
16383
16381
  modelHandle,
16384
16382
  agentName: opts.agentName,
@@ -16387,12 +16385,14 @@ async function executePiTask(claimedTask, reporter, opts) {
16387
16385
  parentTaskId: task.id,
16388
16386
  parentTaskType: task.taskType,
16389
16387
  parentAttemptN: attemptN,
16388
+ contractRegistry: opts.subagentContractRegistry,
16390
16389
  parentCancelSignal: reporter.cancelSignal
16391
16390
  });
16392
16391
  parentSubagentTools.push(subagentHandle.tool);
16393
16392
  }
16394
16393
  session = await buildAgentSession({
16395
16394
  mountPath,
16395
+ cwdPath,
16396
16396
  piAuthDir,
16397
16397
  modelHandle,
16398
16398
  agentName: opts.agentName,
@@ -16477,6 +16477,10 @@ async function executePiTask(claimedTask, reporter, opts) {
16477
16477
  is_error: event.isError,
16478
16478
  result: event.isError ? truncateForWire(event.result) : void 0
16479
16479
  }));
16480
+ if (event.isError) track(emitError("tool_call_error", describeToolErrorMessage(event.result), {
16481
+ tool: event.toolName,
16482
+ result: truncateForWire(event.result)
16483
+ }));
16480
16484
  if (maxBashTimeouts > 0 && event.toolName === "bash" && event.isError && isBashTimeoutResult(event.result)) {
16481
16485
  bashTimeoutCount += 1;
16482
16486
  if (bashTimeoutCount >= maxBashTimeouts) triggerCapAbort("max_bash_timeouts_exceeded", `Aborted after ${bashTimeoutCount} bash timeouts in this attempt (cap ${maxBashTimeouts}).`);
@@ -16632,7 +16636,7 @@ async function executePiTask(claimedTask, reporter, opts) {
16632
16636
  }
16633
16637
  }
16634
16638
  if (managed) await managed.vm.close();
16635
- try {
16639
+ if (workspace) try {
16636
16640
  workspace.cleanup();
16637
16641
  } catch (err) {
16638
16642
  const detail = err instanceof Error ? err.message : String(err);
@@ -16743,6 +16747,23 @@ function truncateForWire(value) {
16743
16747
  };
16744
16748
  }
16745
16749
  }
16750
+ function describeToolErrorMessage(result) {
16751
+ if (typeof result === "string" && result.trim().length > 0) return result.trim();
16752
+ if (result && typeof result === "object") {
16753
+ const content = result.content;
16754
+ if (Array.isArray(content)) {
16755
+ for (const item of content) if (item && typeof item === "object" && typeof item.text === "string") {
16756
+ const text = item.text.trim();
16757
+ if (text.length > 0) return text;
16758
+ }
16759
+ }
16760
+ }
16761
+ try {
16762
+ return JSON.stringify(truncateForWire(result));
16763
+ } catch {
16764
+ return "Tool call failed";
16765
+ }
16766
+ }
16746
16767
  //#endregion
16747
16768
  //#region src/config.ts
16748
16769
  function loadConfig() {
@@ -17619,9 +17640,15 @@ async function runPolling(opts) {
17619
17640
  maxPollIntervalMs
17620
17641
  }, "agent-daemon.starting");
17621
17642
  const outputs = [];
17643
+ const subagentContractRegistry = createSubagentContractRegistry([{
17644
+ name: "judge_eval_variant_result",
17645
+ description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
17646
+ parametersSchema: JudgeEvalVariantResult
17647
+ }]);
17622
17648
  try {
17623
17649
  const executeTask = createPiTaskExecutor({
17624
17650
  agentName: common.agent,
17651
+ subagentContractRegistry,
17625
17652
  mountPath: sandbox.rootDir,
17626
17653
  provider: common.provider,
17627
17654
  model: common.model,
@@ -17667,11 +17694,19 @@ async function runPolling(opts) {
17667
17694
  executeTask: async (claimedTask, reporter) => {
17668
17695
  const executionPlan = executionPlans.getOrCreate(claimedTask);
17669
17696
  const sessionDescriptor = executionPlan.descriptor;
17670
- const expired = slotRegistry.reapExpiredSlots();
17671
- if (expired.length > 0) rootLogger.info({
17672
- expiredCount: expired.length,
17673
- slotKeys: expired.map((item) => item.slot.slotKey)
17674
- }, "agent-daemon.daemon_slots_reaped");
17697
+ let expired;
17698
+ try {
17699
+ expired = slotRegistry.reapExpiredSlots();
17700
+ if (expired.length > 0) rootLogger.info({
17701
+ expiredCount: expired.length,
17702
+ slotKeys: expired.map((item) => item.slot.slotKey)
17703
+ }, "agent-daemon.daemon_slots_reaped");
17704
+ } catch (err) {
17705
+ rootLogger.error({
17706
+ phase: "daemon_slot_reap",
17707
+ err: err instanceof Error ? err.message : String(err)
17708
+ }, "agent-daemon.daemon_slot_reap_failed");
17709
+ }
17675
17710
  rootLogger.debug({
17676
17711
  taskId: claimedTask.task.id,
17677
17712
  taskType: claimedTask.task.taskType,
@@ -17866,9 +17901,15 @@ async function runOnce(argv) {
17866
17901
  process.on("SIGTERM", () => {
17867
17902
  onSignal("SIGTERM");
17868
17903
  });
17904
+ const subagentContractRegistry = createSubagentContractRegistry([{
17905
+ name: "judge_eval_variant_result",
17906
+ description: "Per-variant grading result produced by a subagent of judge_eval_variant: scores against the shared rubric, composite, and a 1-3 sentence verdict for a single variant.",
17907
+ parametersSchema: JudgeEvalVariantResult
17908
+ }]);
17869
17909
  try {
17870
17910
  const rawExecuteTask = createPiTaskExecutor({
17871
17911
  agentName: opts.agent,
17912
+ subagentContractRegistry,
17872
17913
  mountPath: sandbox.rootDir,
17873
17914
  provider: opts.provider,
17874
17915
  model: opts.model,
@@ -17879,11 +17920,18 @@ async function runOnce(argv) {
17879
17920
  maxBashTimeouts: opts.maxBashTimeouts
17880
17921
  });
17881
17922
  const executeTask = async (claimedTask, reporter) => {
17882
- const expired = slotRegistry.reapExpiredSlots();
17883
- if (expired.length > 0) rootLogger.info({
17884
- expiredCount: expired.length,
17885
- slotKeys: expired.map((item) => item.slot.slotKey)
17886
- }, "agent-daemon.daemon_slots_reaped");
17923
+ try {
17924
+ const expired = slotRegistry.reapExpiredSlots();
17925
+ if (expired.length > 0) rootLogger.info({
17926
+ expiredCount: expired.length,
17927
+ slotKeys: expired.map((item) => item.slot.slotKey)
17928
+ }, "agent-daemon.daemon_slots_reaped");
17929
+ } catch (err) {
17930
+ rootLogger.error({
17931
+ phase: "daemon_slot_reap",
17932
+ err: err instanceof Error ? err.message : String(err)
17933
+ }, "agent-daemon.daemon_slot_reap_failed");
17934
+ }
17887
17935
  const executionPlan = executionPlans.getOrCreate(claimedTask);
17888
17936
  if (executionPlan.slotKey && executionPlan.sessionPersistence) slotRegistry.beginSlot({
17889
17937
  ...slotIdentity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "MoltNet agent daemon — claims and executes tasks (fulfill_brief, assess_brief) from the MoltNet task-service via Pi-headless. CLI: moltnet-agent.",
@@ -33,8 +33,8 @@
33
33
  "@opentelemetry/semantic-conventions": "^1.39.0",
34
34
  "pino": "^10.3.1",
35
35
  "pino-pretty": "^13.1.3",
36
- "@themoltnet/pi-extension": "0.17.0",
37
- "@themoltnet/agent-runtime": "0.15.1",
36
+ "@themoltnet/agent-runtime": "0.15.2",
37
+ "@themoltnet/pi-extension": "0.18.0",
38
38
  "@themoltnet/sdk": "0.102.0"
39
39
  },
40
40
  "devDependencies": {