@akagilnc/pi-workflow-roles 0.1.3644 → 0.1.3652

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.
Files changed (92) hide show
  1. package/dist/acp-host/production-host.js +3089 -2187
  2. package/dist/activation-ledger-session.js +105 -0
  3. package/dist/activation-ledger.js +139 -0
  4. package/dist/activation-trace.js +33 -0
  5. package/dist/agent-start-materials.js +11 -0
  6. package/dist/audit-escalation.js +5 -5
  7. package/dist/auditor-role.js +12 -0
  8. package/dist/canonical-skill-binding.js +64 -0
  9. package/dist/collector-identity.js +151 -0
  10. package/dist/collector-receipt.js +95 -0
  11. package/dist/collector-role.js +441 -0
  12. package/dist/compliance-transport.js +11 -57
  13. package/dist/countersign-role.js +28 -0
  14. package/dist/diarist-contracts.js +0 -12
  15. package/dist/diarist-role.js +29 -0
  16. package/dist/doctor-contracts.js +0 -2
  17. package/dist/doctor-role.js +74 -0
  18. package/dist/fixer-bash-seatbelt.js +13 -0
  19. package/dist/gatekeeper-pass-envelope.js +72 -0
  20. package/dist/gleaner-left-contracts.js +0 -7
  21. package/dist/gleaner-left-role.js +26 -0
  22. package/dist/inspector-contracts.js +0 -4
  23. package/dist/inspector-role.js +29 -0
  24. package/dist/judge-role.js +106 -0
  25. package/dist/merger-role.js +66 -0
  26. package/dist/navigator-public-session.js +1 -5
  27. package/dist/navigator-role.js +12 -0
  28. package/dist/notary-contracts.js +0 -18
  29. package/dist/notary-role.js +137 -0
  30. package/dist/oauth-keepalive.js +158 -0
  31. package/dist/package-contracts/auditor-output.js +0 -13
  32. package/dist/package-contracts/gatekeeper-output.js +0 -10
  33. package/dist/package-contracts/judge-output.js +0 -2
  34. package/dist/package-contracts/navigator-output.js +0 -4
  35. package/dist/package-contracts/terminating-tools.js +2 -16
  36. package/dist/packaged-role-registry.js +1 -18
  37. package/dist/pi/adapter.js +211 -0
  38. package/dist/pi/role-turn-host.js +0 -2
  39. package/dist/public-cli/instruction-seat-run.js +2 -6
  40. package/dist/public-cli/invocation.js +0 -7
  41. package/dist/public-cli/main.js +138 -735
  42. package/dist/public-cli/option-definitions.js +0 -14
  43. package/dist/public-cli/registry.js +1 -2
  44. package/dist/public-cli/run-lifecycle.js +1 -3
  45. package/dist/public-cli/settlement.js +66 -537
  46. package/dist/public-role-summons.js +0 -14
  47. package/dist/reviewer-child-executor.js +163 -70
  48. package/dist/reviewer-construction.js +2 -2
  49. package/dist/reviewer-role.js +159 -0
  50. package/dist/reviewer-settlement.js +66 -0
  51. package/dist/role-runtime.js +1394 -0
  52. package/dist/stderr-jsonl.js +26 -0
  53. package/dist/tool-execution-observation.js +141 -0
  54. package/dist/worker-role.js +343 -0
  55. package/dist/worker-submission-gates.js +305 -0
  56. package/extensions/role-runtime.ts +1 -2
  57. package/package.json +1 -1
  58. package/src/acp-host/production-host.ts +0 -1
  59. package/src/audit-escalation.ts +9 -9
  60. package/src/compliance-transport.ts +28 -94
  61. package/src/diarist-contracts.ts +0 -12
  62. package/src/doctor-contracts.ts +0 -1
  63. package/src/doctor-role.ts +2 -2
  64. package/src/gleaner-left-contracts.ts +0 -9
  65. package/src/host-contracts.ts +0 -2
  66. package/src/inspector-contracts.ts +0 -8
  67. package/src/judge-role.ts +4 -5
  68. package/src/navigator-public-session.ts +4 -5
  69. package/src/notary-contracts.ts +0 -18
  70. package/src/package-contracts/auditor-output.ts +0 -11
  71. package/src/package-contracts/gatekeeper-output.ts +0 -11
  72. package/src/package-contracts/judge-output.ts +0 -1
  73. package/src/package-contracts/navigator-output.ts +0 -3
  74. package/src/package-contracts/terminating-tools.ts +1 -19
  75. package/src/packaged-role-registry.ts +1 -19
  76. package/src/pi/role-turn-host.ts +0 -2
  77. package/src/public-cli/cli.ts +1 -5
  78. package/src/public-cli/instruction-seat-run.ts +5 -14
  79. package/src/public-cli/invocation.ts +3 -21
  80. package/src/public-cli/option-definitions.ts +0 -16
  81. package/src/public-cli/registry.ts +1 -2
  82. package/src/public-cli/run-lifecycle.ts +0 -6
  83. package/src/public-cli/settlement.ts +58 -572
  84. package/src/public-cli/terminal.ts +0 -1
  85. package/src/public-role-summons.ts +0 -15
  86. package/src/reviewer-child-executor.ts +199 -98
  87. package/src/reviewer-construction.ts +3 -3
  88. package/src/role-runtime.ts +1 -33
  89. package/dist/package-contracts/evidence-child-output.js +0 -34
  90. package/souls/evidence-child.md +0 -1
  91. package/src/evidence-child-role.ts +0 -22
  92. package/src/package-contracts/evidence-child-output.ts +0 -51
