@akagilnc/pi-workflow-roles 0.1.4586 → 0.1.4631
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 +2 -2
- package/README.zh-CN.md +3 -3
- package/dist/acp-host/production-host.js +1184 -717
- package/dist/activation-ledger-topology.js +0 -4
- package/dist/archivist-record-topology.js +27 -35
- package/dist/headless-host/description.js +7 -4
- package/dist/headless-host/production-host.js +998 -595
- package/dist/migrate-book-topology.js +3 -6
- 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/pi/role-turn-host.js +0 -3
- package/dist/public-cli/auto-resume.js +26 -0
- package/dist/public-cli/judge-run.js +2 -2
- package/dist/public-cli/main.js +1119 -870
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +103 -9
- package/dist/public-cli/process-cancel.js +57 -0
- package/dist/public-cli/run-lifecycle.js +29 -7
- package/dist/public-cli/settlement.js +46 -32
- package/dist/public-cli/terminal.js +8 -27
- package/dist/public-role-summons.js +22 -4
- 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 -2
- package/src/acp-host/role-turn-host.ts +129 -13
- package/src/activation-ledger-topology.ts +1 -6
- package/src/activation-ledger.ts +5 -236
- package/src/analyst-entry.ts +19 -2
- package/src/analyst-ledger.ts +111 -6
- package/src/archivist-record-entry.ts +1 -1
- package/src/headless-host/description.ts +17 -7
- package/src/headless-host/role-turn-host.ts +42 -11
- 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/pi/role-turn-host.ts +0 -3
- package/src/public-cli/auto-resume.ts +34 -0
- package/src/public-cli/cli.ts +30 -0
- package/src/public-cli/coder-run.ts +2 -2
- package/src/public-cli/fixer-run.ts +2 -2
- package/src/public-cli/judge-run.ts +2 -2
- package/src/public-cli/main.ts +25 -3
- package/src/public-cli/merger-run.ts +2 -2
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +175 -7
- package/src/public-cli/process-cancel.ts +75 -0
- package/src/public-cli/run-lifecycle.ts +37 -7
- package/src/public-cli/settlement.ts +42 -33
- package/src/public-cli/terminal.ts +12 -37
- package/src/public-role-summons.ts +34 -5
- package/src/role-envelope.ts +89 -4
- package/src/role-runtime.ts +78 -65
- package/src/submission-ledger.ts +63 -21
- package/dist/activation-reconciliation.js +0 -61
- package/dist/public-cli/command-renderer.js +0 -5
- package/dist/public-command-renderer.js +0 -29
- package/src/activation-reconciliation.ts +0 -163
- package/src/public-cli/command-renderer.ts +0 -9
- package/src/public-command-renderer.ts +0 -55
|
@@ -103,6 +103,12 @@ export type PublicSummonRequest = {
|
|
|
103
103
|
readonly boundTicketNumber?: number;
|
|
104
104
|
/** Caller correlation id for nested leg ledger (ADR 0010 / #924). */
|
|
105
105
|
readonly correlationId?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Resume an existing instruction-seat run via public CLI resume (host-native
|
|
108
|
+
* session continuity). Used by navigator attendance so prior advice stays on
|
|
109
|
+
* the host session — not a package-built advice ledger.
|
|
110
|
+
*/
|
|
111
|
+
readonly resumeRunId?: string;
|
|
106
112
|
/**
|
|
107
113
|
* Ephemeral host-turn cwd override (#946 fresh-copy sandbox). Admission keeps
|
|
108
114
|
* the public --project / cwd identity; the host turn runs under this path.
|
|
@@ -516,7 +522,7 @@ export async function summonPublicRole(
|
|
|
516
522
|
case "navigator":
|
|
517
523
|
case "gatekeeper": {
|
|
518
524
|
const seat = options.role;
|
|
519
|
-
const [{ runPublicInstructionSeat }, invocation] = await Promise.all([
|
|
525
|
+
const [{ runPublicInstructionSeat, runPublicInstructionSeatResume }, invocation] = await Promise.all([
|
|
520
526
|
import("./public-cli/instruction-seat-run.ts"),
|
|
521
527
|
import("./public-cli/invocation.ts"),
|
|
522
528
|
]);
|
|
@@ -526,10 +532,33 @@ export async function summonPublicRole(
|
|
|
526
532
|
: seat === "navigator"
|
|
527
533
|
? invocation.parseNavigatorArgv
|
|
528
534
|
: invocation.parseGatekeeperArgv;
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
535
|
+
const resumeRunId = typeof options.resumeRunId === "string" && options.resumeRunId.trim() !== ""
|
|
536
|
+
? options.resumeRunId.trim()
|
|
537
|
+
: undefined;
|
|
538
|
+
if (resumeRunId !== undefined) {
|
|
539
|
+
// Host CLI resume — same face as `ak-role resume <runId> [message]`.
|
|
540
|
+
// Instruction/materials ride summons.instruction (not package memory).
|
|
541
|
+
const instruction = options.argv[0] ?? "";
|
|
542
|
+
const stepped = await runPrepared(parse, (env) =>
|
|
543
|
+
runPublicInstructionSeatResume(
|
|
544
|
+
{
|
|
545
|
+
runId: resumeRunId,
|
|
546
|
+
summons: {
|
|
547
|
+
instruction,
|
|
548
|
+
instructionEmpty: instruction.trim() === "",
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
env as never,
|
|
552
|
+
io,
|
|
553
|
+
));
|
|
554
|
+
if ("fail" in stepped) return stepped.fail;
|
|
555
|
+
result = stepped.ok;
|
|
556
|
+
} else {
|
|
557
|
+
const stepped = await runPrepared(parse, (env, once) =>
|
|
558
|
+
runPublicInstructionSeat(options.argv, env as never, io, seat, once));
|
|
559
|
+
if ("fail" in stepped) return stepped.fail;
|
|
560
|
+
result = stepped.ok;
|
|
561
|
+
}
|
|
533
562
|
break;
|
|
534
563
|
}
|
|
535
564
|
case "judge": {
|
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,24 +13,14 @@ 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";
|
|
20
|
+
import { homeFromRunDirectory } from "./activation-ledger-topology.ts";
|
|
19
21
|
import {
|
|
20
|
-
homeFromRunDirectory,
|
|
21
|
-
resolveActivationLedgerHomeForPath,
|
|
22
|
-
} from "./activation-ledger-topology.ts";
|
|
23
|
-
import {
|
|
24
|
-
appendAcceptedActivationToBook,
|
|
25
|
-
buildAcceptedActivationFact,
|
|
26
|
-
correlationIdentityFromEnv,
|
|
27
22
|
durableSessionPointer,
|
|
28
|
-
resolveActivationLedgerHome,
|
|
29
23
|
resolveBookKeyFromGit,
|
|
30
|
-
type AcceptedActivationFact,
|
|
31
|
-
type ActivationCorrelationIdentity,
|
|
32
|
-
type ActivationSessionPointer,
|
|
33
24
|
} from "./activation-ledger.ts";
|
|
34
25
|
import { writeStderrJsonlRecord } from "./stderr-jsonl.ts";
|
|
35
26
|
import {
|
|
@@ -300,43 +291,18 @@ export {
|
|
|
300
291
|
export { activationTraceRecordSchema, namedActivationCause } from "./activation-trace.ts";
|
|
301
292
|
export type { ActivationTraceRecord, ActivationTraceWriter } from "./activation-trace.ts";
|
|
302
293
|
export {
|
|
303
|
-
ACCEPTED_ACTIVATION_EVENT,
|
|
304
|
-
ACCEPTED_ACTIVATION_FACT_KEYS,
|
|
305
294
|
ActivationGitRepositoryRequiredError,
|
|
306
295
|
ActivationLedgerError,
|
|
307
296
|
ActivationSessionFileMissingError,
|
|
308
297
|
activationBookDirectory,
|
|
309
|
-
activationWaitingLedgerPath,
|
|
310
|
-
appendAcceptedActivationFact,
|
|
311
|
-
appendAcceptedActivationToBook,
|
|
312
|
-
buildAcceptedActivationFact,
|
|
313
|
-
correlationIdentityFromEnv,
|
|
314
298
|
durableSessionPointer,
|
|
315
299
|
resolveActivationLedgerHome,
|
|
316
300
|
resolveBookKeyFromGit,
|
|
317
|
-
serializeAcceptedActivationFact,
|
|
318
301
|
} from "./activation-ledger.ts";
|
|
319
302
|
export type {
|
|
320
|
-
AcceptedActivationFact,
|
|
321
|
-
AcceptedActivationFactInput,
|
|
322
|
-
AcceptedActivationFactKey,
|
|
323
|
-
ActivationCorrelationIdentity,
|
|
324
303
|
ActivationSessionManager,
|
|
325
304
|
ActivationSessionPointer,
|
|
326
305
|
} from "./activation-ledger.ts";
|
|
327
|
-
export {
|
|
328
|
-
DISPATCH_STUB_EVENT,
|
|
329
|
-
buildDispatchStubFact,
|
|
330
|
-
reconcileInvocation,
|
|
331
|
-
} from "./activation-reconciliation.ts";
|
|
332
|
-
export type {
|
|
333
|
-
DispatchPointer,
|
|
334
|
-
DispatchStubFact,
|
|
335
|
-
DispatchStubFactInput,
|
|
336
|
-
ProcessLivenessFact,
|
|
337
|
-
ReconciliationOutcome,
|
|
338
|
-
ReconciliationSubject,
|
|
339
|
-
} from "./activation-reconciliation.ts";
|
|
340
306
|
export {
|
|
341
307
|
TOOL_EXECUTION_UPDATE_HEARTBEAT,
|
|
342
308
|
TOOL_EXECUTION_UPDATE_THROTTLE_MS,
|
|
@@ -1225,6 +1191,28 @@ export function createCountersignRoleRuntime(
|
|
|
1225
1191
|
);
|
|
1226
1192
|
}
|
|
1227
1193
|
|
|
1194
|
+
/**
|
|
1195
|
+
* #959: last assistant text parts as navigator prose exit.
|
|
1196
|
+
* Tool-call-only messages yield undefined — those already ride the tool path.
|
|
1197
|
+
*/
|
|
1198
|
+
function lastAssistantProse(
|
|
1199
|
+
messages: readonly { role: string; content?: readonly { type: string; text?: string }[] }[],
|
|
1200
|
+
): string | undefined {
|
|
1201
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1202
|
+
const message = messages[index];
|
|
1203
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
1204
|
+
const texts: string[] = [];
|
|
1205
|
+
for (const part of message.content) {
|
|
1206
|
+
if (part.type === "text" && typeof part.text === "string" && part.text.length > 0) {
|
|
1207
|
+
texts.push(part.text);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (texts.length === 0) continue;
|
|
1211
|
+
return texts.join("");
|
|
1212
|
+
}
|
|
1213
|
+
return undefined;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1228
1216
|
export function createRoleRuntimeExtension(
|
|
1229
1217
|
dependencies: RoleRuntimeDependencies,
|
|
1230
1218
|
): (envelopeHost: RoleEnvelopeHost) => void {
|
|
@@ -1299,10 +1287,9 @@ export function createRoleRuntimeExtension(
|
|
|
1299
1287
|
if (settlement === undefined || attendance === undefined) return;
|
|
1300
1288
|
const workContext = navigatorWorkContext;
|
|
1301
1289
|
const pending = (async () => {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1290
|
+
// ADR 0052 / #959: every settlement that starts a post-role feed host round
|
|
1291
|
+
// (accepted, human_decision, role_infrastructure_failure) shares one grace
|
|
1292
|
+
// and the same honest unavailable projection — no unbounded parallel branch.
|
|
1306
1293
|
const settlePromise = attendance.settle(settlement);
|
|
1307
1294
|
// Attach catch immediately so a late rejection after grace timeout cannot
|
|
1308
1295
|
// surface as unhandledRejection / stale-ctx after session dispose (#675).
|
|
@@ -1557,18 +1544,12 @@ export function createRoleRuntimeExtension(
|
|
|
1557
1544
|
pendingSubmissionNonPassByToolCallId.delete(event.toolCallId);
|
|
1558
1545
|
return { details: submissionNonPass, isError: true };
|
|
1559
1546
|
}
|
|
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
1547
|
});
|
|
1567
1548
|
// Queue receipt delivery before `agent_settled`: that event means Pi has
|
|
1568
1549
|
// already decided no queued continuation will run, so a triggerTurn there is
|
|
1569
1550
|
// too late for print/json sessions. `agent_end` is the last production seam
|
|
1570
1551
|
// whose queued next turn is consumed before settlement.
|
|
1571
|
-
roleHost.on("agent_end", (event, ctx) => {
|
|
1552
|
+
roleHost.on("agent_end", async (event, ctx) => {
|
|
1572
1553
|
const lastMessage = event.messages.at(-1);
|
|
1573
1554
|
if (lastMessage?.role === "assistant"
|
|
1574
1555
|
&& (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
|
|
@@ -1578,6 +1559,50 @@ export function createRoleRuntimeExtension(
|
|
|
1578
1559
|
}
|
|
1579
1560
|
return;
|
|
1580
1561
|
}
|
|
1562
|
+
const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
|
|
1563
|
+
// #959: navigator prose exit — final assistant text is the receipt.
|
|
1564
|
+
// No typed-tool 催交; no JSON required. Tool path still wins when already accepted.
|
|
1565
|
+
if (role === "navigator" && receiptDelivery.nextAction() !== "accepted") {
|
|
1566
|
+
const prose = lastAssistantProse(event.messages);
|
|
1567
|
+
if (prose !== undefined && prose.trim() !== "") {
|
|
1568
|
+
const accepted = { prose };
|
|
1569
|
+
await sealAcceptedSubmission({
|
|
1570
|
+
context: ctx,
|
|
1571
|
+
role: "navigator",
|
|
1572
|
+
accepted,
|
|
1573
|
+
toolCallId: `navigator-prose-exit:${randomUUID()}`,
|
|
1574
|
+
});
|
|
1575
|
+
await projectClosedSubmission(
|
|
1576
|
+
{ role: "navigator", kind: "accepted", accepted },
|
|
1577
|
+
ctx,
|
|
1578
|
+
);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
// Attended with neither tool nor prose → honest no_receipt (not typed 催交).
|
|
1582
|
+
// Exhaust delivery budget without sending the typed prompt so facts() stays lawful.
|
|
1583
|
+
if (!noReceiptRecorded) {
|
|
1584
|
+
const runPointer = runDirectoryFromHostContext(ctx);
|
|
1585
|
+
if (runPointer !== undefined) {
|
|
1586
|
+
noReceiptRecorded = true;
|
|
1587
|
+
while (receiptDelivery.nextAction() === "request-delivery") {
|
|
1588
|
+
receiptDelivery.recordDeliveryRequest();
|
|
1589
|
+
}
|
|
1590
|
+
const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
|
|
1591
|
+
envelopeHost.appendEntry(NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, facts);
|
|
1592
|
+
try {
|
|
1593
|
+
sitianReport({
|
|
1594
|
+
level: "event",
|
|
1595
|
+
kind: "no-receipt-lifecycle",
|
|
1596
|
+
cwd: ctx.cwd,
|
|
1597
|
+
sessionParent: ctx.sessionManager.getSessionFile(),
|
|
1598
|
+
payload: facts,
|
|
1599
|
+
source: "role-runtime",
|
|
1600
|
+
});
|
|
1601
|
+
} catch {}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1581
1606
|
if (receiptDelivery.nextAction() === "request-delivery") {
|
|
1582
1607
|
receiptDelivery.recordDeliveryRequest();
|
|
1583
1608
|
// Keep the package-owned continuation off the public input lifecycle:
|
|
@@ -1657,8 +1682,8 @@ export function createRoleRuntimeExtension(
|
|
|
1657
1682
|
process.exitCode = 1;
|
|
1658
1683
|
}
|
|
1659
1684
|
}
|
|
1660
|
-
// Flush any still-pending affirmative attendance before teardown.
|
|
1661
|
-
//
|
|
1685
|
+
// Flush any still-pending affirmative attendance before teardown.
|
|
1686
|
+
// Grace-timeout paths normally emit on agent_settled; abort can skip that hook.
|
|
1662
1687
|
const presentation = pendingNavigatorPresentation;
|
|
1663
1688
|
pendingNavigatorPresentation = undefined;
|
|
1664
1689
|
if (presentation !== undefined) {
|
|
@@ -2135,11 +2160,9 @@ export function createRoleRuntimeExtension(
|
|
|
2135
2160
|
// Production topology only (ADR 0048/0049): no test-only ledger hooks.
|
|
2136
2161
|
// Admit durable session file first so lifecycle getEntries/appendEntry are truthful:
|
|
2137
2162
|
// deferred SM materialization must not wipe an in-memory principal marker.
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
|
|
2142
|
-
const session = durableSessionPointer(ctx.sessionManager);
|
|
2163
|
+
// #855: two-face waiting.jsonl write removed — fail-closed book-key + session only.
|
|
2164
|
+
resolveBookKeyFromGit(ctx.cwd);
|
|
2165
|
+
durableSessionPointer(ctx.sessionManager);
|
|
2143
2166
|
|
|
2144
2167
|
// Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
|
|
2145
2168
|
// Top-level public entry legs still attend automatically.
|
|
@@ -2254,16 +2277,6 @@ export function createRoleRuntimeExtension(
|
|
|
2254
2277
|
if (entry.role === "coder") coder.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2255
2278
|
else fixer.armSubmissionGate(ctx.cwd, ctx.sessionManager);
|
|
2256
2279
|
}
|
|
2257
|
-
appendAcceptedActivationToBook({
|
|
2258
|
-
ledgerHome,
|
|
2259
|
-
fact: buildAcceptedActivationFact({
|
|
2260
|
-
role: entry.role,
|
|
2261
|
-
observedAt: clock(),
|
|
2262
|
-
bookKey,
|
|
2263
|
-
session,
|
|
2264
|
-
correlation,
|
|
2265
|
-
}),
|
|
2266
|
-
});
|
|
2267
2280
|
admitted = true;
|
|
2268
2281
|
} catch (error) {
|
|
2269
2282
|
failInfrastructure(error, ctx);
|
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,61 +0,0 @@
|
|
|
1
|
-
const DISPATCH_STUB_EVENT = "dispatch-stub";
|
|
2
|
-
function buildDispatchStubFact(input) {
|
|
3
|
-
return {
|
|
4
|
-
event: DISPATCH_STUB_EVENT,
|
|
5
|
-
observedAt: input.observedAt,
|
|
6
|
-
bookKey: input.bookKey,
|
|
7
|
-
dispatch: input.dispatch.kind === "process" ? { kind: "process", pid: input.dispatch.pid } : { kind: "opaque", ref: input.dispatch.ref },
|
|
8
|
-
correlation: { kind: "caller", id: input.correlation.id }
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function callerCorrelationId(correlation) {
|
|
12
|
-
return correlation.kind === "caller" ? correlation.id : void 0;
|
|
13
|
-
}
|
|
14
|
-
function dispatchMatchesActivation(dispatch, activation) {
|
|
15
|
-
const activationId = callerCorrelationId(activation.correlation);
|
|
16
|
-
return activationId !== void 0 && activationId === dispatch.correlation.id && activation.bookKey === dispatch.bookKey;
|
|
17
|
-
}
|
|
18
|
-
function reconcileInvocation(subject) {
|
|
19
|
-
const { dispatch, activation, process } = subject;
|
|
20
|
-
if (activation !== void 0) {
|
|
21
|
-
if (dispatch !== void 0 && dispatchMatchesActivation(dispatch, activation)) {
|
|
22
|
-
return {
|
|
23
|
-
kind: "matched",
|
|
24
|
-
correlationId: dispatch.correlation.id,
|
|
25
|
-
bookKey: dispatch.bookKey
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
kind: "activation-without-dispatch",
|
|
30
|
-
correlationId: callerCorrelationId(activation.correlation),
|
|
31
|
-
bookKey: activation.bookKey
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
if (dispatch !== void 0) {
|
|
35
|
-
if (process === void 0) {
|
|
36
|
-
throw new TypeError(
|
|
37
|
-
"reconcileInvocation requires process liveness when activation is absent"
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
if (process.state === "alive") {
|
|
41
|
-
return {
|
|
42
|
-
kind: "pending",
|
|
43
|
-
correlationId: dispatch.correlation.id,
|
|
44
|
-
bookKey: dispatch.bookKey
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
kind: "ghost",
|
|
49
|
-
correlationId: dispatch.correlation.id,
|
|
50
|
-
bookKey: dispatch.bookKey
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
throw new TypeError(
|
|
54
|
-
"reconcileInvocation requires a dispatch stub and/or an accepted-activation fact"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
export {
|
|
58
|
-
DISPATCH_STUB_EVENT,
|
|
59
|
-
buildDispatchStubFact,
|
|
60
|
-
reconcileInvocation
|
|
61
|
-
};
|
|
@@ -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
|
-
};
|