@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca

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 (109) hide show
  1. package/README.md +129 -39
  2. package/README.zh-CN.md +128 -39
  3. package/dist/activation-ledger-topology.js +1 -1
  4. package/dist/archivist-record-entry.js +47 -26
  5. package/dist/auditor-dossier-tool.js +3 -1
  6. package/dist/compliance-transport.js +17 -26
  7. package/dist/doctor-contracts.js +1 -2
  8. package/dist/dossier-resolution.js +4 -8
  9. package/dist/engine-detour-tool.js +33 -8
  10. package/dist/engine-detour.js +1 -0
  11. package/dist/evidence-child-executor.js +277 -424
  12. package/dist/host-contracts.js +22 -0
  13. package/dist/institutional-resolution.js +88 -0
  14. package/dist/merger-contracts.js +1 -2
  15. package/dist/navigator-attendance.js +16 -0
  16. package/dist/notary-contracts.js +2 -3
  17. package/dist/package-contracts/fixer-output.js +1 -2
  18. package/dist/packaged-role-registry.js +102 -9
  19. package/dist/pi/in-process-session.js +671 -0
  20. package/dist/public-cli/config.js +7 -5
  21. package/dist/public-cli/main.js +3767 -4113
  22. package/dist/reviewer-child-executor.js +6 -1
  23. package/dist/session-opening-materials.js +10 -38
  24. package/dist/sitian-appender.js +146 -0
  25. package/dist/sitian-contracts.js +21 -0
  26. package/dist/sitian-facade.js +15 -0
  27. package/dist/sitian-reader.js +52 -0
  28. package/extensions/role-runtime.ts +9 -7
  29. package/package.json +5 -5
  30. package/resources/engines/cursor.md +12 -26
  31. package/resources/engines/hermes.md +2 -23
  32. package/resources/engines/opus.md +1 -16
  33. package/resources/navigator-route-playbook.md +2 -2
  34. package/scripts/build-package.mjs +0 -1
  35. package/souls/quality-law.md +1 -1
  36. package/src/activation-ledger-topology.ts +1 -1
  37. package/src/archivist-record-entry.ts +57 -45
  38. package/src/auditor-dossier-tool.ts +4 -2
  39. package/src/canonical-skill-binding.ts +37 -27
  40. package/src/collector-role.ts +11 -20
  41. package/src/collector-tool-schemas.ts +1 -4
  42. package/src/compliance-transport.ts +19 -44
  43. package/src/doctor-auditor.ts +1 -5
  44. package/src/doctor-contracts.ts +1 -4
  45. package/src/doctor-role.ts +31 -8
  46. package/src/dossier-resolution.ts +8 -8
  47. package/src/engine-detour-tool.ts +48 -19
  48. package/src/engine-detour.ts +3 -0
  49. package/src/evidence-child-executor.ts +277 -485
  50. package/src/factory-board.ts +3 -3
  51. package/src/gatekeeper-role.ts +21 -10
  52. package/src/host-contracts.ts +386 -0
  53. package/src/in-process-session.ts +1 -115
  54. package/src/institutional-resolution.ts +135 -0
  55. package/src/judge-auditor.ts +1 -5
  56. package/src/judge-role.ts +37 -51
  57. package/src/merger-contracts.ts +1 -4
  58. package/src/merger-role.ts +7 -11
  59. package/src/navigator-attendance.ts +20 -1
  60. package/src/notary-contracts.ts +8 -11
  61. package/src/notary-role.ts +7 -23
  62. package/src/notary-source-run.ts +4 -3
  63. package/src/package-contracts/fixer-output.ts +1 -4
  64. package/src/package-resources/method-skill-binding.ts +10 -34
  65. package/src/packaged-role-registry.ts +122 -12
  66. package/src/pi/adapter.ts +192 -0
  67. package/src/pi/durable-principal.ts +98 -0
  68. package/src/pi/in-process-session.ts +819 -0
  69. package/src/pi/known-failure.ts +77 -0
  70. package/src/pi/pi-normalization.ts +284 -0
  71. package/src/pi/role-turn-host.ts +459 -0
  72. package/src/public-cli/auto-resume.ts +42 -44
  73. package/src/public-cli/cli.ts +171 -257
  74. package/src/public-cli/coder-run.ts +115 -433
  75. package/src/public-cli/collector-run.ts +62 -314
  76. package/src/public-cli/config.ts +7 -5
  77. package/src/public-cli/doctor-run.ts +41 -52
  78. package/src/public-cli/fixer-run.ts +122 -427
  79. package/src/public-cli/invocation.ts +287 -96
  80. package/src/public-cli/judge-run.ts +92 -422
  81. package/src/public-cli/merger-run.ts +167 -429
  82. package/src/public-cli/notary-run.ts +41 -47
  83. package/src/public-cli/option-definitions.ts +136 -3
  84. package/src/public-cli/post-admission.ts +497 -0
  85. package/src/public-cli/public-run-credentials.ts +4 -4
  86. package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
  87. package/src/public-cli/reviewer-run.ts +132 -449
  88. package/src/public-cli/run-lifecycle.ts +339 -327
  89. package/src/public-cli/settlement.ts +251 -134
  90. package/src/public-cli/terminal.ts +3 -9
  91. package/src/public-cli/turn-request.ts +87 -0
  92. package/src/reviewer-child-executor.ts +9 -1
  93. package/src/reviewer-role.ts +11 -20
  94. package/src/role-runtime.ts +129 -78
  95. package/src/session-opening-materials.ts +25 -39
  96. package/src/sitian-appender.ts +171 -0
  97. package/src/sitian-contracts.ts +106 -0
  98. package/src/sitian-facade.ts +18 -0
  99. package/src/sitian-reader.ts +62 -0
  100. package/src/worker-role.ts +42 -40
  101. package/src/worker-submission-gates.ts +33 -0
  102. package/dist/archivist-role-run-coordinates.js +0 -28
  103. package/dist/package-contracts/terminating-infrastructure.js +0 -90
  104. package/resources/engines/sonnet.md +0 -27
  105. package/resources/engines/zcode.md +0 -63
  106. package/src/archivist-role-run-coordinates.ts +0 -45
  107. package/src/package-contracts/terminating-infrastructure.ts +0 -132
  108. package/src/public-cli/explicit-internal.ts +0 -427
  109. package/src/public-cli/one-shot-dispatch.ts +0 -300