@@ -0,0 +1,74 @@
1
+ import { disposeComplianceDecision } from "./audit-escalation.js";
2
+ import { ComplianceResponseRetentionError } from "./compliance-transport.js";
3
+ import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.js";
4
+ import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput } from "./doctor-contracts.js";
5
+ import { sitianReport } from "./sitian-facade.js";
6
+ export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME };
7
+ export const DOCTOR_CASE_FLAG = { name: "ak-doctor-case", definition: { description: "Retained .ak-roles/books/<book>/issues/<n>/runs directory", type: "string" } };
8
+ function appendCandidate(ctx, data) {
9
+ try {
10
+ sitianReport({
11
+ level: "event",
12
+ kind: "candidate",
13
+ cwd: ctx.cwd,
14
+ sessionParent: ctx.sessionManager.getSessionFile(),
15
+ payload: data,
16
+ source: "doctor-role",
17
+ });
18
+ }
19
+ catch (error) {
20
+ throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
21
+ }
22
+ const append = ctx.sessionManager.appendCustomEntry;
23
+ if (typeof append === "function") {
24
+ try {
25
+ append.call(ctx.sessionManager, DOCTOR_CANDIDATE_ENTRY_TYPE, data);
26
+ }
27
+ catch (error) {
28
+ throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
29
+ }
30
+ }
31
+ }
32
+ export function createDoctorRoleRuntime(pi, dependencies, host) {
33
+ let activation;
34
+ let registered = false;
35
+ pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
36
+ return { async activate() {
37
+ const path = pi.getFlag(DOCTOR_CASE_FLAG.name);
38
+ if (typeof path !== "string" || !path.trim())
39
+ throw new Error("Doctor requires --ak-doctor-case");
40
+ const soul = (await dependencies.loadSoul()).trim();
41
+ if (!soul)
42
+ throw new Error("Doctor soul is empty");
43
+ const patient = await dependencies.loadCase(path);
44
+ activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
45
+ if (!registered) {
46
+ registered = true;
47
+ pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id, params) { if (!activation)
48
+ throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text", text: JSON.stringify(details) }], details }; } });
49
+ pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id, params, signal, _update, ctx) { if (!activation)
50
+ throw new Error("太医署未激活"); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try {
51
+ appendCandidate(ctx, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity });
52
+ }
53
+ catch (error) {
54
+ host.failInfrastructure(error, ctx, id);
55
+ } let audit; try {
56
+ audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal });
57
+ }
58
+ catch (error) {
59
+ host.failInfrastructure(error, ctx, id);
60
+ } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision(audit, { pass: (usage) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true, ...usageProjection }), received: (auditReceived, usageProjection) => ({ content: [{ type: "text", text: DOCTOR_ACCEPTED_TEXT }], details: { ...acceptedDetails, audit: auditReceived.reply }, terminate: true, ...usageProjection }), bounce: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
61
+ pi.on("before_agent_start", (event) => { if (!activation)
62
+ throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "由留存 session 字节推导,封入受理回执。", cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; });
63
+ }
64
+ const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME];
65
+ const names = pi.getAllTools().map((tool) => tool.name);
66
+ for (const name of required)
67
+ if (names.filter((item) => item === name).length !== 1)
68
+ throw new Error(`Doctor required tool collision or missing: ${name}`);
69
+ pi.setActiveTools(required);
70
+ const active = pi.getActiveTools?.() ?? required;
71
+ if (active.length !== 2 || !required.every((name) => active.includes(name)))
72
+ throw new Error("Doctor active tool narrowing failed");
73
+ } };
74
+ }
@@ -0,0 +1,13 @@
1
+ /** Exact case-sensitive substring literals blocked on Fixer bash (ADR 0008). */
2
+ export const FIXER_BASH_FORBIDDEN_LITERALS = [
3
+ "rm -rf",
4
+ "git reset --hard",
5
+ "git clean",
6
+ "git checkout --",
7
+ ];
8
+ export function matchFixerBashForbiddenLiteral(command) {
9
+ return FIXER_BASH_FORBIDDEN_LITERALS.find((literal) => command.includes(literal));
10
+ }
11
+ export function fixerBashSeatbeltDenyReason(matched) {
12
+ return `修内司 bash 拦截:命中禁用字面量 ${matched}`;
13
+ }
@@ -0,0 +1,72 @@
1
+ import { bookDirectOfficerRunPointer } from "./archivist-record-entry.js";
2
+ import { GatekeeperDecisionError, GatekeeperEscalationError, projectGatekeeperRun, } from "./gatekeeper-role.js";
3
+ import { sessionFileFromPublicSummon } from "./session-assistant-usage.js";
4
+ /**
5
+ * Book a typed pointer under parent session/auditor-roles (archivist-owned write).
6
+ * Offline mocks without a real session leave no nested volume (lawful zero).
7
+ */
8
+ function bookDirectOfficerPointer(context, officer, result, summoned) {
9
+ if (result.status !== "pass"
10
+ && result.status !== "bounce"
11
+ && result.status !== "escalate"
12
+ && result.status !== "unreadable") {
13
+ return;
14
+ }
15
+ const parentFile = context.sessionManager?.getSessionFile?.();
16
+ if (typeof parentFile !== "string" || parentFile.trim() === "")
17
+ return;
18
+ const sessionFile = sessionFileFromPublicSummon(summoned);
19
+ if (sessionFile === undefined) {
20
+ // No independent 正本 to point at — do not synthesize a parallel session.
21
+ return;
22
+ }
23
+ bookDirectOfficerRunPointer({
24
+ parentSessionFile: parentFile,
25
+ officer,
26
+ sessionFile,
27
+ ...(typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== ""
28
+ ? { runDirectory: summoned.runDirectory }
29
+ : {}),
30
+ });
31
+ }
32
+ /**
33
+ * Shared envelope: project gate, book officer pointer, map onto host actions.
34
+ * unreadable = parent stands (ADR 0055); never mechanical NonPass reject.
35
+ */
36
+ export async function requireGatekeeperPass(options) {
37
+ const projected = await projectGatekeeperRun({
38
+ context: options.context,
39
+ subject: options.subject,
40
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
41
+ ...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
42
+ });
43
+ const gatekeeper = projected.result;
44
+ // Envelope-owned pointer book. Failure is host infrastructure — single face.
45
+ if (projected.summoned !== undefined) {
46
+ try {
47
+ bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned);
48
+ }
49
+ catch (error) {
50
+ options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
51
+ }
52
+ }
53
+ if (gatekeeper.status === "pass")
54
+ return;
55
+ // ADR 0055 / #675: shape-unreadable officer output must not mechanically reject parent.
56
+ if (gatekeeper.status === "unreadable")
57
+ return;
58
+ if (gatekeeper.status === "transport_failure") {
59
+ const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
60
+ error.stage = gatekeeper.stage;
61
+ error.reason = gatekeeper.reason;
62
+ if (gatekeeper.submission !== undefined)
63
+ error.submission = gatekeeper.submission;
64
+ options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
65
+ }
66
+ if (gatekeeper.status === "escalate") {
67
+ throw new GatekeeperEscalationError(gatekeeper);
68
+ }
69
+ // bounce / no_receipt: typed non-pass — envelope owns execute→tool_result bridge.
70
+ options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
71
+ throw new GatekeeperDecisionError(gatekeeper);
72
+ }
@@ -22,16 +22,9 @@ function validateRecordedGleanerLeftOutput(value) {
22
22
  }
