@akagilnc/pi-workflow-roles 0.1.4528 → 0.1.4620
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/README.md +5 -4
- package/README.zh-CN.md +6 -5
- package/dist/acp-host/production-host.js +1834 -637
- package/dist/archivist-record-topology.js +27 -35
- package/dist/headless-host/description.js +7 -4
- package/dist/headless-host/production-host.js +1652 -519
- package/dist/migrate-book-topology.js +8 -11
- package/dist/navigator-attendance.js +146 -241
- package/dist/navigator-public-session.js +81 -15
- package/dist/package-contracts/navigator-output.js +30 -12
- package/dist/public-cli/invocation.js +10 -4
- package/dist/public-cli/judge-run.js +2 -2
- package/dist/public-cli/main.js +879 -410
- package/dist/public-cli/option-definitions.js +6 -6
- package/dist/public-cli/post-admission.js +25 -10
- package/dist/public-cli/reviewer-run.js +339 -0
- package/dist/public-cli/run-lifecycle.js +29 -7
- package/dist/public-cli/settlement.js +46 -32
- package/dist/public-cli/terminal.js +17 -27
- package/dist/public-cli/turn-request.js +1 -1
- package/dist/public-role-summons.js +370 -20
- package/dist/submission-ledger.js +56 -23
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +2 -0
- package/resources/navigator-route-playbook.md +9 -0
- package/scripts/build-package.mjs +6 -1
- package/src/acp-host/role-turn-host.ts +129 -13
- package/src/headless-host/description.ts +17 -7
- package/src/headless-host/role-turn-host.ts +42 -11
- package/src/host-contracts.ts +0 -1
- package/src/navigator-attendance.ts +290 -425
- package/src/navigator-public-session.ts +126 -25
- package/src/navigator-role.ts +2 -2
- package/src/package-contracts/navigator-output.ts +41 -25
- package/src/public-cli/coder-run.ts +2 -2
- package/src/public-cli/fixer-run.ts +2 -2
- package/src/public-cli/invocation.ts +19 -9
- package/src/public-cli/judge-run.ts +2 -2
- package/src/public-cli/merger-run.ts +2 -2
- package/src/public-cli/option-definitions.ts +6 -6
- package/src/public-cli/post-admission.ts +41 -10
- package/src/public-cli/reviewer-run.ts +238 -53
- package/src/public-cli/run-lifecycle.ts +37 -7
- package/src/public-cli/settlement.ts +42 -34
- package/src/public-cli/terminal.ts +39 -37
- package/src/public-cli/turn-request.ts +7 -1
- package/src/public-role-summons.ts +544 -22
- package/src/reviewer-role.ts +1 -2
- package/src/role-envelope.ts +89 -4
- package/src/role-runtime.ts +76 -16
- package/src/submission-ledger.ts +63 -21
- package/dist/public-cli/command-renderer.js +0 -5
- package/dist/public-command-renderer.js +0 -29
- package/src/public-cli/command-renderer.ts +0 -9
- package/src/public-command-renderer.ts +0 -55
package/src/reviewer-role.ts
CHANGED
|
@@ -13,7 +13,6 @@ export type { ReviewerIntent };
|
|
|
13
13
|
/** Frozen admitted inputs the behavior layer may consume — no flag surface. */
|
|
14
14
|
export type ReviewerAdmittedInputs = Readonly<{
|
|
15
15
|
baseRevision: string;
|
|
16
|
-
/** Caller-selected single lens; required, no default. */
|
|
17
16
|
lens: "completeness" | "correctness";
|
|
18
17
|
authorityRefs?: readonly string[];
|
|
19
18
|
/** Typed #176 ticketNumber from admitted invocation (Spec self-fetch primary). */
|
|
@@ -32,7 +31,7 @@ const reviewerAmendmentsSchema = Type.Object({
|
|
|
32
31
|
}, {
|
|
33
32
|
additionalProperties: true,
|
|
34
33
|
description:
|
|
35
|
-
"
|
|
34
|
+
"已运行 lens 的 amendments 承载 candidates、逐条处置与 verdict 的完整报告(非仅 verdict 行);显式单轴时另一轴可省略。hard-stop/usage error 为 refused 时,已产出报告仍照录对应 lens 字段。形状指引,非 schema 闸。",
|
|
36
35
|
});
|
|
37
36
|
// #836 r16 class 1: diagnostic is LLM/human-read narrative content — no code
|
|
38
37
|
// branches on its length (src/reviewer-role.ts consumer: reviewer content is
|
package/src/role-envelope.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createServer, type Server, type Socket } from "node:net";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -115,7 +115,11 @@ export async function prepareRoleEnvelope(options: {
|
|
|
115
115
|
await mkdir(request.runDirectory, { recursive: true });
|
|
116
116
|
|
|
117
117
|
// Durable principal file for isAvailable / resumable settlement (public-cli).
|
|
118
|
-
// #617 DK-4:
|
|
118
|
+
// #617 DK-4: never host conversation/tool writeback into Pi JSONL.
|
|
119
|
+
// #959: package-owned custom entries (invocation marker, submission-closure,
|
|
120
|
+
// navigator attendance, no-receipt) MUST flush so parent settlement can read
|
|
121
|
+
// them after the headless process ends — memory-only books left extractNavigatorFact
|
|
122
|
+
// with no durable terminal / attendance on codex/claude/ACP parents.
|
|
119
123
|
let sessionFile = options.sessionFile ?? join(request.runDirectory, "session", "session.jsonl");
|
|
120
124
|
await mkdir(dirname(sessionFile), { recursive: true });
|
|
121
125
|
if (request.continuation.kind !== "resume") {
|
|
@@ -135,6 +139,31 @@ export async function prepareRoleEnvelope(options: {
|
|
|
135
139
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
136
140
|
}
|
|
137
141
|
}
|
|
142
|
+
// Settlement reads the durable file after the turn; write must complete before
|
|
143
|
+
// closeRound returns. Sync-order via chained promise kept on the envelope.
|
|
144
|
+
// Chain itself never rejects (closeRound/dispose stay orderly). Required package
|
|
145
|
+
// entry flush failure arms the existing typed infrastructure-failure slot —
|
|
146
|
+
// never log-and-continue into accepted (#959 失败诚实).
|
|
147
|
+
let durableWriteChain: Promise<void> = Promise.resolve();
|
|
148
|
+
/** Filled by rememberInfrastructureFailure; declared later, closed over here. */
|
|
149
|
+
let armDurableWriteFailure: ((customType: string, diagnostic: string) => void) | undefined;
|
|
150
|
+
/** Append one package-owned session entry to the durable principal (fire-and-order). */
|
|
151
|
+
const persistPackageSessionEntry = (entry: Record<string, unknown>): void => {
|
|
152
|
+
const customType = typeof entry.customType === "string" ? entry.customType : "unknown";
|
|
153
|
+
const line = `${JSON.stringify({
|
|
154
|
+
...entry,
|
|
155
|
+
id: typeof entry.id === "string" ? entry.id : randomUUID(),
|
|
156
|
+
timestamp: typeof entry.timestamp === "string" ? entry.timestamp : new Date().toISOString(),
|
|
157
|
+
})}\n`;
|
|
158
|
+
durableWriteChain = durableWriteChain.then(async () => {
|
|
159
|
+
try {
|
|
160
|
+
await appendFile(sessionFile, line, "utf8");
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
163
|
+
armDurableWriteFailure?.(customType, diagnostic);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
138
167
|
const context: HostContext = {
|
|
139
168
|
cwd: request.cwd,
|
|
140
169
|
mode: "print",
|
|
@@ -155,6 +184,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
155
184
|
const entry = { type: "custom", customType, data };
|
|
156
185
|
sessionEntries.push(entry);
|
|
157
186
|
customEntries.push({ customType, data });
|
|
187
|
+
persistPackageSessionEntry(entry);
|
|
158
188
|
},
|
|
159
189
|
},
|
|
160
190
|
abort() {
|
|
@@ -170,6 +200,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
170
200
|
const entry = { type: "custom_message", customType, ...payload, message: payload };
|
|
171
201
|
sessionEntries.push(entry);
|
|
172
202
|
customEntries.push({ customType, data: message.details ?? message.content });
|
|
203
|
+
persistPackageSessionEntry(entry);
|
|
173
204
|
};
|
|
174
205
|
|
|
175
206
|
const emit = async (event: string, value: unknown): Promise<unknown[]> => {
|
|
@@ -304,6 +335,17 @@ export async function prepareRoleEnvelope(options: {
|
|
|
304
335
|
};
|
|
305
336
|
hostAbort.abort();
|
|
306
337
|
}
|
|
338
|
+
armDurableWriteFailure = (customType, diagnostic) => {
|
|
339
|
+
rememberInfrastructureFailure(
|
|
340
|
+
{
|
|
341
|
+
cause: "infrastructure",
|
|
342
|
+
kind: "role_infrastructure_failure",
|
|
343
|
+
code: "durable-session-write-failed",
|
|
344
|
+
customType,
|
|
345
|
+
},
|
|
346
|
+
[{ type: "text", text: `ak-role: durable session entry flush failed (${customType}): ${diagnostic}` }],
|
|
347
|
+
);
|
|
348
|
+
};
|
|
307
349
|
/**
|
|
308
350
|
* One non-correctable infra pathway for execute throws and pre-execution emits:
|
|
309
351
|
* build fact → fill closeRound slot → arm hostAbort. Projection may follow.
|
|
@@ -500,15 +542,50 @@ export async function prepareRoleEnvelope(options: {
|
|
|
500
542
|
let disposed = false;
|
|
501
543
|
// Per-turn run/court identity lives on HostContext (request-scoped). Engine
|
|
502
544
|
// axis is already a RoleHost flag — never process.env (#818 P1 / #879).
|
|
545
|
+
|
|
546
|
+
/** True once closeRound returned an infrastructure failure to the host. */
|
|
547
|
+
let durableFailureHandedToCloseRound = false;
|
|
548
|
+
/** Drain required package-owned writes; infrastructure outranks every close outcome. */
|
|
549
|
+
const settleDurableWrites = async (): Promise<
|
|
550
|
+
| { readonly accepted: false; readonly failure: RoleTurnKnownFailure }
|
|
551
|
+
| undefined
|
|
552
|
+
> => {
|
|
553
|
+
await durableWriteChain;
|
|
554
|
+
if (infrastructureRoundFailure !== undefined) {
|
|
555
|
+
durableFailureHandedToCloseRound = true;
|
|
556
|
+
return { accepted: false as const, failure: infrastructureRoundFailure };
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
};
|
|
560
|
+
|
|
503
561
|
const dispose = async (): Promise<void> => {
|
|
504
562
|
if (disposed) return;
|
|
505
563
|
disposed = true;
|
|
506
564
|
const cleanupFailures: unknown[] = [];
|
|
565
|
+
// session_shutdown may still append package durable entries (navigator
|
|
566
|
+
// attendance when agent_settled was skipped). Drain only after those
|
|
567
|
+
// handlers — single terminal judgment via settleDurableWrites (#959).
|
|
507
568
|
try {
|
|
508
569
|
await emit("session_shutdown", {});
|
|
509
570
|
} catch (error) {
|
|
510
571
|
cleanupFailures.push(error);
|
|
511
572
|
}
|
|
573
|
+
try {
|
|
574
|
+
// Always drain post-shutdown writes. Only throw when the host has not
|
|
575
|
+
// already received this infrastructure failure via closeRound.
|
|
576
|
+
const alreadyHanded = durableFailureHandedToCloseRound;
|
|
577
|
+
const durableFailure = await settleDurableWrites();
|
|
578
|
+
if (durableFailure !== undefined && !alreadyHanded) {
|
|
579
|
+
cleanupFailures.push(
|
|
580
|
+
Object.assign(
|
|
581
|
+
new Error(durableFailure.failure.diagnostic ?? "durable session entry flush failed"),
|
|
582
|
+
{ code: durableFailure.failure.identity?.code ?? "durable-session-write-failed" },
|
|
583
|
+
),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
} catch (error) {
|
|
587
|
+
cleanupFailures.push(error);
|
|
588
|
+
}
|
|
512
589
|
try {
|
|
513
590
|
const closeAll = (server as unknown as { closeAllConnections?: () => void }).closeAllConnections;
|
|
514
591
|
if (typeof closeAll === "function") closeAll.call(server);
|
|
@@ -532,8 +609,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
532
609
|
// duplicate so structured_output + tool-call does not arm non-sole.
|
|
533
610
|
if (calls.some((call) => call.toolName === terminatingToolName)) return;
|
|
534
611
|
await invokeAkTool(terminatingToolName, params ?? {});
|
|
535
|
-
}
|
|
536
|
-
|
|
612
|
+
};
|
|
537
613
|
const closeRound: PreparedRoleTurn["closeRound"] = async () => {
|
|
538
614
|
// Typed round boundary: hand the complete call list to the shared ledger once.
|
|
539
615
|
if (calls.length > 0) {
|
|
@@ -543,6 +619,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
543
619
|
}
|
|
544
620
|
// Infrastructure failure outranks accepted closure / correctable retry (#593).
|
|
545
621
|
if (infrastructureRoundFailure !== undefined) {
|
|
622
|
+
durableFailureHandedToCloseRound = true;
|
|
546
623
|
return { accepted: false as const, failure: infrastructureRoundFailure };
|
|
547
624
|
}
|
|
548
625
|
let closure: { customType: string; data: unknown } | undefined;
|
|
@@ -553,6 +630,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
553
630
|
// Pi flushes navigator attendance on agent_settled; session/prompt
|
|
554
631
|
// resolution is the ACP host equivalent round boundary.
|
|
555
632
|
await emit("agent_settled", {});
|
|
633
|
+
const durableFailure = await settleDurableWrites();
|
|
634
|
+
if (durableFailure !== undefined) return durableFailure;
|
|
556
635
|
return { accepted: true as const };
|
|
557
636
|
}
|
|
558
637
|
if (rejection !== undefined) {
|
|
@@ -562,11 +641,17 @@ export async function prepareRoleEnvelope(options: {
|
|
|
562
641
|
message: rejection.message,
|
|
563
642
|
};
|
|
564
643
|
rejection = undefined;
|
|
644
|
+
// Drain write chain before retry: a package-owned append may still fail after
|
|
645
|
+
// the correctable rejection was booked; infrastructure outranks retry.
|
|
646
|
+
const durableFailure = await settleDurableWrites();
|
|
647
|
+
if (durableFailure !== undefined) return durableFailure;
|
|
565
648
|
return { accepted: false as const, retry };
|
|
566
649
|
}
|
|
567
650
|
// #836: host ended without a recorded submission is not a failure.
|
|
568
651
|
// Settlement presents ledger contents; empty ledger → no_receipt.
|
|
569
652
|
await emit("agent_settled", {});
|
|
653
|
+
const durableFailure = await settleDurableWrites();
|
|
654
|
+
if (durableFailure !== undefined) return durableFailure;
|
|
570
655
|
return { accepted: true as const };
|
|
571
656
|
};
|
|
572
657
|
|
package/src/role-runtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { readFileSync, writeSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import {
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
|
|
13
14
|
import { Value } from "typebox/value";
|
|
14
15
|
import { sitianReport } from "./sitian-facade.ts";
|
|
15
|
-
import { createSubmissionLedgerHost } from "./submission-ledger.ts";
|
|
16
|
+
import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
|
|
16
17
|
import { createCollectorLedger } from "./collector-ledger.ts";
|
|
17
18
|
|
|
18
19
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
@@ -156,7 +157,7 @@ const REVIEWER_TRANSPORT_FLAGS = Object.freeze([
|
|
|
156
157
|
Object.freeze({
|
|
157
158
|
name: "ak-review-lens",
|
|
158
159
|
definition: Object.freeze({
|
|
159
|
-
description: "
|
|
160
|
+
description: "Single review lens: completeness or correctness",
|
|
160
161
|
type: "string" as const,
|
|
161
162
|
}),
|
|
162
163
|
}),
|
|
@@ -441,7 +442,7 @@ type ActivationRuntime = {
|
|
|
441
442
|
};
|
|
442
443
|
/** Envelope decodes Reviewer transport flags inside the activation stage. */
|
|
443
444
|
decodeReviewerAdmitted(): ReviewerAdmittedInputs;
|
|
444
|
-
/** Envelope stores live
|
|
445
|
+
/** Envelope stores live Reviewer activation for agent_start prompt assembly. */
|
|
445
446
|
bindReviewerParent(activation: ReviewerActivation): void;
|
|
446
447
|
collector: {
|
|
447
448
|
activate(context: HostContext, event: { reason: string }): Promise<void>;
|
|
@@ -1225,6 +1226,28 @@ export function createCountersignRoleRuntime(
|
|
|
1225
1226
|
);
|
|
1226
1227
|
}
|
|
1227
1228
|
|
|
1229
|
+
/**
|
|
1230
|
+
* #959: last assistant text parts as navigator prose exit.
|
|
1231
|
+
* Tool-call-only messages yield undefined — those already ride the tool path.
|
|
1232
|
+
*/
|
|
1233
|
+
function lastAssistantProse(
|
|
1234
|
+
messages: readonly { role: string; content?: readonly { type: string; text?: string }[] }[],
|
|
1235
|
+
): string | undefined {
|
|
1236
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1237
|
+
const message = messages[index];
|
|
1238
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
1239
|
+
const texts: string[] = [];
|
|
1240
|
+
for (const part of message.content) {
|
|
1241
|
+
if (part.type === "text" && typeof part.text === "string" && part.text.length > 0) {
|
|
1242
|
+
texts.push(part.text);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (texts.length === 0) continue;
|
|
1246
|
+
return texts.join("");
|
|
1247
|
+
}
|
|
1248
|
+
return undefined;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1228
1251
|
export function createRoleRuntimeExtension(
|
|
1229
1252
|
dependencies: RoleRuntimeDependencies,
|
|
1230
1253
|
): (envelopeHost: RoleEnvelopeHost) => void {
|
|
@@ -1299,10 +1322,9 @@ export function createRoleRuntimeExtension(
|
|
|
1299
1322
|
if (settlement === undefined || attendance === undefined) return;
|
|
1300
1323
|
const workContext = navigatorWorkContext;
|
|
1301
1324
|
const pending = (async () => {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1325
|
+
// ADR 0052 / #959: every settlement that starts a post-role feed host round
|
|
1326
|
+
// (accepted, human_decision, role_infrastructure_failure) shares one grace
|
|
1327
|
+
// and the same honest unavailable projection — no unbounded parallel branch.
|
|
1306
1328
|
const settlePromise = attendance.settle(settlement);
|
|
1307
1329
|
// Attach catch immediately so a late rejection after grace timeout cannot
|
|
1308
1330
|
// surface as unhandledRejection / stale-ctx after session dispose (#675).
|
|
@@ -1557,18 +1579,12 @@ export function createRoleRuntimeExtension(
|
|
|
1557
1579
|
pendingSubmissionNonPassByToolCallId.delete(event.toolCallId);
|
|
1558
1580
|
return { details: submissionNonPass, isError: true };
|
|
1559
1581
|
}
|
|
1560
|
-
// Recommendation rides the accepted settlement record's content so the one
|
|
1561
|
-
// mandatory last-ak_*_output extraction surfaces route/next/reason without
|
|
1562
|
-
// a second file, grep, or nesting step. Receipt details stay contract-pure;
|
|
1563
|
-
// unavailable/no-advice leave the settlement untouched.
|
|
1564
|
-
if (event.isError) return;
|
|
1565
|
-
return;
|
|
1566
1582
|
});
|
|
1567
1583
|
// Queue receipt delivery before `agent_settled`: that event means Pi has
|
|
1568
1584
|
// already decided no queued continuation will run, so a triggerTurn there is
|
|
1569
1585
|
// too late for print/json sessions. `agent_end` is the last production seam
|
|
1570
1586
|
// whose queued next turn is consumed before settlement.
|
|
1571
|
-
roleHost.on("agent_end", (event, ctx) => {
|
|
1587
|
+
roleHost.on("agent_end", async (event, ctx) => {
|
|
1572
1588
|
const lastMessage = event.messages.at(-1);
|
|
1573
1589
|
if (lastMessage?.role === "assistant"
|
|
1574
1590
|
&& (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
|
|
@@ -1578,6 +1594,50 @@ export function createRoleRuntimeExtension(
|
|
|
1578
1594
|
}
|
|
1579
1595
|
return;
|
|
1580
1596
|
}
|
|
1597
|
+
const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
|
|
1598
|
+
// #959: navigator prose exit — final assistant text is the receipt.
|
|
1599
|
+
// No typed-tool 催交; no JSON required. Tool path still wins when already accepted.
|
|
1600
|
+
if (role === "navigator" && receiptDelivery.nextAction() !== "accepted") {
|
|
1601
|
+
const prose = lastAssistantProse(event.messages);
|
|
1602
|
+
if (prose !== undefined && prose.trim() !== "") {
|
|
1603
|
+
const accepted = { prose };
|
|
1604
|
+
await sealAcceptedSubmission({
|
|
1605
|
+
context: ctx,
|
|
1606
|
+
role: "navigator",
|
|
1607
|
+
accepted,
|
|
1608
|
+
toolCallId: `navigator-prose-exit:${randomUUID()}`,
|
|
1609
|
+
});
|
|
1610
|
+
await projectClosedSubmission(
|
|
1611
|
+
{ role: "navigator", kind: "accepted", accepted },
|
|
1612
|
+
ctx,
|
|
1613
|
+
);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
// Attended with neither tool nor prose → honest no_receipt (not typed 催交).
|
|
1617
|
+
// Exhaust delivery budget without sending the typed prompt so facts() stays lawful.
|
|
1618
|
+
if (!noReceiptRecorded) {
|
|
1619
|
+
const runPointer = runDirectoryFromHostContext(ctx);
|
|
1620
|
+
if (runPointer !== undefined) {
|
|
1621
|
+
noReceiptRecorded = true;
|
|
1622
|
+
while (receiptDelivery.nextAction() === "request-delivery") {
|
|
1623
|
+
receiptDelivery.recordDeliveryRequest();
|
|
1624
|
+
}
|
|
1625
|
+
const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
|
|
1626
|
+
envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
1627
|
+
try {
|
|
1628
|
+
sitianReport({
|
|
1629
|
+
level: "event",
|
|
1630
|
+
kind: "no-receipt-lifecycle",
|
|
1631
|
+
cwd: ctx.cwd,
|
|
1632
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1633
|
+
payload: facts,
|
|
1634
|
+
source: "role-runtime",
|
|
1635
|
+
});
|
|
1636
|
+
} catch {}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1581
1641
|
if (receiptDelivery.nextAction() === "request-delivery") {
|
|
1582
1642
|
receiptDelivery.recordDeliveryRequest();
|
|
1583
1643
|
// Keep the package-owned continuation off the public input lifecycle:
|
|
@@ -1657,8 +1717,8 @@ export function createRoleRuntimeExtension(
|
|
|
1657
1717
|
process.exitCode = 1;
|
|
1658
1718
|
}
|
|
1659
1719
|
}
|
|
1660
|
-
// Flush any still-pending affirmative attendance before teardown.
|
|
1661
|
-
//
|
|
1720
|
+
// Flush any still-pending affirmative attendance before teardown.
|
|
1721
|
+
// Grace-timeout paths normally emit on agent_settled; abort can skip that hook.
|
|
1662
1722
|
const presentation = pendingNavigatorPresentation;
|
|
1663
1723
|
pendingNavigatorPresentation = undefined;
|
|
1664
1724
|
if (presentation !== undefined) {
|
package/src/submission-ledger.ts
CHANGED
|
@@ -517,6 +517,65 @@ async function restoreState(cwd: string, runId: string, scope: SubmissionLedgerR
|
|
|
517
517
|
};
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
/** Sole HostContext-derived session parent for ledger restore/append (never process.env). */
|
|
521
|
+
function sessionParentFromHostContext(context: HostContext): string | undefined {
|
|
522
|
+
const runDirectory = runDirectoryFromHostContext(context);
|
|
523
|
+
if (runDirectory !== undefined) return join(runDirectory, "session", "session.jsonl");
|
|
524
|
+
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
525
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0) return sessionFile;
|
|
526
|
+
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
527
|
+
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
528
|
+
return join(sessionDir, "session.jsonl");
|
|
529
|
+
}
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function homeFromHostContext(context: HostContext, home?: string): string | undefined {
|
|
534
|
+
if (home !== undefined) return home;
|
|
535
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
536
|
+
return sessionParent !== undefined ? tryHomeFromAkRolesPath(sessionParent) : undefined;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* #959: seal one accepted submission without a model tool call.
|
|
541
|
+
* Same ledger row shape and priorEventId chain as the terminating-tool wrap.
|
|
542
|
+
* Used when a prose-exit seat (navigator) harvests the final assistant text.
|
|
543
|
+
*/
|
|
544
|
+
export async function sealAcceptedSubmission(options: {
|
|
545
|
+
readonly context: HostContext;
|
|
546
|
+
readonly role: TerminalRoleName;
|
|
547
|
+
readonly accepted: unknown;
|
|
548
|
+
readonly toolCallId: string;
|
|
549
|
+
readonly home?: string;
|
|
550
|
+
}): Promise<void> {
|
|
551
|
+
const runId = runIdentity(options.context);
|
|
552
|
+
const attemptId = attemptIdentity(options.context, runId);
|
|
553
|
+
const sessionParent = sessionParentFromHostContext(options.context);
|
|
554
|
+
const home = homeFromHostContext(options.context, options.home);
|
|
555
|
+
const state = await restoreState(options.context.cwd, runId, {
|
|
556
|
+
...(home === undefined ? {} : { home }),
|
|
557
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
558
|
+
});
|
|
559
|
+
const pointer = sitianReport({
|
|
560
|
+
level: "event",
|
|
561
|
+
kind: "sealed",
|
|
562
|
+
subject: { runId, attemptId },
|
|
563
|
+
...(state.prior === undefined ? {} : { priorEventId: state.prior.identity }),
|
|
564
|
+
payload: {
|
|
565
|
+
type: "sealed",
|
|
566
|
+
attemptId,
|
|
567
|
+
toolCallId: options.toolCallId,
|
|
568
|
+
role: options.role,
|
|
569
|
+
accepted: options.accepted,
|
|
570
|
+
},
|
|
571
|
+
source: "role-runtime",
|
|
572
|
+
cwd: options.context.cwd,
|
|
573
|
+
...(home !== undefined ? { home } : {}),
|
|
574
|
+
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
575
|
+
});
|
|
576
|
+
state.prior = pointer;
|
|
577
|
+
}
|
|
578
|
+
|
|
520
579
|
/**
|
|
521
580
|
* Submission ledger host — record only (#836).
|
|
522
581
|
* Each terminating submission is appended with the role's original payload.
|
|
@@ -531,28 +590,11 @@ export function createSubmissionLedgerHost(
|
|
|
531
590
|
options?: { home?: string },
|
|
532
591
|
): RoleHost {
|
|
533
592
|
const states = new Map<string, Promise<LedgerState>>();
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const runDirectory = runDirectoryFromHostContext(context);
|
|
537
|
-
if (runDirectory !== undefined) {
|
|
538
|
-
return join(runDirectory, "session", "session.jsonl");
|
|
539
|
-
}
|
|
540
|
-
const sessionFile = context.sessionManager.getSessionFile?.();
|
|
541
|
-
if (typeof sessionFile === "string" && sessionFile.length > 0) return sessionFile;
|
|
542
|
-
const sessionDir = context.sessionManager.getSessionDir?.();
|
|
543
|
-
if (typeof sessionDir === "string" && sessionDir.length > 0) {
|
|
544
|
-
return join(sessionDir, "session.jsonl");
|
|
545
|
-
}
|
|
546
|
-
return undefined;
|
|
547
|
-
};
|
|
548
|
-
const resolveHomeFromContext = (context: HostContext): string | undefined => {
|
|
549
|
-
if (options?.home !== undefined) return options.home;
|
|
550
|
-
const sessionParent = sessionParentFromContext(context);
|
|
551
|
-
return sessionParent !== undefined ? tryHomeFromAkRolesPath(sessionParent) : undefined;
|
|
552
|
-
};
|
|
593
|
+
const resolveHomeFromContext = (context: HostContext): string | undefined =>
|
|
594
|
+
homeFromHostContext(context, options?.home);
|
|
553
595
|
const stateFor = (context: HostContext, runId: string) => states.get(runId) ?? (() => {
|
|
554
596
|
const home = resolveHomeFromContext(context);
|
|
555
|
-
const sessionParent =
|
|
597
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
556
598
|
const pending = restoreState(context.cwd, runId, {
|
|
557
599
|
...(home === undefined ? {} : { home }),
|
|
558
600
|
...(sessionParent === undefined ? {} : { sessionParent }),
|
|
@@ -562,7 +604,7 @@ export function createSubmissionLedgerHost(
|
|
|
562
604
|
})();
|
|
563
605
|
const appendFor = (state: LedgerState, context: HostContext, runId: string, attemptId: string, event: SubmissionLedgerEvent): RecordPointer => {
|
|
564
606
|
const home = resolveHomeFromContext(context);
|
|
565
|
-
const sessionParent =
|
|
607
|
+
const sessionParent = sessionParentFromHostContext(context);
|
|
566
608
|
const pointer = sitianReport({
|
|
567
609
|
level: "event",
|
|
568
610
|
kind: event.type,
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PACKAGED_ROLE_REGISTRY,
|
|
3
|
-
packagedRoleMetadata
|
|
4
|
-
} from "./packaged-role-registry.js";
|
|
5
|
-
const PUBLIC_CALLABLE_ROLES = new Set(
|
|
6
|
-
PACKAGED_ROLE_REGISTRY.map((entry) => entry.role)
|
|
7
|
-
);
|
|
8
|
-
function isPublicCallableRole(role) {
|
|
9
|
-
return PUBLIC_CALLABLE_ROLES.has(role);
|
|
10
|
-
}
|
|
11
|
-
function renderPublicAkRoleCommand(target) {
|
|
12
|
-
if (!isPublicCallableRole(target.role)) return void 0;
|
|
13
|
-
const role = target.role;
|
|
14
|
-
const metadata = packagedRoleMetadata(role);
|
|
15
|
-
if (metadata !== void 0 && "bareCommand" in metadata && metadata.bareCommand === false) {
|
|
16
|
-
return void 0;
|
|
17
|
-
}
|
|
18
|
-
if (target.phase === null || target.phase === void 0) {
|
|
19
|
-
return `ak-role ${role}`;
|
|
20
|
-
}
|
|
21
|
-
if (role === "coder" || role === "fixer") {
|
|
22
|
-
return `ak-role ${role} ${target.phase}`;
|
|
23
|
-
}
|
|
24
|
-
return `ak-role ${role}`;
|
|
25
|
-
}
|
|
26
|
-
export {
|
|
27
|
-
isPublicCallableRole,
|
|
28
|
-
renderPublicAkRoleCommand
|
|
29
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public CLI re-export of the shared registry-owned command renderer.
|
|
3
|
-
* Single implementation lives in src/public-command-renderer.ts.
|
|
4
|
-
*/
|
|
5
|
-
export {
|
|
6
|
-
isPublicCallableRole,
|
|
7
|
-
renderPublicAkRoleCommand,
|
|
8
|
-
type PublicCommandTarget,
|
|
9
|
-
} from "../public-command-renderer.ts";
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry-owned public command renderer (ADR 0052 / #106).
|
|
3
|
-
* Typed role/phase facts → public `ak-role` command text.
|
|
4
|
-
* Model-authored command prose is never executable output.
|
|
5
|
-
*
|
|
6
|
-
* Low-level shared owner: Navigator attendance and the public CLI both import
|
|
7
|
-
* this module so dist/navigator-attendance.js keeps a complete dependency closure
|
|
8
|
-
* without duplicating renderer logic into the preparation graph or bin bundle.
|
|
9
|
-
*/
|
|
10
|
-
import {
|
|
11
|
-
PACKAGED_ROLE_REGISTRY,
|
|
12
|
-
packagedRoleMetadata,
|
|
13
|
-
type PackagedRole,
|
|
14
|
-
} from "./packaged-role-registry.ts";
|
|
15
|
-
|
|
16
|
-
export type PublicCommandPhase = "plan" | "apply" | null;
|
|
17
|
-
|
|
18
|
-
export type PublicCommandTarget = {
|
|
19
|
-
role: string;
|
|
20
|
-
phase: PublicCommandPhase;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const PUBLIC_CALLABLE_ROLES = new Set<string>(
|
|
24
|
-
PACKAGED_ROLE_REGISTRY.map((entry) => entry.role),
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
export function isPublicCallableRole(role: string): role is PackagedRole {
|
|
28
|
-
return PUBLIC_CALLABLE_ROLES.has(role);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Render one public ak-role invocation from typed next-role facts.
|
|
33
|
-
* Returns undefined when the role is not callable or role/phase cannot supply
|
|
34
|
-
* all required admission values for a truthful command.
|
|
35
|
-
*/
|
|
36
|
-
export function renderPublicAkRoleCommand(
|
|
37
|
-
target: PublicCommandTarget,
|
|
38
|
-
): string | undefined {
|
|
39
|
-
if (!isPublicCallableRole(target.role)) return undefined;
|
|
40
|
-
const role = target.role as PackagedRole;
|
|
41
|
-
const metadata = packagedRoleMetadata(role);
|
|
42
|
-
// A role whose admission requires typed values outside role/phase has no
|
|
43
|
-
// truthful bare command. Navigator may still recommend the typed direction.
|
|
44
|
-
if (metadata !== undefined && "bareCommand" in metadata && metadata.bareCommand === false) {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
if (target.phase === null || target.phase === undefined) {
|
|
48
|
-
return `ak-role ${role}`;
|
|
49
|
-
}
|
|
50
|
-
// Coder/Fixer expose plan|apply; other roles have no public phase selector.
|
|
51
|
-
if (role === "coder" || role === "fixer") {
|
|
52
|
-
return `ak-role ${role} ${target.phase}`;
|
|
53
|
-
}
|
|
54
|
-
return `ak-role ${role}`;
|
|
55
|
-
}
|