@@ -0,0 +1,135 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { RoleTurnModelConfig } from "./host-contracts.ts";
4
+ import { seatModelOnly, type PublicCliConfig } from "./public-cli/config.ts";
5
+
6
+ export const INSTITUTIONAL_RESOLUTION_FILE = "institutional-resolution.json" as const;
7
+
8
+ /** Non-secret per-seat model selection — alias of the single host-neutral RoleTurnModelConfig. */
9
+ export type InstitutionalSeatSelection = RoleTurnModelConfig;
10
+
11
+ export type InstitutionalResolutionPage = {
12
+ readonly version: 1;
13
+ readonly seats: {
14
+ readonly gatekeeper?: InstitutionalSeatSelection;
15
+ readonly inspector?: InstitutionalSeatSelection;
16
+ readonly notary?: InstitutionalSeatSelection;
17
+ readonly auditor?: InstitutionalSeatSelection;
18
+ readonly evidenceChild?: InstitutionalSeatSelection;
19
+ readonly [seat: string]: InstitutionalSeatSelection | undefined;
20
+ };
21
+ };
22
+
23
+ export class InstitutionalResolutionError extends Error {
24
+ constructor(message: string, options?: ErrorOptions) {
25
+ super(message, options);
26
+ this.name = "InstitutionalResolutionError";
27
+ }
28
+ }
29
+
30
+ function cleanSelection(model?: { provider?: string; model?: string; thinking?: string }): InstitutionalSeatSelection | undefined {
31
+ if (model === undefined) return undefined;
32
+ if (typeof model.provider !== "string" || typeof model.model !== "string") return undefined;
33
+ return {
34
+ provider: model.provider,
35
+ model: model.model,
36
+ ...(model.thinking === undefined ? {} : { thinking: model.thinking }),
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Resolve effective per-seat institutional selections (#518 §2 Hop 1):
42
+ * - gatekeeper / inspector / notary: seat override > gatekeeper override > parent effective
43
+ * - auditor / evidenceChild: parent effective
44
+ */
45
+ export function resolveInstitutionalSeatSelections(
46
+ config: PublicCliConfig,
47
+ parentEffectiveModel?: { provider: string; model: string; thinking?: string },
48
+ ): InstitutionalResolutionPage {
49
+ const parentSelection = cleanSelection(parentEffectiveModel);
50
+
51
+ const ownInspector = cleanSelection(seatModelOnly(config.seats?.inspector));
52
+ const ownNotary = cleanSelection(seatModelOnly(config.seats?.notary));
53
+ const ownGatekeeper = cleanSelection(seatModelOnly(config.seats?.gatekeeper));
54
+
55
+ const gatekeeper = ownGatekeeper ?? parentSelection;
56
+ const inspector = ownInspector ?? ownGatekeeper ?? parentSelection;
57
+ const notary = ownNotary ?? ownGatekeeper ?? parentSelection;
58
+ const auditor = parentSelection;
59
+ const evidenceChild = parentSelection;
60
+
61
+ return {
62
+ version: 1,
63
+ seats: {
64
+ ...(gatekeeper === undefined ? {} : { gatekeeper }),
65
+ ...(inspector === undefined ? {} : { inspector }),
66
+ ...(notary === undefined ? {} : { notary }),
67
+ ...(auditor === undefined ? {} : { auditor }),
68
+ ...(evidenceChild === undefined ? {} : { evidenceChild }),
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Persist institutional resolution page to run directory (#518 §2 Hop 2).
75
+ * Rewritten on each dispatch and resume. Zero secrets inside page.
76
+ */
77
+ export async function writeInstitutionalResolutionPage(
78
+ runDirectory: string,
79
+ page: InstitutionalResolutionPage,
80
+ ): Promise<void> {
81
+ const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
82
+ await writeFile(filePath, `${JSON.stringify(page, null, 2)}\n`, "utf8");
83
+ }
84
+
85
+ /**
86
+ * Read institutional seat selection from run directory (#518 §2 Hop 3).
87
+ * Fails loud if page is missing, corrupted, or does not contain a resolution for the seat.
88
+ */
89
+ export async function readInstitutionalSeatSelection(
90
+ runDirectory: string,
91
+ seat: string,
92
+ ): Promise<InstitutionalSeatSelection> {
93
+ const filePath = join(runDirectory, INSTITUTIONAL_RESOLUTION_FILE);
94
+ let raw: string;
95
+ try {
96
+ raw = await readFile(filePath, "utf8");
97
+ } catch (error) {
98
+ throw new InstitutionalResolutionError(
99
+ `institutional resolution page is missing at ${filePath}`,
100
+ { cause: error },
101
+ );
102
+ }
103
+
104
+ let page: unknown;
105
+ try {
106
+ page = JSON.parse(raw);
107
+ } catch (error) {
108
+ throw new InstitutionalResolutionError(
109
+ `institutional resolution page is corrupted at ${filePath}`,
110
+ { cause: error },
111
+ );
112
+ }
113
+
114
+ if (typeof page !== "object" || page === null || (page as { version?: unknown }).version !== 1) {
115
+ throw new InstitutionalResolutionError(
116
+ `institutional resolution page format is invalid at ${filePath}`,
117
+ );
118
+ }
119
+
120
+ const seats = (page as { seats?: Record<string, unknown> }).seats;
121
+ if (typeof seats !== "object" || seats === null) {
122
+ throw new InstitutionalResolutionError(
123
+ `institutional resolution page missing seats object at ${filePath}`,
124
+ );
125
+ }
126
+
127
+ const selection = cleanSelection(seats[seat] as InstitutionalSeatSelection | undefined);
128
+ if (selection === undefined) {
129
+ throw new InstitutionalResolutionError(
130
+ `institutional resolution page has no resolution for seat "${seat}" at ${filePath}`,
131
+ );
132
+ }
133
+
134
+ return selection;
135
+ }
@@ -3,7 +3,6 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  createComplianceDecisionTool,
5
5
  runComplianceAudit,
6
- type ComplianceCompletion,
7
6
  type ComplianceDecision,
8
7
  } from "./compliance-transport.ts";
9
8
  import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
@@ -32,9 +31,7 @@ const auditDecisionTool = createComplianceDecisionTool(
32
31
  * parent-session books. Public CLI injects AK_ROLE_RUN_DIR; bare Pi (ADR 0052)
33
32
  * proceeds without that pointer and self-locates per soul.
34
33
  */
35
- export function createPiJudgeAuditor(
36
- runCompletion?: ComplianceCompletion,
37
- ): (options: JudgeAuditOptions) => Promise<ComplianceDecision> {
34
+ export function createPiJudgeAuditor(): (options: JudgeAuditOptions) => Promise<ComplianceDecision> {
38
35
  return async (options) => {
39
36
  const dossier = resolveAuditDossier();
40
37
  requireAuditMaterials(dossier);
@@ -46,7 +43,6 @@ export function createPiJudgeAuditor(
46
43
  systemPrompt: await loadAuditorSoul("judge"),
47
44
  roleLabel: "Judge compliance audit",
48
45
  invalidDecisionLabel: "invalid judge audit decision",
49
- ...(runCompletion === undefined ? {} : { runCompletion }),
50
46
  context: options.context,
51
47
  ...(auditorRunDirectory(options.context) === undefined ? {} : { runDirectory: auditorRunDirectory(options.context) }),
52
48
  ...(options.signal === undefined ? {} : { signal: options.signal }),
package/src/judge-role.ts CHANGED
@@ -1,18 +1,9 @@
1
- import { StringEnum } from "@earendil-works/pi-ai";
2
- import type {
3
- ExtensionAPI,
4
- ExtensionContext,
5
- AgentToolResult,
6
- } from "@earendil-works/pi-coding-agent";
1
+ import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
+ import { stringEnum } from "./host-contracts.ts";
7
3
  import { Type, type Static } from "typebox";
8
4
 
9
5
  import { disposeComplianceDecision } from "./audit-escalation.ts";
10
6
  import type { ComplianceDecision } from "./compliance-transport.ts";
11
- import { requireGatekeeperPass, type GatekeeperPassHostActions } from "./gatekeeper-role.ts";
12
- import {
13
- failOnInfrastructureFailureDeclaration,
14
- withInfrastructureFailureDeclaration,
15
- } from "./package-contracts/terminating-infrastructure.ts";
16
7
 
17
8
  import {
18
9
  JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT,
@@ -25,36 +16,34 @@ import {
25
16
  export { JUDGE_OUTPUT_TOOL_NAME };
26
17
  export type { JudgeVerdict };
27
18
 
28
- export const judgeVerdictSchema = withInfrastructureFailureDeclaration(
29
- Type.Object(
30
- {
31
- judgeStatus: StringEnum(["converged", "continue", "escalate"] as const, { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
32
- fix: Type.Optional(
33
- Type.Object(
34
- { summary: Type.String({ minLength: 1, description: "continue 时的补救摘要" }) },
35
- { additionalProperties: false, description: "continue 时的补救说明" },
36
- ),
19
+ const judgeVerdictSchema = Type.Object(
20
+ {
21
+ judgeStatus: stringEnum(["converged", "continue", "escalate"] as const, { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
22
+ fix: Type.Optional(
23
+ Type.Object(
24
+ { summary: Type.String({ minLength: 1, description: "continue 时的补救摘要" }) },
25
+ { additionalProperties: false, description: "continue 时的补救说明" },
37
26
  ),
38
- classes: Type.Optional(Type.Array(Type.Object({
39
- name: Type.String({ minLength: 1 }),
40
- owner: Type.String({ minLength: 1 }),
41
- boundary: Type.String({ minLength: 1 }),
42
- disposition: Type.String({ minLength: 1 }),
43
- }, { additionalProperties: false }), { minItems: 1, description: "已裁决 finding 类及其 owner 与修理边界" })),
44
- note: Type.Optional(Type.String({ minLength: 1, description: "可选裁决附注" })),
45
- evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
46
- decisionGate: Type.Optional(
47
- Type.Object(
48
- {
49
- question: Type.String({ minLength: 1 }),
50
- options: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
51
- },
52
- { additionalProperties: false, description: "需人权威处置的问题与选项" },
53
- ),
27
+ ),
28
+ classes: Type.Optional(Type.Array(Type.Object({
29
+ name: Type.String({ minLength: 1 }),
30
+ owner: Type.String({ minLength: 1 }),
31
+ boundary: Type.String({ minLength: 1 }),
32
+ disposition: Type.String({ minLength: 1 }),
33
+ }, { additionalProperties: false }), { minItems: 1, description: "已裁决 finding 类及其 owner 与修理边界" })),
34
+ note: Type.Optional(Type.String({ minLength: 1, description: "可选裁决附注" })),
35
+ evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
36
+ decisionGate: Type.Optional(
37
+ Type.Object(
38
+ {
39
+ question: Type.String({ minLength: 1 }),
40
+ options: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
41
+ },
42
+ { additionalProperties: false, description: "需人权威处置的问题与选项" },
54
43
  ),
55
- },
56
- { additionalProperties: true },
57
- ),
44
+ ),
45
+ },
46
+ { additionalProperties: true },
58
47
  );
59
48
  (judgeVerdictSchema as unknown as { required: string[] }).required = [];
60
49
 
@@ -65,11 +54,11 @@ export type SoulAuditResult = ComplianceDecision;
65
54
  export type JudgeRoleDependencies = {
66
55
  loadSoul(): Promise<string>;
67
56
  auditSoulCompliance(
68
- options: { context: ExtensionContext; signal?: AbortSignal },
57
+ options: { context: HostContext; signal?: AbortSignal },
69
58
  ): Promise<SoulAuditResult>;
70
59
  };
71
60
 
72
- export type JudgeRoleHostActions = GatekeeperPassHostActions;
61
+ export type JudgeRoleHostActions = HostGatekeeperActions;
73
62
 
74
63
  export function validateVerdict(verdict: JudgeVerdictParameters): JudgeVerdict {
75
64
  return validateAcceptedJudgeDetails(verdict);
@@ -77,13 +66,13 @@ export function validateVerdict(verdict: JudgeVerdictParameters): JudgeVerdict {
77
66
 
78
67
  function requireSingletonSubmissionCall(
79
68
  toolCallId: string,
80
- ctx: ExtensionContext,
69
+ ctx: HostContext,
81
70
  ): void {
82
71
  const leaf = ctx.sessionManager.getLeafEntry();
83
- if (leaf?.type !== "message" || leaf.message.role !== "assistant") {
72
+ if (leaf?.type !== "message" || leaf.message === undefined || leaf.message.role !== "assistant" || !Array.isArray(leaf.message.content)) {
84
73
  throw new Error("大理寺回执非唯一终局工具调用");
85
74
  }
86
- const calls = leaf.message.content.filter((part) => part.type === "toolCall");
75
+ const calls = leaf.message.content.filter((part): part is Extract<typeof part, { type: "toolCall" }> => part.type === "toolCall");
87
76
  const call = calls[0];
88
77
  if (
89
78
  calls.length !== 1 || call === undefined || call.id !== toolCallId ||
@@ -94,7 +83,7 @@ function requireSingletonSubmissionCall(
94
83
  }
95
84
 
96
85
  export function createJudgeRoleRuntime(
97
- pi: ExtensionAPI,
86
+ pi: RoleHost,
98
87
  dependencies: JudgeRoleDependencies,
99
88
  hostActions: JudgeRoleHostActions,
100
89
  ): { activate(): Promise<void> } {
@@ -113,17 +102,14 @@ export function createJudgeRoleRuntime(
113
102
  description: "提交大理寺终局判词;受理前经审刑院审计。",
114
103
  promptSnippet: "提交大理寺终局判词",
115
104
  parameters: judgeVerdictSchema,
116
- async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
105
+ async execute(toolCallId: string, parameters: Static<typeof judgeVerdictSchema>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
117
106
  if (soul === undefined) throw new Error("大理寺职分未装载");
118
- // #541: infra declaration fails via the shared host seam before any
119
- // Gatekeeper + audit courtyard work.
120
- failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
121
107
  requireSingletonSubmissionCall(toolCallId, ctx);
122
108
  const verdict = validateVerdict(parameters);
123
109
  // Candidate verdict is already on the parent session books as this
124
110
  // tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
125
111
  // Gatekeeper runs after the draft is booked and before existing auditor.
126
- await requireGatekeeperPass({
112
+ await pi.requireGatekeeperPass!({
127
113
  context: ctx,
128
114
  subject: { kind: "judge_draft", material: JSON.stringify(verdict) },
129
115
  ...(signal === undefined ? {} : { signal }),
@@ -141,7 +127,7 @@ export function createJudgeRoleRuntime(
141
127
  hostActions.failInfrastructure(error, ctx, toolCallId);
142
128
  }
143
129
  const acceptedDetails = verdict;
144
- return disposeComplianceDecision<AgentToolResult<unknown>>(
130
+ return disposeComplianceDecision<HostToolResult<unknown>>(
145
131
  audit,
146
132
  {
147
133
  pass: (usage) => ({
@@ -3,7 +3,6 @@ import { isFullGitObjectId } from "./git-object-id.ts";
3
3
  import { exactUtf8 } from "./exact-utf8.ts";
4
4
  import { sha256Hex } from "./sha256.ts";
5
5
  import { openToolObjectFromUnion } from "./open-tool-schema.ts";
6
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
7
6
 
8
7
  const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
9
8
  const materialSchema = Type.Object({ bytesBase64: Type.String(), sha256: Type.String() }, { additionalProperties: false });
@@ -20,9 +19,7 @@ const mergerOutputVariants = Type.Union([
20
19
  Type.Object({ status: Type.Literal("completed", { description: "completed — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), mergeCommitId: Type.String({ pattern: oidPattern, description: "已核验的完成合并 commit object ID" }) }, { additionalProperties: false }),
21
20
  Type.Object({ status: Type.Literal("escalate", { description: "escalate — 形状指引,非 schema 闸" }), attemptId: Type.String({ minLength: 1, description: "已受理合并 attempt 身份" }), diagnosis: Type.String({ minLength: 1, description: "合并完成需升级的原因" }), report: Type.String({ minLength: 1, description: "如实结果报告" }) }, { additionalProperties: false }),
22
21
  ]);
23
- export const mergerOutputSchema = withInfrastructureFailureDeclaration(
24
- openToolObjectFromUnion(mergerOutputVariants),
25
- );
22
+ export const mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
26
23
 
27
24
  export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
28
25
  export type MergerMaterial = DeepReadonly<Static<typeof materialSchema>>;
@@ -1,7 +1,6 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
2
2
  import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, validateMergerOutput, type MergerInput } from "./merger-contracts.ts";
3
3
  import type { MergerGitState } from "./merger-git-state.ts";
4
- import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
5
4
  import { exactUtf8 } from "./exact-utf8.ts";
6
5
  import { isFullGitObjectId } from "./git-object-id.ts";
7
6
 
@@ -9,13 +8,13 @@ export { MERGER_OUTPUT_TOOL_NAME };
9
8
  export const MERGER_INPUT_FLAG = { name: "ak-merger-input", definition: { description: "Path to an immutable digest-bound Merger v1 input JSON file", type: "string" as const } } as const;
10
9
  export const MERGER_ACTIVE_TOOLS = ["read", "grep", "find", "ls", "bash", "write", "edit", MERGER_OUTPUT_TOOL_NAME] as const;
11
10
  export type MergerRoleDependencies = { loadSoul(): Promise<string>; loadInput(path: string): Promise<unknown>; gitState: MergerGitState };
12
- export type MergerRoleHostActions = { failInfrastructure(error: unknown, ctx: ExtensionContext, toolCallId?: string): never };
11
+ export type MergerRoleHostActions = { failInfrastructure(error: unknown, ctx: HostContext, toolCallId?: string): never };
13
12
 
14
13
  function same(a: readonly string[], b: readonly string[]): boolean { return a.length === b.length && a.every((value, i) => value === b[i]); }
15
- function singleton(id: string, ctx: ExtensionContext): void { const leaf = ctx.sessionManager.getLeafEntry(); if (leaf?.type !== "message" || leaf.message.role !== "assistant") throw new Error("合并回执非唯一终局工具调用"); const calls = leaf.message.content.filter(part => part.type === "toolCall"); if (calls.length !== 1 || calls[0]?.id !== id || calls[0]?.name !== MERGER_OUTPUT_TOOL_NAME) throw new Error("合并回执非唯一终局工具调用"); }
14
+ function singleton(id: string, ctx: HostContext): void { const leaf = ctx.sessionManager.getLeafEntry(); if (leaf?.type !== "message" || leaf.message === undefined || leaf.message.role !== "assistant" || !Array.isArray(leaf.message.content)) throw new Error("合并回执非唯一终局工具调用"); const calls = leaf.message.content.filter((part): part is Extract<typeof part, { type: "toolCall" }> => part.type === "toolCall"); if (calls.length !== 1 || calls[0]?.id !== id || calls[0]?.name !== MERGER_OUTPUT_TOOL_NAME) throw new Error("合并回执非唯一终局工具调用"); }
16
15
  function materialText(input: MergerInput, key: keyof MergerInput["materials"]): string { return exactUtf8(Buffer.from(input.materials[key].bytesBase64, "base64"), `Merger ${key}`); }
17
16
 
18
- export function createMergerRoleRuntime(pi: ExtensionAPI, dependencies: MergerRoleDependencies, host: MergerRoleHostActions) {
17
+ export function createMergerRoleRuntime(pi: RoleHost, dependencies: MergerRoleDependencies, host: MergerRoleHostActions) {
19
18
  let activation: { soul: string; input: Readonly<MergerInput>; automaticMergeTreeId: string } | undefined;
20
19
  let registered = false;
21
20
  let accepted = false;
@@ -29,13 +28,10 @@ export function createMergerRoleRuntime(pi: ExtensionAPI, dependencies: MergerRo
29
28
  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) throw new Error("Merger activation rejected repository parent, merge, automatic-result, or complete conflict-set drift");
30
29
  activation = { soul, input, automaticMergeTreeId: state.automaticMergeTreeId }; accepted = false;
31
30
  if (!registered) { registered = true;
32
- pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
33
- async execute(id, params, _signal, _update, ctx) {
31
+ pi.registerTool({ name: MERGER_OUTPUT_TOOL_NAME, label: "合并输出", description: "提交合并结果;输出分支为 completed 与 escalate;基础设施及 Git 失败由 abort 通道承接。", promptSnippet: "提交合并结果", parameters: mergerOutputSchema,
32
+ async execute(id: string, params: unknown, _signal: AbortSignal | undefined, _update: unknown, ctx: HostContext) {
34
33
  if (!activation) throw new Error("校书郎未激活");
35
34
  if (accepted) throw new Error("合并回执已受理");
36
- // #541: infra declaration fails via the shared host seam before output
37
- // validation / Git verification / accepted=true.
38
- failOnInfrastructureFailureDeclaration(params, host, ctx, id);
39
35
  let output;
40
36
  try {
41
37
  singleton(id, ctx);
@@ -54,7 +50,7 @@ export function createMergerRoleRuntime(pi: ExtensionAPI, dependencies: MergerRo
54
50
  const acceptedDetails = output;
55
51
  return { content: [{ type: "text" as const, text: MERGER_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const };
56
52
  } });
57
- pi.on("before_agent_start", event => { if (!activation) 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>` }; });
53
+ pi.on("before_agent_start", (event) => { if (!activation) 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>` }; });
58
54
  }
59
55
  const all = pi.getAllTools().map(tool => tool.name);
60
56
  for (const name of MERGER_ACTIVE_TOOLS) if (all.filter(item => item === name).length !== 1) throw new Error(`Merger required tool collision or missing: ${name}`);
@@ -13,6 +13,11 @@ import {
13
13
  mintNavigatorInvocationId,
14
14
  } from "./navigator-invocation-identity.ts";
15
15
  import { PACKAGED_ROLE_REGISTRY, type PackagedRole, packagedRoleMetadata } from "./packaged-role-registry.ts";
16
+ import {
17
+ activationBookDirectory,
18
+ resolveActivationLedgerHome,
19
+ } from "./activation-ledger-topology.ts";
20
+ import { sitianReport } from "./sitian-facade.ts";
16
21
  import { openInProcessAgentSession } from "./in-process-session.ts";
17
22
  import { renderPublicAkRoleCommand } from "./public-command-renderer.ts";
18
23
  import { issueRoot, subjectPath } from "./work-subject-identity.ts";
@@ -1308,7 +1313,21 @@ export function createNativeNavigatorSessionFactory(defaultModelSettingPath = na
1308
1313
  }
1309
1314
  },
1310
1315
  providerFailure: () => providerFailure,
1311
- appendEntry: (customType, data) => { opened.session.sessionManager.appendCustomEntry(customType, data); },
1316
+ appendEntry: (customType, data) => {
1317
+ opened.session.sessionManager.appendCustomEntry(customType, data);
1318
+ try {
1319
+ sitianReport({
1320
+ level: "event",
1321
+ kind: "attendance",
1322
+ cwd: context.cwd,
1323
+ sessionParent: opened.session.sessionManager.getSessionFile(),
1324
+ payload: { customType, data },
1325
+ source: "navigator-attendance",
1326
+ });
1327
+ } catch {
1328
+ // best-effort persistence in attendance adapter
1329
+ }
1330
+ },
1312
1331
  entries: () => opened.session.sessionManager.getEntries(),
1313
1332
  setModel: async (next, thinkingLevel) => {
1314
1333
  let nextParsed: ReturnType<typeof parseNavigatorModelSetting>;
@@ -6,7 +6,6 @@
6
6
  import { Type } from "typebox";
7
7
 
8
8
  import { openToolObject } from "./open-tool-schema.ts";
9
- import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
10
9
 
11
10
  export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
12
11
  export const NOTARY_ACCEPTED_TEXT = "符宝郎回执已接受";
@@ -22,17 +21,15 @@ export const NOTARY_SOURCE_RUN_FLAG = {
22
21
  export const NOTARY_FIXED_KICKOFF =
23
22
  "符宝郎案卷已受理;来源 run 定位见会话材料。";
24
23
 
25
- export const notaryOutputSchema = withInfrastructureFailureDeclaration(
26
- openToolObject(
27
- Type.Object({
28
- status: Type.Unknown({
29
- description: "pass | bounce — 形状指引,非 schema 闸",
30
- }),
31
- findings: Type.Unknown({
32
- description: "string[] findings,随 pass 或 bounce 留存",
33
- }),
24
+ export const notaryOutputSchema = openToolObject(
25
+ Type.Object({
26
+ status: Type.Unknown({
27
+ description: "pass | bounce — 形状指引,非 schema 闸",
34
28
  }),
35
- ),
29
+ findings: Type.Unknown({
30
+ description: "string[] findings,随 pass 或 bounce 留存",
31
+ }),
32
+ }),
36
33
  );
37
34
 
38
35
  export type NotarySourceRunLocator = {
@@ -1,12 +1,8 @@
1
+ import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
1
2
  /**
2
3
  * Public Notary role runtime — direct officer seat (not through Gatekeeper province).
3
4
  * Caller supplies only a source-run locator; Notary self-fetches authoritative materials.
4
5
  */
5
- import type {
6
- AgentToolResult,
7
- ExtensionAPI,
8
- ExtensionContext,
9
- } from "@earendil-works/pi-coding-agent";
10
6
 
11
7
  import {
12
8
  NOTARY_ACCEPTED_TEXT,
@@ -17,9 +13,6 @@ import {
17
13
  retainNotarySubmission,
18
14
  type NotarySourceRunLocator,
19
15
  } from "./notary-contracts.ts";
20
- import {
21
- failOnInfrastructureFailureDeclaration,
22
- } from "./package-contracts/terminating-infrastructure.ts";
23
16
 
24
17
  export {
25
18
  NOTARY_ACCEPTED_TEXT,
@@ -35,20 +28,20 @@ export type NotaryRoleDependencies = {
35
28
  export type NotaryRoleHostActions = {
36
29
  failInfrastructure(
37
30
  error: unknown,
38
- ctx: ExtensionContext,
31
+ ctx: HostContext,
39
32
  toolCallId?: string,
40
33
  ): never;
41
34
  };
42
35
 
43
36
  function requireSingletonSubmissionCall(
44
37
  toolCallId: string,
45
- ctx: ExtensionContext,
38
+ ctx: HostContext,
46
39
  ): void {
47
40
  const leaf = ctx.sessionManager.getLeafEntry();
48
- if (leaf?.type !== "message" || leaf.message.role !== "assistant") {
41
+ if (leaf?.type !== "message" || leaf.message === undefined || leaf.message.role !== "assistant" || !Array.isArray(leaf.message.content)) {
49
42
  throw new Error("符宝郎回执非唯一终局工具调用");
50
43
  }
51
- const calls = leaf.message.content.filter((part) => part.type === "toolCall");
44
+ const calls = leaf.message.content.filter((part): part is Extract<typeof part, { type: "toolCall" }> => part.type === "toolCall");
52
45
  if (
53
46
  calls.length !== 1 ||
54
47
  calls[0]?.id !== toolCallId ||
@@ -59,7 +52,7 @@ function requireSingletonSubmissionCall(
59
52
  }
60
53
 
61
54
  export function createNotaryRoleRuntime(
62
- pi: ExtensionAPI,
55
+ pi: RoleHost,
63
56
  dependencies: NotaryRoleDependencies,
64
57
  host: NotaryRoleHostActions,
65
58
  ) {
@@ -91,19 +84,10 @@ export function createNotaryRoleRuntime(
91
84
  description: "提交引文保真与票面对齐的 typed pass/bounce 决议。",
92
85
  promptSnippet: "提交符宝郎决议",
93
86
  parameters: notaryOutputSchema,
94
- async execute(
95
- toolCallId,
96
- parameters,
97
- _signal,
98
- _onUpdate,
99
- ctx,
100
- ): Promise<AgentToolResult<unknown>> {
87
+ async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext, ): Promise<HostToolResult<unknown>> {
101
88
  if (activation === undefined) {
102
89
  throw new Error("符宝郎未激活");
103
90
  }
104
- // #541: infra declaration fails via the shared host seam before any
105
- // pass/bounce projection.
106
- failOnInfrastructureFailureDeclaration(parameters, host, ctx, toolCallId);
107
91
  // Unique submission + terminate only. Shape is not an admission gate
108
92
  // (第 0 条 / ADR 0055): lawful pass/bounce projected; else params as-is.
109
93
  requireSingletonSubmissionCall(toolCallId, ctx);
@@ -13,7 +13,7 @@ import {
13
13
  resolveActivationLedgerHome,
14
14
  } from "./activation-ledger-topology.ts";
15
15
  import type { NotarySourceRunLocator } from "./notary-contracts.ts";
16
- import { readRoleRunState } from "./public-cli/run-lifecycle.ts";
16
+ import { readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
17
17
 
18
18
  const RUN_DIR_NAME =
19
19
  /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})@([A-Za-z][A-Za-z0-9_-]*)$/i;
@@ -109,7 +109,8 @@ export async function resolveNotarySourceRunLocator(options: {
109
109
  }
110
110
 
111
111
  // Authoritative retained record only — legal role/state via shared reader (DRY #14).
112
- const runState = await readRoleRunState(real);
112
+ // Identity envelope only: principal payload is not interpreted here.
113
+ const runState = await readRoleRunIdentity(real);
113
114
  if (runState === undefined) {
114
115
  throw new NotarySourceRunError(
115
116
  "notary --source-run lacks retained run-state identity",
@@ -144,7 +145,7 @@ export async function loadNotarySourceRunLocator(
144
145
  ): Promise<NotarySourceRunLocator> {
145
146
  const real = await requireRunDirectory(path, path);
146
147
  const identity = parseRunDirectoryName(basename(real))!;
147
- const runState = await readRoleRunState(real);
148
+ const runState = await readRoleRunIdentity(real);
148
149
  if (runState === undefined) {
149
150
  throw new NotarySourceRunError(
150
151
  "notary source-run lacks retained run-state identity",
@@ -1,7 +1,6 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import { FIXER_PREREQUISITE_ID_PATTERN, type FixerInvocationInput } from "./fixer-packet.ts";
3
3
  import { openToolObjectFromUnion } from "../open-tool-schema.ts";
4
- import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
5
4
 
6
5
  export const FIXER_OUTPUT_TOOL_NAME = "ak_fixer_output";
7
6
  export const FIXER_ACCEPTED_TEXT = "修内司回执已接受";
@@ -41,9 +40,7 @@ const fixerOutputVariants = Type.Union([
41
40
  Type.Object({ status: Type.Literal("refused", { description: "refused — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类拒绝结算" }) }),
42
41
  Type.Object({ status: Type.Literal("partially_completed", { description: "partially_completed — 形状指引,非 schema 闸" }), report: Type.String({ minLength: 1, description: "如实结果报告" }), classResults: Type.Array(classResultSchema, { minItems: 1, description: "各类完成或拒绝结算" }), testEvidence: Type.Optional(testEvidenceSchema) }),
43
42
  ]);
44
- export const fixerOutputSchema = withInfrastructureFailureDeclaration(
45
- openToolObjectFromUnion(fixerOutputVariants),
46
- );
43
+ export const fixerOutputSchema = openToolObjectFromUnion(fixerOutputVariants);
47
44
 
48
45
  export type FixerBlocker = Static<typeof blockerSchema>;
49
46
  export type FixerClassResult = Static<typeof classResultSchema>;
@@ -1,15 +1,13 @@
1
1
  /**
2
2
  * Canonical Skill binding built from package-owned method material.
3
- * Lives beside the method seam but may import Pi skill parsers — used by the
4
- * Internal role runtime, not the public ak-role bin bundle.
3
+ * Lives beside the method seam — used by the Internal role runtime, not the
4
+ * public ak-role bin bundle.
5
5
  */
6
6
  import { dirname } from "node:path";
7
7
 
8
- import { parseSkillBlock } from "@earendil-works/pi-coding-agent";
9
-
10
8
  import {
9
+ captureCanonicalSkillExpansion,
11
10
  type CanonicalSkillBinding,
12
- type CanonicalSkillEvidence,
13
11
  type CanonicalSkillName,
14
12
  } from "../canonical-skill-binding.ts";
15
13
  import {
@@ -51,36 +49,14 @@ export async function loadPackagedCanonicalSkillBinding<
51
49
  invocation(originalRequest) {
52
50
  return `/skill:${name} ${originalRequest}`;
53
51
  },
54
- captureExpansion(
55
- prompt,
56
- originalRequest,
57
- ): CanonicalSkillEvidence<Name> | undefined {
58
- const parsed = parseSkillBlock(prompt);
59
- const matchedPath =
60
- parsed?.location === configuredPath
61
- ? configuredPath
62
- : parsed?.location === snapshot.path
63
- ? snapshot.path
64
- : undefined;
65
- const expectedContent =
66
- matchedPath === undefined
67
- ? undefined
68
- : `References are relative to ${dirname(matchedPath)}.\n\n${snapshot.body}`;
69
- const userMessage = parsed?.userMessage ?? "";
70
- if (
71
- parsed?.name !== name ||
72
- matchedPath === undefined ||
73
- parsed.content !== expectedContent ||
74
- userMessage !== originalRequest
75
- ) {
76
- return undefined;
77
- }
78
- return Object.freeze({
52
+ captureExpansion(evidence, originalRequest) {
53
+ return captureCanonicalSkillExpansion(
79
54
  name,
80
- location: parsed.location,
81
- content: parsed.content,
82
- userMessage,
83
- });
55
+ snapshot,
56
+ configuredPath,
57
+ evidence,
58
+ originalRequest,
59
+ );
84
60
  },
85
61
  };
86
62
  return Object.freeze(binding);