@goah/cli 0.13.3 → 0.13.4
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/dist/{chunk-KJ4HJCTK.js → chunk-5PZFP5CO.js} +5 -21
- package/dist/{chunk-YH6YJ2IB.js → chunk-CDELLMIJ.js} +37 -10
- package/dist/{chunk-7E7V6OIU.js → chunk-EK6BTCFG.js} +12 -5
- package/dist/{chunk-D2DVAH37.js → chunk-R6GKCNTB.js} +1 -1
- package/dist/{chunk-QBD6EJ2Z.js → chunk-SQJZ5OFH.js} +52 -37
- package/dist/cli.js +22 -16
- package/dist/{dist-KNR2XL6J.js → dist-QW77PKUJ.js} +1 -1
- package/dist/execution.d.ts +6 -6
- package/dist/faux-runner-worker.js +13 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +5 -5
- package/dist/pi-worker.js +35 -14
- package/dist/runner-pi.d.ts +8 -7
- package/dist/runner-pi.js +1 -1
- package/dist/{runner-registry-GD4BYCQC.js → runner-registry-3WSOMXRW.js} +2 -2
- package/dist/sqlite.d.ts +4 -2
- package/dist/sqlite.js +1 -1
- package/dist/supervisor.d.ts +3 -5
- package/dist/supervisor.js +1 -1
- package/dist/testkit.d.ts +10 -7
- package/dist/testkit.js +5 -1
- package/package.json +1 -1
|
@@ -2,30 +2,27 @@ import { createRequire as __goahCreateRequire } from "node:module";
|
|
|
2
2
|
const require = __goahCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
runnerPlugin
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-R6GKCNTB.js";
|
|
6
6
|
import {
|
|
7
7
|
SQLITE_SCHEMA_VERSION,
|
|
8
8
|
SqliteLedger
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CDELLMIJ.js";
|
|
10
10
|
import {
|
|
11
11
|
RunnerRouter,
|
|
12
12
|
Supervisor,
|
|
13
13
|
deriveRecoveryViews
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-SQJZ5OFH.js";
|
|
15
15
|
import {
|
|
16
16
|
ProcessRunner,
|
|
17
17
|
piWorkerPath,
|
|
18
18
|
resolveEnvSpec
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EK6BTCFG.js";
|
|
20
20
|
import {
|
|
21
21
|
createPiModel
|
|
22
22
|
} from "./chunk-6M3OOCKO.js";
|
|
23
23
|
import {
|
|
24
24
|
replayTranscript
|
|
25
25
|
} from "./chunk-R6I3VKME.js";
|
|
26
|
-
import {
|
|
27
|
-
normalizeAssistantText
|
|
28
|
-
} from "./chunk-TJON22J4.js";
|
|
29
26
|
|
|
30
27
|
// node_modules/.dist-original/index.js
|
|
31
28
|
import { accessSync, chmodSync as chmodSync3, constants, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync, closeSync, readFileSync as readFileSync3, renameSync, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -440,10 +437,7 @@ async function* turnFrames(turnId, ledger, isActive) {
|
|
|
440
437
|
throw new Error("Turn disappeared");
|
|
441
438
|
if (current.status !== "in_progress") {
|
|
442
439
|
yield* drain();
|
|
443
|
-
|
|
444
|
-
const answerText = String(answer?.data?.text ?? "");
|
|
445
|
-
const streamed = answerText !== "" && ledger.readStream(`turn:${turnId}`).some((event) => event.type === "response.committed" ? event.data.text === answerText : event.type === "message.assistant.completed" && assistantEventText(event.data) === answerText);
|
|
446
|
-
yield { type: "result", value: { turn: { ...current, leaseToken: null }, ...answer && !streamed ? { response: { content: answerText } } : {} } };
|
|
440
|
+
yield { type: "result", value: { turn: { ...current, leaseToken: null } } };
|
|
447
441
|
return;
|
|
448
442
|
}
|
|
449
443
|
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
@@ -461,16 +455,6 @@ function requiredGoal(ledger, id) {
|
|
|
461
455
|
throw new Error("goal not found");
|
|
462
456
|
return goal;
|
|
463
457
|
}
|
|
464
|
-
function assistantEventText(value) {
|
|
465
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || !value.message || typeof value.message !== "object" || Array.isArray(value.message))
|
|
466
|
-
return "";
|
|
467
|
-
const content = value.message.content;
|
|
468
|
-
if (typeof content === "string")
|
|
469
|
-
return normalizeAssistantText(content);
|
|
470
|
-
if (!Array.isArray(content))
|
|
471
|
-
return "";
|
|
472
|
-
return content.map((item) => item && typeof item === "object" && !Array.isArray(item) && item.type === "text" && typeof item.text === "string" ? normalizeAssistantText(item.text) : "").filter(Boolean).join("\n");
|
|
473
|
-
}
|
|
474
458
|
function isTurnPresentationEvent(type) {
|
|
475
459
|
return type.startsWith("message.") || type.startsWith("tool.") || type.startsWith("item.") || type.startsWith("turn.") || type === "response.committed" || type === "handoff.recorded" || type === "transcript.interrupted" || type === "transcript.completed";
|
|
476
460
|
}
|
|
@@ -751,9 +751,25 @@ var SqliteLedger = class {
|
|
|
751
751
|
return this.#transaction(() => this.#repairOpenTurnItems(id, reason, now, actor));
|
|
752
752
|
}
|
|
753
753
|
finishTurn(id, status, error, now, actor, mailIds = []) {
|
|
754
|
+
if (status === "completed")
|
|
755
|
+
throw new Error("successful Turn completion requires commitTurnResponse or commitHandoff");
|
|
754
756
|
const current = this.#requiredTurn(id);
|
|
755
757
|
return this.#transaction(() => this.#finishTurn(current, status, error, now, actor, mailIds));
|
|
756
758
|
}
|
|
759
|
+
commitTurnResponse(id, responseItemId, now, actor, mailIds = []) {
|
|
760
|
+
return this.#transaction(() => {
|
|
761
|
+
const current = this.#requiredTurn(id);
|
|
762
|
+
if (current.goalId !== null)
|
|
763
|
+
throw new Error("Goal-bound Turn response requires commitHandoff");
|
|
764
|
+
const responseItem = this.turnItems(id).find((item) => item.id === responseItemId);
|
|
765
|
+
const text = responseItem && responseItem.type === "assistant_message" && responseItem.status === "completed" ? normalizeAssistantText(String(responseItem.data.text ?? "")) : "";
|
|
766
|
+
if (!responseItem || !text)
|
|
767
|
+
throw new Error("Turn response does not match a readable Assistant Item");
|
|
768
|
+
const streamId = `turn:${id}`;
|
|
769
|
+
this.#insertEvent({ streamId, ts: now, actor, type: "response.committed", data: { messageItemId: responseItem.id, text } });
|
|
770
|
+
return this.#finishTurn(current, "completed", null, now, actor, mailIds);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
757
773
|
releaseTurnProcess(id, actor) {
|
|
758
774
|
if (actor !== "supervisor")
|
|
759
775
|
throw new Error("only supervisor may release Runner process ownership");
|
|
@@ -1451,25 +1467,36 @@ var SqliteLedger = class {
|
|
|
1451
1467
|
} else {
|
|
1452
1468
|
if (event.type !== `turn.${turn.status}` || turn.attempt !== current.attempt || turn.runnerPid !== current.runnerPid)
|
|
1453
1469
|
throw new Error(`turn event ${event.seq} has an invalid terminal transition`);
|
|
1454
|
-
if (turn.status === "completed"
|
|
1455
|
-
|
|
1470
|
+
if (turn.status === "completed") {
|
|
1471
|
+
if (turn.goalId !== null)
|
|
1472
|
+
this.#assertReplayGoalCompletion(turn, event);
|
|
1473
|
+
else
|
|
1474
|
+
this.#assertReplayResponseCompletion(turn, event);
|
|
1475
|
+
}
|
|
1456
1476
|
}
|
|
1457
1477
|
}
|
|
1478
|
+
#assertReplayResponseCompletion(turn, event) {
|
|
1479
|
+
const responseRow = this.db.prepare("SELECT data FROM events WHERE stream_id=? AND type='response.committed' AND seq<? ORDER BY seq DESC LIMIT 1").get(`turn:${turn.id}`, event.seq);
|
|
1480
|
+
if (!responseRow)
|
|
1481
|
+
throw new Error(`turn event ${event.seq} completes without a committed response`);
|
|
1482
|
+
const response = JSON.parse(responseRow.data);
|
|
1483
|
+
const responseItem = this.turnItems(turn.id).find((item) => item.id === response.messageItemId && item.type === "assistant_message" && item.status === "completed");
|
|
1484
|
+
if (!responseItem || normalizeAssistantText(String(responseItem.data.text ?? "")) !== response.text)
|
|
1485
|
+
throw new Error(`turn event ${event.seq} has a mismatched committed response`);
|
|
1486
|
+
return responseItem;
|
|
1487
|
+
}
|
|
1458
1488
|
#assertReplayGoalCompletion(turn, event) {
|
|
1459
1489
|
if (turn.goalId === null || turn.goalRevision === null)
|
|
1460
1490
|
throw new Error(`turn event ${event.seq} has no Goal commitment`);
|
|
1461
1491
|
const items = this.turnItems(turn.id);
|
|
1462
|
-
|
|
1492
|
+
this.#assertReplayResponseCompletion(turn, event);
|
|
1463
1493
|
const handoffItem = items.findLast((item) => item.type === "handoff" && item.status === "completed");
|
|
1464
|
-
const responseRow = this.db.prepare("SELECT data FROM events WHERE stream_id=? AND type='response.committed' AND seq<? ORDER BY seq DESC LIMIT 1").get(`turn:${turn.id}`, event.seq);
|
|
1465
1494
|
const handoffRow = this.db.prepare("SELECT data FROM events WHERE stream_id=? AND type='handoff.recorded' AND seq<? ORDER BY seq DESC LIMIT 1").get(`turn:${turn.id}`, event.seq);
|
|
1466
|
-
if (!
|
|
1467
|
-
throw new Error(`turn event ${event.seq} completes a Goal without
|
|
1468
|
-
const response = JSON.parse(responseRow.data);
|
|
1495
|
+
if (!handoffRow || !handoffItem)
|
|
1496
|
+
throw new Error(`turn event ${event.seq} completes a Goal without Handoff`);
|
|
1469
1497
|
const handoff = JSON.parse(handoffRow.data);
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
throw new Error(`turn event ${event.seq} has mismatched response or Handoff facts`);
|
|
1498
|
+
if (!isDeepStrictEqual(handoffItem.data, handoff))
|
|
1499
|
+
throw new Error(`turn event ${event.seq} has mismatched Handoff facts`);
|
|
1473
1500
|
assertHandoff(handoff);
|
|
1474
1501
|
const record = this.workRecord(turn.goalId);
|
|
1475
1502
|
if (handoff.goalId !== turn.goalId || handoff.goalRevision !== turn.goalRevision || record?.recordRevision !== handoff.recordRevision)
|
|
@@ -124,18 +124,23 @@ var PiRunnerAdapter = class {
|
|
|
124
124
|
}
|
|
125
125
|
async #run(request) {
|
|
126
126
|
const runnerSession = await this.driver.createRunnerSession(request);
|
|
127
|
+
let messageSequence = 0;
|
|
127
128
|
try {
|
|
128
129
|
while (true) {
|
|
129
130
|
const step = await runnerSession.step();
|
|
130
131
|
for (const trace of step.trace ?? [])
|
|
131
132
|
request.emit(trace);
|
|
132
|
-
if (step.response)
|
|
133
|
-
|
|
133
|
+
if (step.response) {
|
|
134
|
+
const finalMessageId = `adapter:${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageSequence}`;
|
|
135
|
+
request.emit({ type: "message.assistant.completed", data: { message: { id: finalMessageId, role: "assistant", content: [{ type: "text", text: step.response.content }] }, commitState: "committed" } });
|
|
136
|
+
return { outcome: "completed", finalMessageId };
|
|
137
|
+
}
|
|
134
138
|
if (step.handoff) {
|
|
135
|
-
const
|
|
139
|
+
const messageItemId = `adapter:${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageSequence}`;
|
|
140
|
+
const validation = await request.rpc?.("goal.handoff.validate", { handoff: step.handoff.handoff, candidateMessageId: messageItemId, candidateMessage: step.handoff.response.content });
|
|
136
141
|
if (!validation)
|
|
137
142
|
return { outcome: "abnormal", reason: "Handoff validation is unavailable" };
|
|
138
|
-
request.emit({ type: "message.assistant.completed", data: { message: { id:
|
|
143
|
+
request.emit({ type: "message.assistant.completed", data: { message: { id: messageItemId, role: "assistant", content: [{ type: "text", text: step.handoff.response.content }], stopReason: "toolUse" }, commitState: "provisional" } });
|
|
139
144
|
if (!validation.accepted) {
|
|
140
145
|
if (validation.fatal)
|
|
141
146
|
return { outcome: "abnormal", reason: validation.issues.map((issue) => issue.message).join("; ") };
|
|
@@ -143,9 +148,11 @@ var PiRunnerAdapter = class {
|
|
|
143
148
|
request.emit({ type: "runner.handoff_rejected", data: { attemptId: validation.attemptId, issues: validation.issues } });
|
|
144
149
|
continue;
|
|
145
150
|
}
|
|
151
|
+
if (validation.messageItemId !== messageItemId)
|
|
152
|
+
return { outcome: "abnormal", reason: "Handoff validation returned a different assistant message identity" };
|
|
146
153
|
const output = { validationAttemptId: validation.attemptId, validationToken: validation.token, handoff: step.handoff.handoff };
|
|
147
154
|
assertTurnOutput(output);
|
|
148
|
-
return { outcome: "completed",
|
|
155
|
+
return { outcome: "completed", finalMessageId: messageItemId, handoff: output };
|
|
149
156
|
}
|
|
150
157
|
if (step.stopped)
|
|
151
158
|
return { outcome: "abnormal", reason: request.turn.goalCommitment ? "runner stopped without a readable response and valid handoff" : "runner stopped without a response" };
|
|
@@ -3,7 +3,7 @@ const require = __goahCreateRequire(import.meta.url);
|
|
|
3
3
|
import {
|
|
4
4
|
createPiProcessRunner,
|
|
5
5
|
piRunnerConfigurator
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EK6BTCFG.js";
|
|
7
7
|
|
|
8
8
|
// node_modules/.dist-original/runner-registry.js
|
|
9
9
|
var plugins = /* @__PURE__ */ new Map([
|
|
@@ -92,14 +92,20 @@ function field(value, key) {
|
|
|
92
92
|
|
|
93
93
|
// node_modules/goah-supervisor/dist/roles.js
|
|
94
94
|
var prompts = {
|
|
95
|
-
child:
|
|
96
|
-
|
|
95
|
+
child: `Own the assigned Child Goal. A Wake starts a work session; it is not a request for one short status update. Follow the observation method, verify completion with the verification method, inspect shared Work Records, cite Ledger evidence, and 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, 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.
|
|
96
|
+
|
|
97
|
+
Handoff is declarative and ends this Turn. Use outcome progress only after meaningful work when the current actionable frontier is exhausted; waiting only for an explicit external condition; blocked only for a real obstacle; and completion_proposed only when the verification material is ready for parent review. Use mail.send to notify or escalate, setting its typed goalId route to the parent Goal when it should wake the parent's next committed Turn; use schedule.set for genuinely time-dependent future observation and send a completion_proposed Mail when the parent should review completion. You are not a task-only worker and cannot redefine or complete your own Goal.`,
|
|
98
|
+
ceo: `You are the user's sole operating interface and the durable CEO identity for this Goal tree. You organize work and execute directly wherever work is not delegated. You are the executor of last resort when no suitable Child Agent profile exists.
|
|
97
99
|
|
|
98
100
|
On the first wake for a root with no observation method, operationalize the goal before claiming measurable progress: inspect the current directory with read/edit/write/bash; read project documentation, configuration, code, scripts, local CLIs, and existing ledger evidence; clarify the objective, constraints, baseline, and meaning of success; identify the data source or qualitative inspection protocol; define freshness, cadence, time zone, sustain window, and missing-data behavior when relevant; list the exact access required; then propose a textual observation method to the human. Ask only for facts or permissions that cannot be discovered locally. Continue safe reversible exploration while waiting. Never invent a baseline, data source, permission, success criterion, or observation result.
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
During every Goal Turn: (1) orient from the root and descendants, each current observation method and revision, Team motion, incoming mail, handoffs, blockers, unknown Tool Calls, and recovery facts; (2) diagnose every active child's motion, ownership, and adherence to its authoritative observation method; (3) decide whether to keep, delegate, revise the objective/method pair, reassign, pause, resume, complete, or escalate; (4) apply organization changes only through the high-level atomic tools; (5) repair every idle_unplanned child and every child missing an observation method; (6) execute the current work frontier instead of stopping at planning; (7) before ending, leave active child motion plus a CEO review, an explicit wait/blocker, a human request, or a completion recommendation carrying evidence produced under the current method.
|
|
103
|
+
|
|
104
|
+
Use goal.delegate rather than separate goal/mail/schedule calls. Execute ambiguous or exploratory work yourself until a stream has a bounded objective, independent observation and verification methods, and a reviewable result; only then create a distinct Goal-owning Agent. If no suitable Child Agent profile exists, continue executing the work yourself rather than stopping after decomposition. Use team.list as the roster source of truth.
|
|
105
|
+
|
|
106
|
+
A Wake starts a work session; it does not imply that the Turn should be short. Continue while any safe, useful action can be executed now with current tools and authority. Use the bound Goal's Work Record as a durable checkpoint whenever 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. Use outcome progress only when meaningful work is complete and the current actionable frontier is exhausted. Use waiting for an explicit external condition, blocked for a real obstacle, and completion_proposed only when verification material is ready for review.
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
Handoff is declarative and ends the Turn: use mail.send for explicit organization communication, schedule.set only for genuinely time-dependent future motion, and human.request when Human authority is required. Never claim authority to confirm Root methods, complete, or materially change a Root Goal: request the Human instead.`,
|
|
103
109
|
verifier: "Verify one Turn's handoff claims against its trace and runner facts. Do not trust self-report. Persist concise findings with exact evidence sequences.",
|
|
104
110
|
audit: "Independently reconstruct outcomes from durable facts and external observations. Persist independent audit judgment with memory_append; it never substitutes for fresh evidence."
|
|
105
111
|
};
|
|
@@ -550,27 +556,18 @@ var Supervisor = class {
|
|
|
550
556
|
this.#scheduleTurnRecovery(sourceWake, sourceWakeTriggers, current.id, agent);
|
|
551
557
|
return;
|
|
552
558
|
}
|
|
553
|
-
const
|
|
554
|
-
if (!response)
|
|
555
|
-
throw new Error("completed Turn requires a readable assistant response");
|
|
559
|
+
const responseItem = this.#finalAssistantItem(current.id, result.finalMessageId);
|
|
556
560
|
if (turnContext.goalCommitment) {
|
|
557
561
|
if (!result.handoff)
|
|
558
562
|
throw new Error("committed Turn requires Handoff");
|
|
559
|
-
this.#commitTurnGoalWork(current.id, agent, turnContext,
|
|
563
|
+
this.#commitTurnGoalWork(current.id, agent, turnContext, responseItem, result.handoff, sourceWake?.id ?? null, deliveredMailIds, recordRevisionAtStart);
|
|
560
564
|
return;
|
|
561
565
|
}
|
|
562
566
|
if (result.handoff)
|
|
563
567
|
throw new Error("uncommitted Turn cannot finish with Handoff");
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (traced !== response)
|
|
568
|
-
throw new Error("completed Turn response does not match its latest assistant trace");
|
|
569
|
-
if (this.#assistantCommitState(current.id, traced) !== "committed")
|
|
570
|
-
throw new Error("uncommitted Turn requires a committed assistant trace");
|
|
571
|
-
} else
|
|
572
|
-
this.#appendTurnItem(current.id, "assistant_message", { text: response }, agent);
|
|
573
|
-
this.ledger.finishTurn(current.id, "completed", null, this.#now(), "supervisor", deliveredMailIds);
|
|
568
|
+
if (this.#assistantCommitState(current.id, responseItem.id) !== "committed")
|
|
569
|
+
throw new Error("uncommitted Turn requires a committed assistant trace");
|
|
570
|
+
this.ledger.commitTurnResponse(current.id, responseItem.id, this.#now(), "supervisor", deliveredMailIds);
|
|
574
571
|
return;
|
|
575
572
|
} catch (error) {
|
|
576
573
|
if (renewal)
|
|
@@ -727,19 +724,32 @@ ${history}` } : {} };
|
|
|
727
724
|
const facts = this.ledger.readStream(`turn:${turnId}`).filter((event) => event.type === "turn.retry_started").map((event) => `${event.type}: ${JSON.stringify(event.data)}`);
|
|
728
725
|
return [...items, ...facts].join("\n");
|
|
729
726
|
}
|
|
730
|
-
#
|
|
731
|
-
|
|
732
|
-
const event = this.ledger.readStream(`turn:${turnId}`).findLast((candidate) => {
|
|
727
|
+
#assistantMessageEvent(turnId, messageItemId) {
|
|
728
|
+
return this.ledger.readStream(`turn:${turnId}`).findLast((candidate) => {
|
|
733
729
|
if (candidate.type !== "message.assistant.completed" || !candidate.data || typeof candidate.data !== "object" || Array.isArray(candidate.data))
|
|
734
730
|
return false;
|
|
735
731
|
const message = candidate.data.message;
|
|
736
|
-
return
|
|
732
|
+
return message?.id === messageItemId;
|
|
737
733
|
});
|
|
734
|
+
}
|
|
735
|
+
#assistantCommitState(turnId, messageItemId) {
|
|
736
|
+
const event = this.#assistantMessageEvent(turnId, messageItemId);
|
|
738
737
|
if (!event || !event.data || typeof event.data !== "object" || Array.isArray(event.data))
|
|
739
738
|
return null;
|
|
740
739
|
const state = event.data.commitState;
|
|
741
740
|
return state === "committed" || state === "provisional" ? state : null;
|
|
742
741
|
}
|
|
742
|
+
#finalAssistantItem(turnId, messageItemId) {
|
|
743
|
+
const id = messageItemId.trim();
|
|
744
|
+
const item = id ? this.ledger.turnItems(turnId).find((candidate) => candidate.id === id) : void 0;
|
|
745
|
+
const text = item && item.type === "assistant_message" && item.status === "completed" ? normalizeAssistantText(String(item.data.text ?? "")) : "";
|
|
746
|
+
const event = id ? this.#assistantMessageEvent(turnId, id) : void 0;
|
|
747
|
+
const turn = this.ledger.turn(turnId);
|
|
748
|
+
const retryStart = turn && turn.attempt > 1 ? this.ledger.readStream(`turn:${turnId}`).findLast((candidate) => candidate.type === "turn.retry_started" && candidate.data.attempt === turn.attempt) : void 0;
|
|
749
|
+
if (!item || !text || !event || retryStart && event.seq <= retryStart.seq)
|
|
750
|
+
throw new Error("completed Turn finalMessageId must reference a readable Assistant Item from the current attempt");
|
|
751
|
+
return item;
|
|
752
|
+
}
|
|
743
753
|
#failTurn(turnId, reason) {
|
|
744
754
|
const current = this.ledger.turn(turnId);
|
|
745
755
|
if (!current || current.status !== "in_progress")
|
|
@@ -826,8 +836,12 @@ ${history}` } : {} };
|
|
|
826
836
|
if (type === "message.assistant.completed") {
|
|
827
837
|
const value = data;
|
|
828
838
|
const text = messageTextContent(value.message?.content);
|
|
829
|
-
if (text)
|
|
830
|
-
|
|
839
|
+
if (text) {
|
|
840
|
+
const id = typeof value.message?.id === "string" ? value.message.id.trim() : "";
|
|
841
|
+
if (!id)
|
|
842
|
+
throw new Error("Runner assistant completion is missing a message Item id");
|
|
843
|
+
this.#appendTurnItem(turnId, "assistant_message", { text }, actor, id);
|
|
844
|
+
}
|
|
831
845
|
} else if (type === "plan.updated")
|
|
832
846
|
this.#appendTurnItem(turnId, "plan", data, actor);
|
|
833
847
|
else if (type === "message.assistant.delta") {
|
|
@@ -1195,7 +1209,7 @@ ${history}` } : {} };
|
|
|
1195
1209
|
return agent === "ceo" && role === "ceo";
|
|
1196
1210
|
return role === "verifier" || role === "audit";
|
|
1197
1211
|
}
|
|
1198
|
-
#commitTurnGoalWork(turnId, agent, turn,
|
|
1212
|
+
#commitTurnGoalWork(turnId, agent, turn, responseItem, raw, sourceWakeId, mailIds, revisionAtStart) {
|
|
1199
1213
|
const binding = turn.goalCommitment;
|
|
1200
1214
|
const goal = this.#goal(binding.goalId);
|
|
1201
1215
|
if (goal.revision !== binding.goalRevision)
|
|
@@ -1206,11 +1220,9 @@ ${history}` } : {} };
|
|
|
1206
1220
|
assertTurnOutput(raw);
|
|
1207
1221
|
const validation = this.#handoffValidations.get(raw.validationToken);
|
|
1208
1222
|
const execution = this.ledger.turn(turnId);
|
|
1209
|
-
|
|
1223
|
+
const response = normalizeAssistantText(String(responseItem.data.text ?? ""));
|
|
1224
|
+
if (!validation || !execution || validation.turnId !== turnId || validation.attemptId !== raw.validationAttemptId || this.#handoffAttemptSeq.get(turnId) !== raw.validationAttemptId || validation.agent !== agent || validation.attempt !== execution.attempt || validation.leaseToken !== execution.leaseToken || validation.goalId !== goal.id || validation.goalRevision !== goal.revision || validation.messageItemId !== responseItem.id || validation.message !== response || validation.handoff.outcome !== raw.handoff.outcome || !isSameNumbers(validation.handoff.evidence, raw.handoff.evidence))
|
|
1210
1225
|
throw new Error("Handoff validation token is stale or does not match this Turn result");
|
|
1211
|
-
const responseItem = [...this.ledger.turnItems(turnId)].reverse().find((item2) => item2.type === "assistant_message" && item2.status === "completed" && item2.data.text === validation.message);
|
|
1212
|
-
if (!responseItem)
|
|
1213
|
-
throw new Error("accepted Handoff response was not recorded in the Turn");
|
|
1214
1226
|
const output = this.#committedTurnOutput(raw, binding);
|
|
1215
1227
|
assertHandoff(output.handoff);
|
|
1216
1228
|
const now = this.#now();
|
|
@@ -1341,7 +1353,8 @@ ${workText}`, activeGoal: turn.activeGoal, goalCommitment: turn.goalCommitment,
|
|
|
1341
1353
|
const input = asRecord(params);
|
|
1342
1354
|
if (method === "goal.handoff.validate") {
|
|
1343
1355
|
const candidate = String(input.candidateMessage ?? "");
|
|
1344
|
-
|
|
1356
|
+
const candidateMessageId = String(input.candidateMessageId ?? "");
|
|
1357
|
+
this.ledger.appendEvent({ streamId: `turn:${turnId}`, ts: this.#now(), actor: agent, type: "rpc.goal.handoff.validate", data: { attemptId: handoffAttemptId, handoff: input.handoff ?? null, candidateMessageId, candidateMessagePresent: Boolean(candidate.trim()), candidateMessageChars: candidate.length }, ignorable: true });
|
|
1345
1358
|
return this.#validateHandoffDraft(turnId, handoffAttemptId, agent, execution, context, input);
|
|
1346
1359
|
}
|
|
1347
1360
|
const profile = this.#profiles.get(agent) ?? { agent, role: "child" };
|
|
@@ -1480,17 +1493,19 @@ ${workText}`, activeGoal: turn.activeGoal, goalCommitment: turn.goalCommitment,
|
|
|
1480
1493
|
#validateHandoffDraft(turnId, attemptId, agent, execution, context, input) {
|
|
1481
1494
|
if (!context.goalCommitment || execution.goalId === null)
|
|
1482
1495
|
return { accepted: false, fatal: true, attemptId, issues: [{ code: "turn_not_committed", message: "This Turn no longer has a Goal commitment." }] };
|
|
1483
|
-
const request = { handoff: input.handoff ?? null, candidateMessage: String(input.candidateMessage ?? "") };
|
|
1496
|
+
const request = { handoff: input.handoff ?? null, candidateMessageId: String(input.candidateMessageId ?? "").trim(), candidateMessage: String(input.candidateMessage ?? "") };
|
|
1484
1497
|
const issues = [];
|
|
1485
1498
|
try {
|
|
1486
1499
|
assertAgentHandoff(request.handoff);
|
|
1487
1500
|
} catch {
|
|
1488
1501
|
issues.push({ code: "handoff_invalid", message: "Handoff requires a valid outcome and at least one evidence event." });
|
|
1489
1502
|
}
|
|
1490
|
-
const
|
|
1491
|
-
const
|
|
1492
|
-
if (!message)
|
|
1493
|
-
issues.push({ code: "message_missing", message: "This committed Turn needs a readable assistant message before it can finish." });
|
|
1503
|
+
const message = normalizeAssistantText(request.candidateMessage);
|
|
1504
|
+
const existingMessage = request.candidateMessageId ? this.ledger.turnItems(turnId).find((item) => item.id === request.candidateMessageId) : void 0;
|
|
1505
|
+
if (!request.candidateMessageId || !message)
|
|
1506
|
+
issues.push({ code: "message_missing", message: "This committed Turn needs a readable assistant message Item before it can finish." });
|
|
1507
|
+
else if (existingMessage && (existingMessage.type !== "assistant_message" || existingMessage.status !== "completed" || normalizeAssistantText(String(existingMessage.data.text ?? "")) !== message))
|
|
1508
|
+
issues.push({ code: "message_mismatch", message: "Handoff message identity does not match its assistant Item." });
|
|
1494
1509
|
const goal = this.ledger.goal(context.goalCommitment.goalId);
|
|
1495
1510
|
if (!goal || goal.phase !== "active" || goal.owner !== agent || goal.revision !== context.goalCommitment.goalRevision)
|
|
1496
1511
|
return { accepted: false, fatal: true, attemptId, issues: [{ code: "goal_fence_changed", message: "Goal ownership, phase, or revision changed; this Turn can no longer finish against the old commitment." }] };
|
|
@@ -1504,8 +1519,8 @@ ${workText}`, activeGoal: turn.activeGoal, goalCommitment: turn.goalCommitment,
|
|
|
1504
1519
|
if (issues.length)
|
|
1505
1520
|
return { accepted: false, fatal: false, attemptId, issues };
|
|
1506
1521
|
const token = randomUUID();
|
|
1507
|
-
this.#handoffValidations.set(token, { turnId, attemptId, agent, attempt: execution.attempt, leaseToken: execution.leaseToken, goalId: goal.id, goalRevision: goal.revision, message, handoff: { outcome: request.handoff.outcome, evidence: [...request.handoff.evidence] } });
|
|
1508
|
-
return { accepted: true, fatal: false, attemptId, token, goalId: goal.id, goalRevision: goal.revision };
|
|
1522
|
+
this.#handoffValidations.set(token, { turnId, attemptId, agent, attempt: execution.attempt, leaseToken: execution.leaseToken, goalId: goal.id, goalRevision: goal.revision, messageItemId: request.candidateMessageId, message, handoff: { outcome: request.handoff.outcome, evidence: [...request.handoff.evidence] } });
|
|
1523
|
+
return { accepted: true, fatal: false, attemptId, token, goalId: goal.id, goalRevision: goal.revision, messageItemId: request.candidateMessageId };
|
|
1509
1524
|
}
|
|
1510
1525
|
#beginHandoffAttempt(turnId) {
|
|
1511
1526
|
this.#invalidateHandoffTokens(turnId);
|
package/dist/cli.js
CHANGED
|
@@ -25,14 +25,14 @@ import {
|
|
|
25
25
|
streamEvents,
|
|
26
26
|
updateWorkspaceRunnerProfile,
|
|
27
27
|
writeDefaultConfig
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-5PZFP5CO.js";
|
|
29
29
|
import {
|
|
30
30
|
runnerManifests,
|
|
31
31
|
runnerPlugin
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-
|
|
35
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-R6GKCNTB.js";
|
|
33
|
+
import "./chunk-CDELLMIJ.js";
|
|
34
|
+
import "./chunk-SQJZ5OFH.js";
|
|
35
|
+
import "./chunk-EK6BTCFG.js";
|
|
36
36
|
import "./chunk-6M3OOCKO.js";
|
|
37
37
|
import "./chunk-K26BVR6O.js";
|
|
38
38
|
import "./chunk-IIJQ3DW4.js";
|
|
@@ -12670,6 +12670,9 @@ var ConversationView = class {
|
|
|
12670
12670
|
this.entries.push({ kind: "component", component });
|
|
12671
12671
|
}
|
|
12672
12672
|
addText(content) {
|
|
12673
|
+
const previous = this.entries.at(-1);
|
|
12674
|
+
if (previous?.kind === "text" && previous.content === content)
|
|
12675
|
+
return;
|
|
12673
12676
|
this.entries.push({ kind: "text", content });
|
|
12674
12677
|
this.trim();
|
|
12675
12678
|
}
|
|
@@ -12677,15 +12680,17 @@ var ConversationView = class {
|
|
|
12677
12680
|
this.entries.push({ kind: "user", content });
|
|
12678
12681
|
this.trim();
|
|
12679
12682
|
}
|
|
12680
|
-
addMarkdown(content) {
|
|
12683
|
+
addMarkdown(content, messageId) {
|
|
12681
12684
|
this.liveMarkdown = "";
|
|
12682
12685
|
content = content.trim();
|
|
12683
12686
|
if (!content)
|
|
12684
12687
|
return;
|
|
12688
|
+
if (messageId && this.entries.some((entry) => entry.kind === "markdown" && entry.messageId === messageId))
|
|
12689
|
+
return;
|
|
12685
12690
|
const previous = this.entries.at(-1);
|
|
12686
12691
|
if (previous?.kind === "markdown" && previous.content === content)
|
|
12687
12692
|
return;
|
|
12688
|
-
this.entries.push({ kind: "markdown", content });
|
|
12693
|
+
this.entries.push({ kind: "markdown", content, ...messageId ? { messageId } : {} });
|
|
12689
12694
|
this.trim();
|
|
12690
12695
|
}
|
|
12691
12696
|
appendLiveMarkdown(content) {
|
|
@@ -12990,14 +12995,14 @@ async function runGoahTui(configPath, stateDir, initialMessage) {
|
|
|
12990
12995
|
transcriptView.appendLiveMarkdown(text);
|
|
12991
12996
|
tui.requestRender();
|
|
12992
12997
|
};
|
|
12993
|
-
const commitLive = (text) => {
|
|
12998
|
+
const commitLive = (text, messageId) => {
|
|
12994
12999
|
transcriptView.clearLiveMarkdown();
|
|
12995
13000
|
if (text)
|
|
12996
|
-
transcriptView.addMarkdown(text);
|
|
13001
|
+
transcriptView.addMarkdown(text, messageId);
|
|
12997
13002
|
tui.requestRender();
|
|
12998
13003
|
};
|
|
12999
|
-
const pushResponse = (text) => {
|
|
13000
|
-
transcriptView.addMarkdown(text);
|
|
13004
|
+
const pushResponse = (text, messageId) => {
|
|
13005
|
+
transcriptView.addMarkdown(text, messageId);
|
|
13001
13006
|
tui.requestRender();
|
|
13002
13007
|
};
|
|
13003
13008
|
const updateTool = (activity) => {
|
|
@@ -13457,15 +13462,16 @@ function renderFrame(frame, push, appendLive = push, commitLive = push, pushResp
|
|
|
13457
13462
|
return;
|
|
13458
13463
|
}
|
|
13459
13464
|
const text = messageText(message.content);
|
|
13465
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
13460
13466
|
if (data.commitState === "provisional") {
|
|
13461
13467
|
clearLive();
|
|
13462
13468
|
return;
|
|
13463
13469
|
}
|
|
13464
13470
|
if (text)
|
|
13465
|
-
commitLive(text);
|
|
13471
|
+
commitLive(text, messageId);
|
|
13466
13472
|
} else if (record.type === "response.committed") {
|
|
13467
13473
|
if (typeof data.text === "string" && data.text.trim())
|
|
13468
|
-
commitLive(data.text.trim());
|
|
13474
|
+
commitLive(data.text.trim(), typeof data.messageItemId === "string" ? data.messageItemId : void 0);
|
|
13469
13475
|
} else if (record.type === "handoff.recorded") {
|
|
13470
13476
|
if (typeof data.goalId === "string")
|
|
13471
13477
|
push(`${data.outcome === "blocked" ? tuiTheme.error("goal blocked") : tuiTheme.success("goal saved")} ${tuiTheme.muted(`${String(data.outcome).replaceAll("_", " ")} \xB7 record r${String(data.recordRevision)}`)}`);
|
|
@@ -13955,7 +13961,7 @@ async function main() {
|
|
|
13955
13961
|
console.log(JSON.stringify({ goal }, null, 2));
|
|
13956
13962
|
} else if (command === "dashboard") {
|
|
13957
13963
|
const path4 = option("--output") ?? join7(config.stateDir, "status.html");
|
|
13958
|
-
writeFileSync(path4, (await import("./dist-
|
|
13964
|
+
writeFileSync(path4, (await import("./dist-QW77PKUJ.js")).renderDashboard(ledger));
|
|
13959
13965
|
console.log(path4);
|
|
13960
13966
|
} else
|
|
13961
13967
|
throw new Error(`unknown command: ${command}`);
|
|
@@ -13965,7 +13971,7 @@ async function main() {
|
|
|
13965
13971
|
}
|
|
13966
13972
|
}
|
|
13967
13973
|
async function run(supervisor, signal) {
|
|
13968
|
-
const { runSupervisorDaemon } = await import("./dist-
|
|
13974
|
+
const { runSupervisorDaemon } = await import("./dist-QW77PKUJ.js");
|
|
13969
13975
|
await runSupervisorDaemon(supervisor, { signal });
|
|
13970
13976
|
}
|
|
13971
13977
|
async function waitForConsole(stateDir) {
|
|
@@ -14200,7 +14206,7 @@ async function runRunnerCommand(command, commandArgs, configPath) {
|
|
|
14200
14206
|
async function runRunnerEarly(configPath) {
|
|
14201
14207
|
const action = args[1] ?? "list";
|
|
14202
14208
|
if (action === "list") {
|
|
14203
|
-
for (const manifest of (await import("./runner-registry-
|
|
14209
|
+
for (const manifest of (await import("./runner-registry-3WSOMXRW.js")).runnerManifests())
|
|
14204
14210
|
console.log(`${manifest.id.padEnd(16)} ${manifest.description}`);
|
|
14205
14211
|
return;
|
|
14206
14212
|
}
|
package/dist/execution.d.ts
CHANGED
|
@@ -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 {
|
|
@@ -418,9 +420,6 @@ interface TurnContext {
|
|
|
418
420
|
activeGoal: GoalSnapshot | null;
|
|
419
421
|
goalCommitment: GoalCommitment | null;
|
|
420
422
|
}
|
|
421
|
-
interface AssistantResponse {
|
|
422
|
-
content: string;
|
|
423
|
-
}
|
|
424
423
|
/** Canonical user-visible assistant prose. Raw provider events remain unchanged. */
|
|
425
424
|
declare function normalizeAssistantText(text: string): string;
|
|
426
425
|
interface RunRequest {
|
|
@@ -436,7 +435,7 @@ interface RunRequest {
|
|
|
436
435
|
}
|
|
437
436
|
type RunnerCandidateResult = {
|
|
438
437
|
outcome: "completed";
|
|
439
|
-
|
|
438
|
+
finalMessageId: string;
|
|
440
439
|
handoff?: TurnOutput;
|
|
441
440
|
} | {
|
|
442
441
|
outcome: "abnormal";
|
|
@@ -514,7 +513,8 @@ interface Ledger extends EventStore {
|
|
|
514
513
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
515
514
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
516
515
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
517
|
-
finishTurn(id: string, status: "
|
|
516
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
517
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
518
518
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
519
519
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
520
520
|
putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
|
@@ -554,4 +554,4 @@ declare function assertTurnOutput(value: TurnOutput): void;
|
|
|
554
554
|
declare function assertGoalSnapshot(value: GoalSnapshot): void;
|
|
555
555
|
|
|
556
556
|
export { assertAgentHandoff, assertGoalSnapshot, assertGoalTransition, assertHandoff, assertTurnOutput, assertWakeTransition, goalAutomaticTarget, goalCommitment, goalRoute, humanInboxRoute, humanRequestRoute, noGoalCommitment, normalizeAssistantText, specialistAutomaticTarget, specialistInboxRoute };
|
|
557
|
-
export type { AgentCapability, AgentHandoff, AgentProfile, AgentRole,
|
|
557
|
+
export type { AgentCapability, AgentHandoff, AgentProfile, AgentRole, 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 };
|
|
@@ -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-
|
|
5
|
+
} from "./chunk-EK6BTCFG.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
|
-
|
|
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
|
|
68
|
-
|
|
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",
|
|
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
|
@@ -337,9 +337,6 @@ interface TurnContext {
|
|
|
337
337
|
activeGoal: GoalSnapshot | null;
|
|
338
338
|
goalCommitment: GoalCommitment | null;
|
|
339
339
|
}
|
|
340
|
-
interface AssistantResponse {
|
|
341
|
-
content: string;
|
|
342
|
-
}
|
|
343
340
|
interface RunRequest {
|
|
344
341
|
agent: string;
|
|
345
342
|
execution: TurnSnapshot;
|
|
@@ -353,7 +350,7 @@ interface RunRequest {
|
|
|
353
350
|
}
|
|
354
351
|
type RunnerCandidateResult = {
|
|
355
352
|
outcome: "completed";
|
|
356
|
-
|
|
353
|
+
finalMessageId: string;
|
|
357
354
|
handoff?: TurnOutput;
|
|
358
355
|
} | {
|
|
359
356
|
outcome: "abnormal";
|
|
@@ -431,7 +428,8 @@ interface Ledger extends EventStore {
|
|
|
431
428
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
432
429
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
433
430
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
434
|
-
finishTurn(id: string, status: "
|
|
431
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
432
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
435
433
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
436
434
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
437
435
|
putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
|
@@ -518,7 +516,8 @@ declare class SqliteLedger implements Ledger {
|
|
|
518
516
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
519
517
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
520
518
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
521
|
-
finishTurn(id: string, status: "
|
|
519
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
520
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
522
521
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
523
522
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
524
523
|
putMails(mails: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
package/dist/index.js
CHANGED
|
@@ -31,11 +31,11 @@ import {
|
|
|
31
31
|
updateWorkspaceRunnerProfile,
|
|
32
32
|
writeDefaultConfig,
|
|
33
33
|
writeDefaultRunnerProfile
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import "./chunk-
|
|
34
|
+
} from "./chunk-5PZFP5CO.js";
|
|
35
|
+
import "./chunk-R6GKCNTB.js";
|
|
36
|
+
import "./chunk-CDELLMIJ.js";
|
|
37
|
+
import "./chunk-SQJZ5OFH.js";
|
|
38
|
+
import "./chunk-EK6BTCFG.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-
|
|
8
|
+
} from "./chunk-EK6BTCFG.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;
|
|
@@ -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,7 +118,7 @@ Follow the current Goal and explicit tools.
|
|
|
113
118
|
let output = null;
|
|
114
119
|
let acceptedHandoff = null;
|
|
115
120
|
let revokedReason = "";
|
|
116
|
-
let
|
|
121
|
+
let lastReadableMessage = null;
|
|
117
122
|
let responseFailure = "";
|
|
118
123
|
let compactions = 0;
|
|
119
124
|
let messageCounter = 0;
|
|
@@ -125,7 +130,7 @@ Follow the current Goal and explicit tools.
|
|
|
125
130
|
const existing = messageIds.get(message);
|
|
126
131
|
if (existing)
|
|
127
132
|
return existing;
|
|
128
|
-
const id =
|
|
133
|
+
const id = `${request.execution.id}:attempt:${request.execution.attempt}:message:${++messageCounter}`;
|
|
129
134
|
messageIds.set(message, id);
|
|
130
135
|
return id;
|
|
131
136
|
};
|
|
@@ -145,8 +150,11 @@ Follow the current Goal and explicit tools.
|
|
|
145
150
|
const sourceSeqs = Array.isArray(contextRecord.sourceSeqs) ? contextRecord.sourceSeqs.filter((value) => Number.isInteger(value)) : [];
|
|
146
151
|
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
152
|
const systemPrompt = goalState.bound ? `${basePrompt}
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
|
|
154
|
+
${GOAL_TURN_POLICY}` : request.turn?.trigger.kind === "user_message" ? `${basePrompt}
|
|
155
|
+
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:
|
|
156
|
+
|
|
157
|
+
${GOAL_TURN_POLICY}` : `${basePrompt}
|
|
150
158
|
This Turn has no Goal commitment. Finish with an ordinary response and do not call handoff. Treat the supplied context as authoritative.`;
|
|
151
159
|
const agent = new Agent({
|
|
152
160
|
initialState: {
|
|
@@ -196,10 +204,16 @@ This Turn has no Goal commitment. Finish with an ordinary response and do not ca
|
|
|
196
204
|
acceptedHandoff = null;
|
|
197
205
|
output = null;
|
|
198
206
|
const handoff = args;
|
|
207
|
+
const currentText = assistantResponseText(assistantMessage);
|
|
208
|
+
const candidate = currentText ? { id: idFor(assistantMessage), text: currentText } : lastReadableMessage;
|
|
199
209
|
try {
|
|
200
|
-
const result = await rpc("goal.handoff.validate", { handoff, candidateMessage:
|
|
210
|
+
const result = await rpc("goal.handoff.validate", { handoff, candidateMessageId: candidate?.id ?? "", candidateMessage: candidate?.text ?? "" });
|
|
201
211
|
if (result.accepted) {
|
|
202
|
-
|
|
212
|
+
if (!candidate || result.messageItemId !== candidate.id) {
|
|
213
|
+
revokedReason = "Handoff validation returned a different assistant message identity";
|
|
214
|
+
return { block: true, reason: revokedReason, terminate: true };
|
|
215
|
+
}
|
|
216
|
+
acceptedHandoff = { attemptId: result.attemptId, token: result.token, messageItemId: result.messageItemId, handoff };
|
|
203
217
|
return;
|
|
204
218
|
}
|
|
205
219
|
const reason = result.issues.map((issue) => `- ${issue.message}`).join("\n");
|
|
@@ -234,11 +248,14 @@ ${reason}`, terminate: result.fatal };
|
|
|
234
248
|
} else if (event.type === "message_update") {
|
|
235
249
|
emit({ type: "message.assistant.delta", data: { messageId: idFor(event.message), delta: JSON.parse(JSON.stringify(event.assistantMessageEvent)) } });
|
|
236
250
|
} else if (event.type === "message_end" && event.message.role === "assistant") {
|
|
237
|
-
|
|
251
|
+
const messageId = idFor(event.message);
|
|
252
|
+
const text = assistantResponseText(event.message);
|
|
253
|
+
if (text)
|
|
254
|
+
lastReadableMessage = { id: messageId, text };
|
|
238
255
|
if ((event.message.stopReason === "error" || event.message.stopReason === "aborted") && event.message.errorMessage)
|
|
239
256
|
responseFailure = event.message.errorMessage;
|
|
240
257
|
const handoffIntent = hasAgentToolCall(event.message, "handoff");
|
|
241
|
-
emit({ type: "message.assistant.completed", data: { message: transcriptMessage(event.message,
|
|
258
|
+
emit({ type: "message.assistant.completed", data: { message: transcriptMessage(event.message, messageId), commitState: handoffIntent ? "provisional" : "committed" } });
|
|
242
259
|
} else if (event.type === "tool_execution_start")
|
|
243
260
|
emit({ type: "tool.called", data: { callId: event.toolCallId, name: event.toolName, arguments: JSON.parse(JSON.stringify(event.args)) } });
|
|
244
261
|
else if (event.type === "tool_execution_end")
|
|
@@ -262,13 +279,17 @@ Runner root: ${root}. Manage local files directly when the goal requires them.`)
|
|
|
262
279
|
}
|
|
263
280
|
if (revokedReason)
|
|
264
281
|
return { outcome: "abnormal", reason: revokedReason };
|
|
282
|
+
const finalMessage = lastReadableMessage;
|
|
265
283
|
if (!goalState.bound)
|
|
266
|
-
return
|
|
284
|
+
return finalMessage ? { outcome: "completed", finalMessageId: finalMessage.id } : { outcome: "abnormal", reason: responseFailure || "Pi worker exited without a response" };
|
|
267
285
|
if (responseFailure)
|
|
268
286
|
return { outcome: "abnormal", reason: responseFailure };
|
|
269
287
|
if (!output)
|
|
270
288
|
return { outcome: "abnormal", reason: "Pi worker exited without an accepted Handoff" };
|
|
271
|
-
|
|
289
|
+
const finalHandoff = acceptedHandoff;
|
|
290
|
+
if (!finalHandoff)
|
|
291
|
+
return { outcome: "abnormal", reason: "Pi worker exited without an accepted Handoff message" };
|
|
292
|
+
return { outcome: "completed", finalMessageId: finalHandoff.messageItemId, handoff: output };
|
|
272
293
|
});
|
|
273
294
|
}
|
|
274
295
|
function snapshotModelConfig(options) {
|
|
@@ -294,7 +315,7 @@ function createTools(root, handoff, rpc, capabilities, goalState, protectedPaths
|
|
|
294
315
|
const handoffTool = {
|
|
295
316
|
name: "handoff",
|
|
296
317
|
label: "Handoff",
|
|
297
|
-
description: "
|
|
318
|
+
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
319
|
parameters: typebox_exports.Object({
|
|
299
320
|
outcome: typebox_exports.Union([typebox_exports.Literal("progress"), typebox_exports.Literal("waiting"), typebox_exports.Literal("blocked"), typebox_exports.Literal("completion_proposed")]),
|
|
300
321
|
evidence: typebox_exports.Array(typebox_exports.Number())
|
|
@@ -488,7 +509,7 @@ function createRpcTools(rpc, allowed, onResult) {
|
|
|
488
509
|
["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
510
|
["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
511
|
["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
|
|
512
|
+
["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
513
|
["team.list", tool("team_list", "Read the ledger-derived team roster and liveness state.", "team.list", typebox_exports.Object({}))],
|
|
493
514
|
["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
515
|
["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()) }))],
|
package/dist/runner-pi.d.ts
CHANGED
|
@@ -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;
|
|
@@ -181,9 +182,6 @@ interface TurnContext {
|
|
|
181
182
|
activeGoal: GoalSnapshot | null;
|
|
182
183
|
goalCommitment: GoalCommitment | null;
|
|
183
184
|
}
|
|
184
|
-
interface AssistantResponse {
|
|
185
|
-
content: string;
|
|
186
|
-
}
|
|
187
185
|
interface RunRequest {
|
|
188
186
|
agent: string;
|
|
189
187
|
execution: TurnSnapshot;
|
|
@@ -197,7 +195,7 @@ interface RunRequest {
|
|
|
197
195
|
}
|
|
198
196
|
type RunnerCandidateResult = {
|
|
199
197
|
outcome: "completed";
|
|
200
|
-
|
|
198
|
+
finalMessageId: string;
|
|
201
199
|
handoff?: TurnOutput;
|
|
202
200
|
} | {
|
|
203
201
|
outcome: "abnormal";
|
|
@@ -267,14 +265,17 @@ interface EnvSpecSource {
|
|
|
267
265
|
*/
|
|
268
266
|
declare function resolveEnvSpec(spec: Record<string, string> | undefined, source: EnvSpecSource | undefined): Record<string, string>;
|
|
269
267
|
|
|
268
|
+
interface PiAssistantResponse {
|
|
269
|
+
content: string;
|
|
270
|
+
}
|
|
270
271
|
interface PiStep {
|
|
271
272
|
trace?: Array<{
|
|
272
273
|
type: string;
|
|
273
274
|
data: JsonValue;
|
|
274
275
|
}>;
|
|
275
|
-
response?:
|
|
276
|
+
response?: PiAssistantResponse;
|
|
276
277
|
handoff?: {
|
|
277
|
-
response:
|
|
278
|
+
response: PiAssistantResponse;
|
|
278
279
|
handoff: AgentHandoff;
|
|
279
280
|
};
|
|
280
281
|
stopped?: boolean;
|
|
@@ -339,4 +340,4 @@ type WorkerRun = (request: WorkerRequest, emit: (event: {
|
|
|
339
340
|
declare function runProcessWorker(run: WorkerRun): Promise<void>;
|
|
340
341
|
|
|
341
342
|
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 };
|
|
343
|
+
export type { ModelSummary, PiAssistantResponse, PiDriver, PiRunnerConfig, PiRunnerSession, PiStep, ProcessRunnerOptions, ProviderSummary, WorkerControls, WorkerRpc, WorkerRun };
|
package/dist/runner-pi.js
CHANGED
|
@@ -3,8 +3,8 @@ const require = __goahCreateRequire(import.meta.url);
|
|
|
3
3
|
import {
|
|
4
4
|
runnerManifests,
|
|
5
5
|
runnerPlugin
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-R6GKCNTB.js";
|
|
7
|
+
import "./chunk-EK6BTCFG.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: "
|
|
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: "
|
|
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
package/dist/supervisor.d.ts
CHANGED
|
@@ -304,9 +304,6 @@ interface TurnContext {
|
|
|
304
304
|
activeGoal: GoalSnapshot | null;
|
|
305
305
|
goalCommitment: GoalCommitment | null;
|
|
306
306
|
}
|
|
307
|
-
interface AssistantResponse {
|
|
308
|
-
content: string;
|
|
309
|
-
}
|
|
310
307
|
interface RunRequest {
|
|
311
308
|
agent: string;
|
|
312
309
|
execution: TurnSnapshot;
|
|
@@ -320,7 +317,7 @@ interface RunRequest {
|
|
|
320
317
|
}
|
|
321
318
|
type RunnerCandidateResult = {
|
|
322
319
|
outcome: "completed";
|
|
323
|
-
|
|
320
|
+
finalMessageId: string;
|
|
324
321
|
handoff?: TurnOutput;
|
|
325
322
|
} | {
|
|
326
323
|
outcome: "abnormal";
|
|
@@ -398,7 +395,8 @@ interface Ledger extends EventStore {
|
|
|
398
395
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
399
396
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
400
397
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
401
|
-
finishTurn(id: string, status: "
|
|
398
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
399
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
402
400
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
403
401
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
404
402
|
putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
package/dist/supervisor.js
CHANGED
package/dist/testkit.d.ts
CHANGED
|
@@ -269,6 +269,7 @@ type HandoffValidationResult = {
|
|
|
269
269
|
token: string;
|
|
270
270
|
goalId: string;
|
|
271
271
|
goalRevision: number;
|
|
272
|
+
messageItemId: string;
|
|
272
273
|
} | {
|
|
273
274
|
accepted: false;
|
|
274
275
|
fatal: boolean;
|
|
@@ -296,9 +297,6 @@ interface TurnContext {
|
|
|
296
297
|
activeGoal: GoalSnapshot | null;
|
|
297
298
|
goalCommitment: GoalCommitment | null;
|
|
298
299
|
}
|
|
299
|
-
interface AssistantResponse {
|
|
300
|
-
content: string;
|
|
301
|
-
}
|
|
302
300
|
interface RunRequest {
|
|
303
301
|
agent: string;
|
|
304
302
|
execution: TurnSnapshot;
|
|
@@ -370,7 +368,8 @@ interface Ledger extends EventStore {
|
|
|
370
368
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
371
369
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
372
370
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
373
|
-
finishTurn(id: string, status: "
|
|
371
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
372
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
374
373
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
375
374
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
376
375
|
putMails(mail: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
|
@@ -457,7 +456,8 @@ declare class SqliteLedger implements Ledger {
|
|
|
457
456
|
renewTurnLease(id: string, leaseToken: string, leaseUntil: string, now: string): TurnSnapshot;
|
|
458
457
|
appendTurnEvent(input: EventInput, leaseToken: string): EventRecord;
|
|
459
458
|
repairTurnAttempt(id: string, reason: string, now: string, actor: string): TurnItemSnapshot[];
|
|
460
|
-
finishTurn(id: string, status: "
|
|
459
|
+
finishTurn(id: string, status: "failed" | "interrupted", error: JsonValue, now: string, actor: string): TurnSnapshot;
|
|
460
|
+
commitTurnResponse(id: string, responseItemId: string, now: string, actor: string, mailIds?: string[]): TurnSnapshot;
|
|
461
461
|
releaseTurnProcess(id: string, actor: string): TurnSnapshot;
|
|
462
462
|
putMail(mail: MailSnapshot, actor: string, wakeId?: string): EventRecord;
|
|
463
463
|
putMails(mails: MailSnapshot[], actor: string, wakeId?: string): EventRecord[];
|
|
@@ -491,14 +491,17 @@ declare class SqliteLedger implements Ledger {
|
|
|
491
491
|
rebuildProjections(): void;
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
+
interface PiAssistantResponse {
|
|
495
|
+
content: string;
|
|
496
|
+
}
|
|
494
497
|
interface PiStep {
|
|
495
498
|
trace?: Array<{
|
|
496
499
|
type: string;
|
|
497
500
|
data: JsonValue;
|
|
498
501
|
}>;
|
|
499
|
-
response?:
|
|
502
|
+
response?: PiAssistantResponse;
|
|
500
503
|
handoff?: {
|
|
501
|
-
response:
|
|
504
|
+
response: PiAssistantResponse;
|
|
502
505
|
handoff: AgentHandoff;
|
|
503
506
|
};
|
|
504
507
|
stopped?: boolean;
|
package/dist/testkit.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __goahCreateRequire } from "node:module";
|
|
|
2
2
|
const require = __goahCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
SqliteLedger
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CDELLMIJ.js";
|
|
6
6
|
import "./chunk-4KA63VNZ.js";
|
|
7
7
|
import "./chunk-KAO7WIXG.js";
|
|
8
8
|
import "./chunk-R6I3VKME.js";
|
|
@@ -107,6 +107,10 @@ function assertLedgerConformance(create) {
|
|
|
107
107
|
throw new Error("ledger conformance: consumed Wake did not link its Turn");
|
|
108
108
|
if (ledger.wakeTriggers("z").some((trigger) => trigger.status !== "resolved"))
|
|
109
109
|
throw new Error("ledger conformance: consumed Wake retained pending triggers");
|
|
110
|
+
ledger.putTurnItem({ id: "turn:a:answer", turnId: turn.id, ordinal: 1, type: "assistant_message", status: "completed", data: { text: "verified" }, createdAt: clock.now().toISOString(), completedAt: clock.now().toISOString() }, "a");
|
|
111
|
+
ledger.commitTurnResponse(turn.id, "turn:a:answer", clock.now().toISOString(), "supervisor");
|
|
112
|
+
if (ledger.turn(turn.id)?.status !== "completed" || !ledger.readStream(`turn:${turn.id}`).some((item) => item.type === "response.committed" && item.data.messageItemId === "turn:a:answer"))
|
|
113
|
+
throw new Error("ledger conformance: canonical Assistant response was not committed");
|
|
110
114
|
if (first.event.ts !== clock.now().toISOString())
|
|
111
115
|
throw new Error("ledger conformance: injected clock was ignored");
|
|
112
116
|
const informational = ledger.appendEvent({ streamId: "conformance:events", ts: clock.now().toISOString(), actor: "a", type: "transcript.conformance_info", data: {}, ignorable: true });
|