23
23
  throw new Error("Gleaner-left output has no execution discriminator");
24
24
  }
25
- function gleanerLeftDecisiveFacts(output) {
26
- const facts = { status: output.status };
27
- const findings = output.findings;
28
- if (Array.isArray(findings)) facts.findings = findings;
29
- return facts;
30
- }
31
25
  export {
32
26
  GLEANER_LEFT_ACCEPTED_TEXT,
33
27
  GLEANER_LEFT_BASE_FLAG,
34
28
  GLEANER_LEFT_OUTPUT_TOOL_NAME,
35
- gleanerLeftDecisiveFacts,
36
29
  validateRecordedGleanerLeftOutput
37
30
  };
@@ -0,0 +1,26 @@
1
+ import { Type } from "typebox";
2
+ import { openToolObject } from "./open-tool-schema.js";
3
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
4
+ import { GLEANER_LEFT_OUTPUT_TOOL_NAME, validateRecordedGleanerLeftOutput, } from "./gleaner-left-contracts.js";
5
+ export { GLEANER_LEFT_ACCEPTED_TEXT, GLEANER_LEFT_OUTPUT_TOOL_NAME, } from "./gleaner-left-contracts.js";
6
+ export { validateRecordedGleanerLeftOutput };
7
+ /** 左拾遗弹章交卷形状;形状指引,非 schema 闸。 */
8
+ export const gleanerLeftOutputSchema = withInfrastructureFailureDeclaration(openToolObject(Type.Object({
9
+ status: Type.Unknown({
10
+ description: "completed — 形状指引,非 schema 闸",
11
+ }),
12
+ findings: Type.Array(Type.Object({
13
+ pointer: Type.String({ description: "文件/行指针" }),
14
+ statement: Type.String({ description: "疑点陈述" }),
15
+ }, { additionalProperties: true, description: "一条弹章" }), { description: "弹章列表;空列表合法完局" }),
16
+ })));
17
+ /**
18
+ * 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
19
+ */
20
+ export const GLEANER_LEFT_TOOL_SPEC = {
21
+ name: GLEANER_LEFT_OUTPUT_TOOL_NAME,
22
+ label: "左拾遗输出",
23
+ description: "左拾遗弹章。",
24
+ promptSnippet: "左拾遗弹章",
25
+ parameters: gleanerLeftOutputSchema,
26
+ };
@@ -15,12 +15,8 @@ function validateRecordedInspectorOutput(value) {
15
15
  }
