@akagilnc/pi-workflow-roles 0.1.4980 → 0.1.4989
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/acp-host/production-host.js +438 -456
- package/dist/analyst-gate-cycles-read.js +16 -11
- package/dist/analyst-metric-families/gate-cycles.js +2 -2
- package/dist/audit-escalation.js +4 -4
- package/dist/compliance-transport.js +2 -2
- package/dist/countersign-contracts.js +6 -5
- package/dist/doctor-evidence.js +2 -1
- package/dist/gatekeeper-role.js +20 -41
- package/dist/headless-host/production-host.js +438 -456
- package/dist/inspector-contracts.js +3 -2
- package/dist/migrate-book-topology.js +59 -54
- package/dist/navigator-invocation-identity.js +6 -4
- package/dist/notary-contracts.js +3 -19
- package/dist/package-contracts/auditor-output.js +4 -19
- package/dist/package-contracts/judge-output.js +6 -5
- package/dist/package-contracts/terminating-tools.js +9 -3
- package/dist/packaged-role-registry.js +10 -12
- package/dist/public-cli/countersign-run.js +1 -1
- package/dist/public-cli/main.js +90 -86
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/public-cli/settlement.js +11 -11
- package/dist/review-submission.js +20 -0
- package/dist/submission-errors.js +2 -2
- package/dist/submission-ledger.js +14 -2
- package/package.json +1 -1
- package/souls/doctor-auditor.md +1 -1
- package/souls/fixer-auditor.md +4 -4
- package/souls/gate-output-guide.md +2 -2
- package/souls/inspector.md +1 -1
- package/souls/judge-auditor.md +2 -2
- package/souls/notary.md +1 -1
- package/souls/quality-law.md +1 -1
- package/src/analyst-gate-cycles-read.ts +17 -9
- package/src/analyst-metric-families/gate-cycles.ts +2 -2
- package/src/audit-escalation.ts +8 -8
- package/src/auditor-role.ts +1 -1
- package/src/compliance-transport.ts +7 -7
- package/src/countersign-contracts.ts +10 -8
- package/src/countersign-role.ts +6 -35
- package/src/doctor-evidence.ts +2 -1
- package/src/doctor-role.ts +1 -2
- package/src/gatekeeper-role.ts +30 -55
- package/src/host-contracts.ts +4 -3
- package/src/inspector-contracts.ts +7 -6
- package/src/inspector-role.ts +3 -20
- package/src/judge-role.ts +29 -49
- package/src/navigator-invocation-identity.ts +16 -4
- package/src/notary-contracts.ts +8 -26
- package/src/notary-role.ts +1 -1
- package/src/package-contracts/auditor-output.ts +6 -27
- package/src/package-contracts/judge-output.ts +10 -8
- package/src/package-contracts/terminating-tools.ts +9 -3
- package/src/packaged-role-registry.ts +12 -13
- package/src/pi/adapter.ts +6 -5
- package/src/public-cli/countersign-run.ts +1 -1
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/settlement.ts +13 -14
- package/src/review-submission.ts +29 -0
- package/src/role-envelope.ts +17 -9
- package/src/role-runtime.ts +32 -15
- package/src/submission-errors.ts +6 -6
- package/src/{gatekeeper-pass-envelope.ts → submission-gate.ts} +29 -16
- package/src/submission-ledger.ts +13 -3
- package/src/ticket-trajectory.ts +9 -4
- package/src/worker-role.ts +16 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** One shared terminating receipt contract for review officers (#1028). */
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
import { openToolObject } from "./open-tool-schema.ts";
|
|
5
|
+
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
6
|
+
|
|
7
|
+
export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output" as const;
|
|
8
|
+
|
|
9
|
+
/** Open receipt: the queue reads only status; all other submitted fields ride unchanged. */
|
|
10
|
+
export const reviewSubmissionSchema = withTerminatingOutputDeclarations(
|
|
11
|
+
openToolObject(
|
|
12
|
+
Type.Object({
|
|
13
|
+
status: Type.Optional(Type.Unknown({
|
|
14
|
+
description: "converged | continue | escalate — 非三态时请重读后重交,勿改标。",
|
|
15
|
+
})),
|
|
16
|
+
findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
|
|
17
|
+
reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
|
|
18
|
+
violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
|
|
19
|
+
conflicts: Type.Optional(Type.Unknown({ description: "待决冲突" })),
|
|
20
|
+
fix: Type.Optional(Type.Unknown({ description: "continue 时的补救说明;可写 summary 摘要,其余内容原样留存" })),
|
|
21
|
+
classes: Type.Optional(Type.Unknown({ description: "已裁决 finding 类;可说明 name、owner、boundary、disposition,原样留存" })),
|
|
22
|
+
note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
|
|
23
|
+
evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
|
|
24
|
+
decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export type ReviewSubmission = { readonly status?: unknown; readonly [key: string]: unknown };
|
package/src/role-envelope.ts
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "./engine-detour.ts";
|
|
12
12
|
import {
|
|
13
13
|
createDefaultGateOfficerSummon,
|
|
14
|
-
|
|
15
|
-
} from "./
|
|
14
|
+
requireSubmissionGate,
|
|
15
|
+
} from "./submission-gate.ts";
|
|
16
16
|
import type {
|
|
17
17
|
HostContext,
|
|
18
18
|
HostEventRegistration,
|
|
@@ -233,12 +233,12 @@ export async function prepareRoleEnvelope(options: {
|
|
|
233
233
|
// seat remains reachable through MCP.
|
|
234
234
|
setActiveTools(names) { preferredTools = [...names]; },
|
|
235
235
|
getActiveTools() { return [...preferredTools]; },
|
|
236
|
-
async
|
|
236
|
+
async requireSubmissionGate(gateOptions) {
|
|
237
237
|
const packageRoot =
|
|
238
238
|
typeof options.dependencies.packageRoot === "string"
|
|
239
239
|
? options.dependencies.packageRoot
|
|
240
240
|
: undefined;
|
|
241
|
-
return
|
|
241
|
+
return requireSubmissionGate({
|
|
242
242
|
context: gateOptions.context,
|
|
243
243
|
subject: gateOptions.subject,
|
|
244
244
|
...(gateOptions.signal === undefined ? {} : { signal: gateOptions.signal }),
|
|
@@ -301,7 +301,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
301
301
|
if (record.kind === "role_infrastructure_failure") return;
|
|
302
302
|
const code = typeof record.code === "string" && record.code.length > 0
|
|
303
303
|
? record.code
|
|
304
|
-
: record.status === "
|
|
304
|
+
: record.status === "continue" || record.status === "escalate" || record.status === "no_receipt"
|
|
305
305
|
? record.status
|
|
306
306
|
: undefined;
|
|
307
307
|
if (code === undefined) return;
|
|
@@ -314,11 +314,12 @@ export async function prepareRoleEnvelope(options: {
|
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
316
|
function textDiagnostic(content: ContentPart[]): string | undefined {
|
|
317
|
-
// Keep
|
|
318
|
-
// only
|
|
317
|
+
// Keep each text item's bytes intact and its boundary readable on the
|
|
318
|
+
// string-only host continuation channel.
|
|
319
319
|
const text = content
|
|
320
|
-
.
|
|
321
|
-
.
|
|
320
|
+
.filter((part) => part.type === "text")
|
|
321
|
+
.map((part) => part.text)
|
|
322
|
+
.join("\n\n");
|
|
322
323
|
return text.trim().length > 0 ? text : undefined;
|
|
323
324
|
}
|
|
324
325
|
/** Arm closeRound + abort path with the durable infrastructure failure for this round. */
|
|
@@ -483,6 +484,13 @@ export async function prepareRoleEnvelope(options: {
|
|
|
483
484
|
details: result.details,
|
|
484
485
|
isError: false,
|
|
485
486
|
});
|
|
487
|
+
if (result.terminate === false) {
|
|
488
|
+
rejection = {
|
|
489
|
+
code: "continue",
|
|
490
|
+
toolCallIds: [toolCallId],
|
|
491
|
+
message: textDiagnostic(projected.content) ?? "continue",
|
|
492
|
+
};
|
|
493
|
+
}
|
|
486
494
|
// Candidate only: seal waits for closeRound after the host round boundary.
|
|
487
495
|
return { content: projected.content, isError: projected.isError };
|
|
488
496
|
} catch (error) {
|
package/src/role-runtime.ts
CHANGED
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
type ReviewerActivation,
|
|
120
120
|
type ReviewerAdmittedInputs,
|
|
121
121
|
} from "./reviewer-role.ts";
|
|
122
|
-
import type {
|
|
122
|
+
import type { SubmissionGateNonPassResult } from "./gatekeeper-role.ts";
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Private transport flag names/definitions for Reviewer admitted inputs.
|
|
@@ -256,7 +256,7 @@ import {
|
|
|
256
256
|
|
|
257
257
|
/** One envelope map for Gatekeeper non-pass and other correct submission rejects (#525). */
|
|
258
258
|
type SubmissionNonPassResult =
|
|
259
|
-
|
|
|
259
|
+
| SubmissionGateNonPassResult
|
|
260
260
|
| CoderSkillExpansionEvidenceMissingResult;
|
|
261
261
|
import { JUDGE_OUTPUT_TOOL_NAME } from "./package-contracts/judge-output.ts";
|
|
262
262
|
import { REVIEWER_OUTPUT_TOOL_NAME } from "./package-contracts/reviewer-output.ts";
|
|
@@ -319,7 +319,7 @@ export {
|
|
|
319
319
|
runGatekeeper,
|
|
320
320
|
gateOfficerForSubject,
|
|
321
321
|
};
|
|
322
|
-
export type { GatekeeperResult, GatekeeperSubject,
|
|
322
|
+
export type { GatekeeperResult, GatekeeperSubject, SubmissionGateNonPassResult, GateOfficer, RunGatekeeperOptions } from "./gatekeeper-role.ts";
|
|
323
323
|
import { ParentQueueReaskError } from "./submission-errors.ts";
|
|
324
324
|
|
|
325
325
|
export {
|
|
@@ -490,7 +490,7 @@ export type RoleRuntimeDependencies = {
|
|
|
490
490
|
/**
|
|
491
491
|
* Composition-root adapter table for nested gate officer summons (#969).
|
|
492
492
|
* Production leaves unset (default pi + packaged externals). Tests inject
|
|
493
|
-
* faux nested hosts so prepareRoleEnvelope.
|
|
493
|
+
* faux nested hosts so prepareRoleEnvelope.requireSubmissionGate is bitten
|
|
494
494
|
* without reimplementing the envelope summon closure.
|
|
495
495
|
*/
|
|
496
496
|
hostAdapters?: readonly import("./public-cli/role-turn-host-resolution.ts").NamedRoleTurnHostAdapter[];
|
|
@@ -667,6 +667,14 @@ function createFiledOfficerRuntime(
|
|
|
667
667
|
if (isAuditEscalationProjection(projected)) {
|
|
668
668
|
return { content: [], details: projected, terminate: true as const };
|
|
669
669
|
}
|
|
670
|
+
if (projected !== null && typeof projected === "object" && "status" in projected
|
|
671
|
+
&& projected.status === "continue" && "receipt" in projected) {
|
|
672
|
+
return {
|
|
673
|
+
content: [{ type: "text" as const, text: readableGateItem(projected.receipt) }],
|
|
674
|
+
details: projected,
|
|
675
|
+
terminate: false,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
670
678
|
const pass = spec.relayGatePass === true && projected !== null && typeof projected === "object" && "officer" in projected && "receipt" in projected
|
|
671
679
|
? projected
|
|
672
680
|
: undefined;
|
|
@@ -937,15 +945,15 @@ export function createDiaristRoleRuntime(
|
|
|
937
945
|
);
|
|
938
946
|
}
|
|
939
947
|
|
|
940
|
-
/**
|
|
948
|
+
/** Shared review-submission status words the queue reads (#1028). */
|
|
941
949
|
const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
|
|
942
950
|
|
|
943
951
|
/**
|
|
944
|
-
* Plain-language re-ask when
|
|
952
|
+
* Plain-language re-ask when status is not a known queue word.
|
|
945
953
|
* Back to countersign itself — notary is not summoned (#753).
|
|
946
954
|
*/
|
|
947
955
|
const COUNTERSIGN_STATUS_REASK =
|
|
948
|
-
"
|
|
956
|
+
"status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
|
|
949
957
|
|
|
950
958
|
/** Secretariat status words the submission gate reads. */
|
|
951
959
|
const SECRETARIAT_QUEUE_STATUSES = new Set(["converged", "escalate"]);
|
|
@@ -969,7 +977,7 @@ export function createSecretariatRoleRuntime(
|
|
|
969
977
|
const beforeAccept: FiledOfficerBeforeAccept | undefined =
|
|
970
978
|
hostActions !== undefined
|
|
971
979
|
? async ({ toolCallId, parameters, signal, ctx }) => {
|
|
972
|
-
if (roleHost.
|
|
980
|
+
if (roleHost.requireSubmissionGate === undefined) {
|
|
973
981
|
const error = new Error("host cannot mount the secretariat submission gate");
|
|
974
982
|
error.name = "InfrastructureFailure";
|
|
975
983
|
return hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
@@ -990,7 +998,7 @@ export function createSecretariatRoleRuntime(
|
|
|
990
998
|
return undefined;
|
|
991
999
|
}
|
|
992
1000
|
try {
|
|
993
|
-
const pass = await roleHost.
|
|
1001
|
+
const pass = await roleHost.requireSubmissionGate!({
|
|
994
1002
|
context: ctx,
|
|
995
1003
|
subject: { kind: "secretariat_verdict" },
|
|
996
1004
|
...(signal === undefined ? {} : { signal }),
|
|
@@ -1005,6 +1013,7 @@ export function createSecretariatRoleRuntime(
|
|
|
1005
1013
|
pass !== undefined
|
|
1006
1014
|
&& pass !== null
|
|
1007
1015
|
&& typeof pass === "object"
|
|
1016
|
+
&& pass.status === "converged"
|
|
1008
1017
|
&& "receipt" in pass
|
|
1009
1018
|
) {
|
|
1010
1019
|
const {
|
|
@@ -1112,7 +1121,7 @@ export function createCountersignRoleRuntime(
|
|
|
1112
1121
|
) {
|
|
1113
1122
|
// Notary inner gate difference only — lifecycle stays on the shared envelope.
|
|
1114
1123
|
// Pointer-only summons: officer self-fetches from run dossier (#632 / ADR 0079).
|
|
1115
|
-
// #
|
|
1124
|
+
// #1028 queue: read shared status — escalate skips gate (thrown to caller);
|
|
1116
1125
|
// unreadable status returns to countersign; else notary inner gate.
|
|
1117
1126
|
const beforeAccept: FiledOfficerBeforeAccept | undefined =
|
|
1118
1127
|
hostActions !== undefined
|
|
@@ -1122,8 +1131,8 @@ export function createCountersignRoleRuntime(
|
|
|
1122
1131
|
? (parameters as Record<string, unknown>)
|
|
1123
1132
|
: undefined;
|
|
1124
1133
|
const status =
|
|
1125
|
-
record !== undefined && typeof record.
|
|
1126
|
-
? record.
|
|
1134
|
+
record !== undefined && typeof record.status === "string"
|
|
1135
|
+
? record.status
|
|
1127
1136
|
: undefined;
|
|
1128
1137
|
if (status === undefined || !COUNTERSIGN_QUEUE_STATUSES.has(status)) {
|
|
1129
1138
|
// Parent status unreadable → back to countersign itself; do not summon notary,
|
|
@@ -1134,13 +1143,13 @@ export function createCountersignRoleRuntime(
|
|
|
1134
1143
|
// Parent escalate → throw to caller as-is; notary does not attend (#753).
|
|
1135
1144
|
return undefined;
|
|
1136
1145
|
}
|
|
1137
|
-
if (roleHost.
|
|
1146
|
+
if (roleHost.requireSubmissionGate === undefined) {
|
|
1138
1147
|
const error = new Error("host cannot mount the countersign gate");
|
|
1139
1148
|
error.name = "InfrastructureFailure";
|
|
1140
1149
|
return hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
1141
1150
|
}
|
|
1142
1151
|
try {
|
|
1143
|
-
return await roleHost.
|
|
1152
|
+
return await roleHost.requireSubmissionGate({
|
|
1144
1153
|
context: ctx,
|
|
1145
1154
|
subject: { kind: "countersign_verdict" },
|
|
1146
1155
|
...(signal === undefined ? {} : { signal }),
|
|
@@ -1202,7 +1211,15 @@ export function createRoleRuntimeExtension(
|
|
|
1202
1211
|
};
|
|
1203
1212
|
const roleHost = createSubmissionLedgerHost(
|
|
1204
1213
|
envelopeHost.host,
|
|
1205
|
-
new Map(PACKAGED_ROLE_REGISTRY.
|
|
1214
|
+
new Map(PACKAGED_ROLE_REGISTRY.reduce<(readonly [string, import("./public-cli/terminal.ts").TerminalRoleName | readonly import("./public-cli/terminal.ts").TerminalRoleName[]])[]>((entries, { role, outputTool }) => {
|
|
1215
|
+
const existingIndex = entries.findIndex(([name]) => name === outputTool);
|
|
1216
|
+
if (existingIndex < 0) entries.push([outputTool, role]);
|
|
1217
|
+
else {
|
|
1218
|
+
const previous = entries[existingIndex]![1];
|
|
1219
|
+
entries[existingIndex] = [outputTool, [...(Array.isArray(previous) ? previous : [previous]), role]];
|
|
1220
|
+
}
|
|
1221
|
+
return entries;
|
|
1222
|
+
}, [])),
|
|
1206
1223
|
failInfrastructure,
|
|
1207
1224
|
async (closed, context) => projectClosedSubmission(closed, context),
|
|
1208
1225
|
);
|
package/src/submission-errors.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SubmissionGateNonPassResult } from "./gatekeeper-role.ts";
|
|
2
2
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Tool-result text the parent model sees (#753 / #750 evidence).
|
|
6
|
-
*
|
|
6
|
+
* continue | escalate → officer receipt verbatim (JSON when structured).
|
|
7
7
|
* no_receipt → honest lifecycle fact. No findings rewrite, no「(无 findings)」.
|
|
8
8
|
* #775: structured field content via readableGateItem (DRY with other gate seams).
|
|
9
9
|
*/
|
|
@@ -11,8 +11,8 @@ function serializeReceipt(receipt: unknown): string {
|
|
|
11
11
|
return readableGateItem(receipt);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function gatekeeperNonPassMessage(result:
|
|
15
|
-
if (result.status === "
|
|
14
|
+
function gatekeeperNonPassMessage(result: SubmissionGateNonPassResult): string {
|
|
15
|
+
if (result.status === "continue" || result.status === "escalate") {
|
|
16
16
|
return serializeReceipt(result.receipt);
|
|
17
17
|
}
|
|
18
18
|
if (result.status === "transport_failure") {
|
|
@@ -26,8 +26,8 @@ function gatekeeperNonPassMessage(result: GatekeeperNonPassResult): string {
|
|
|
26
26
|
|
|
27
27
|
/** Structured non-pass; `.result` is session-projected via tool_result, message feeds the model. */
|
|
28
28
|
export class GatekeeperDecisionError extends Error {
|
|
29
|
-
readonly result:
|
|
30
|
-
constructor(result:
|
|
29
|
+
readonly result: SubmissionGateNonPassResult;
|
|
30
|
+
constructor(result: SubmissionGateNonPassResult, message?: string) {
|
|
31
31
|
super(message ?? gatekeeperNonPassMessage(result));
|
|
32
32
|
this.name = "GatekeeperDecisionError";
|
|
33
33
|
this.result = result;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
2
|
+
* Shared submissionGate path for review officers (ADR 0018 / #675 / #753).
|
|
3
3
|
* Owns officer-pointer book + host abort/non-pass faces + review queue loop.
|
|
4
4
|
* Role modules only project via projectGatekeeperRun / runGatekeeper — no book, no catch.
|
|
5
5
|
*
|
|
6
6
|
* Queue guarantee only (#753 / #756 / #750):
|
|
7
7
|
* parent submit → summon officer → read conclusion field
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* converged → accept end
|
|
9
|
+
* continue → raw officer receipt as a nonterminal result; parent may resubmit
|
|
10
|
+
* escalate → audit_escalation terminal, without binding a correctable parent submission
|
|
11
|
+
* unrecognized status → resume officer with plain-language re-ask (no round cap)
|
|
12
12
|
* transport / no_receipt → present honestly
|
|
13
13
|
* Four pairs: countersign↔notary, judge↔auditor, worker↔inspector, secretariat↔countersign (#969).
|
|
14
14
|
* Code does not judge content, map next-step for parent, or label unreadable/unusable.
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
GatekeeperDecisionError,
|
|
21
21
|
officerConclusionReask,
|
|
22
22
|
projectGatekeeperRun,
|
|
23
|
-
type
|
|
23
|
+
type SubmissionGateHostActions,
|
|
24
24
|
type GatekeeperResult,
|
|
25
25
|
type GatekeeperSubject,
|
|
26
26
|
type GateOfficer,
|
|
@@ -71,8 +71,8 @@ function bookDirectOfficerPointer(
|
|
|
71
71
|
summoned: PublicSummonResult,
|
|
72
72
|
): void {
|
|
73
73
|
if (
|
|
74
|
-
result.status !== "
|
|
75
|
-
&& result.status !== "
|
|
74
|
+
result.status !== "converged"
|
|
75
|
+
&& result.status !== "continue"
|
|
76
76
|
&& result.status !== "escalate"
|
|
77
77
|
&& result.status !== "needs_reask"
|
|
78
78
|
&& result.status !== "transport_failure"
|
|
@@ -101,7 +101,8 @@ function bookDirectOfficerPointer(
|
|
|
101
101
|
* Carries officer receipt + nested runId so seat settlement can project the
|
|
102
102
|
* public terminal without a second authority.
|
|
103
103
|
*/
|
|
104
|
-
export type
|
|
104
|
+
export type SubmissionGateOutcome = {
|
|
105
|
+
readonly status: "converged" | "continue";
|
|
105
106
|
readonly officer: GateOfficer;
|
|
106
107
|
readonly receipt: unknown;
|
|
107
108
|
readonly runId?: string;
|
|
@@ -110,13 +111,13 @@ export type GatekeeperPassOutcome = {
|
|
|
110
111
|
/**
|
|
111
112
|
* Shared envelope: project gate, book officer pointer, map onto host actions.
|
|
112
113
|
* Review loop has no round cap (#753 no-round-cap).
|
|
113
|
-
* On
|
|
114
|
+
* On converged, returns the officer snapshot (receipt + nested runId) for seat projection.
|
|
114
115
|
*/
|
|
115
|
-
export async function
|
|
116
|
+
export async function requireSubmissionGate(options: {
|
|
116
117
|
readonly context: ExtensionContext | HostContext;
|
|
117
118
|
readonly subject: GatekeeperSubject;
|
|
118
119
|
readonly signal?: AbortSignal;
|
|
119
|
-
readonly hostActions:
|
|
120
|
+
readonly hostActions: SubmissionGateHostActions;
|
|
120
121
|
readonly toolCallId: string;
|
|
121
122
|
/**
|
|
122
123
|
* In-flight parent typed payload for this gate turn (#879). Relayed verbatim
|
|
@@ -125,9 +126,9 @@ export async function requireGatekeeperPass(options: {
|
|
|
125
126
|
readonly submission?: unknown;
|
|
126
127
|
/** Lowest seam: same as runGatekeeper options.summonOfficer — offline tracers only. */
|
|
127
128
|
readonly summonOfficer?: GateOfficerSummon;
|
|
128
|
-
}): Promise<
|
|
129
|
+
}): Promise<SubmissionGateOutcome | void> {
|
|
129
130
|
let reask: string | undefined;
|
|
130
|
-
// No round cap — end only on
|
|
131
|
+
// No round cap — end only on converged, continue/escalate, or real failure.
|
|
131
132
|
const summonOfficer =
|
|
132
133
|
options.summonOfficer ??
|
|
133
134
|
createDefaultGateOfficerSummon({
|
|
@@ -156,8 +157,19 @@ export async function requireGatekeeperPass(options: {
|
|
|
156
157
|
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
|
-
if (gatekeeper.status === "
|
|
160
|
+
if (gatekeeper.status === "converged") {
|
|
160
161
|
return {
|
|
162
|
+
status: "converged",
|
|
163
|
+
officer: projected.officer,
|
|
164
|
+
receipt: gatekeeper.receipt,
|
|
165
|
+
...(typeof gatekeeper.runId === "string" && gatekeeper.runId.trim() !== ""
|
|
166
|
+
? { runId: gatekeeper.runId }
|
|
167
|
+
: {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (gatekeeper.status === "continue") {
|
|
171
|
+
return {
|
|
172
|
+
status: "continue",
|
|
161
173
|
officer: projected.officer,
|
|
162
174
|
receipt: gatekeeper.receipt,
|
|
163
175
|
...(typeof gatekeeper.runId === "string" && gatekeeper.runId.trim() !== ""
|
|
@@ -181,7 +193,8 @@ export async function requireGatekeeperPass(options: {
|
|
|
181
193
|
if (gatekeeper.status === "escalate") {
|
|
182
194
|
throw new GatekeeperDecisionError(gatekeeper);
|
|
183
195
|
}
|
|
184
|
-
//
|
|
196
|
+
// no_receipt: keep the lifecycle failure channel; continue is an ordinary
|
|
197
|
+
// nonterminal tool result above, not an exception or correctable rejection.
|
|
185
198
|
options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
|
|
186
199
|
throw new GatekeeperDecisionError(gatekeeper);
|
|
187
200
|
}
|
package/src/submission-ledger.ts
CHANGED
|
@@ -593,7 +593,7 @@ export async function sealAcceptedSubmission(options: {
|
|
|
593
593
|
*/
|
|
594
594
|
export function createSubmissionLedgerHost(
|
|
595
595
|
host: RoleHost,
|
|
596
|
-
outputTools: ReadonlyMap<string, TerminalRoleName>,
|
|
596
|
+
outputTools: ReadonlyMap<string, TerminalRoleName | readonly TerminalRoleName[]>,
|
|
597
597
|
failInfrastructure: (error: unknown, context: HostContext) => never = (error) => { throw error; },
|
|
598
598
|
projectClosure: (closed: ClosedSubmission, context: HostContext) => void | Promise<void> = () => undefined,
|
|
599
599
|
options?: { home?: string },
|
|
@@ -646,7 +646,16 @@ export function createSubmissionLedgerHost(
|
|
|
646
646
|
return {
|
|
647
647
|
...host,
|
|
648
648
|
registerTool(tool) {
|
|
649
|
-
const
|
|
649
|
+
const registeredRole = outputTools.get(tool.name);
|
|
650
|
+
let role: TerminalRoleName | undefined;
|
|
651
|
+
if (typeof registeredRole === "string") {
|
|
652
|
+
role = registeredRole;
|
|
653
|
+
} else if (registeredRole !== undefined) {
|
|
654
|
+
const activeRole = host.getFlag("ak-role");
|
|
655
|
+
role = typeof activeRole === "string" && registeredRole.includes(activeRole as TerminalRoleName)
|
|
656
|
+
? activeRole as TerminalRoleName
|
|
657
|
+
: undefined;
|
|
658
|
+
}
|
|
650
659
|
if (role === undefined) return host.registerTool(tool);
|
|
651
660
|
host.registerTool({
|
|
652
661
|
...tool,
|
|
@@ -735,7 +744,8 @@ export function createSubmissionLedgerHost(
|
|
|
735
744
|
await projectClosure(closed, context);
|
|
736
745
|
return result;
|
|
737
746
|
}
|
|
738
|
-
//
|
|
747
|
+
// A continuing gate leaves the candidate recorded but has not accepted it.
|
|
748
|
+
if (result.terminate === false) return result;
|
|
739
749
|
const closed: ClosedSubmission = {
|
|
740
750
|
role,
|
|
741
751
|
kind: "accepted",
|
package/src/ticket-trajectory.ts
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
validateAcceptedDetails,
|
|
45
45
|
type TerminatingToolName,
|
|
46
46
|
} from "./package-contracts/terminating-tools.ts";
|
|
47
|
-
import { PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
|
|
47
|
+
import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
/** Declared refresh bound for the same viewing surface (seconds). */
|
|
@@ -140,9 +140,11 @@ async function realpathOrLexicalIfMissing(path: string): Promise<string> {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const TOOL_TO_ROLE: ReadonlyMap<string, string> = new Map(
|
|
144
|
-
PACKAGED_ROLE_REGISTRY.
|
|
145
|
-
)
|
|
143
|
+
const TOOL_TO_ROLE: ReadonlyMap<string, string> = new Map([
|
|
144
|
+
...PACKAGED_ROLE_REGISTRY.filter((entry) => entry.outputTool !== "ak_submission_output")
|
|
145
|
+
.map((entry) => [entry.outputTool, entry.role] as const),
|
|
146
|
+
...LEGACY_REVIEW_OUTPUT_ROLES,
|
|
147
|
+
]);
|
|
146
148
|
|
|
147
149
|
const NAME_PREFIX_ROLES: readonly string[] = PACKAGED_ROLE_REGISTRY.map((entry) => entry.role);
|
|
148
150
|
|
|
@@ -324,6 +326,9 @@ function resolveStation(input: {
|
|
|
324
326
|
invocationRole?: string;
|
|
325
327
|
runId: string;
|
|
326
328
|
}): { station: string; stationSource: StationSource } {
|
|
329
|
+
if (input.invocationRole && input.toolNames.includes("ak_submission_output")) {
|
|
330
|
+
return { station: input.invocationRole, stationSource: "invocation" };
|
|
331
|
+
}
|
|
327
332
|
for (const toolName of input.toolNames) {
|
|
328
333
|
const role = roleFromToolName(toolName);
|
|
329
334
|
if (role) return { station: role, stationSource: "tool" };
|
package/src/worker-role.ts
CHANGED
|
@@ -287,7 +287,7 @@ export function createFixerRoleRuntime(
|
|
|
287
287
|
let pass;
|
|
288
288
|
try {
|
|
289
289
|
pass = WORKER_DONE_STATUSES.has(workerStatusOf(output))
|
|
290
|
-
? await pi.
|
|
290
|
+
? await pi.requireSubmissionGate!({
|
|
291
291
|
context: ctx,
|
|
292
292
|
subject: { kind: "worker_completion" },
|
|
293
293
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
@@ -303,6 +303,13 @@ export function createFixerRoleRuntime(
|
|
|
303
303
|
}
|
|
304
304
|
throw error;
|
|
305
305
|
}
|
|
306
|
+
if (pass?.status === "continue") {
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: "text" as const, text: readableGateItem(pass.receipt) }],
|
|
309
|
+
details: output,
|
|
310
|
+
terminate: false,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
306
313
|
const acceptedDetails = output;
|
|
307
314
|
return {
|
|
308
315
|
content: pass === undefined ? [] : [{ type: "text" as const, text: readableGateItem(pass.receipt) }],
|
|
@@ -435,7 +442,7 @@ export function createCoderRoleRuntime(
|
|
|
435
442
|
let pass;
|
|
436
443
|
try {
|
|
437
444
|
pass = WORKER_DONE_STATUSES.has(workerStatusOf(output))
|
|
438
|
-
? await pi.
|
|
445
|
+
? await pi.requireSubmissionGate!({
|
|
439
446
|
context: ctx,
|
|
440
447
|
subject: { kind: "worker_completion" },
|
|
441
448
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
@@ -451,6 +458,13 @@ export function createCoderRoleRuntime(
|
|
|
451
458
|
}
|
|
452
459
|
throw error;
|
|
453
460
|
}
|
|
461
|
+
if (pass?.status === "continue") {
|
|
462
|
+
return {
|
|
463
|
+
content: [{ type: "text" as const, text: readableGateItem(pass.receipt) }],
|
|
464
|
+
details: output,
|
|
465
|
+
terminate: false,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
454
468
|
const acceptedDetails = output;
|
|
455
469
|
return {
|
|
456
470
|
content: pass === undefined ? [] : [{ type: "text" as const, text: readableGateItem(pass.receipt) }],
|