totemlayer 0.1.15 → 0.1.17

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.
@@ -10,7 +10,6 @@ import {
10
10
  readPrivateRuntimeJson,
11
11
  } from "./mission-agent-session.mjs";
12
12
  import { inspectLocalRepository } from "./repository.mjs";
13
- import { customerInspector } from "./local-inspector.mjs";
14
13
 
15
14
  const execute = promisify(execFile);
16
15
  const sha = (value) => createHash("sha256").update(value).digest("hex");
@@ -252,13 +251,10 @@ export async function runInfrastructureWorkerCycle({
252
251
  infrastructureProvider,
253
252
  signal,
254
253
  emit = () => {},
255
- inspector,
256
- inspectorOptions,
257
254
  }) {
258
255
  const kind = candidateWorkItem?.metadata?.kind;
259
256
  if (!recoveryOnly && !KINDS.has(kind)) return false;
260
257
  if (recoveryOnly && !infrastructureProvider) return false;
261
- inspector = customerInspector({ inspector, inspectorOptions });
262
258
  if (!_session || !infrastructureProvider)
263
259
  throw new Error("Infrastructure work requires the connected agent service.");
264
260
  assertMissionAgentSession(_session, baseUrl, expectedAgentId);
@@ -305,18 +301,6 @@ export async function runInfrastructureWorkerCycle({
305
301
  };
306
302
  await persist();
307
303
  const mission = await client.missions.get(missionId, { signal: activeSignal });
308
- await inspector.assertAllowed(
309
- "input",
310
- {
311
- goal: mission.goal,
312
- workItem: item,
313
- infrastructure: mission.infrastructure,
314
- workItems: mission.workItems,
315
- repository: mission.repository,
316
- },
317
- { signal: activeSignal },
318
- );
319
- record.originalTask = String(mission.goal ?? "");
320
304
  if (mission.infrastructure?.provider !== "local-static")
321
305
  throw new Error("Unsupported infrastructure provider.");
322
306
  const repository = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
@@ -382,11 +366,6 @@ export async function runInfrastructureWorkerCycle({
382
366
  emit({ type: "work_started", missionId, workItemId: record.workItemId, stage: record.kind });
383
367
  const action = record.kind === "infrastructure_deploy" ? "app.deploy" : "app.verify";
384
368
  if (record.state === "infra_source") {
385
- await inspector.assertAllowed(
386
- "action",
387
- { action: "repository.read", resource: record.repositoryResource, source: record.source },
388
- { task: record.originalTask, signal: activeSignal },
389
- );
390
369
  const effect = await client.missions.reserveEffect(
391
370
  missionId,
392
371
  {
@@ -415,20 +394,6 @@ export async function runInfrastructureWorkerCycle({
415
394
  record.source.sourceDirectory,
416
395
  activeSignal,
417
396
  );
418
- for (const file of bundle.files) {
419
- const mediaType = MIME[extname(file.path).toLowerCase()];
420
- if (
421
- mediaType?.startsWith("text/") ||
422
- mediaType === "application/json" ||
423
- mediaType === "image/svg+xml"
424
- )
425
- await inspector.inspectFile(
426
- file.path,
427
- mediaType === "image/svg+xml" ? "application/xml" : mediaType,
428
- Buffer.from(file.content, "base64"),
429
- { task: record.originalTask, signal: activeSignal },
430
- );
431
- }
432
397
  if (
433
398
  record.kind === "infrastructure_verify" &&
434
399
  (bundle.bundleSha256 !== record.source.bundleSha256 ||
@@ -472,16 +437,6 @@ export async function runInfrastructureWorkerCycle({
472
437
  await persist();
473
438
  }
474
439
  if (record.state === "infra_reserved") {
475
- await inspector.assertAllowed(
476
- "action",
477
- {
478
- action,
479
- resource: `totem:mission:${missionId}:app`,
480
- parameters: record.parameters,
481
- url: record.deploymentUrl,
482
- },
483
- { task: record.originalTask, signal: activeSignal },
484
- );
485
440
  const effect = await client.missions.reserveEffect(
486
441
  missionId,
487
442
  {
@@ -548,10 +503,6 @@ export async function runInfrastructureWorkerCycle({
548
503
  await persist();
549
504
  }
550
505
  const summary = `${action === "app.deploy" ? "Published" : "Verified"} the reviewed static app locally. The preview is available while its deployment agent is connected.`;
551
- await inspector.assertAllowed("output", record.receipt, {
552
- task: record.originalTask,
553
- signal: activeSignal,
554
- });
555
506
  await client.missions.complete(
556
507
  missionId,
557
508
  record.workItemId,
@@ -21,7 +21,6 @@ import { setTimeout as sleep } from "node:timers/promises";
21
21
  import { inspectLocalRepository } from "./repository.mjs";
22
22
  import { localClientEnvironment } from "./platform-client.mjs";
23
23
  import { localModelEndpoint } from "./local-model-provider.mjs";
24
- import { customerInspector } from "./local-inspector.mjs";
25
24
  import { externalModelReply, isExternalModelProvider } from "./model-adapter.mjs";
26
25
  import { runDockerNodeTests } from "./docker-test-sandbox.mjs";
27
26
  import { acquireMissionAgentSession, assertMissionAgentSession } from "./mission-agent-session.mjs";
@@ -753,10 +752,7 @@ export async function runCodexMissionAgent({
753
752
  stage,
754
753
  agentName,
755
754
  agentAddress,
756
- inspector = customerInspector(),
757
- task,
758
755
  }) {
759
- await inspector.assertAllowed("input", prompt, { task, signal });
760
756
  if (provider !== "ollama-local")
761
757
  throw new Error(
762
758
  "The legacy Codex adapter requires explicit provider: ollama-local. Use runLocalMissionAgent for native Qwen by default.",
@@ -842,7 +838,6 @@ export async function runCodexMissionAgent({
842
838
  });
843
839
  }
844
840
  if (!final.trim()) throw new Error("The connected agent returned no final result.");
845
- await inspector.assertAllowed("output", { text: final, commands }, { task, signal });
846
841
  return { text: final.slice(0, 32000), commands: commands.slice(-30) };
847
842
  }
848
843
 
@@ -909,14 +904,7 @@ export function redactRepositorySource(source) {
909
904
  return output;
910
905
  }
911
906
 
912
- export async function repositoryEvidence(
913
- checkout,
914
- stage,
915
- checkoutInfo,
916
- inspector = customerInspector(),
917
- task,
918
- signal,
919
- ) {
907
+ export async function repositoryEvidence(checkout, stage, checkoutInfo) {
920
908
  if (!["engineering", "review"].includes(stage)) return "";
921
909
  const tracked = (await git(checkout, "ls-files"))
922
910
  .split("\n")
@@ -947,9 +935,7 @@ export async function repositoryEvidence(
947
935
  throw new Error(`The reviewer cannot inspect the complete changed file: ${path}`);
948
936
  continue;
949
937
  }
950
- const original = await readFile(absolute, "utf8");
951
- await inspector.assertAllowed("tool-result", { path, source: original }, { task, signal });
952
- const source = redactRepositorySource(original);
938
+ const source = redactRepositorySource(await readFile(absolute, "utf8"));
953
939
  const numbered = source
954
940
  .split("\n")
955
941
  .map((line, index) => `${index + 1}: ${line}`)
@@ -964,10 +950,7 @@ export async function repositoryEvidence(
964
950
  return JSON.stringify({ trackedFiles: paths, changedFiles: changed, files });
965
951
  }
966
952
 
967
- async function codeRepositoryEvidence(
968
- checkout,
969
- { repair = false, inspector = customerInspector(), task, signal } = {},
970
- ) {
953
+ async function codeRepositoryEvidence(checkout, { repair = false } = {}) {
971
954
  const checkoutPath = await realpath(checkout);
972
955
  const changed = repair
973
956
  ? [
@@ -1005,7 +988,6 @@ async function codeRepositoryEvidence(
1005
988
  continue;
1006
989
  }
1007
990
  const content = await readFile(absolute, "utf8");
1008
- await inspector.assertAllowed("tool-result", { path, content }, { task, signal });
1009
991
  if (
1010
992
  /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16})\b/.test(
1011
993
  content,
@@ -1587,10 +1569,7 @@ async function runStructuredMissionAgent({
1587
1569
  reasoning = false,
1588
1570
  rejectionEvidenceDirectory,
1589
1571
  adapterSettings = {},
1590
- inspector = customerInspector(),
1591
- task,
1592
1572
  }) {
1593
- await inspector.assertAllowed("input", prompt, { task, signal });
1594
1573
  const regressionFile =
1595
1574
  stage === "code" && !repair ? await selectMissionRegressionTestFile(checkout) : null;
1596
1575
  const properties =
@@ -1651,9 +1630,8 @@ async function runStructuredMissionAgent({
1651
1630
  throw new Error("Specify the installed model for this provider.");
1652
1631
  const evidence =
1653
1632
  stage === "code"
1654
- ? await codeRepositoryEvidence(checkout, { repair, inspector, task, signal })
1655
- : await repositoryEvidence(checkout, stage, checkoutInfo, inspector, task, signal);
1656
- await inspector.assertAllowed("tool-result", evidence, { task, signal });
1633
+ ? await codeRepositoryEvidence(checkout, { repair })
1634
+ : await repositoryEvidence(checkout, stage, checkoutInfo);
1657
1635
  const regressionInstruction = regressionFile
1658
1636
  ? `\n\nWorker-selected regression file: ${JSON.stringify(regressionFile.path)}. Return exactly this one path in testChanges, as a new file. Do not edit, replace, or rename any existing test file in initial generation. The new .test.js file uses ${regressionFile.moduleType === "module" ? "ES modules: import test from 'node:test' and assert from 'node:assert/strict'" : "CommonJS: require('node:test') and require('node:assert/strict'); use an async test with dynamic import() if the source API is an ES module"}. Import the actual source API with paths relative to this file. Node --test discovers this filename. Baseline expectation changes for accepted new fields are reserved for exact-edit repair after actual test failure.`
1659
1637
  : "";
@@ -1662,7 +1640,6 @@ async function runStructuredMissionAgent({
1662
1640
  let correction = "";
1663
1641
  const useReasoning = reasoning === true && ["code", "review"].includes(stage);
1664
1642
  for (let attempt = 0; attempt < (stage === "code" ? 2 : 1); attempt += 1) {
1665
- await inspector.assertAllowed("input", correction, { task, signal });
1666
1643
  const generationLimit = useReasoning ? 8192 : stage === "code" ? 4096 : 2048;
1667
1644
  const data = await requestModel(endpoint, {
1668
1645
  method: "POST",
@@ -1709,25 +1686,15 @@ async function runStructuredMissionAgent({
1709
1686
  provider !== "ollama-local" ? data?.choices?.[0]?.message?.content : data?.message?.content;
1710
1687
  if (typeof result !== "string" || !result.trim())
1711
1688
  throw new Error("The local model returned no structured result.");
1712
- await inspector.assertAllowed("output", result, { task, signal: combined });
1713
1689
  if (stage !== "code") return { text: result.slice(0, 32000), commands: [] };
1714
1690
  try {
1715
1691
  return repair
1716
- ? await inspector.runAction(
1717
- { checkout, result },
1718
- (input) => applyModelFileEdits(input.checkout, input.result),
1719
- { task, signal: combined },
1720
- )
1721
- : await inspector.runAction(
1722
- { checkout, result },
1723
- (input) =>
1724
- applyModelFileChanges(input.checkout, input.result, {
1725
- requireRegressionTests: true,
1726
- requireSeparateTestChanges: true,
1727
- newRegressionTestPath: regressionFile.path,
1728
- }),
1729
- { task, signal: combined },
1730
- );
1692
+ ? await applyModelFileEdits(checkout, result)
1693
+ : await applyModelFileChanges(checkout, result, {
1694
+ requireRegressionTests: true,
1695
+ requireSeparateTestChanges: true,
1696
+ newRegressionTestPath: regressionFile.path,
1697
+ });
1731
1698
  } catch (error) {
1732
1699
  if (rejectionEvidenceDirectory) {
1733
1700
  await mkdir(rejectionEvidenceDirectory, {
@@ -2419,7 +2386,6 @@ async function settleCompletedResult(client, missionId, workItemId, path, record
2419
2386
  stage: record.stage,
2420
2387
  result: record.result,
2421
2388
  leaseToken: record.leaseToken,
2422
- originalTask: record.originalTask,
2423
2389
  });
2424
2390
  }
2425
2391
 
@@ -2431,7 +2397,6 @@ async function recoverReadyCompletions(
2431
2397
  signal,
2432
2398
  emit,
2433
2399
  limit = Infinity,
2434
- inspector = customerInspector(),
2435
2400
  ) {
2436
2401
  let handled = 0;
2437
2402
  for (const name of await readdir(directory)) {
@@ -2479,7 +2444,6 @@ async function recoverReadyCompletions(
2479
2444
  await reportBlockedCompletion(record);
2480
2445
  continue;
2481
2446
  }
2482
- await inspector.assertAllowed("output", record.result, { task: record.originalTask, signal });
2483
2447
  if (record.state === "result_settling")
2484
2448
  await settleCompletedResult(client, missionId, workItemId, path, record, signal);
2485
2449
  await client.missions.complete(
@@ -2557,26 +2521,7 @@ export async function runMissionWorker({
2557
2521
  emit = () => {},
2558
2522
  runAgent = runLocalMissionAgent,
2559
2523
  adapterSettings = {},
2560
- inspector,
2561
- inspectorOptions,
2562
2524
  }) {
2563
- inspector = customerInspector({ inspector, inspectorOptions });
2564
- const uncheckedRunAgent = runAgent;
2565
- let originalTask;
2566
- runAgent = async (input) => {
2567
- const result = await inspector.runAction(
2568
- {
2569
- prompt: input.prompt,
2570
- checkout: input.checkout,
2571
- write: input.write ?? false,
2572
- stage: input.stage,
2573
- },
2574
- (snapshot) => uncheckedRunAgent({ ...input, ...snapshot, inspector, task: originalTask }),
2575
- { task: originalTask, signal: input.signal },
2576
- );
2577
- await inspector.assertAllowed("output", result, { task: originalTask, signal: input.signal });
2578
- return result;
2579
- };
2580
2525
  if (!UUID.test(missionId)) throw new Error("Provide a valid mission ID.");
2581
2526
  missionModelEndpoint(provider, { ollamaUrl, qwenUrl, nimUrl });
2582
2527
  model ??=
@@ -2655,7 +2600,6 @@ export async function runMissionWorker({
2655
2600
  signal,
2656
2601
  emit,
2657
2602
  _singleCycle ? 1 : Infinity,
2658
- inspector,
2659
2603
  );
2660
2604
  if (_singleCycle && recovered) return { activity: "recovery", handled: recovered };
2661
2605
  const reconciled = await recoverKnownLocalTestFailures(
@@ -2668,7 +2612,6 @@ export async function runMissionWorker({
2668
2612
  );
2669
2613
  if (_singleCycle && reconciled) return { activity: "recovery", handled: reconciled };
2670
2614
  const mission = await client.missions.get(missionId, { signal });
2671
- originalTask = String(mission.goal ?? "");
2672
2615
  if (
2673
2616
  mission.status === "canceled" ||
2674
2617
  mission.status === "completed" ||
@@ -2713,32 +2656,6 @@ export async function runMissionWorker({
2713
2656
  )
2714
2657
  continue;
2715
2658
  processed += 1;
2716
- try {
2717
- await inspector.assertAllowed(
2718
- "input",
2719
- {
2720
- goal: mission.goal,
2721
- desiredOutput: mission.desiredOutput,
2722
- repository: mission.repository,
2723
- workItem,
2724
- events: mission.events ?? [],
2725
- artifacts: mission.artifacts ?? [],
2726
- },
2727
- { task: originalTask, signal },
2728
- );
2729
- } catch (error) {
2730
- if (!error.code?.startsWith("INSPECTION_")) throw error;
2731
- if (!reportedAttention.has(workItem.id)) {
2732
- await recordRecoveryBlocker(workItem, error.code);
2733
- emit({
2734
- type: "needs_attention",
2735
- workItemId: workItem.id,
2736
- code: error.code,
2737
- message: "The customer-side inspector blocked this assignment before launch.",
2738
- });
2739
- }
2740
- continue;
2741
- }
2742
2659
  let stage;
2743
2660
  try {
2744
2661
  stage = stageOf(workItem);
@@ -3091,16 +3008,6 @@ export async function runMissionWorker({
3091
3008
  ? ["repository.read"]
3092
3009
  : [];
3093
3010
  const authorizeLocalLaunch = async () => {
3094
- await inspector.assertAllowed(
3095
- "action",
3096
- {
3097
- stage,
3098
- actions: repositoryActions,
3099
- resource: mission.repository?.url ?? null,
3100
- objective: workItem.objective,
3101
- },
3102
- { task: originalTask, signal: combined },
3103
- );
3104
3011
  // Renewal rechecks mission state, current credential, membership, and the lease.
3105
3012
  await client.missions.renew(missionId, workItem.id, claim.leaseToken, {
3106
3013
  signal: combined,
@@ -3149,15 +3056,6 @@ export async function runMissionWorker({
3149
3056
  for (const repositoryAction of repositoryActions) {
3150
3057
  if (!mission.repository)
3151
3058
  throw new Error("This assignment needs a selected repository.");
3152
- await inspector.assertAllowed(
3153
- "action",
3154
- {
3155
- action: repositoryAction,
3156
- resource: mission.repository.url,
3157
- objective: workItem.objective,
3158
- },
3159
- { task: originalTask, signal: combined },
3160
- );
3161
3059
  const reservation =
3162
3060
  (prior?.reservedEffects ?? []).find(
3163
3061
  (effect) =>
@@ -3199,16 +3097,6 @@ export async function runMissionWorker({
3199
3097
  effectIds,
3200
3098
  });
3201
3099
  for (const effectId of effectIds) {
3202
- await inspector.assertAllowed(
3203
- "action",
3204
- {
3205
- effectId,
3206
- actions: repositoryActions,
3207
- resource: mission.repository.url,
3208
- objective: workItem.objective,
3209
- },
3210
- { task: originalTask, signal: combined },
3211
- );
3212
3100
  const dispatch = await client.missions.dispatchEffect(
3213
3101
  missionId,
3214
3102
  effectId,
@@ -3548,7 +3436,6 @@ export async function runMissionWorker({
3548
3436
  state: "result_settling",
3549
3437
  stage,
3550
3438
  result,
3551
- originalTask,
3552
3439
  leaseToken: claim.leaseToken,
3553
3440
  effectIds: [...effectIds],
3554
3441
  repositoryUrl: mission.repository.url,
@@ -3569,15 +3456,10 @@ export async function runMissionWorker({
3569
3456
  state: "ready",
3570
3457
  stage,
3571
3458
  result,
3572
- originalTask,
3573
3459
  leaseToken: claim.leaseToken,
3574
3460
  });
3575
3461
  }
3576
3462
  }
3577
- await inspector.assertAllowed("output", result, {
3578
- task: originalTask,
3579
- signal: combined,
3580
- });
3581
3463
  await client.missions.complete(
3582
3464
  missionId,
3583
3465
  workItem.id,
@@ -4,6 +4,37 @@ import { tmpdir } from "node:os";
4
4
  import { isAbsolute, join } from "node:path";
5
5
  import { createCommandAdapter, createHttpAdapter } from "./worker-core/adapters.mjs";
6
6
  import { installedClientInvocation, localClientEnvironment } from "./platform-client.mjs";
7
+ import { WorkerError } from "./worker-core/errors.mjs";
8
+
9
+ export function providerFailureCode(text) {
10
+ if (
11
+ /unexpected argument|unknown option|unrecognized option|no such option|unknown flag/i.test(text)
12
+ )
13
+ return "AGENT_CLI_INCOMPATIBLE";
14
+ if (
15
+ /not logged in|authentication required|invalid[_ ]api[_ ]key|unauthorized|refresh token.*(?:expired|reused)|sign in.*required/i.test(
16
+ text,
17
+ )
18
+ )
19
+ return "AGENT_AUTH_REQUIRED";
20
+ if (
21
+ /usage limit|rate limit|quota.*(?:exceeded|insufficient)|insufficient_quota|credit balance/i.test(
22
+ text,
23
+ )
24
+ )
25
+ return "AGENT_QUOTA_EXCEEDED";
26
+ if (
27
+ /sandbox.*(?:setup failed|not available|not configured|unsupported|error)|CreateProcessAsUser|error 1385/i.test(
28
+ text,
29
+ )
30
+ )
31
+ return "AGENT_SANDBOX_UNAVAILABLE";
32
+ if (
33
+ /model.*(?:not found|not supported|does not exist|not have access)|model_not_found/i.test(text)
34
+ )
35
+ return "AGENT_MODEL_UNAVAILABLE";
36
+ return "AGENT_EXIT_FAILED";
37
+ }
7
38
 
8
39
  export const EXTERNAL_PROVIDERS = ["codex", "claude", "command", "http"];
9
40
  export const isExternalModelProvider = (provider) => EXTERNAL_PROVIDERS.includes(provider);
@@ -88,7 +119,7 @@ export async function externalModelReply({
88
119
  continue;
89
120
  }
90
121
  if (event.type === "turn.failed" || event.type === "error")
91
- throw new Error("Codex turn failed.");
122
+ throw new WorkerError(providerFailureCode(event.error?.message ?? event.message ?? ""));
92
123
  if (event.type === "item.completed" && event.item?.type === "agent_message")
93
124
  final = event.item.text;
94
125
  }
@@ -113,7 +144,8 @@ export async function externalModelReply({
113
144
  ];
114
145
  parseResult = (output) => {
115
146
  const result = JSON.parse(output);
116
- if (result.is_error || result.subtype !== "success") throw new Error("Claude turn failed.");
147
+ if (result.is_error || result.subtype !== "success")
148
+ throw new WorkerError(providerFailureCode(String(result.result ?? "")));
117
149
  return {
118
150
  status: "completed",
119
151
  output: result.structured_output
@@ -153,6 +185,7 @@ export async function externalModelReply({
153
185
  env,
154
186
  timeoutMs,
155
187
  parseResult,
188
+ ...(["codex", "claude"].includes(provider) ? { classifyFailure: providerFailureCode } : {}),
156
189
  });
157
190
  }
158
191
  const result = await adapter.run({ prompt, signal, runId: randomUUID() });
@@ -0,0 +1,26 @@
1
+ import { pause } from "./network.mjs";
2
+
3
+ export function transientNetworkError(error) {
4
+ return (
5
+ error?.status === 429 ||
6
+ error?.status >= 500 ||
7
+ ["NETWORK_BUSY", "NETWORK_ERROR", "TIMEOUT"].includes(error?.code)
8
+ );
9
+ }
10
+
11
+ // Only use for reads or mutations protected by a durable idempotency key.
12
+ export async function retryNetwork(
13
+ operation,
14
+ { signal, onRetry = () => {}, delayMs = 1000, attempts = 4 } = {},
15
+ ) {
16
+ for (let attempt = 0; ; attempt++) {
17
+ signal?.throwIfAborted();
18
+ try {
19
+ return await operation();
20
+ } catch (error) {
21
+ if (signal?.aborted || !transientNetworkError(error) || attempt + 1 >= attempts) throw error;
22
+ onRetry(error);
23
+ await pause(Math.min(delayMs * 2 ** attempt, 10000), signal);
24
+ }
25
+ }
26
+ }