16
16
  throw new Error("Inspector output has no execution discriminator");
17
17
  }
18
- function inspectorDecisiveFacts(output) {
19
- return { ...output };
20
- }
21
18
  export {
22
19
  INSPECTOR_ACCEPTED_TEXT,
23
20
  INSPECTOR_OUTPUT_TOOL_NAME,
24
- inspectorDecisiveFacts,
25
21
  validateRecordedInspectorOutput
26
22
  };
@@ -0,0 +1,29 @@
1
+ import { Type } from "typebox";
2
+ import { openToolObject } from "./open-tool-schema.js";
3
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
4
+ import { INSPECTOR_ACCEPTED_TEXT, INSPECTOR_OUTPUT_TOOL_NAME, validateRecordedInspectorOutput, } from "./inspector-contracts.js";
5
+ export { INSPECTOR_ACCEPTED_TEXT, INSPECTOR_OUTPUT_TOOL_NAME, };
6
+ export { INSPECTOR_OUTPUT_TOOL_NAME as INSPECTOR_OUTPUT_TOOL };
7
+ export { validateRecordedInspectorOutput };
8
+ /** 察院事后察举交卷形状;形状指引,非 schema 闸。 */
9
+ export const inspectorOutputSchema = withInfrastructureFailureDeclaration(openToolObject(Type.Object({
10
+ status: Type.Unknown({
11
+ description: "pass | bounce | escalate — 形状指引,非 schema 闸",
12
+ }),
13
+ findings: Type.Unknown({
14
+ description: "随交卷留存的问题记录",
15
+ }),
16
+ reason: Type.Optional(Type.Unknown({
17
+ description: "status 为 escalate 时的上呈理由",
18
+ })),
19
+ })));
20
+ /**
21
+ * 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
22
+ */
23
+ export const INSPECTOR_TOOL_SPEC = {
24
+ name: INSPECTOR_OUTPUT_TOOL_NAME,
25
+ label: "察院输出",
26
+ description: "察院终局回执,状态为 pass、bounce 或 escalate。",
27
+ promptSnippet: "察院终局回执",
28
+ parameters: inspectorOutputSchema,
29
+ };
@@ -0,0 +1,106 @@
1
+ import { stringEnum } from "./host-contracts.js";
2
+ import { Type } from "typebox";
3
+ import { disposeComplianceDecision } from "./audit-escalation.js";
4
+ import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
5
+ import { JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./package-contracts/judge-output.js";
6
+ export { JUDGE_OUTPUT_TOOL_NAME };
7
+ export const judgeVerdictSchema = withInfrastructureFailureDeclaration(Type.Object({
8
+ judgeStatus: stringEnum(["converged", "continue", "escalate"], { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
9
+ fix: Type.Optional(Type.Object({ summary: Type.String({ minLength: 1, description: "continue 时的补救摘要" }) }, { additionalProperties: false, description: "continue 时的补救说明" })),
10
+ classes: Type.Optional(Type.Array(Type.Object({
11
+ name: Type.String({ minLength: 1 }),
12
+ owner: Type.String({ minLength: 1 }),
13
+ boundary: Type.String({ minLength: 1 }),
14
+ disposition: Type.String({ minLength: 1 }),
15
+ }, { additionalProperties: false }), { minItems: 1, description: "已裁决 finding 类及其 owner 与修理边界" })),
16
+ note: Type.Optional(Type.String({ minLength: 1, description: "可选裁决附注" })),
17
+ evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
18
+ decisionGate: Type.Optional(Type.Object({
19
+ question: Type.String({ minLength: 1 }),
20
+ options: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
21
+ }, { additionalProperties: false, description: "需人权威处置的问题与选项" })),
22
+ }, { additionalProperties: true }));
23
+ judgeVerdictSchema.required = [];
24
+ export function validateVerdict(verdict) {
25
+ return validateAcceptedJudgeDetails(verdict);
26
+ }
27
+ export function createJudgeRoleRuntime(pi, dependencies, hostActions) {
28
+ let soul;
29
+ let lifecycleRegistered = false;
30
+ return {
31
+ async activate() {
32
+ soul = (await dependencies.loadSoul()).trim();
33
+ if (soul.length === 0)
34
+ throw new Error("Judge soul is empty");
35
+ if (!lifecycleRegistered) {
36
+ lifecycleRegistered = true;
37
+ pi.registerTool({
38
+ name: JUDGE_OUTPUT_TOOL_NAME,
39
+ label: "大理寺输出",
40
+ description: "提交大理寺终局判词;受理前经审刑院审计。",
41
+ promptSnippet: "提交大理寺终局判词",
42
+ parameters: judgeVerdictSchema,
43
+ async execute(toolCallId, parameters, signal, _onUpdate, ctx) {
44
+ if (soul === undefined)
45
+ throw new Error("大理寺职分未装载");
46
+ const verdict = validateVerdict(parameters);
47
+ // Candidate verdict is already on the parent session books as this
48
+ // tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
49
+ // Gatekeeper runs after the draft is booked and before existing auditor.
50
+ await pi.requireGatekeeperPass({
51
+ context: ctx,
52
+ subject: { kind: "judge_draft" },
53
+ ...(signal === undefined ? {} : { signal }),
54
+ hostActions,
55
+ toolCallId,
56
+ });
57
+ let audit;
58
+ try {
59
+ audit = await dependencies.auditSoulCompliance(signal === undefined
60
+ ? { context: ctx }
61
+ : { context: ctx, signal });
62
+ }
63
+ catch (error) {
64
+ hostActions.failInfrastructure(error, ctx, toolCallId);
65
+ }
66
+ const acceptedDetails = verdict;
67
+ return disposeComplianceDecision(audit, {
68
+ pass: (usage) => ({
69
+ content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
70
+ details: acceptedDetails,
71
+ terminate: true,
72
+ ...(usage === undefined ? {} : { usage }),
73
+ }),
74
+ noReceipt: (auditNoReceipt, usageProjection) => ({
75
+ content: [{ type: "text", text: JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }],
76
+ details: { ...acceptedDetails, auditNoReceipt },
77
+ terminate: true,
78
+ ...usageProjection,
79
+ }),
80
+ // #757: parent stands with auditor raw reply — no unreadable judgment.
81
+ received: (auditReceived, usageProjection) => ({
82
+ content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
83
+ details: { ...acceptedDetails, audit: auditReceived.reply },
84
+ terminate: true,
85
+ ...usageProjection,
86
+ }),
87
+ bounce: (violations) => {
88
+ throw new Error(`大理寺回执违 soul:${violations.join("; ")}`);
89
+ },
90
+ escalate: (result) => result,
91
+ },
92
+ // #380: escalate deliveredOutput must carry the same mechanical projection.
93
+ acceptedDetails);
94
+ },
95
+ });
96
+ pi.on("before_agent_start", (event) => {
97
+ if (soul === undefined)
98
+ throw new Error("大理寺职分未装载");
99
+ return {
100
+ systemPrompt: `${event.systemPrompt}\n\n<judge_soul>\n${soul}\n</judge_soul>`,
101
+ };
102
+ });
103
+ }
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,66 @@
1
+ import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, validateMergerOutput } from "./merger-contracts.js";
2
+ import { exactUtf8 } from "./exact-utf8.js";
3
+ import { isFullGitObjectId } from "./git-object-id.js";
4
+ export { MERGER_OUTPUT_TOOL_NAME };
5
+ export const MERGER_INPUT_FLAG = { name: "ak-merger-input", definition: { description: "Path to an immutable digest-bound Merger v1 input JSON file", type: "string" } };
6
+ function same(a, b) { return a.length === b.length && a.every((value, i) => value === b[i]); }
7
+ function materialText(input, key) { return exactUtf8(Buffer.from(input.materials[key].bytesBase64, "base64"), `Merger ${key}`); }
8
+ export function createMergerRoleRuntime(pi, dependencies, host) {
9
+ let activation;
10
+ let registered = false;
11
+ let accepted = false;
12
+ pi.registerFlag(MERGER_INPUT_FLAG.name, MERGER_INPUT_FLAG.definition);
13
+ return { async activate() {
14
+ const path = pi.getFlag(MERGER_INPUT_FLAG.name);
15
+ if (typeof path !== "string" || path.trim().length === 0)
16
+ throw new Error("Merger requires --ak-merger-input");
17
+ const soul = (await dependencies.loadSoul()).trim();
18
+ if (!soul)
19
+ throw new Error("Merger soul is empty");
20
+ const input = validateMergerInput(await dependencies.loadInput(path));
21
+ const state = await dependencies.gitState.activeMerge();
22
+ if (state.targetObjectId !== input.targetObjectId || state.sourceObjectId !== input.sourceObjectId || !same(state.unmergedPaths, input.expectedConflictPaths) || state.unmergedPaths.length === 0 || !isFullGitObjectId(state.automaticMergeTreeId) || state.automaticMergeTreeId.length !== input.targetObjectId.length)
23
+ throw new Error("Merger activation rejected repository parent, merge, automatic-result, or complete conflict-set drift");
24
+ activation = { soul, input, automaticMergeTreeId: state.automaticMergeTreeId };
25
+ accepted = false;
26
+ if (!registered) {
27
+ registered = true;
28
+ pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate;基础设施及 Git 失败由 abort 通道承接。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
29
+ async execute(id, params, _signal, _update, ctx) {
30
+ if (!activation)
31
+ throw new Error("校书郎未激活");
32
+ if (accepted)
33
+ throw new Error("合并回执已受理");
34
+ let output;
35
+ try {
36
+ output = validateMergerOutput(params, activation.input.attemptId);
37
+ }
38
+ catch (error) {
39
+ host.failInfrastructure(error, ctx, id);
40
+ }
41
+ if (output.status === "completed") {
42
+ let state;
43
+ try {
44
+ state = await dependencies.gitState.completedMerge(output.mergeCommitId, activation.automaticMergeTreeId);
45
+ }
46
+ catch (error) {
47
+ host.failInfrastructure(error, ctx, id);
48
+ }
49
+ const scope = new Set(activation.input.resolutionScope);
50
+ if (state.mergeCommitId !== output.mergeCommitId || !same(state.parentObjectIds, [activation.input.targetObjectId, activation.input.sourceObjectId]) || state.unmergedPaths.length !== 0 || !state.worktreeClean || state.resolutionChangedPaths.some(path => !scope.has(path)))
51
+ host.failInfrastructure(new Error("Merger completed-state verification failed"), ctx, id);
52
+ }
53
+ accepted = true;
54
+ const acceptedDetails = output;
55
+ return { content: [{ type: "text", text: MERGER_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true };
56
+ } });
57
+ pi.on("before_agent_start", (event) => { if (!activation)
58
+ throw new Error("校书郎未激活"); const admitted = { attemptId: activation.input.attemptId, targetObjectId: activation.input.targetObjectId, sourceObjectId: activation.input.sourceObjectId, task: materialText(activation.input, "task"), authority: materialText(activation.input, "authority"), targetIntent: materialText(activation.input, "targetIntent"), sourceIntent: materialText(activation.input, "sourceIntent"), expectedConflictPaths: activation.input.expectedConflictPaths, resolutionScope: activation.input.resolutionScope, authorizedChecks: activation.input.authorizedChecks }; return { systemPrompt: `${event.systemPrompt}\n\n<merger_soul>\n${activation.soul}\n</merger_soul>\n\n<merger_assignment>\n${JSON.stringify(admitted)}\n</merger_assignment>` }; });
59
+ }
60
+ // Host-neutral AK output only (ADR 0018 fail-closed); no host-builtin name hard-require.
61
+ const all = pi.getAllTools().map(tool => tool.name);
62
+ if (all.filter(item => item === MERGER_OUTPUT_TOOL_NAME).length !== 1) {
63
+ throw new Error(`Merger required tool collision or missing: ${MERGER_OUTPUT_TOOL_NAME}`);
64
+ }
65
+ } };
66
+ }
@@ -98,11 +98,7 @@ function createNativeNavigatorSessionFactory() {
98
98
  return;
99
99
  }
100
100
  if (outcome.kind !== "accepted") {
101
- providerFailure = { source: "session", cause: "session" };
102
- throw navigatorUnavailableError(
103
- "session",
104
- new Error("Navigator public summon returned unusable terminal")
105
- );
101
+ return;
106
102
  }
107
103
  const candidates = outcome.decisiveFacts.candidates;
108
104
  if (!Array.isArray(candidates)) {
@@ -0,0 +1,12 @@
1
+ import { NAVIGATOR_OUTPUT_TOOL_NAME, navigatorOutputSchema } from "./package-contracts/navigator-output.js";
2
+ /**
3
+ * 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
4
+ * Candidates share the route-advice shape the attendance prepare tool owns.
5
+ */
6
+ export const NAVIGATOR_TOOL_SPEC = {
7
+ name: NAVIGATOR_OUTPUT_TOOL_NAME,
8
+ label: "游奕使建议",
9
+ description: "游奕使终局回执:排好序的下一步角色路线建议。",
10
+ promptSnippet: "游奕使建议",
11
+ parameters: navigatorOutputSchema,
12
+ };
@@ -74,30 +74,12 @@ function validateRecordedNotaryOutput(value) {
74
74
  }
75
75
  return projected;
76
76
  }
77
- function notaryDecisiveFacts(output) {
78
- const status = String(output.status);
79
- const facts = { status, officer: "notary" };
80
- if (status === "pass" || status === "bounce") {
81
- const findings = output.findings;
82
- facts.findingsCount = Array.isArray(findings) ? findings.length : 0;
83
- facts.findings = Array.isArray(findings) ? findings : [];
84
- }
85
- if (status === "bounce") {
86
- facts.disposition = "rewrite";
87
- }
88
- if (status === "escalate") {
89
- const reason = output.reason;
90
- if (reason !== void 0) facts.reason = reason;
91
- }
92
- return facts;
93
- }
94
77
  export {
95
78
  NOTARY_ACCEPTED_TEXT,
96
79
  NOTARY_FIXED_KICKOFF,
97
80
  NOTARY_OUTPUT_TOOL_NAME,
98
81
  NOTARY_SOURCE_RUN_FLAG,
99
82
  NOTARY_TICKET_FLAG,
100
- notaryDecisiveFacts,
101
83
  notaryOutputSchema,
102
84
  projectLawfulNotaryOutput,
103
85
  retainNotarySubmission,
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Public Notary role runtime — direct officer seat (not through Gatekeeper province).
3
+ * Caller supplies only a source-run locator; Notary self-fetches authoritative materials.
4
+ * Ticket flag register/read/session bind is envelope-owned (ADR 0018 / #582).
5
+ * This module keeps evidence assembly + projection only.
6
+ */
7
+ import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, notaryOutputSchema, projectLawfulNotaryOutput, retainNotarySubmission, } from "./notary-contracts.js";
8
+ export { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, };
9
+ /** Optional ticket flag: absent/blank = unbound; non-empty invalid = honest fail. */
10
+ export function readNotaryTicketFlag(flag) {
11
+ if (flag === undefined)
12
+ return undefined;
13
+ if (typeof flag !== "string") {
14
+ throw new Error("Notary ak-notary-ticket-number is present but not a safe positive integer string");
15
+ }
16
+ if (flag.trim() === "")
17
+ return undefined;
18
+ const n = Number(flag);
19
+ if (!Number.isSafeInteger(n) || n < 1) {
20
+ throw new Error("Notary ak-notary-ticket-number is present but not a safe positive integer string");
21
+ }
22
+ return n;
23
+ }
24
+ /**
25
+ * Typed session bound (locator + optional ticket).
26
+ * Sole production projection for agent-start material + session custom entry.
27
+ */
28
+ export function projectNotarySessionBound(input) {
29
+ return {
30
+ sourceRun: input.sourceRun,
31
+ ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
32
+ };
33
+ }
34
+ /**
35
+ * Session custom-entry type for typed notary bound (written by shared envelope).
36
+ * Role module only projects; lifecycle write is envelope-owned (ADR 0018).
37
+ */
38
+ export const NOTARY_SESSION_BOUND_ENTRY = "notary-session-bound";
39
+ /**
40
+ * Project notary bound from host flags (envelope consumption seam).
41
+ * undefined when source-run flag absent/blank.
42
+ * Ticket invalid → throws (honest fail; envelope owns the read).
43
+ */
44
+ export function projectNotaryBoundFromFlags(getFlag) {
45
+ const path = getFlag(NOTARY_SOURCE_RUN_FLAG.name);
46
+ if (typeof path !== "string" || path.trim() === "")
47
+ return undefined;
48
+ const ticketNumber = readNotaryTicketFlag(getFlag(NOTARY_TICKET_FLAG.name));
49
+ return {
50
+ sourceRunPath: path,
51
+ ...(ticketNumber === undefined ? {} : { ticketNumber }),
52
+ };
53
+ }
54
+ /** Assemble systemPrompt body from base + soul only (NO embedded bound JSON).
55
+ * The typed session bound travels separately as `readingMaterial`; adapters fold
56
+ * it into the provider-visible prompt at the send boundary. */
57
+ export function assembleNotaryAgentStartPrompt(input) {
58
+ return `${input.baseSystemPrompt}\n\n<notary_soul>\n${input.soul}\n</notary_soul>`;
59
+ }
60
+ export function createNotaryRoleRuntime(pi, dependencies, host) {
61
+ let activation;
62
+ let registered = false;
63
+ // Baseline source-run flag registration (pre-#582). Ticket flag is envelope-owned.
64
+ pi.registerFlag(NOTARY_SOURCE_RUN_FLAG.name, NOTARY_SOURCE_RUN_FLAG.definition);
65
+ return {
66
+ /**
67
+ * @param admitted Optional ticket from envelope flag decode (ADR 0018).
68
+ * Role module never getFlag's the ticket — envelope owns that read.
69
+ */
70
+ async activate(admitted) {
71
+ const path = pi.getFlag(NOTARY_SOURCE_RUN_FLAG.name);
72
+ if (typeof path !== "string" || path.trim() === "") {
73
+ throw new Error("Notary requires --ak-notary-source-run");
74
+ }
75
+ const soul = (await dependencies.loadSoul()).trim();
76
+ if (soul.length === 0)
77
+ throw new Error("Notary soul is empty");
78
+ const sourceRun = await dependencies.loadSourceRunLocator(path);
79
+ activation = {
80
+ soul,
81
+ sourceRun,
82
+ ...(admitted?.ticketNumber === undefined
83
+ ? {}
84
+ : { ticketNumber: admitted.ticketNumber }),
85
+ };
86
+ if (!registered) {
87
+ registered = true;
88
+ pi.registerTool({
89
+ name: NOTARY_OUTPUT_TOOL_NAME,
90
+ label: "符宝郎输出",
91
+ description: "提交引文保真与票面对齐的 typed pass/bounce/escalate 决议。",
92
+ promptSnippet: "提交符宝郎决议",
93
+ parameters: notaryOutputSchema,
94
+ async execute(toolCallId, parameters, _signal, _onUpdate, ctx) {
95
+ if (activation === undefined) {
96
+ throw new Error("符宝郎未激活");
97
+ }
98
+ // Unique submission + terminate only. Shape is not an admission gate
99
+ // (第 0 条 / ADR 0055): lawful pass/bounce/escalate projected; else params as-is.
100
+ // #541 infra declaration + sole-final barrier are ledger-owned (#575).
101
+ const lawful = projectLawfulNotaryOutput(parameters);
102
+ const details = lawful ?? retainNotarySubmission(parameters);
103
+ return {
104
+ content: [{ type: "text", text: NOTARY_ACCEPTED_TEXT }],
105
+ details,
106
+ terminate: true,
107
+ };
108
+ },
109
+ });
110
+ // Evidence assembly only (ADR 0018): soul + bound projection.
111
+ // Ticket value is envelope-admitted at activate — not re-read from flags here.
112
+ pi.on("before_agent_start", (event) => {
113
+ if (activation === undefined) {
114
+ throw new Error("符宝郎未激活");
115
+ }
116
+ return {
117
+ systemPrompt: assembleNotaryAgentStartPrompt({
118
+ baseSystemPrompt: event.systemPrompt,
119
+ soul: activation.soul,
120
+ }),
121
+ readingMaterial: projectNotarySessionBound({
122
+ sourceRun: activation.sourceRun,
123
+ ...(activation.ticketNumber === undefined
124
+ ? {}
125
+ : { ticketNumber: activation.ticketNumber }),
126
+ }),
127
+ };
128
+ });
129
+ }
130
+ // Evidence role: keep Pi default tools + notary output (ADR 0064 unrestricted).
131
+ const all = pi.getAllTools().map((tool) => tool.name);
132
+ if (all.filter((name) => name === NOTARY_OUTPUT_TOOL_NAME).length !== 1) {
133
+ throw new Error(`Notary required tool collision or missing: ${NOTARY_OUTPUT_TOOL_NAME}`);
134
+ }
135
+ },
136
+ };
137
+ }