totemlayer 0.1.5 → 0.1.6

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.
@@ -7,9 +7,9 @@ import { setTimeout as sleep } from "node:timers/promises";
7
7
 
8
8
  import { inspectLocalRepository } from "./repository.mjs";
9
9
  import { localClientEnvironment, streamingClientInvocation } from "./platform-client.mjs";
10
+ import { runFile } from "./mission-worker.mjs";
10
11
 
11
12
  const MAX_RUNS_PER_TASK = 16;
12
- const MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
13
13
  const MAX_PROMPT_BYTES = 32 * 1024;
14
14
  const DEFAULT_RUN_TIMEOUT_MS = 15 * 60 * 1000;
15
15
 
@@ -84,7 +84,10 @@ function runnerCommand(provider, sessionId, platform = process.platform) {
84
84
  if (provider === "codex")
85
85
  invocation = sessionId
86
86
  ? { command: "codex", args: ["exec", "resume", "--json", sessionId, "-"] }
87
- : { command: "codex", args: ["exec", "--sandbox", "workspace-write", "--json", "-"] };
87
+ : {
88
+ command: "codex",
89
+ args: ["exec", "--sandbox", "workspace-write", "--json", "-"],
90
+ };
88
91
  else if (provider === "claude")
89
92
  invocation = {
90
93
  command: "claude",
@@ -106,7 +109,9 @@ function runnerCommand(provider, sessionId, platform = process.platform) {
106
109
  }
107
110
 
108
111
  function runnerEnvironment(repositoryRoot, environment = process.env) {
109
- return localClientEnvironment(environment, { TOTEM_REPOSITORY_ROOT: repositoryRoot });
112
+ return localClientEnvironment(environment, {
113
+ TOTEM_REPOSITORY_ROOT: repositoryRoot,
114
+ });
110
115
  }
111
116
 
112
117
  export async function runLocalCollaborationAgent({
@@ -120,79 +125,42 @@ export async function runLocalCollaborationAgent({
120
125
  spawnProcess = spawn,
121
126
  }) {
122
127
  const { command, args } = runnerCommand(provider, sessionId, platform);
123
- return new Promise((resolveRun, rejectRun) => {
124
- const child = spawnProcess(command, args, {
125
- cwd: repositoryRoot,
126
- env: runnerEnvironment(repositoryRoot),
127
- stdio: ["pipe", "pipe", "pipe"],
128
- shell: false,
129
- });
130
- let output = "";
131
- let totalOutputBytes = 0;
132
- let observedSession = sessionId ?? null;
133
- let finalResponse = "";
134
- let finished = false;
135
- const fail = (error) => {
136
- if (finished) return;
137
- finished = true;
138
- clearTimeout(timer);
139
- signal?.removeEventListener("abort", abort);
140
- rejectRun(error);
141
- };
142
- const abort = () => child.kill("SIGTERM");
143
- const timer = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
144
- signal?.addEventListener("abort", abort, { once: true });
145
- if (signal?.aborted) abort();
146
- child.stdout.on("data", (chunk) => {
147
- totalOutputBytes += chunk.byteLength;
148
- output += chunk.toString("utf8");
149
- if (totalOutputBytes > MAX_OUTPUT_BYTES) child.kill("SIGTERM");
150
- if (provider !== "codex") return;
151
- let newline;
152
- while ((newline = output.indexOf("\n")) >= 0) {
153
- const line = output.slice(0, newline);
154
- output = output.slice(newline + 1);
155
- try {
156
- const event = JSON.parse(line);
157
- if (event.type === "thread.started" && typeof event.thread_id === "string")
158
- observedSession = event.thread_id;
159
- if (event.type === "item.completed" && event.item?.type === "agent_message")
160
- finalResponse = String(event.item.text ?? "");
161
- } catch {
162
- // Ignore non-JSON progress lines; never echo model output to CLI logs.
163
- }
164
- }
165
- });
166
- child.stderr.on("data", () => {});
167
- child.once("error", (error) => fail(error));
168
- child.once("close", (code) => {
169
- if (finished) return;
170
- if (signal?.aborted) return fail(new Error("The collaboration listener was stopped."));
171
- if (code !== 0)
172
- return fail(
173
- new Error(
174
- `The ${provider} run exited with status ${code ?? "unknown"}. Check that client's local logs and permissions.`,
175
- ),
176
- );
177
- if (provider === "claude") {
178
- try {
179
- const result = JSON.parse(output);
180
- observedSession = result.session_id ?? observedSession;
181
- finalResponse = typeof result.result === "string" ? result.result : "";
182
- if (result.is_error) return fail(new Error("Claude reported a failed run."));
183
- } catch {
184
- return fail(new Error("Claude returned an invalid JSON result."));
185
- }
186
- }
187
- if (!observedSession)
188
- return fail(new Error(`${provider} did not return a resumable session ID.`));
189
- finished = true;
190
- clearTimeout(timer);
191
- signal?.removeEventListener("abort", abort);
192
- resolveRun({ sessionId: observedSession, finalResponse });
193
- });
194
- child.stdin.end(prompt);
128
+ const { stdout } = await runFile(command, args, {
129
+ cwd: repositoryRoot,
130
+ env: runnerEnvironment(repositoryRoot),
131
+ input: prompt,
132
+ signal,
133
+ timeoutMs,
134
+ platform,
135
+ spawnProcess,
195
136
  });
137
+ let observedSession = sessionId ?? null;
138
+ let finalResponse = "";
139
+ if (provider === "claude") {
140
+ let result;
141
+ try {
142
+ result = JSON.parse(stdout);
143
+ } catch {
144
+ throw new Error("Claude returned an invalid JSON result.");
145
+ }
146
+ observedSession = result.session_id ?? observedSession;
147
+ finalResponse = typeof result.result === "string" ? result.result : "";
148
+ if (result.is_error) throw new Error("Claude reported a failed run.");
149
+ } else {
150
+ for (const line of stdout.split("\n")) {
151
+ try {
152
+ const event = JSON.parse(line);
153
+ if (event.type === "thread.started" && typeof event.thread_id === "string")
154
+ observedSession = event.thread_id;
155
+ if (event.type === "item.completed" && event.item?.type === "agent_message")
156
+ finalResponse = String(event.item.text ?? "");
157
+ } catch {
158
+ // Ignore non-JSON progress lines; never echo model output to CLI logs.
159
+ }
160
+ }
161
+ }
162
+ if (!observedSession) throw new Error(`${provider} did not return a resumable session ID.`);
163
+ return { sessionId: observedSession, finalResponse };
196
164
  }
197
165
 
198
166
  async function readJournal(file) {
@@ -216,7 +184,12 @@ async function latestOutboundTaskCursor(client, taskId, cursor, signal) {
216
184
  let latest = cursor;
217
185
  for (let page = 0; page < 20; page++) {
218
186
  const result = await client.messages.list(
219
- { direction: "outbound", taskId, limit: 100, ...(latest ? { cursor: latest } : {}) },
187
+ {
188
+ direction: "outbound",
189
+ taskId,
190
+ limit: 100,
191
+ ...(latest ? { cursor: latest } : {}),
192
+ },
220
193
  { signal },
221
194
  );
222
195
  const messages = result.data ?? [];
@@ -247,7 +220,9 @@ export async function runCollaborationListener({
247
220
  if (!Number.isInteger(pollMs) || pollMs < 1000 || pollMs > 60000)
248
221
  throw new Error("--poll-ms must be between 1000 and 60000.");
249
222
  const root = resolve(repositoryRoot);
250
- const repository = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: root });
223
+ const repository = await inspectLocalRepository({
224
+ TOTEM_REPOSITORY_ROOT: root,
225
+ });
251
226
  const identity = await client.agent({ signal });
252
227
  const peer = await client.resolve(peerAddress, { signal });
253
228
  if (!peer?.id || peer.address !== peerAddress) throw new Error("The allowed peer was not found.");
@@ -267,7 +242,12 @@ export async function runCollaborationListener({
267
242
  }
268
243
  try {
269
244
  const journal = await readJournal(journalFile);
270
- const binding = { agentId: identity.id, peerAddress, repository: repository.repository, root };
245
+ const binding = {
246
+ agentId: identity.id,
247
+ peerAddress,
248
+ repository: repository.repository,
249
+ root,
250
+ };
271
251
  if (journal.binding && JSON.stringify(journal.binding) !== JSON.stringify(binding))
272
252
  throw new Error("This journal belongs to a different agent, peer, or checkout.");
273
253
  if (!journal.binding) {
@@ -301,7 +281,11 @@ export async function runCollaborationListener({
301
281
  continue;
302
282
  }
303
283
  if (prior?.status === "started" || prior?.status === "failed") {
304
- emit({ type: "needs_attention", messageId: message.id, taskId: message.taskId });
284
+ emit({
285
+ type: "needs_attention",
286
+ messageId: message.id,
287
+ taskId: message.taskId,
288
+ });
305
289
  continue;
306
290
  }
307
291
  const task = await client.tasks.get(message.taskId, { signal });
@@ -352,7 +336,12 @@ export async function runCollaborationListener({
352
336
  const outboundBefore = await latestOutboundTaskCursor(client, task.id, undefined, signal);
353
337
  journal.messages[message.id] = { status: "started", taskId: task.id };
354
338
  await saveJournal(journalFile, journal);
355
- emit({ type: "started", messageId: message.id, taskId: task.id, provider });
339
+ emit({
340
+ type: "started",
341
+ messageId: message.id,
342
+ taskId: task.id,
343
+ provider,
344
+ });
356
345
  try {
357
346
  const result = await runAgent({
358
347
  provider,
@@ -377,12 +366,23 @@ export async function runCollaborationListener({
377
366
  sessionId: result.sessionId,
378
367
  runCount: (savedTask?.runCount ?? 0) + 1,
379
368
  };
380
- journal.messages[message.id] = { status: "completed", taskId: task.id };
369
+ journal.messages[message.id] = {
370
+ status: "completed",
371
+ taskId: task.id,
372
+ };
381
373
  await saveJournal(journalFile, journal);
382
374
  await client.messages.ack(message.id, { signal });
383
- emit({ type: "completed", messageId: message.id, taskId: task.id, provider });
375
+ emit({
376
+ type: "completed",
377
+ messageId: message.id,
378
+ taskId: task.id,
379
+ provider,
380
+ });
384
381
  } catch (error) {
385
- journal.messages[message.id] = { status: "failed", taskId: task.id };
382
+ journal.messages[message.id] = {
383
+ status: "failed",
384
+ taskId: task.id,
385
+ };
386
386
  await saveJournal(journalFile, journal);
387
387
  await client.tasks
388
388
  .postUpdate(task.id, listenerStatusText(provider, "failed"), {
@@ -36,7 +36,7 @@ export async function runDockerNodeTests(checkout, signal, runFile) {
36
36
  "--env",
37
37
  "TMPDIR=/tmp",
38
38
  "--mount",
39
- `type=bind,source=${root},target=/workspace`,
39
+ `type=bind,source=${root},target=/workspace,readonly`,
40
40
  "--workdir",
41
41
  "/workspace",
42
42
  NODE_TEST_IMAGE,
package/bin/mcp.mjs CHANGED
@@ -8,7 +8,7 @@ import * as z from "zod/v4";
8
8
  import { Totem } from "./sdk/index.js";
9
9
  import { inspectLocalRepository, verifyLocalRepoClaim } from "./repository.mjs";
10
10
 
11
- const VERSION = "0.1.5";
11
+ const VERSION = "0.1.6";
12
12
  const MAX_KEY_BYTES = 8 * 1024;
13
13
  const MAX_ATTACHMENT_BYTES = 2 * 1024 * 1024;
14
14
  const collaborationSchema = z
@@ -52,7 +52,10 @@ const SOURCE_CHANGE = {
52
52
  ...FILE_CHANGE,
53
53
  properties: {
54
54
  ...FILE_CHANGE.properties,
55
- path: { ...CODE_PATH, pattern: CODE_PATH.pattern.replace("^(src|test|tests)/", "^src/") },
55
+ path: {
56
+ ...CODE_PATH,
57
+ pattern: CODE_PATH.pattern.replace("^(src|test|tests)/", "^src/"),
58
+ },
56
59
  },
57
60
  };
58
61
  const TEST_CHANGE = {
@@ -98,7 +101,10 @@ const OUTPUT_SCHEMAS = {
98
101
  },
99
102
  engineering: {
100
103
  summary: TEXT,
101
- verdict: { type: "string", enum: ["feasible", "changes_required", "rejected"] },
104
+ verdict: {
105
+ type: "string",
106
+ enum: ["feasible", "changes_required", "rejected"],
107
+ },
102
108
  findings: STRINGS,
103
109
  spec: TEXT,
104
110
  candidateTitle: TEXT,
@@ -113,11 +119,19 @@ const OUTPUT_SCHEMAS = {
113
119
  code: {
114
120
  summary: TEXT,
115
121
  changes: { type: "array", items: SOURCE_CHANGE, minItems: 1, maxItems: 3 },
116
- testChanges: { type: "array", items: TEST_CHANGE, minItems: 1, maxItems: 1 },
122
+ testChanges: {
123
+ type: "array",
124
+ items: TEST_CHANGE,
125
+ minItems: 1,
126
+ maxItems: 1,
127
+ },
117
128
  },
118
129
  review: {
119
130
  summary: TEXT,
120
- verdict: { type: "string", enum: ["approved", "changes_required", "rejected"] },
131
+ verdict: {
132
+ type: "string",
133
+ enum: ["approved", "changes_required", "rejected"],
134
+ },
121
135
  findings: STRINGS,
122
136
  probes: { type: "array", items: REVIEW_PROBE, maxItems: 3 },
123
137
  },
@@ -252,7 +266,12 @@ function rejectedResponseEvidence(response) {
252
266
  } catch {
253
267
  // Malformed text cannot be safely interpreted as credential-bearing fields.
254
268
  // Preserve a fingerprint and length without storing its raw fragments.
255
- return { ...metadata, response: null, responseOmitted: "malformed_json", truncated: false };
269
+ return {
270
+ ...metadata,
271
+ response: null,
272
+ responseOmitted: "malformed_json",
273
+ truncated: false,
274
+ };
256
275
  }
257
276
  }
258
277
 
@@ -449,7 +468,9 @@ function structuredEvent(stage, response) {
449
468
  outcome: output.outcome.slice(0, 4000),
450
469
  selected: output.selected,
451
470
  ...(typeof output.selectedCandidateTitle === "string"
452
- ? { selectedCandidateTitle: output.selectedCandidateTitle.slice(0, 160) }
471
+ ? {
472
+ selectedCandidateTitle: output.selectedCandidateTitle.slice(0, 160),
473
+ }
453
474
  : {}),
454
475
  ...(typeof output.rationale === "string"
455
476
  ? { rationale: output.rationale.slice(0, 4000) }
@@ -461,7 +482,10 @@ function structuredEvent(stage, response) {
461
482
  break;
462
483
  return {
463
484
  type: "completion",
464
- payload: { summary: output.summary.slice(0, 4000), result: output.result },
485
+ payload: {
486
+ summary: output.summary.slice(0, 4000),
487
+ result: output.result,
488
+ },
465
489
  };
466
490
  }
467
491
  throw new Error("The agent returned a malformed stage result.");
@@ -540,13 +564,22 @@ export function verifiedHandoff(mission, events, response) {
540
564
  export function runFile(
541
565
  command,
542
566
  args,
543
- { cwd, input, signal, timeoutMs = 15 * 60_000, env, acceptNonZero = false } = {},
567
+ {
568
+ cwd,
569
+ input,
570
+ signal,
571
+ timeoutMs = 15 * 60_000,
572
+ env,
573
+ acceptNonZero = false,
574
+ platform = process.platform,
575
+ spawnProcess = spawn,
576
+ } = {},
544
577
  ) {
545
578
  if (signal?.aborted)
546
579
  return Promise.reject(signal.reason ?? new Error("The command was interrupted."));
547
580
  return new Promise((resolveRun, rejectRun) => {
548
- const grouped = process.platform !== "win32";
549
- const child = spawn(command, args, {
581
+ const grouped = platform !== "win32";
582
+ const child = spawnProcess(command, args, {
550
583
  cwd,
551
584
  env,
552
585
  shell: false,
@@ -597,13 +630,24 @@ export function runFile(
597
630
  if (!grouped) {
598
631
  // Windows has no POSIX process groups; taskkill /T includes descendants.
599
632
  await new Promise((resolveKill, rejectKill) => {
600
- const killer = spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
633
+ const killer = spawnProcess("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
601
634
  shell: false,
602
635
  stdio: "ignore",
603
636
  windowsHide: true,
604
637
  });
605
- killer.once("error", rejectKill);
606
- killer.once("close", resolveKill);
638
+ const killTimer = setTimeout(() => {
639
+ killer.kill("SIGKILL");
640
+ rejectKill(new Error("Windows process-tree cleanup timed out."));
641
+ }, 5_000);
642
+ killer.once("error", (error) => {
643
+ clearTimeout(killTimer);
644
+ rejectKill(error);
645
+ });
646
+ killer.once("close", (code) => {
647
+ clearTimeout(killTimer);
648
+ if (code === 0) resolveKill();
649
+ else rejectKill(new Error(`Windows process-tree cleanup exited with status ${code}.`));
650
+ });
607
651
  });
608
652
  return;
609
653
  }
@@ -622,6 +666,8 @@ export function runFile(
622
666
  // Give the kernel time to reap killed descendants before checkout cleanup.
623
667
  for (let wait = 0; wait < 20 && groupExists(); wait += 1) await sleep(25);
624
668
  })().catch((cleanupError) => {
669
+ // Report an uncertain cleanup failure even if the client never closes.
670
+ closed = true;
625
671
  stopError = new Error(
626
672
  `${command} was interrupted but process cleanup failed: ${cleanupError.message}`,
627
673
  );
@@ -1021,7 +1067,15 @@ function testSourceTokens(source) {
1021
1067
  }
1022
1068
  if (char === "\\") {
1023
1069
  const escaped = source[index++];
1024
- const simple = { n: "\n", r: "\r", t: "\t", b: "\b", f: "\f", v: "\v", 0: "\0" };
1070
+ const simple = {
1071
+ n: "\n",
1072
+ r: "\r",
1073
+ t: "\t",
1074
+ b: "\b",
1075
+ f: "\f",
1076
+ v: "\v",
1077
+ 0: "\0",
1078
+ };
1025
1079
  if (escaped === "\n") continue;
1026
1080
  if (escaped === "\r") {
1027
1081
  if (source[index] === "\n") index += 1;
@@ -1294,7 +1348,9 @@ async function existingNodeTestTitles(checkout) {
1294
1348
  let entriesSeen = 0;
1295
1349
  let bytes = 0;
1296
1350
  const visit = async (relative = "") => {
1297
- for (const entry of await readdir(join(checkout, relative), { withFileTypes: true })) {
1351
+ for (const entry of await readdir(join(checkout, relative), {
1352
+ withFileTypes: true,
1353
+ })) {
1298
1354
  if ([".git", "node_modules"].includes(entry.name)) continue;
1299
1355
  if (++entriesSeen > 5000)
1300
1356
  throw new Error(
@@ -1498,7 +1554,10 @@ async function runStructuredMissionAgent({
1498
1554
  ...TEST_CHANGE,
1499
1555
  properties: {
1500
1556
  ...TEST_CHANGE.properties,
1501
- path: { ...TEST_CHANGE.properties.path, const: regressionFile.path },
1557
+ path: {
1558
+ ...TEST_CHANGE.properties.path,
1559
+ const: regressionFile.path,
1560
+ },
1502
1561
  },
1503
1562
  },
1504
1563
  },
@@ -1511,7 +1570,11 @@ async function runStructuredMissionAgent({
1511
1570
  required: Object.keys(properties),
1512
1571
  additionalProperties: false,
1513
1572
  };
1514
- const endpoint = missionModelEndpoint(provider, { ollamaUrl, qwenUrl, nimUrl });
1573
+ const endpoint = missionModelEndpoint(provider, {
1574
+ ollamaUrl,
1575
+ qwenUrl,
1576
+ nimUrl,
1577
+ });
1515
1578
  model ??=
1516
1579
  provider === "qwen-local"
1517
1580
  ? "qwen3.5-9b"
@@ -1556,7 +1619,11 @@ async function runStructuredMissionAgent({
1556
1619
  : {
1557
1620
  think: useReasoning,
1558
1621
  format: schema,
1559
- options: { num_ctx: 16_384, num_predict: generationLimit, temperature: 0.1 },
1622
+ options: {
1623
+ num_ctx: 16_384,
1624
+ num_predict: generationLimit,
1625
+ temperature: 0.1,
1626
+ },
1560
1627
  }),
1561
1628
  messages: [
1562
1629
  {
@@ -1585,7 +1652,10 @@ async function runStructuredMissionAgent({
1585
1652
  });
1586
1653
  } catch (error) {
1587
1654
  if (rejectionEvidenceDirectory) {
1588
- await mkdir(rejectionEvidenceDirectory, { recursive: true, mode: 0o700 });
1655
+ await mkdir(rejectionEvidenceDirectory, {
1656
+ recursive: true,
1657
+ mode: 0o700,
1658
+ });
1589
1659
  await chmod(rejectionEvidenceDirectory, 0o700);
1590
1660
  await saveJournal(join(rejectionEvidenceDirectory, `response-${attempt + 1}.json`), {
1591
1661
  capturedAt: new Date().toISOString(),
@@ -1654,7 +1724,9 @@ export async function preflightMissionRepository(mission, repositoryRoot) {
1654
1724
  const base = mission.repository?.baseRevision;
1655
1725
  if (!mission.repository || !COMMIT.test(base ?? ""))
1656
1726
  throw new Error("The mission needs a selected repository with a full commit revision.");
1657
- const local = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
1727
+ const local = await inspectLocalRepository({
1728
+ TOTEM_REPOSITORY_ROOT: repositoryRoot,
1729
+ });
1658
1730
  if (local.repository !== mission.repository.url)
1659
1731
  throw new Error("The configured local repository does not match the mission repository.");
1660
1732
  if ((await git(repositoryRoot, "cat-file", "-t", base)) !== "commit")
@@ -1676,7 +1748,9 @@ export async function prepareMissionCheckout(
1676
1748
  throw new Error("Mission code work needs an immutable repository revision and work item ID.");
1677
1749
  if (retryAttempt !== undefined && (!Number.isInteger(retryAttempt) || retryAttempt < 2))
1678
1750
  throw new Error("A retry checkout needs a later server work-item attempt.");
1679
- const local = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
1751
+ const local = await inspectLocalRepository({
1752
+ TOTEM_REPOSITORY_ROOT: repositoryRoot,
1753
+ });
1680
1754
  if (local.repository !== mission.repository.url)
1681
1755
  throw new Error("The selected local checkout does not match the mission repository.");
1682
1756
  const base = await git(repositoryRoot, "rev-parse", mission.repository.baseRevision);
@@ -1714,7 +1788,10 @@ export async function prepareMissionCheckout(
1714
1788
  } catch (error) {
1715
1789
  if (error.code !== "ENOENT") throw error;
1716
1790
  }
1717
- await mkdir(join(stateDirectory, "checkouts"), { recursive: true, mode: 0o700 });
1791
+ await mkdir(join(stateDirectory, "checkouts"), {
1792
+ recursive: true,
1793
+ mode: 0o700,
1794
+ });
1718
1795
  await git(repositoryRoot, "worktree", "add", "-b", branch, checkout, startRevision);
1719
1796
  return { checkout, branch, baseRevision: base, startRevision };
1720
1797
  }
@@ -1732,7 +1809,9 @@ export async function prepareReviewCheckout(mission, workItem, repositoryRoot, s
1732
1809
  !UUID.test(workItem.id)
1733
1810
  )
1734
1811
  throw new Error("Review requires a visible local code artifact with full Git revisions.");
1735
- const local = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
1812
+ const local = await inspectLocalRepository({
1813
+ TOTEM_REPOSITORY_ROOT: repositoryRoot,
1814
+ });
1736
1815
  if (local.repository !== mission.repository.url)
1737
1816
  throw new Error("The selected checkout does not match the mission repository.");
1738
1817
  const [resolvedHead, mergeBase] = await Promise.all([
@@ -1753,16 +1832,26 @@ export async function prepareReviewCheckout(mission, workItem, repositoryRoot, s
1753
1832
  } catch (error) {
1754
1833
  if (error.code !== "ENOENT") throw error;
1755
1834
  }
1756
- await mkdir(join(stateDirectory, "checkouts"), { recursive: true, mode: 0o700 });
1835
+ await mkdir(join(stateDirectory, "checkouts"), {
1836
+ recursive: true,
1837
+ mode: 0o700,
1838
+ });
1757
1839
  await git(repositoryRoot, "worktree", "add", "--detach", checkout, head);
1758
- return { checkout, baseRevision: base, headCommit: head, branch: codeArtifact.metadata?.branch };
1840
+ return {
1841
+ checkout,
1842
+ baseRevision: base,
1843
+ headCommit: head,
1844
+ branch: codeArtifact.metadata?.branch,
1845
+ };
1759
1846
  }
1760
1847
 
1761
1848
  export async function prepareBaseReadCheckout(mission, workItem, repositoryRoot, stateDirectory) {
1762
1849
  const base = mission.repository?.baseRevision;
1763
1850
  if (!mission.repository || !COMMIT.test(base ?? "") || !UUID.test(workItem.id))
1764
1851
  throw new Error("Engineering needs a selected immutable repository revision.");
1765
- const local = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
1852
+ const local = await inspectLocalRepository({
1853
+ TOTEM_REPOSITORY_ROOT: repositoryRoot,
1854
+ });
1766
1855
  if (local.repository !== mission.repository.url)
1767
1856
  throw new Error("The local repository does not match the mission selection.");
1768
1857
  const resolved = await git(repositoryRoot, "rev-parse", base);
@@ -1777,7 +1866,10 @@ export async function prepareBaseReadCheckout(mission, workItem, repositoryRoot,
1777
1866
  } catch (error) {
1778
1867
  if (error.code !== "ENOENT") throw error;
1779
1868
  }
1780
- await mkdir(join(stateDirectory, "checkouts"), { recursive: true, mode: 0o700 });
1869
+ await mkdir(join(stateDirectory, "checkouts"), {
1870
+ recursive: true,
1871
+ mode: 0o700,
1872
+ });
1781
1873
  await git(repositoryRoot, "worktree", "add", "--detach", checkout, base);
1782
1874
  return { checkout, baseRevision: base };
1783
1875
  }
@@ -1950,7 +2042,10 @@ export async function runIndependentReviewProbes(checkout, changedFiles, probes,
1950
2042
  try {
1951
2043
  await writeFile(probePath, contents, { flag: "wx", mode: 0o600 });
1952
2044
  const check = await runIsolatedNodeTests(checkoutPath, signal);
1953
- return { ...check, command: `${check.command}; independent reviewer probes` };
2045
+ return {
2046
+ ...check,
2047
+ command: `${check.command}; independent reviewer probes`,
2048
+ };
1954
2049
  } finally {
1955
2050
  await rm(probePath, { force: true });
1956
2051
  }
@@ -2106,7 +2201,9 @@ async function settleKnownLocalTestFailure(
2106
2201
  checkoutInfo: record.checkoutInfo,
2107
2202
  failure: record.failure,
2108
2203
  });
2109
- const remote = await client.missions.listEffects(missionId, workItemId, { signal });
2204
+ const remote = await client.missions.listEffects(missionId, workItemId, {
2205
+ signal,
2206
+ });
2110
2207
  for (const [index, effectId] of record.effectIds.entries()) {
2111
2208
  const effect = remote.find((item) => item.id === effectId);
2112
2209
  if (
@@ -2171,7 +2268,11 @@ async function recoverKnownLocalTestFailures(
2171
2268
  );
2172
2269
  } catch (error) {
2173
2270
  if (error.status === 401 || error.status === 403) throw error;
2174
- emit({ type: "recovery_waiting", workItemId, message: String(error.message ?? error) });
2271
+ emit({
2272
+ type: "recovery_waiting",
2273
+ workItemId,
2274
+ message: String(error.message ?? error),
2275
+ });
2175
2276
  }
2176
2277
  }
2177
2278
  return handled;
@@ -2207,7 +2308,9 @@ async function settleCompletedResult(client, missionId, workItemId, path, record
2207
2308
  throw new CompletedResultReconciliationFailed(
2208
2309
  "The completed local result cannot be safely reconciled.",
2209
2310
  );
2210
- const remote = await client.missions.listEffects(missionId, workItemId, { signal });
2311
+ const remote = await client.missions.listEffects(missionId, workItemId, {
2312
+ signal,
2313
+ });
2211
2314
  const effects = record.effectIds.map((id, index) => {
2212
2315
  const effect = remote.find((entry) => entry.id === id);
2213
2316
  if (
@@ -2284,7 +2387,10 @@ async function recoverReadyCompletions(
2284
2387
  "Review the saved result and its lease or assignment before continuing. The worker will not repeat completed actions.",
2285
2388
  },
2286
2389
  },
2287
- { idempotencyKey: key(agentId, workItemId, "completion-blocker"), signal },
2390
+ {
2391
+ idempotencyKey: key(agentId, workItemId, "completion-blocker"),
2392
+ signal,
2393
+ },
2288
2394
  );
2289
2395
  await saveJournal(path, { ...blocked, blockerRecorded: true });
2290
2396
  };
@@ -2301,7 +2407,11 @@ async function recoverReadyCompletions(
2301
2407
  completionInput(workItemId, record.leaseToken, record.result),
2302
2408
  { idempotencyKey: key(agentId, workItemId, "complete"), signal },
2303
2409
  );
2304
- await saveJournal(path, { state: "done", stage: record.stage, result: record.result });
2410
+ await saveJournal(path, {
2411
+ state: "done",
2412
+ stage: record.stage,
2413
+ result: record.result,
2414
+ });
2305
2415
  emit({ type: "work_recovered", workItemId });
2306
2416
  } catch (error) {
2307
2417
  if (error.status === 401 || error.status === 403) throw error;
@@ -2323,7 +2433,11 @@ async function recoverReadyCompletions(
2323
2433
  }
2324
2434
  continue;
2325
2435
  }
2326
- emit({ type: "recovery_waiting", workItemId, message: String(error.message ?? error) });
2436
+ emit({
2437
+ type: "recovery_waiting",
2438
+ workItemId,
2439
+ message: String(error.message ?? error),
2440
+ });
2327
2441
  }
2328
2442
  }
2329
2443
  return handled;
@@ -2509,7 +2623,10 @@ export async function runMissionWorker({
2509
2623
  "Assign a runtime that supports this work item, or set its stage to a supported kind.",
2510
2624
  },
2511
2625
  },
2512
- { idempotencyKey: key(agent.id, workItem.id, "unsupported-stage"), signal },
2626
+ {
2627
+ idempotencyKey: key(agent.id, workItem.id, "unsupported-stage"),
2628
+ signal,
2629
+ },
2513
2630
  )
2514
2631
  .catch(() => {});
2515
2632
  emit({
@@ -2525,7 +2642,11 @@ export async function runMissionWorker({
2525
2642
  if (prior?.state === "claim_blocked") {
2526
2643
  if (!reportedAttention.has(workItem.id)) {
2527
2644
  await recordRecoveryBlocker(prior.pendingWorkItem, prior.message);
2528
- emit({ type: "needs_attention", workItemId: workItem.id, message: prior.message });
2645
+ emit({
2646
+ type: "needs_attention",
2647
+ workItemId: workItem.id,
2648
+ message: prior.message,
2649
+ });
2529
2650
  }
2530
2651
  continue;
2531
2652
  }
@@ -2745,7 +2866,10 @@ export async function runMissionWorker({
2745
2866
  // preceded the request, so another worker's later effects cannot
2746
2867
  // be bypassed by a claim key which never owned that attempt.
2747
2868
  if (claimRecord.previousState) {
2748
- const restored = { ...claimRecord, state: claimRecord.previousState };
2869
+ const restored = {
2870
+ ...claimRecord,
2871
+ state: claimRecord.previousState,
2872
+ };
2749
2873
  for (const field of [
2750
2874
  "claimKey",
2751
2875
  "expectedPriorAttempt",
@@ -2769,7 +2893,11 @@ export async function runMissionWorker({
2769
2893
  };
2770
2894
  await saveJournal(journalPath, blocked);
2771
2895
  await recordRecoveryBlocker(claimRecord.pendingWorkItem, blocked.message);
2772
- emit({ type: "needs_attention", workItemId: workItem.id, message: blocked.message });
2896
+ emit({
2897
+ type: "needs_attention",
2898
+ workItemId: workItem.id,
2899
+ message: blocked.message,
2900
+ });
2773
2901
  continue;
2774
2902
  }
2775
2903
  // Another worker may have claimed a stale listing, or the mission may
@@ -2810,7 +2938,9 @@ export async function runMissionWorker({
2810
2938
  }, 10_000);
2811
2939
  const renew = setInterval(() => {
2812
2940
  void client.missions
2813
- .renew(missionId, workItem.id, claim.leaseToken, { signal: combined })
2941
+ .renew(missionId, workItem.id, claim.leaseToken, {
2942
+ signal: combined,
2943
+ })
2814
2944
  .catch((error) => {
2815
2945
  renewError = error;
2816
2946
  workSignal.abort();
@@ -2909,7 +3039,11 @@ export async function runMissionWorker({
2909
3039
  });
2910
3040
  }
2911
3041
  if (effectIds.length) {
2912
- await saveJournal(journalPath, { state: "dispatching", stage, effectIds });
3042
+ await saveJournal(journalPath, {
3043
+ state: "dispatching",
3044
+ stage,
3045
+ effectIds,
3046
+ });
2913
3047
  for (const effectId of effectIds) {
2914
3048
  const dispatch = await client.missions.dispatchEffect(
2915
3049
  missionId,
@@ -3029,7 +3163,9 @@ export async function runMissionWorker({
3029
3163
  }
3030
3164
  for (const event of promptEvents(events).filter((item) => UUID.test(item.id ?? ""))) {
3031
3165
  try {
3032
- await client.missions.events.ack(missionId, event.id, { signal: combined });
3166
+ await client.missions.events.ack(missionId, event.id, {
3167
+ signal: combined,
3168
+ });
3033
3169
  } catch (error) {
3034
3170
  emit({
3035
3171
  type: "event_ack_pending",
@@ -3046,7 +3182,10 @@ export async function runMissionWorker({
3046
3182
  for (let repair = 0; repair < 3; repair += 1) {
3047
3183
  await authorizeLocalLaunch();
3048
3184
  const check = await runIsolatedNodeTests(checkout, combined);
3049
- commands.push({ command: check.command, exitCode: check.exitCode });
3185
+ commands.push({
3186
+ command: check.command,
3187
+ exitCode: check.exitCode,
3188
+ });
3050
3189
  localTestRuns.push(check);
3051
3190
  if (check.exitCode === 0) break;
3052
3191
  if (repair === 2) throw new LocalCodeTestsFailed(localTestRuns);
@@ -3186,12 +3325,18 @@ export async function runMissionWorker({
3186
3325
  if (reviewCheck)
3187
3326
  generated.commands = [
3188
3327
  ...(generated.commands ?? []),
3189
- { command: reviewCheck.command, exitCode: reviewCheck.exitCode },
3328
+ {
3329
+ command: reviewCheck.command,
3330
+ exitCode: reviewCheck.exitCode,
3331
+ },
3190
3332
  ];
3191
3333
  if (reviewProbeCheck)
3192
3334
  generated.commands = [
3193
3335
  ...(generated.commands ?? []),
3194
- { command: reviewProbeCheck.command, exitCode: reviewProbeCheck.exitCode },
3336
+ {
3337
+ command: reviewProbeCheck.command,
3338
+ exitCode: reviewProbeCheck.exitCode,
3339
+ },
3195
3340
  ];
3196
3341
  const resultSummary = handoff?.summary ?? generated.text.slice(0, 2000);
3197
3342
  result = {
@@ -3224,7 +3369,10 @@ export async function runMissionWorker({
3224
3369
  contentText: handoff
3225
3370
  ? JSON.stringify(handoff.detail, null, 2).slice(0, 32000)
3226
3371
  : generated.text.slice(0, 32000),
3227
- metadata: { ...(evidence ?? {}), commands: generated.commands ?? [] },
3372
+ metadata: {
3373
+ ...(evidence ?? {}),
3374
+ commands: generated.commands ?? [],
3375
+ },
3228
3376
  },
3229
3377
  };
3230
3378
  if (effectIds.length) {
@@ -3260,7 +3408,10 @@ export async function runMissionWorker({
3260
3408
  missionId,
3261
3409
  workItem.id,
3262
3410
  completionInput(workItem.id, claim.leaseToken, result),
3263
- { idempotencyKey: key(agent.id, workItem.id, "complete"), signal: combined },
3411
+ {
3412
+ idempotencyKey: key(agent.id, workItem.id, "complete"),
3413
+ signal: combined,
3414
+ },
3264
3415
  );
3265
3416
  await saveJournal(journalPath, { state: "done", stage, result });
3266
3417
  reportedAttention.delete(workItem.id);
@@ -3301,7 +3452,10 @@ export async function runMissionWorker({
3301
3452
  prior?.state === "local_tests_failed"
3302
3453
  ? (prior.verifiedFailedEffectIds ?? prior.effectIds ?? [])
3303
3454
  : [],
3304
- failure: { checks: error.checks, completedAt: new Date().toISOString() },
3455
+ failure: {
3456
+ checks: error.checks,
3457
+ completedAt: new Date().toISOString(),
3458
+ },
3305
3459
  };
3306
3460
  await saveJournal(journalPath, record);
3307
3461
  knownLocalTestFailure = true;
package/bin/totem.mjs CHANGED
@@ -4,7 +4,7 @@ import { createHash, randomUUID } from "node:crypto";
4
4
  import { createReadStream } from "node:fs";
5
5
  import { stripVTControlCharacters } from "node:util";
6
6
 
7
- const CLI_VERSION = "0.1.5";
7
+ const CLI_VERSION = "0.1.6";
8
8
  const DEFAULT_BASE_URL = "https://totemlayer.com";
9
9
  const API_KEY_PATTERN = /^at_(?:live|test)_[A-Za-z0-9_-]{20,}$/;
10
10
  const ACTION_PATTERN = /^[a-z0-9][a-z0-9._:-]*$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totemlayer",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Cross-platform CLI for TotemLayer agent identity and authorization",
5
5
  "type": "module",
6
6
  "bin": {