agents-pod 0.3.5 → 0.3.7

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/src/wrapper.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  withRegistryLock,
29
29
  writePods,
30
30
  type PodRecord,
31
+ type WakeUpEntry,
31
32
  type WakeUpOutcome,
32
33
  } from "./registry.ts";
33
34
  import { snapshotDocs } from "./workspace.ts";
@@ -70,7 +71,7 @@ export function composePrompt(goal: string, docsDir: string, roleInstructions =
70
71
  `1. ${join(docsDir, "HANDOFF.md")} — your predecessor's handoff; start where it ended.`,
71
72
  `2. ${join(docsDir, "TASKS.md")} — the plan and its checkboxes.`,
72
73
  `3. ${join(docsDir, "DECISIONS.md")} — the why behind the plan.`,
73
- "When this window's work is done, end with pod_handoff (status: rotating or done) so your successor starts clean. Write-then-exit is the protocol: an exit without a fresh handoff counts as a failure.",
74
+ "When this window's work is done, end with pod_handoff so your successor starts clean: status rotating (the goal is open and the next wake-up continues), quiet (this window is satisfied, nothing is scheduled — wake me when work arrives), or done (the goal is reached and the pod ends). Write-then-exit is the protocol: an exit without a fresh handoff counts as a failure.",
74
75
  ].join("\n");
75
76
  const goalBlock = `Goal: ${goal}`;
76
77
  const sections = [preamble, roleInstructions.trim(), goalBlock].filter(
@@ -170,14 +171,19 @@ export interface Adjudication {
170
171
  async function freshDeclaration(
171
172
  docsDir: string,
172
173
  wakeStart: number,
173
- ): Promise<"more" | "done" | undefined> {
174
+ ): Promise<"more" | "quiet" | "done" | undefined> {
174
175
  const handoff = join(docsDir, "HANDOFF.md");
175
176
  const info = await stat(handoff).catch(() => undefined);
176
177
  if (!info || info.mtimeMs <= wakeStart) return undefined;
177
178
  const firstLine = (await readFile(handoff, "utf8")).split("\n", 1)[0]!.trim();
178
- const declared = /^status:\s*(rotating|done)\s*$/.exec(firstLine);
179
+ // Three words, and no fourth (ADR 0009 amendment, #36): `rotating` — another
180
+ // wake-up is the work; `quiet` — this window is satisfied and nothing is
181
+ // scheduled; `done` — terminal. Anything else is not a declaration.
182
+ const declared = /^status:\s*(rotating|quiet|done)\s*$/.exec(firstLine);
179
183
  if (!declared) return undefined;
180
- return declared[1] === "done" ? "done" : "more";
184
+ const word = declared[1];
185
+ if (word === "done") return "done";
186
+ return word === "quiet" ? "quiet" : "more";
181
187
  }
182
188
 
183
189
  /**
@@ -265,7 +271,13 @@ async function recordUnresolved(
265
271
  what: string,
266
272
  ): Promise<boolean> {
267
273
  return updatePodRecord(podId, podRoot, n, (record) => {
268
- record.wakeUps.push({ n, outcome: "failed", detail: what });
274
+ record.wakeUps.push({
275
+ n,
276
+ outcome: "failed",
277
+ detail: what,
278
+ at: Date.now(),
279
+ ...(record.admitted === undefined ? {} : { trigger: record.admitted }),
280
+ });
269
281
  record.state = "idle";
270
282
  });
271
283
  }
@@ -493,11 +505,13 @@ export async function runWrapper(argv: string[]): Promise<number> {
493
505
  // a wedged kill. A human cancel has no intent and records exactly as
494
506
  // before.
495
507
  if (record.killIntent?.n !== n) {
496
- record.wakeUps.push(
497
- killedAfterGrace
498
- ? { n, outcome: "cancelled", detail: "killed after grace" }
499
- : { n, outcome: "cancelled" },
500
- );
508
+ record.wakeUps.push({
509
+ n,
510
+ outcome: "cancelled",
511
+ ...(killedAfterGrace ? { detail: "killed after grace" } : {}),
512
+ at: Date.now(),
513
+ ...(record.admitted === undefined ? {} : { trigger: record.admitted }),
514
+ });
501
515
  record.state = "cancelled";
502
516
  }
503
517
  });
@@ -518,7 +532,17 @@ export async function runWrapper(argv: string[]): Promise<number> {
518
532
  // (ADR 0025). Failures are logged inside the flush and never change outcome.
519
533
  await flushOutbox(podId, pod.project, n);
520
534
  const recorded = await updatePodRecord(podId, podRoot, n, (record) => {
521
- record.wakeUps.push(detail === undefined ? { n, outcome } : { n, outcome, detail });
535
+ // Provenance (ADR 0032): when, and what admitted the window. `admitted` is
536
+ // written by the launcher under the registry lock, so a wake-up the tick
537
+ // started and one a human started stop looking identical after the fact.
538
+ const entry: WakeUpEntry = {
539
+ n,
540
+ outcome,
541
+ ...(detail === undefined ? {} : { detail }),
542
+ at: Date.now(),
543
+ ...(record.admitted === undefined ? {} : { trigger: record.admitted }),
544
+ };
545
+ record.wakeUps.push(entry);
522
546
  record.state = outcome === "done" ? "done" : "idle";
523
547
  });
524
548
  if (!recorded) return 1;