@goah/cli 0.13.3 → 0.13.5

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#f7f7f4" />
7
7
  <title>Goah Console</title>
8
- <script type="module" crossorigin src="/assets/index-B1vGMXKr.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DYdJZskp.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-3sYrDMmt.css">
10
10
  </head>
11
11
  <body>
@@ -15,7 +15,7 @@ import {
15
15
  renderDashboard,
16
16
  runSupervisorDaemon,
17
17
  selectRecoveryEvents
18
- } from "./chunk-QBD6EJ2Z.js";
18
+ } from "./chunk-RDUUXBZ2.js";
19
19
  import "./chunk-4KA63VNZ.js";
20
20
  import "./chunk-KAO7WIXG.js";
21
21
  import "./chunk-R6I3VKME.js";
@@ -304,6 +304,7 @@ type HandoffValidationResult = {
304
304
  token: string;
305
305
  goalId: string;
306
306
  goalRevision: number;
307
+ messageItemId: string;
307
308
  } | {
308
309
  accepted: false;
309
310
  fatal: boolean;
@@ -312,6 +313,7 @@ type HandoffValidationResult = {
312
313
  };
313
314
  interface HandoffValidationRequest {
314
315
  handoff: AgentHandoff;
316
+ candidateMessageId: string;
315
317
  candidateMessage: string;
316
318
  }
317
319
  interface TurnOutput {
@@ -326,6 +328,28 @@ interface RunnerTraceEvent {
326
328
  type: string;
327
329
  data: JsonValue;
328
330
  }
331
+ type AssistantLiveDelta = {
332
+ type: "start" | "text_start" | "text_end" | "thinking_start" | "thinking_end" | "toolcall_start" | "toolcall_end";
333
+ contentIndex?: number;
334
+ } | {
335
+ type: "text_delta" | "thinking_delta" | "toolcall_delta";
336
+ contentIndex: number;
337
+ delta: string;
338
+ };
339
+ interface RunnerLiveEvent {
340
+ type: "message.assistant.delta";
341
+ data: {
342
+ messageId: string;
343
+ delta: AssistantLiveDelta;
344
+ };
345
+ }
346
+ interface TurnLiveSnapshot {
347
+ revision: number;
348
+ messageId: string;
349
+ text: string;
350
+ thinking: string;
351
+ thinkingActive: boolean;
352
+ }
329
353
  type AgentCapability = "ledger.search" | "mail.send" | "schedule.set" | "goal.put" | "team.list" | "goal.get" | "goal.create" | "goal.work" | "goal.delegate" | "goal.reassign" | "goal.revise" | "goal.pause" | "goal.resume" | "goal.complete" | "human.request" | "work_record.list" | "work_record.read" | "work_record.history" | "work_record.diff" | "work_record.search" | "work_record.update" | "memory.append";
330
354
  type RunnerControlMethod = "goal.handoff.validate";
331
355
  type RunnerRpcMethod = AgentCapability | RunnerControlMethod;
@@ -418,9 +442,6 @@ interface TurnContext {
418
442
  activeGoal: GoalSnapshot | null;
419
443
  goalCommitment: GoalCommitment | null;
420
444
  }
421
- interface AssistantResponse {
422
- content: string;
423
- }
424
445
  /** Canonical user-visible assistant prose. Raw provider events remain unchanged. */
425
446
  declare function normalizeAssistantText(text: string): string;
426
447
  interface RunRequest {
@@ -432,11 +453,12 @@ interface RunRequest {
432
453
  context: JsonValue;
433
454
  now(): string;
434
455
  emit(event: RunnerTraceEvent): void;
456
+ emitLive?(event: RunnerLiveEvent): void;
435
457
  rpc?(method: RunnerRpcMethod, params: JsonValue): Promise<JsonValue>;
436
458
  }
437
459
  type RunnerCandidateResult = {
438
460
  outcome: "completed";
439
- response: AssistantResponse;
461
+ finalMessageId: string;
440
462
  handoff?: TurnOutput;
441
463
  } | {
442
464
  outcome: "abnormal";
@@ -514,7 +536,8 @@ interface Ledger extends EventStore {
514
536
  renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
515
537
  appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
516
538
  repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
517
- finishTurn(id: string, status: "completed" | "failed" | "interrupted", error: JsonValue | null, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
539
+ finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
540
+ commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
518
541
  releaseTurnProcess(id: string, actor: string): TurnSnapshot;
519
542
  putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
520
543
  putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
@@ -554,4 +577,4 @@ declare function assertTurnOutput(value: TurnOutput): void;
554
577
  declare function assertGoalSnapshot(value: GoalSnapshot): void;
555
578
 
556
579
  export { assertAgentHandoff, assertGoalSnapshot, assertGoalTransition, assertHandoff, assertTurnOutput, assertWakeTransition, goalAutomaticTarget, goalCommitment, goalRoute, humanInboxRoute, humanRequestRoute, noGoalCommitment, normalizeAssistantText, specialistAutomaticTarget, specialistInboxRoute };
557
- export type { AgentCapability, AgentHandoff, AgentProfile, AgentRole, AssistantResponse, AutomaticTarget, CommittedTurnOutput, DelegationRequest, DelegationResult, GoalChangeAuthority, GoalChangeMetadata, GoalChangeOperation, GoalChangedData, GoalCommitment, GoalCompletionRequest, GoalHandoff, GoalOutcome, GoalPhase, GoalSnapshot, Handoff, HandoffCommit, HandoffValidationIssue, HandoffValidationRequest, HandoffValidationResult, HumanTurnAdmissionRequest, HumanTurnAdmissionResult, Ledger, MailLevel, MailRoute, MailSnapshot, ReassignmentRequest, ReassignmentResult, RunRequest, Runner, RunnerCandidateResult, RunnerChoice, RunnerCommandResult, RunnerConfigurator, RunnerControlMethod, RunnerDiagnostic, RunnerHandle, RunnerManifest, RunnerProfile, RunnerRpcMethod, RunnerSetupInteraction, RunnerSetupProgress, RunnerSetupTransaction, RunnerTraceEvent, ScheduleSnapshot, ScheduleStatus, SpecialistRole, TeamMemberMotion, TeamMemberView, ThreadSnapshot, TurnContext, TurnItemSnapshot, TurnItemStatus, TurnItemType, TurnOutput, TurnSnapshot, TurnStatus, TurnTrigger, TurnTriggerKind, WakeSnapshot, WakeStatus, WakeTriggerSnapshot, WakeTriggerStatus, WorkRecordDiff, WorkRecordSnapshot, WorkRecordUpdateRequest };
580
+ export type { AgentCapability, AgentHandoff, AgentProfile, AgentRole, AssistantLiveDelta, AutomaticTarget, CommittedTurnOutput, DelegationRequest, DelegationResult, GoalChangeAuthority, GoalChangeMetadata, GoalChangeOperation, GoalChangedData, GoalCommitment, GoalCompletionRequest, GoalHandoff, GoalOutcome, GoalPhase, GoalSnapshot, Handoff, HandoffCommit, HandoffValidationIssue, HandoffValidationRequest, HandoffValidationResult, HumanTurnAdmissionRequest, HumanTurnAdmissionResult, Ledger, MailLevel, MailRoute, MailSnapshot, ReassignmentRequest, ReassignmentResult, RunRequest, Runner, RunnerCandidateResult, RunnerChoice, RunnerCommandResult, RunnerConfigurator, RunnerControlMethod, RunnerDiagnostic, RunnerHandle, RunnerLiveEvent, RunnerManifest, RunnerProfile, RunnerRpcMethod, RunnerSetupInteraction, RunnerSetupProgress, RunnerSetupTransaction, RunnerTraceEvent, ScheduleSnapshot, ScheduleStatus, SpecialistRole, TeamMemberMotion, TeamMemberView, ThreadSnapshot, TurnContext, TurnItemSnapshot, TurnItemStatus, TurnItemType, TurnLiveSnapshot, TurnOutput, TurnSnapshot, TurnStatus, TurnTrigger, TurnTriggerKind, WakeSnapshot, WakeStatus, WakeTriggerSnapshot, WakeTriggerStatus, WorkRecordDiff, WorkRecordSnapshot, WorkRecordUpdateRequest };
@@ -2,7 +2,7 @@ import { createRequire as __goahCreateRequire } from "node:module";
2
2
  const require = __goahCreateRequire(import.meta.url);
3
3
  import {
4
4
  runProcessWorker
5
- } from "./chunk-7E7V6OIU.js";
5
+ } from "./chunk-24NAHA3D.js";
6
6
  import "./chunk-6M3OOCKO.js";
7
7
  import "./chunk-K26BVR6O.js";
8
8
  import "./chunk-IIJQ3DW4.js";
@@ -32,6 +32,7 @@ await runProcessWorker(async (request, emit, rpc) => {
32
32
  const steps = triggerSteps ?? byAgent[request.agent] ?? JSON.parse(process.env.GOAH_FAUX_STEPS ?? "[]");
33
33
  let goalCommitment = request.turn?.goalCommitment;
34
34
  let recordUpdated = false;
35
+ let messageSequence = 0;
35
36
  for (const step of steps) {
36
37
  if (step.write) {
37
38
  const path = join(process.cwd(), step.write.path);
@@ -54,8 +55,11 @@ await runProcessWorker(async (request, emit, rpc) => {
54
55
  await new Promise((resolve) => setTimeout(resolve, step.delayMs));
55
56
  if (step.hang)
56
57
  await new Promise(() => void 0);
57
- if (step.response !== void 0)
58
- return { outcome: "completed", response: { content: step.response } };
58
+ if (step.response !== void 0) {
59
+ const finalMessageId = `faux:${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageSequence}`;
60
+ emit({ type: "message.assistant.completed", data: { message: { id: finalMessageId, role: "assistant", content: [{ type: "text", text: step.response }] }, commitState: "committed" } });
61
+ return { outcome: "completed", finalMessageId };
62
+ }
59
63
  if (step.handoff) {
60
64
  const draft = step.handoff;
61
65
  if (goalCommitment && !recordUpdated) {
@@ -64,16 +68,19 @@ await runProcessWorker(async (request, emit, rpc) => {
64
68
  const evidence = sourceSeqs(request.context);
65
69
  await rpc("work_record.update", { expectedRevision: Number(record.recordRevision ?? 0), content: handoffRecord(draft.handoff, request.execution.id), reason: "record faux Goal progress", evidence: evidence.length ? [Math.max(...evidence)] : [] });
66
70
  }
67
- const validation = await rpc("goal.handoff.validate", { handoff: draft.handoff, candidateMessage: draft.response.content });
68
- emit({ type: "message.assistant.completed", data: { message: { id: `faux:${request.execution.id}:handoff:${validation.attemptId}`, role: "assistant", content: [{ type: "text", text: draft.response.content }], stopReason: "toolUse" }, commitState: "provisional" } });
71
+ const messageItemId = `faux:${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageSequence}`;
72
+ const validation = await rpc("goal.handoff.validate", { handoff: draft.handoff, candidateMessageId: messageItemId, candidateMessage: draft.response.content });
73
+ emit({ type: "message.assistant.completed", data: { message: { id: messageItemId, role: "assistant", content: [{ type: "text", text: draft.response.content }], stopReason: "toolUse" }, commitState: "provisional" } });
69
74
  if (!validation.accepted) {
70
75
  emit({ type: "runner.handoff_rejected", data: { attemptId: validation.attemptId, issues: validation.issues } });
71
76
  if (validation.fatal)
72
77
  return { outcome: "abnormal", reason: validation.issues.map((issue) => issue.message).join("; ") };
73
78
  continue;
74
79
  }
80
+ if (validation.messageItemId !== messageItemId)
81
+ return { outcome: "abnormal", reason: "Handoff validation returned a different assistant message identity" };
75
82
  const output = { validationAttemptId: validation.attemptId, validationToken: validation.token, handoff: draft.handoff };
76
- return { outcome: "completed", response: draft.response, handoff: output };
83
+ return { outcome: "completed", finalMessageId: messageItemId, handoff: output };
77
84
  }
78
85
  }
79
86
  return { outcome: "abnormal", reason: "faux worker stopped without handoff" };
package/dist/index.d.ts CHANGED
@@ -310,6 +310,28 @@ interface RunnerTraceEvent {
310
310
  type: string;
311
311
  data: JsonValue;
312
312
  }
313
+ type AssistantLiveDelta = {
314
+ type: "start" | "text_start" | "text_end" | "thinking_start" | "thinking_end" | "toolcall_start" | "toolcall_end";
315
+ contentIndex?: number;
316
+ } | {
317
+ type: "text_delta" | "thinking_delta" | "toolcall_delta";
318
+ contentIndex: number;
319
+ delta: string;
320
+ };
321
+ interface RunnerLiveEvent {
322
+ type: "message.assistant.delta";
323
+ data: {
324
+ messageId: string;
325
+ delta: AssistantLiveDelta;
326
+ };
327
+ }
328
+ interface TurnLiveSnapshot {
329
+ revision: number;
330
+ messageId: string;
331
+ text: string;
332
+ thinking: string;
333
+ thinkingActive: boolean;
334
+ }
313
335
  type AgentCapability = "ledger.search" | "mail.send" | "schedule.set" | "goal.put" | "team.list" | "goal.get" | "goal.create" | "goal.work" | "goal.delegate" | "goal.reassign" | "goal.revise" | "goal.pause" | "goal.resume" | "goal.complete" | "human.request" | "work_record.list" | "work_record.read" | "work_record.history" | "work_record.diff" | "work_record.search" | "work_record.update" | "memory.append";
314
336
  type RunnerControlMethod = "goal.handoff.validate";
315
337
  type RunnerRpcMethod = AgentCapability | RunnerControlMethod;
@@ -337,9 +359,6 @@ interface TurnContext {
337
359
  activeGoal: GoalSnapshot | null;
338
360
  goalCommitment: GoalCommitment | null;
339
361
  }
340
- interface AssistantResponse {
341
- content: string;
342
- }
343
362
  interface RunRequest {
344
363
  agent: string;
345
364
  execution: TurnSnapshot;
@@ -349,11 +368,12 @@ interface RunRequest {
349
368
  context: JsonValue;
350
369
  now(): string;
351
370
  emit(event: RunnerTraceEvent): void;
371
+ emitLive?(event: RunnerLiveEvent): void;
352
372
  rpc?(method: RunnerRpcMethod, params: JsonValue): Promise<JsonValue>;
353
373
  }
354
374
  type RunnerCandidateResult = {
355
375
  outcome: "completed";
356
- response: AssistantResponse;
376
+ finalMessageId: string;
357
377
  handoff?: TurnOutput;
358
378
  } | {
359
379
  outcome: "abnormal";
@@ -431,7 +451,8 @@ interface Ledger extends EventStore {
431
451
  renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
432
452
  appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
433
453
  repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
434
- finishTurn(id: string, status: "completed" | "failed" | "interrupted", error: JsonValue | null, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
454
+ finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
455
+ commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
435
456
  releaseTurnProcess(id: string, actor: string): TurnSnapshot;
436
457
  putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
437
458
  putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
@@ -518,7 +539,8 @@ declare class SqliteLedger implements Ledger {
518
539
  renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
519
540
  appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
520
541
  repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
521
- finishTurn(id: string, status: "completed" | "failed" | "interrupted", error: JsonValue | null, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
542
+ finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
543
+ commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
522
544
  releaseTurnProcess(id: string, actor: string): TurnSnapshot;
523
545
  putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
524
546
  putMails(mails: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
@@ -598,6 +620,7 @@ declare class Supervisor {
598
620
  goal: GoalSnapshot;
599
621
  }>;
600
622
  interruptTurn(turnId: string): Promise<TurnSnapshot>;
623
+ liveTurnSnapshot(turnId: string, afterRevision?: number): TurnLiveSnapshot | null;
601
624
  sendToCeo(body: JsonValue): Promise<{
602
625
  threadId: string;
603
626
  turnId: string;
package/dist/index.js CHANGED
@@ -31,11 +31,11 @@ import {
31
31
  updateWorkspaceRunnerProfile,
32
32
  writeDefaultConfig,
33
33
  writeDefaultRunnerProfile
34
- } from "./chunk-KJ4HJCTK.js";
35
- import "./chunk-D2DVAH37.js";
36
- import "./chunk-YH6YJ2IB.js";
37
- import "./chunk-QBD6EJ2Z.js";
38
- import "./chunk-7E7V6OIU.js";
34
+ } from "./chunk-ORAANUAR.js";
35
+ import "./chunk-J32EKOQ4.js";
36
+ import "./chunk-CDELLMIJ.js";
37
+ import "./chunk-RDUUXBZ2.js";
38
+ import "./chunk-24NAHA3D.js";
39
39
  import "./chunk-6M3OOCKO.js";
40
40
  import "./chunk-K26BVR6O.js";
41
41
  import "./chunk-IIJQ3DW4.js";
package/dist/pi-worker.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-XALZLRYK.js";
6
6
  import {
7
7
  runProcessWorker
8
- } from "./chunk-7E7V6OIU.js";
8
+ } from "./chunk-24NAHA3D.js";
9
9
  import {
10
10
  createPiModel,
11
11
  fauxAssistantMessage,
@@ -35,6 +35,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
35
35
  import { delimiter, dirname, join, resolve, sep } from "node:path";
36
36
  import { homedir, tmpdir } from "node:os";
37
37
  import { fileURLToPath } from "node:url";
38
+ var GOAL_TURN_POLICY = `A Goal commitment starts a sustained work session, not a short status round. Continue working while any safe, useful action can be executed now with current tools and authority. Use the Work Record as a durable checkpoint whenever the semantic state changes; updating it does not end the Turn. Planning, partial progress, decomposition, or scheduling a future Wake are not by themselves reasons to stop. Before Handoff, ask whether another useful action can be completed without new authority, unavailable data, another Agent's result, or the passage of time; if yes, do it now.
39
+
40
+ Call handoff exactly once, only at a genuine control boundary: the current actionable frontier is exhausted after meaningful work, an explicit external wait is required, a real blocker prevents safe progress, or completion evidence is ready for review. Use outcome progress only for the first case, waiting only for the second, blocked only for the third, and completion_proposed only for the fourth. Write a concise human-readable assistant message describing what happened, and keep Handoff machine-readable with only outcome and evidence. Treat the supplied context as authoritative.`;
38
41
  function compactMessages(messages, maxRecent = 8) {
39
42
  if (messages.length <= maxRecent + 1)
40
43
  return messages;
@@ -48,7 +51,7 @@ ${summary}`, timestamp: Date.now() },
48
51
  ];
49
52
  }
50
53
  async function runPiWorker() {
51
- await runProcessWorker(async (request, emit, rpc, controls) => {
54
+ await runProcessWorker(async (request, emit, rpc, controls, emitLive) => {
52
55
  const contextRecord = typeof request.context === "object" && request.context !== null && !Array.isArray(request.context) ? request.context : {};
53
56
  const legacyRequest = request.turn === void 0;
54
57
  const binding = request.turn?.goalCommitment;
@@ -100,8 +103,10 @@ Follow the current Goal and explicit tools.
100
103
  `;
101
104
  const finalResponse = fauxAssistantMessage([fauxText(String(handoff.message ?? `Goal work recorded with outcome ${outcome}.`)), fauxToolCall("handoff", { outcome, evidence: handoffEvidence })], { stopReason: "toolUse" });
102
105
  const recordResponse = fauxAssistantMessage(fauxToolCall("work_record_update", { expectedRevision: Number(current.recordRevision ?? 0), content: record, reason: "record faux Goal progress", evidence: evidence.length ? [Math.max(...evidence)] : [] }), { stopReason: "toolUse" });
106
+ const narratedScheduleResponse = fauxAssistantMessage([fauxText(String(handoff.message ?? "Work Record updated; the next wake is scheduled.")), fauxToolCall("schedule_wake", { at: new Date(Date.parse(request.execution.startedAt) + 6e4).toISOString(), reason: "continue Goal work" })], { stopReason: "toolUse" });
107
+ const toolOnlyHandoff = fauxAssistantMessage(fauxToolCall("handoff", { outcome, evidence: handoffEvidence }), { stopReason: "toolUse" });
103
108
  const fatalBatch = fauxAssistantMessage([fauxText("Stale completion attempt."), fauxToolCall("handoff", { outcome, evidence: handoffEvidence }), fauxToolCall("write", { path: "fatal.txt", content: "must not be written" })], { stopReason: "toolUse" });
104
- faux.setResponses(process.env.GOAH_PI_FAUX_FATAL_BATCH === "1" ? [fatalBatch] : process.env.GOAH_PI_FAUX_HANDOFF_REPAIR === "1" ? [fauxAssistantMessage([fauxText("Premature completion attempt."), fauxToolCall("handoff", { outcome, evidence: handoffEvidence })], { stopReason: "toolUse" }), recordResponse, finalResponse] : [recordResponse, finalResponse]);
109
+ faux.setResponses(process.env.GOAH_PI_FAUX_FATAL_BATCH === "1" ? [fatalBatch] : process.env.GOAH_PI_FAUX_HANDOFF_REPAIR === "1" ? [fauxAssistantMessage([fauxText("Premature completion attempt."), fauxToolCall("handoff", { outcome, evidence: handoffEvidence })], { stopReason: "toolUse" }), recordResponse, finalResponse] : process.env.GOAH_PI_FAUX_TOOL_ONLY_HANDOFF === "1" ? [recordResponse, narratedScheduleResponse, toolOnlyHandoff] : [recordResponse, finalResponse]);
105
110
  } else if (!goalState.bound) {
106
111
  faux.setResponses([fauxAssistantMessage([fauxText(process.env.GOAH_PI_FAUX_RESPONSE ?? "Hello from Goah.")])]);
107
112
  } else {
@@ -113,10 +118,11 @@ Follow the current Goal and explicit tools.
113
118
  let output = null;
114
119
  let acceptedHandoff = null;
115
120
  let revokedReason = "";
116
- let response = "";
121
+ let lastReadableMessage = null;
117
122
  let responseFailure = "";
118
123
  let compactions = 0;
119
124
  let messageCounter = 0;
125
+ let activeAssistantMessageId = null;
120
126
  const messageIds = /* @__PURE__ */ new WeakMap();
121
127
  const emittedUsers = /* @__PURE__ */ new Set();
122
128
  const idFor = (message) => {
@@ -125,7 +131,7 @@ Follow the current Goal and explicit tools.
125
131
  const existing = messageIds.get(message);
126
132
  if (existing)
127
133
  return existing;
128
- const id = `message:${++messageCounter}`;
134
+ const id = `${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageCounter}`;
129
135
  messageIds.set(message, id);
130
136
  return id;
131
137
  };
@@ -145,8 +151,11 @@ Follow the current Goal and explicit tools.
145
151
  const sourceSeqs = Array.isArray(contextRecord.sourceSeqs) ? contextRecord.sourceSeqs.filter((value) => Number.isInteger(value)) : [];
146
152
  const basePrompt = process.env.GOAH_PI_SYSTEM_PROMPT ?? suppliedPrompt ?? (request.turn?.trigger.kind === "user_message" ? "You are Goah's primary Agent. Respond naturally to the Human." : `You are Goah Agent ${request.agent}. Inspect the supplied context and respond appropriately.`);
147
153
  const systemPrompt = goalState.bound ? `${basePrompt}
148
- This Turn has a Goal commitment. You must update its Work Record, write a concise human-readable assistant message describing what happened, and call handoff exactly once with only machine-readable outcome and evidence. The message and Handoff are separate outputs. Treat the supplied context as authoritative.` : request.turn?.trigger.kind === "user_message" ? `${basePrompt}
149
- The active Goal, when supplied, is context rather than an assignment. Finish with an ordinary response unless a Goal tool establishes a commitment during this Turn. After commitment, update that Goal's Work Record, write a concise human-readable assistant message, and call handoff exactly once with separate machine-readable outcome and evidence. Treat the supplied context as authoritative.` : `${basePrompt}
154
+
155
+ ${GOAL_TURN_POLICY}` : request.turn?.trigger.kind === "user_message" ? `${basePrompt}
156
+ The active Goal, when supplied, is context rather than an assignment. Finish with an ordinary response unless a Goal tool establishes a commitment during this Turn. If a Goal commitment is established, follow this policy for the rest of the Turn:
157
+
158
+ ${GOAL_TURN_POLICY}` : `${basePrompt}
150
159
  This Turn has no Goal commitment. Finish with an ordinary response and do not call handoff. Treat the supplied context as authoritative.`;
151
160
  const agent = new Agent({
152
161
  initialState: {
@@ -196,10 +205,16 @@ This Turn has no Goal commitment. Finish with an ordinary response and do not ca
196
205
  acceptedHandoff = null;
197
206
  output = null;
198
207
  const handoff = args;
208
+ const currentText = assistantResponseText(assistantMessage);
209
+ const candidate = currentText ? activeAssistantMessageId ? { id: activeAssistantMessageId, text: currentText } : lastReadableMessage?.text === currentText ? lastReadableMessage : { id: idFor(assistantMessage), text: currentText } : lastReadableMessage;
199
210
  try {
200
- const result = await rpc("goal.handoff.validate", { handoff, candidateMessage: assistantResponseText(assistantMessage) });
211
+ const result = await rpc("goal.handoff.validate", { handoff, candidateMessageId: candidate?.id ?? "", candidateMessage: candidate?.text ?? "" });
201
212
  if (result.accepted) {
202
- acceptedHandoff = { attemptId: result.attemptId, token: result.token, handoff };
213
+ if (!candidate || result.messageItemId !== candidate.id) {
214
+ revokedReason = "Handoff validation returned a different assistant message identity";
215
+ return { block: true, reason: revokedReason, terminate: true };
216
+ }
217
+ acceptedHandoff = { attemptId: result.attemptId, token: result.token, messageItemId: result.messageItemId, handoff };
203
218
  return;
204
219
  }
205
220
  const reason = result.issues.map((issue) => `- ${issue.message}`).join("\n");
@@ -225,20 +240,33 @@ ${reason}`, terminate: result.fatal };
225
240
  agent.subscribe((event) => {
226
241
  if (event.type === "turn_start")
227
242
  emit({ type: "turn.started", data: {} });
228
- else if (event.type === "message_start" && event.message.role === "user") {
243
+ else if (event.type === "message_start") {
244
+ if (event.message.role === "assistant") {
245
+ activeAssistantMessageId = idFor(event.message);
246
+ return;
247
+ }
248
+ if (event.message.role !== "user")
249
+ return;
229
250
  const id = idFor(event.message);
230
251
  if (!emittedUsers.has(id)) {
231
252
  emittedUsers.add(id);
232
253
  emit({ type: "message.user", data: { message: transcriptMessage(event.message, id) } });
233
254
  }
234
255
  } else if (event.type === "message_update") {
235
- emit({ type: "message.assistant.delta", data: { messageId: idFor(event.message), delta: JSON.parse(JSON.stringify(event.assistantMessageEvent)) } });
256
+ const delta = minimalLiveDelta(event.assistantMessageEvent);
257
+ if (delta)
258
+ emitLive({ type: "message.assistant.delta", data: { messageId: activeAssistantMessageId ?? idFor(event.message), delta } });
236
259
  } else if (event.type === "message_end" && event.message.role === "assistant") {
237
- response = assistantResponseText(event.message);
260
+ const messageId = activeAssistantMessageId ?? idFor(event.message);
261
+ messageIds.set(event.message, messageId);
262
+ const text = assistantResponseText(event.message);
263
+ if (text)
264
+ lastReadableMessage = { id: messageId, text };
238
265
  if ((event.message.stopReason === "error" || event.message.stopReason === "aborted") && event.message.errorMessage)
239
266
  responseFailure = event.message.errorMessage;
240
267
  const handoffIntent = hasAgentToolCall(event.message, "handoff");
241
- emit({ type: "message.assistant.completed", data: { message: transcriptMessage(event.message, idFor(event.message)), commitState: handoffIntent ? "provisional" : "committed" } });
268
+ emit({ type: "message.assistant.completed", data: { message: transcriptMessage(event.message, messageId), commitState: handoffIntent ? "provisional" : "committed" } });
269
+ activeAssistantMessageId = null;
242
270
  } else if (event.type === "tool_execution_start")
243
271
  emit({ type: "tool.called", data: { callId: event.toolCallId, name: event.toolName, arguments: JSON.parse(JSON.stringify(event.args)) } });
244
272
  else if (event.type === "tool_execution_end")
@@ -262,13 +290,17 @@ Runner root: ${root}. Manage local files directly when the goal requires them.`)
262
290
  }
263
291
  if (revokedReason)
264
292
  return { outcome: "abnormal", reason: revokedReason };
293
+ const finalMessage = lastReadableMessage;
265
294
  if (!goalState.bound)
266
- return response ? { outcome: "completed", response: { content: response } } : { outcome: "abnormal", reason: responseFailure || "Pi worker exited without a response" };
295
+ return finalMessage ? { outcome: "completed", finalMessageId: finalMessage.id } : { outcome: "abnormal", reason: responseFailure || "Pi worker exited without a response" };
267
296
  if (responseFailure)
268
297
  return { outcome: "abnormal", reason: responseFailure };
269
298
  if (!output)
270
299
  return { outcome: "abnormal", reason: "Pi worker exited without an accepted Handoff" };
271
- return { outcome: "completed", response: { content: response }, handoff: output };
300
+ const finalHandoff = acceptedHandoff;
301
+ if (!finalHandoff)
302
+ return { outcome: "abnormal", reason: "Pi worker exited without an accepted Handoff message" };
303
+ return { outcome: "completed", finalMessageId: finalHandoff.messageItemId, handoff: output };
272
304
  });
273
305
  }
274
306
  function snapshotModelConfig(options) {
@@ -290,11 +322,20 @@ function transcriptMessage(message, id) {
290
322
  const role = message.role === "assistant" ? "assistant" : message.role === "user" ? "user" : "tool";
291
323
  return { id, role, content: value.content ?? value, ...value.usage !== void 0 ? { usage: value.usage } : {}, ...typeof value.stopReason === "string" ? { stopReason: value.stopReason } : {}, ...typeof value.errorMessage === "string" ? { errorMessage: value.errorMessage } : {} };
292
324
  }
325
+ function minimalLiveDelta(event) {
326
+ if (event.type === "start")
327
+ return { type: "start" };
328
+ if (event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta")
329
+ return { type: event.type, contentIndex: event.contentIndex, delta: event.delta };
330
+ if (event.type === "text_start" || event.type === "text_end" || event.type === "thinking_start" || event.type === "thinking_end" || event.type === "toolcall_start" || event.type === "toolcall_end")
331
+ return { type: event.type, contentIndex: event.contentIndex };
332
+ return null;
333
+ }
293
334
  function createTools(root, handoff, rpc, capabilities, goalState, protectedPaths = []) {
294
335
  const handoffTool = {
295
336
  name: "handoff",
296
337
  label: "Handoff",
297
- description: "Record a structured handoff and end a Turn with a Goal commitment.",
338
+ description: "End the current Goal Turn at a genuine control boundary. Do not call while another safe and useful action can be completed now. Planning, partial progress, a Work Record update, or a scheduled Wake does not by itself justify Handoff.",
298
339
  parameters: typebox_exports.Object({
299
340
  outcome: typebox_exports.Union([typebox_exports.Literal("progress"), typebox_exports.Literal("waiting"), typebox_exports.Literal("blocked"), typebox_exports.Literal("completion_proposed")]),
300
341
  evidence: typebox_exports.Array(typebox_exports.Number())
@@ -488,7 +529,7 @@ function createRpcTools(rpc, allowed, onResult) {
488
529
  ["ledger.search", tool("ledger_search", "Search durable ledger facts.", "ledger.search", typebox_exports.Object({ query: typebox_exports.String(), limit: typebox_exports.Optional(typebox_exports.Number()) }))],
489
530
  ["memory.append", tool("memory_append", "Append a durable working-memory note that is injected into your future wakes. Record procedural knowledge, active hypotheses, and abandoned approaches with the reason; keep notes concise.", "memory.append", typebox_exports.Object({ note: typebox_exports.String() }))],
490
531
  ["mail.send", tool("send_mail", "Send durable Mail to another Goal owner. goalId is required and routes the Mail into that owner's next committed Turn. Human communication must use request_human.", "mail.send", typebox_exports.Object({ to: typebox_exports.String(), goalId: typebox_exports.String(), level: typebox_exports.Union([typebox_exports.Literal("fyi"), typebox_exports.Literal("decision"), typebox_exports.Literal("emergency")]), body: typebox_exports.Any() }))],
491
- ["schedule.set", tool("schedule_wake", "Schedule this agent's next wake.", "schedule.set", typebox_exports.Object({ at: typebox_exports.String(), reason: typebox_exports.String() }))],
532
+ ["schedule.set", tool("schedule_wake", "Schedule genuinely time-dependent future observation. Scheduling does not end the current Turn; continue all useful work that can be completed before waiting.", "schedule.set", typebox_exports.Object({ at: typebox_exports.String(), reason: typebox_exports.String() }))],
492
533
  ["team.list", tool("team_list", "Read the ledger-derived team roster and liveness state.", "team.list", typebox_exports.Object({}))],
493
534
  ["goal.get", tool("get_goal", "Read the active Goal visible to this Turn. Visibility does not establish a Goal commitment.", "goal.get", typebox_exports.Object({}))],
494
535
  ["goal.create", tool("create_goal", "Create a Root Goal from durable Human intent and bind this Turn. Do not use for greetings, questions, or routine single-turn work.", "goal.create", typebox_exports.Object({ objective: typebox_exports.String(), id: typebox_exports.Optional(typebox_exports.String()) }))],
@@ -662,6 +703,7 @@ export {
662
703
  compactMessages,
663
704
  compactMessagesToTokenBudget,
664
705
  linuxSandboxArgs,
706
+ minimalLiveDelta,
665
707
  resolveContextPolicy,
666
708
  runBashCommand,
667
709
  runPiWorker,
@@ -87,6 +87,7 @@ type HandoffValidationResult = {
87
87
  token: string;
88
88
  goalId: string;
89
89
  goalRevision: number;
90
+ messageItemId: string;
90
91
  } | {
91
92
  accepted: false;
92
93
  fatal: boolean;
@@ -102,6 +103,21 @@ interface RunnerTraceEvent {
102
103
  type: string;
103
104
  data: JsonValue;
104
105
  }
106
+ type AssistantLiveDelta = {
107
+ type: "start" | "text_start" | "text_end" | "thinking_start" | "thinking_end" | "toolcall_start" | "toolcall_end";
108
+ contentIndex?: number;
109
+ } | {
110
+ type: "text_delta" | "thinking_delta" | "toolcall_delta";
111
+ contentIndex: number;
112
+ delta: string;
113
+ };
114
+ interface RunnerLiveEvent {
115
+ type: "message.assistant.delta";
116
+ data: {
117
+ messageId: string;
118
+ delta: AssistantLiveDelta;
119
+ };
120
+ }
105
121
  type AgentCapability = "ledger.search" | "mail.send" | "schedule.set" | "goal.put" | "team.list" | "goal.get" | "goal.create" | "goal.work" | "goal.delegate" | "goal.reassign" | "goal.revise" | "goal.pause" | "goal.resume" | "goal.complete" | "human.request" | "work_record.list" | "work_record.read" | "work_record.history" | "work_record.diff" | "work_record.search" | "work_record.update" | "memory.append";
106
122
  type RunnerControlMethod = "goal.handoff.validate";
107
123
  type RunnerRpcMethod = AgentCapability | RunnerControlMethod;
@@ -181,9 +197,6 @@ interface TurnContext {
181
197
  activeGoal: GoalSnapshot | null;
182
198
  goalCommitment: GoalCommitment | null;
183
199
  }
184
- interface AssistantResponse {
185
- content: string;
186
- }
187
200
  interface RunRequest {
188
201
  agent: string;
189
202
  execution: TurnSnapshot;
@@ -193,11 +206,12 @@ interface RunRequest {
193
206
  context: JsonValue;
194
207
  now(): string;
195
208
  emit(event: RunnerTraceEvent): void;
209
+ emitLive?(event: RunnerLiveEvent): void;
196
210
  rpc?(method: RunnerRpcMethod, params: JsonValue): Promise<JsonValue>;
197
211
  }
198
212
  type RunnerCandidateResult = {
199
213
  outcome: "completed";
200
- response: AssistantResponse;
214
+ finalMessageId: string;
201
215
  handoff?: TurnOutput;
202
216
  } | {
203
217
  outcome: "abnormal";
@@ -267,14 +281,17 @@ interface EnvSpecSource {
267
281
  */
268
282
  declare function resolveEnvSpec(spec: Record<string, string> | undefined, source: EnvSpecSource | undefined): Record<string, string>;
269
283
 
284
+ interface PiAssistantResponse {
285
+ content: string;
286
+ }
270
287
  interface PiStep {
271
288
  trace?: Array<{
272
289
  type: string;
273
290
  data: JsonValue;
274
291
  }>;
275
- response?: AssistantResponse;
292
+ response?: PiAssistantResponse;
276
293
  handoff?: {
277
- response: AssistantResponse;
294
+ response: PiAssistantResponse;
278
295
  handoff: AgentHandoff;
279
296
  };
280
297
  stopped?: boolean;
@@ -315,7 +332,7 @@ interface ProcessRunnerOptions {
315
332
  }
316
333
  declare function piWorkerPath(): string;
317
334
  declare function verificationWorkerPath(): string;
318
- type WorkerRequest = Omit<RunRequest, "now" | "emit" | "rpc" | "turn"> & {
335
+ type WorkerRequest = Omit<RunRequest, "now" | "emit" | "emitLive" | "rpc" | "turn"> & {
319
336
  turn?: TurnContext;
320
337
  runtime?: JsonValue;
321
338
  };
@@ -334,9 +351,9 @@ interface WorkerControls {
334
351
  type WorkerRun = (request: WorkerRequest, emit: (event: {
335
352
  type: string;
336
353
  data: JsonValue;
337
- }) => void, rpc: WorkerRpc, controls: WorkerControls) => Promise<RunnerCandidateResult>;
354
+ }) => void, rpc: WorkerRpc, controls: WorkerControls, emitLive: (event: RunnerLiveEvent) => void) => Promise<RunnerCandidateResult>;
338
355
  /** Entry helper for runner executables. It exits when its parent disappears. */
339
356
  declare function runProcessWorker(run: WorkerRun): Promise<void>;
340
357
 
341
358
  export { LOCAL_PROVIDERS, PiRunnerAdapter, ProcessRunner, createPiProcessRunner, defaultAuthFile, modelCatalog, piConfig, piEnvironment, piRunnerConfigurator, piWorkerPath, providerCatalog, resolveEnvSpec, runProcessWorker, verificationWorkerPath };
342
- export type { ModelSummary, PiDriver, PiRunnerConfig, PiRunnerSession, PiStep, ProcessRunnerOptions, ProviderSummary, WorkerControls, WorkerRpc, WorkerRun };
359
+ export type { ModelSummary, PiAssistantResponse, PiDriver, PiRunnerConfig, PiRunnerSession, PiStep, ProcessRunnerOptions, ProviderSummary, WorkerControls, WorkerRpc, WorkerRun };
package/dist/runner-pi.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  resolveEnvSpec,
12
12
  runProcessWorker,
13
13
  verificationWorkerPath
14
- } from "./chunk-7E7V6OIU.js";
14
+ } from "./chunk-24NAHA3D.js";
15
15
  import {
16
16
  LOCAL_PROVIDERS,
17
17
  defaultAuthFile,
@@ -3,8 +3,8 @@ const require = __goahCreateRequire(import.meta.url);
3
3
  import {
4
4
  runnerManifests,
5
5
  runnerPlugin
6
- } from "./chunk-D2DVAH37.js";
7
- import "./chunk-7E7V6OIU.js";
6
+ } from "./chunk-J32EKOQ4.js";
7
+ import "./chunk-24NAHA3D.js";
8
8
  import "./chunk-6M3OOCKO.js";
9
9
  import "./chunk-K26BVR6O.js";
10
10
  import "./chunk-IIJQ3DW4.js";
package/dist/sqlite.d.ts CHANGED
@@ -320,7 +320,8 @@ interface Ledger extends EventStore {
320
320
  renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
321
321
  appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
322
322
  repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
323
- finishTurn(id: string, status: "completed" | "failed" | "interrupted", error: JsonValue | null, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
323
+ finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
324
+ commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
324
325
  releaseTurnProcess(id: string, actor: string): TurnSnapshot;
325
326
  putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
326
327
  putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
@@ -408,7 +409,8 @@ declare class SqliteLedger implements Ledger {
408
409
  renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
409
410
  appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
410
411
  repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
411
- finishTurn(id: string, status: "completed" | "failed" | "interrupted", error: JsonValue | null, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
412
+ finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
413
+ commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
412
414
  releaseTurnProcess(id: string, actor: string): TurnSnapshot;
413
415
  putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
414
416
  putMails(mails: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
package/dist/sqlite.js CHANGED
@@ -3,7 +3,7 @@ const require = __goahCreateRequire(import.meta.url);
3
3
  import {
4
4
  SQLITE_SCHEMA_VERSION,
5
5
  SqliteLedger
6
- } from "./chunk-YH6YJ2IB.js";
6
+ } from "./chunk-CDELLMIJ.js";
7
7
  import "./chunk-4KA63VNZ.js";
8
8
  import "./chunk-KAO7WIXG.js";
9
9
  import "./chunk-R6I3VKME.js";