@akagilnc/pi-workflow-roles 0.1.3262 → 0.1.3280
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -122
- package/README.zh-CN.md +9 -123
- package/dist/gatekeeper-role.js +22 -16
- package/dist/grok/production-host.js +745 -571
- package/dist/package-contracts/gatekeeper-output.js +71 -0
- package/dist/package-contracts/navigator-output.js +48 -0
- package/dist/package-contracts/terminating-tools.js +18 -2
- package/dist/packaged-role-registry.js +24 -0
- package/dist/public-cli/config.js +7 -21
- package/dist/public-cli/main.js +689 -365
- package/dist/public-cli/registry.js +2 -14
- package/dist/session-opening-materials.js +6 -11
- package/extensions/role-runtime.ts +3 -1
- package/package.json +1 -1
- package/resources/navigator-route-playbook.md +1 -1
- package/src/gatekeeper-role.ts +32 -17
- package/src/grok/production-host.ts +3 -1
- package/src/host-contracts.ts +3 -1
- package/src/navigator-role.ts +17 -0
- package/src/package-contracts/gatekeeper-output.ts +89 -0
- package/src/package-contracts/navigator-output.ts +59 -0
- package/src/package-contracts/terminating-tools.ts +24 -2
- package/src/packaged-role-registry.ts +25 -2
- package/src/pi/role-turn-host.ts +4 -0
- package/src/public-cli/cli.ts +37 -10
- package/src/public-cli/config.ts +6 -30
- package/src/public-cli/instruction-seat-run.ts +147 -0
- package/src/public-cli/invocation.ts +47 -3
- package/src/public-cli/option-definitions.ts +36 -3
- package/src/public-cli/registry.ts +3 -26
- package/src/public-cli/run-lifecycle.ts +8 -2
- package/src/public-cli/settlement.ts +115 -3
- package/src/public-cli/terminal.ts +3 -1
- package/src/role-runtime.ts +64 -1
- package/src/session-opening-materials.ts +6 -11
|
@@ -6,22 +6,9 @@ import { PACKAGED_ROLE_REGISTRY, } from "../packaged-role-registry.js";
|
|
|
6
6
|
/** Package-relative path of the Internal role entrypoint (explicit load only). */
|
|
7
7
|
export const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
|
|
8
8
|
export const PUBLIC_CALLABLE_ROLES = PACKAGED_ROLE_REGISTRY.map((entry) => entry.role);
|
|
9
|
-
/** Automatic attendance seat — configurable, never a caller-selected command. */
|
|
10
|
-
export const AUTOMATIC_NAVIGATOR_SEAT = "navigator";
|
|
11
|
-
/** Automatic province seat — configurable model only; never a caller command. */
|
|
12
|
-
export const AUTOMATIC_GATEKEEPER_SEAT = "gatekeeper";
|
|
13
|
-
/** Automatic-only configurable seats. Inspector remains automatic-capable via its callable seat. */
|
|
14
|
-
export const AUTOMATIC_CONFIGURABLE_SEATS = [
|
|
15
|
-
AUTOMATIC_GATEKEEPER_SEAT,
|
|
16
|
-
AUTOMATIC_NAVIGATOR_SEAT,
|
|
17
|
-
];
|
|
18
9
|
export const PUBLIC_CONFIGURABLE_SEATS = [
|
|
19
10
|
...PUBLIC_CALLABLE_ROLES,
|
|
20
|
-
...AUTOMATIC_CONFIGURABLE_SEATS,
|
|
21
11
|
];
|
|
22
|
-
export function isAutomaticConfigurableSeat(value) {
|
|
23
|
-
return AUTOMATIC_CONFIGURABLE_SEATS.includes(value);
|
|
24
|
-
}
|
|
25
12
|
export const PUBLIC_CLI_SUPPORT_COMMANDS = [
|
|
26
13
|
"roles",
|
|
27
14
|
"config",
|
|
@@ -82,7 +69,8 @@ const STARTUP_CANDIDATES = {
|
|
|
82
69
|
],
|
|
83
70
|
// #620: subordinate officers inherit gatekeeper; no package startup model.
|
|
84
71
|
notary: [],
|
|
85
|
-
// #453/#620: gatekeeper
|
|
72
|
+
// #453/#620/#639: gatekeeper callable but no package startup model — caller
|
|
73
|
+
// configures it or the institutional resolution applies on the province path.
|
|
86
74
|
gatekeeper: [],
|
|
87
75
|
inspector: [],
|
|
88
76
|
navigator: [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session opening materials (#443 / #524). Missing files fail as native readFile
|
|
3
|
-
* errors. Main-role materials derive from PUBLIC_ROLE_RECORDS
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* errors. Main-role materials derive from PUBLIC_ROLE_RECORDS (including
|
|
4
|
+
* gatekeeper and navigator). Province map reuses that public gatekeeper record
|
|
5
|
+
* plus officer material constants (notary shared). Auditor composition stays in
|
|
6
6
|
* loadAuditorSoul; loaders share joinPackageMaterials.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
@@ -42,22 +42,17 @@ export async function joinPackageMaterials(relativePaths) {
|
|
|
42
42
|
}
|
|
43
43
|
return chunks.join("\n\n");
|
|
44
44
|
}
|
|
45
|
-
/** Derived projection: public records
|
|
45
|
+
/** Derived projection: public records (#639 includes navigator/gatekeeper). */
|
|
46
46
|
export const MAIN_ROLE_SESSION_MATERIALS = {
|
|
47
47
|
...Object.fromEntries(PUBLIC_ROLE_RECORDS.map((record) => [record.role, record.sessionMaterials])),
|
|
48
|
-
navigator: ["CLAUDE.md", "souls/navigator.md"],
|
|
49
48
|
};
|
|
50
49
|
export function loadMainRoleSessionMaterials(role) {
|
|
51
50
|
return joinPackageMaterials(MAIN_ROLE_SESSION_MATERIALS[role]);
|
|
52
51
|
}
|
|
53
52
|
/** Gatekeeper province; notary reuses the public notary materials definition. */
|
|
54
53
|
export const GATEKEEPER_SESSION_MATERIALS = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"souls/gatekeeper.md",
|
|
58
|
-
"souls/quality-law.md",
|
|
59
|
-
"souls/gate-output-guide.md",
|
|
60
|
-
],
|
|
54
|
+
// #639: single authority — the public gatekeeper record owns the province list.
|
|
55
|
+
gatekeeper: PUBLIC_ROLE_RECORDS.find((entry) => entry.role === "gatekeeper").sessionMaterials,
|
|
61
56
|
inspector: INSPECTOR_SESSION_MATERIALS,
|
|
62
57
|
notary: NOTARY_SESSION_MATERIALS,
|
|
63
58
|
};
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
formatNavigatorRoleHelp,
|
|
37
37
|
} from "../src/role-runtime.ts";
|
|
38
38
|
import { createPiJudgeAuditor } from "../src/judge-auditor.ts";
|
|
39
|
-
import { loadMainRoleSessionMaterials } from "../src/session-opening-materials.ts";
|
|
39
|
+
import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "../src/session-opening-materials.ts";
|
|
40
40
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
41
41
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
42
42
|
const navigatorRoutePlaybookPath = fileURLToPath(new URL("../resources/navigator-route-playbook.md", import.meta.url));
|
|
@@ -128,6 +128,8 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
128
128
|
loadCountersignSoul: () => loadMainRoleSessionMaterials("countersign"),
|
|
129
129
|
loadGleanerLeftSoul: () => loadMainRoleSessionMaterials("gleaner-left"),
|
|
130
130
|
loadInspectorSoul: () => loadMainRoleSessionMaterials("inspector"),
|
|
131
|
+
loadGatekeeperSoul: () => loadGatekeeperSessionMaterials("gatekeeper"),
|
|
132
|
+
loadNavigatorSoul: () => loadMainRoleSessionMaterials("navigator"),
|
|
131
133
|
loadNotarySourceRun: loadNotarySourceRunLocator,
|
|
132
134
|
loadNavigatorWorkContext: (options) => loadNavigatorWorkContext(pi, options),
|
|
133
135
|
createNavigatorAttendance: (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## 宿主轴(与角色命令面)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
游奕使自动出席,亦可 `ak-role navigator` 直调(自动出席不变)。主会话宿主由公开角色的 host 轴决定:调用 `--host` → 席位 `config set-host` → 默认 `pi`。配置默认 host 后,调用者仍用与 Pi 相同的 `ak-role <role> …` 命令面;游奕使随该宿主下的角色跑次出席,席位模型走 run 目录 `institutional-resolution.json` 的 navigator 座(显式 `config set navigator` 优先,否则继承父席有效模型),不再依赖父宿主 ExtensionContext。
|
|
4
4
|
|
|
5
5
|
## 常用交付线
|
|
6
6
|
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -8,8 +8,12 @@ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
|
8
8
|
import { loadGatekeeperSessionMaterials } from "./session-opening-materials.ts";
|
|
9
9
|
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
10
10
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
13
|
+
gatekeeperDecisionSchema,
|
|
14
|
+
gatekeeperOutputSchema,
|
|
15
|
+
projectLawfulGatekeeperOutput,
|
|
16
|
+
} from "./package-contracts/gatekeeper-output.ts";
|
|
13
17
|
export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
|
|
14
18
|
export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
|
|
15
19
|
const SUBJECT_TOOL = "ak_gatekeeper_subject";
|
|
@@ -82,11 +86,23 @@ const officerDecisionSchema = openToolObject(Type.Object({
|
|
|
82
86
|
findings: Type.Unknown({ description: "string[] findings,随 pass 或 bounce 留存" }),
|
|
83
87
|
}));
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Direct-seat decision tool spec (#639). Lifecycle assembly stays on the
|
|
92
|
+
* registration envelope — src/role-runtime.ts (ADR 0018). Schema authority is
|
|
93
|
+
* the shared contract module (with infrastructure-failure declaration).
|
|
94
|
+
*/
|
|
95
|
+
export const GATEKEEPER_TOOL_SPEC = {
|
|
96
|
+
name: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
97
|
+
label: "门下省决议",
|
|
98
|
+
description: "门下省终局决议,状态为 dispatch 或 pass。",
|
|
99
|
+
promptSnippet: "门下省决议",
|
|
100
|
+
parameters: gatekeeperOutputSchema,
|
|
101
|
+
} as const;
|
|
102
|
+
|
|
103
|
+
export type GatekeeperRuntimeDependencies = {
|
|
104
|
+
loadSoul(): Promise<string>;
|
|
105
|
+
};
|
|
90
106
|
|
|
91
107
|
function result(content: string, details: unknown) {
|
|
92
108
|
return { content: [{ type: "text" as const, text: content }], details };
|
|
@@ -111,10 +127,10 @@ export function createOfficerDecisionTool(name: string): AuditorDecisionTool {
|
|
|
111
127
|
};
|
|
112
128
|
}
|
|
113
129
|
|
|
114
|
-
/** Gatekeeper province decision tool — open transport; projection owns legality. */
|
|
130
|
+
/** Gatekeeper province decision tool — open transport; package-contract projection owns legality. */
|
|
115
131
|
export function createGatekeeperOutputTool(): AuditorDecisionTool {
|
|
116
132
|
return {
|
|
117
|
-
name:
|
|
133
|
+
name: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
118
134
|
description: "提交门下省派官决定。",
|
|
119
135
|
parameters: gatekeeperDecisionSchema,
|
|
120
136
|
async execute(_id, args) { return result(`已收 ${String((args as { status?: unknown })?.status)}`, args); },
|
|
@@ -174,17 +190,16 @@ function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
174
190
|
}
|
|
175
191
|
|
|
176
192
|
function projectProvinceDecision(decision: unknown): GatekeeperResult | { status: "dispatch"; officer: "inspector" | "notary" } {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
// Lawful dispatch/pass discriminant is owned by package-contracts; province
|
|
194
|
+
// wraps undefined into transport_failure while retaining the submission.
|
|
195
|
+
const projected = projectLawfulGatekeeperOutput(decision);
|
|
196
|
+
if (projected === undefined) return noUsableReleaseFailure("gatekeeper", decision);
|
|
197
|
+
if (projected.status === "dispatch") {
|
|
198
|
+
return { status: "dispatch", officer: projected.officer };
|
|
181
199
|
}
|
|
182
200
|
// Lawful non-dispatch release — province may pass without dispatching an officer
|
|
183
201
|
// (ADR 0074 gate-non-mandatory; gate-output-guide pass = 正常放行; #597).
|
|
184
|
-
|
|
185
|
-
return { status: "pass", findings: asStringArray(record.findings) };
|
|
186
|
-
}
|
|
187
|
-
return noUsableReleaseFailure("gatekeeper", decision);
|
|
202
|
+
return { status: "pass", findings: projected.findings ?? [] };
|
|
188
203
|
}
|
|
189
204
|
|
|
190
205
|
function projectOfficerDecision(
|
|
@@ -32,7 +32,7 @@ import { loadPackagedCanonicalSkillBinding } from "../package-resources/method-s
|
|
|
32
32
|
import { createPerDispatchReviewerAgent } from "../reviewer-agent.ts";
|
|
33
33
|
import { formatNavigatorRoleHelp, type RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
34
34
|
import { createReviewerPinnedGitReader } from "../reviewer-pinned-git.ts";
|
|
35
|
-
import { loadMainRoleSessionMaterials } from "../session-opening-materials.ts";
|
|
35
|
+
import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "../session-opening-materials.ts";
|
|
36
36
|
import { createComposedGrokRoleTurnHost } from "./role-envelope.ts";
|
|
37
37
|
import {
|
|
38
38
|
assertControlledGrokAuthIsNotSymlink,
|
|
@@ -221,6 +221,8 @@ export function createGrokRoleRuntimeDependencies(packageRoot: string): RoleRunt
|
|
|
221
221
|
loadDoctorSoul: () => loadMainRoleSessionMaterials("doctor"),
|
|
222
222
|
loadDoctorCase,
|
|
223
223
|
loadInspectorSoul: () => loadMainRoleSessionMaterials("inspector"),
|
|
224
|
+
loadGatekeeperSoul: () => loadGatekeeperSessionMaterials("gatekeeper"),
|
|
225
|
+
loadNavigatorSoul: () => loadMainRoleSessionMaterials("navigator"),
|
|
224
226
|
loadNotarySoul: () => loadMainRoleSessionMaterials("notary"),
|
|
225
227
|
loadCountersignSoul: () => loadMainRoleSessionMaterials("countersign"),
|
|
226
228
|
loadGleanerLeftSoul: () => loadMainRoleSessionMaterials("gleaner-left"),
|
package/src/host-contracts.ts
CHANGED
|
@@ -123,7 +123,9 @@ export type RoleTurnActivation =
|
|
|
123
123
|
/** Required comparison-base revision for the unanchored merge-candidate diff. */
|
|
124
124
|
readonly baseRevision: string;
|
|
125
125
|
}
|
|
126
|
-
| { readonly role: "inspector" }
|
|
126
|
+
| { readonly role: "inspector" }
|
|
127
|
+
| { readonly role: "gatekeeper" }
|
|
128
|
+
| { readonly role: "navigator" };
|
|
127
129
|
|
|
128
130
|
export type RoleTurnContinuation =
|
|
129
131
|
| { readonly kind: "initial"; readonly prompt: string }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NAVIGATOR_OUTPUT_TOOL_NAME, navigatorOutputSchema } from "./package-contracts/navigator-output.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
5
|
+
* Candidates share the route-advice shape the attendance prepare tool owns.
|
|
6
|
+
*/
|
|
7
|
+
export const NAVIGATOR_TOOL_SPEC = {
|
|
8
|
+
name: NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
9
|
+
label: "游奕使建议",
|
|
10
|
+
description: "游奕使终局回执:排好序的下一步角色路线建议。",
|
|
11
|
+
promptSnippet: "游奕使建议",
|
|
12
|
+
parameters: navigatorOutputSchema,
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type NavigatorRuntimeDependencies = {
|
|
16
|
+
loadSoul(): Promise<string>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Gatekeeper (门下省) terminating receipt contracts (#639).
|
|
3
|
+
* Direct public seat shares the province decision shape: dispatch | pass.
|
|
4
|
+
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
5
|
+
*/
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
|
|
8
|
+
import { openToolObject } from "../open-tool-schema.ts";
|
|
9
|
+
import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
|
|
10
|
+
|
|
11
|
+
export const GATEKEEPER_OUTPUT_TOOL_NAME = "ak_gatekeeper_output";
|
|
12
|
+
export const GATEKEEPER_ACCEPTED_TEXT = "门下省决议已受理";
|
|
13
|
+
|
|
14
|
+
/** Same open decision shape the province uses inside audit sessions. */
|
|
15
|
+
export const gatekeeperDecisionSchema = openToolObject(
|
|
16
|
+
Type.Object({
|
|
17
|
+
status: Type.Unknown({
|
|
18
|
+
description: "dispatch | pass — 形状指引,非 schema 闸",
|
|
19
|
+
}),
|
|
20
|
+
officer: Type.Unknown({
|
|
21
|
+
description: "status 为 dispatch 时为 inspector | notary",
|
|
22
|
+
}),
|
|
23
|
+
findings: Type.Unknown({
|
|
24
|
+
description: "status 为 pass 时可选 string[] findings",
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const gatekeeperOutputSchema = withInfrastructureFailureDeclaration(
|
|
30
|
+
gatekeeperDecisionSchema,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export type GatekeeperDirectOutput =
|
|
34
|
+
| { readonly status: "dispatch"; readonly officer: "inspector" | "notary" }
|
|
35
|
+
| { readonly status: "pass"; readonly findings?: readonly string[] };
|
|
36
|
+
|
|
37
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
38
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function asStringArray(value: unknown): readonly string[] {
|
|
42
|
+
if (!Array.isArray(value)) return [];
|
|
43
|
+
return value.filter((item): item is string => typeof item === "string");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project one lawful explicit Gatekeeper decision (dispatch | pass).
|
|
48
|
+
* No throw on shape — ADR 0055 / 第 0 条: already-submitted params are retained as-is;
|
|
49
|
+
* public-terminal projects non-usable releases via typed failure cause.
|
|
50
|
+
*/
|
|
51
|
+
export function projectLawfulGatekeeperOutput(value: unknown): GatekeeperDirectOutput | undefined {
|
|
52
|
+
if (!isRecord(value)) return undefined;
|
|
53
|
+
if (value.status === "pass") {
|
|
54
|
+
return Array.isArray(value.findings)
|
|
55
|
+
? { status: "pass", findings: asStringArray(value.findings) }
|
|
56
|
+
: { status: "pass" };
|
|
57
|
+
}
|
|
58
|
+
if (
|
|
59
|
+
value.status === "dispatch" &&
|
|
60
|
+
(value.officer === "inspector" || value.officer === "notary")
|
|
61
|
+
) {
|
|
62
|
+
return { status: "dispatch", officer: value.officer };
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Settlement/recording path: only lawful recorded dispatch/pass.
|
|
69
|
+
* Does not gate role admission — callers must not use this to reject a submission.
|
|
70
|
+
*/
|
|
71
|
+
export function validateRecordedGatekeeperOutput(value: unknown): GatekeeperDirectOutput {
|
|
72
|
+
const projected = projectLawfulGatekeeperOutput(value);
|
|
73
|
+
if (projected === undefined) {
|
|
74
|
+
throw new Error("Gatekeeper output has no recognized execution discriminator");
|
|
75
|
+
}
|
|
76
|
+
return projected;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function gatekeeperDecisiveFacts(
|
|
80
|
+
output: GatekeeperDirectOutput,
|
|
81
|
+
): Record<string, unknown> {
|
|
82
|
+
const facts: Record<string, unknown> = { status: output.status };
|
|
83
|
+
if (output.status === "dispatch") {
|
|
84
|
+
facts.officer = output.officer;
|
|
85
|
+
} else if (Array.isArray(output.findings)) {
|
|
86
|
+
facts.findingsCount = output.findings.length;
|
|
87
|
+
}
|
|
88
|
+
return facts;
|
|
89
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Navigator (游奕使) terminating receipt contracts (#639).
|
|
3
|
+
* Direct public seat submits route advice with the same candidate shape the
|
|
4
|
+
* attendance prepare tool owns — one shape authority for navigator advice.
|
|
5
|
+
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
6
|
+
*/
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
|
|
9
|
+
import { openToolObject } from "../open-tool-schema.ts";
|
|
10
|
+
import { withInfrastructureFailureDeclaration } from "./terminating-infrastructure.ts";
|
|
11
|
+
|
|
12
|
+
export const NAVIGATOR_OUTPUT_TOOL_NAME = "ak_navigator_output";
|
|
13
|
+
export const NAVIGATOR_ACCEPTED_TEXT = "游奕使建议已受理";
|
|
14
|
+
|
|
15
|
+
export const navigatorOutputSchema = withInfrastructureFailureDeclaration(
|
|
16
|
+
openToolObject(
|
|
17
|
+
Type.Object({
|
|
18
|
+
status: Type.Unknown({
|
|
19
|
+
description: "advice — 形状指引,非 schema 闸",
|
|
20
|
+
}),
|
|
21
|
+
candidates: Type.Unknown({
|
|
22
|
+
description: "排好序的路线建议数组,元素含 next/phase/reason — 形状指引,非 schema 闸",
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
29
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type NavigatorAdvice = { readonly status: "advice"; readonly candidates: readonly unknown[] };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Project one lawful explicit Navigator advice receipt: candidates array,
|
|
36
|
+
* each candidate a record. Broken ancillary fields are preserved as submitted —
|
|
37
|
+
* shape is not an admission gate (ADR 0055 / 第 0 条).
|
|
38
|
+
*/
|
|
39
|
+
export function projectLawfulNavigatorOutput(value: unknown): NavigatorAdvice | undefined {
|
|
40
|
+
if (!isRecord(value) || !Array.isArray(value.candidates)) return undefined;
|
|
41
|
+
if (value.status !== "advice") return undefined;
|
|
42
|
+
return { status: "advice", candidates: value.candidates.filter(isRecord) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Settlement/recording path: only lawful recorded candidate arrays.
|
|
47
|
+
* Does not gate role admission — callers must not use this to reject a submission.
|
|
48
|
+
*/
|
|
49
|
+
export function validateRecordedNavigatorOutput(value: unknown): NavigatorAdvice {
|
|
50
|
+
const projected = projectLawfulNavigatorOutput(value);
|
|
51
|
+
if (projected === undefined) {
|
|
52
|
+
throw new Error("Navigator output has no recognized execution discriminator");
|
|
53
|
+
}
|
|
54
|
+
return projected;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function navigatorDecisiveFacts(output: { readonly candidates: readonly unknown[] }): Record<string, unknown> {
|
|
58
|
+
return { status: "advice", candidates: output.candidates };
|
|
59
|
+
}
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
import { isAuditEscalationResult } from "../audit-escalation.ts";
|
|
28
28
|
import { CorrectableSubmissionError } from "../submission-correctable-error.ts";
|
|
29
29
|
import { DOCTOR_ACCEPTED_TEXT, DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput, type DoctorOutput, type DoctorSubmission } from "../doctor-contracts.ts";
|
|
30
|
+
import { GATEKEEPER_ACCEPTED_TEXT, GATEKEEPER_OUTPUT_TOOL_NAME, validateRecordedGatekeeperOutput, type GatekeeperDirectOutput } from "./gatekeeper-output.ts";
|
|
31
|
+
import { NAVIGATOR_ACCEPTED_TEXT, NAVIGATOR_OUTPUT_TOOL_NAME, validateRecordedNavigatorOutput, type NavigatorAdvice } from "./navigator-output.ts";
|
|
30
32
|
import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, validateMergerOutput, type MergerOutput } from "../merger-contracts.ts";
|
|
31
33
|
import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, validateRecordedNotaryOutput, type NotaryOutput } from "../notary-contracts.ts";
|
|
32
34
|
import { COUNTERSIGN_ACCEPTED_TEXT, COUNTERSIGN_OUTPUT_TOOL_NAME, validateRecordedCountersignOutput, type CountersignVerdict } from "../countersign-contracts.ts";
|
|
@@ -67,6 +69,8 @@ export {
|
|
|
67
69
|
validateRecordedCountersignOutput,
|
|
68
70
|
validateRecordedGleanerLeftOutput,
|
|
69
71
|
validateRecordedInspectorOutput,
|
|
72
|
+
validateRecordedGatekeeperOutput,
|
|
73
|
+
validateRecordedNavigatorOutput,
|
|
70
74
|
};
|
|
71
75
|
export type {
|
|
72
76
|
CollectorReceipt,
|
|
@@ -81,6 +85,8 @@ export type {
|
|
|
81
85
|
CountersignVerdict,
|
|
82
86
|
GleanerLeftOutput,
|
|
83
87
|
InspectorOutput,
|
|
88
|
+
GatekeeperDirectOutput,
|
|
89
|
+
NavigatorAdvice,
|
|
84
90
|
};
|
|
85
91
|
|
|
86
92
|
export const TERMINATING_TOOL_NAMES = [
|
|
@@ -95,6 +101,8 @@ export const TERMINATING_TOOL_NAMES = [
|
|
|
95
101
|
COUNTERSIGN_OUTPUT_TOOL_NAME,
|
|
96
102
|
GLEANER_LEFT_OUTPUT_TOOL_NAME,
|
|
97
103
|
INSPECTOR_OUTPUT_TOOL_NAME,
|
|
104
|
+
GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
105
|
+
NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
98
106
|
] as const;
|
|
99
107
|
|
|
100
108
|
export type TerminatingToolName = (typeof TERMINATING_TOOL_NAMES)[number];
|
|
@@ -109,7 +117,9 @@ export type AcceptedDetails =
|
|
|
109
117
|
| NotaryOutput
|
|
110
118
|
| CountersignVerdict
|
|
111
119
|
| GleanerLeftOutput
|
|
112
|
-
| InspectorOutput
|
|
120
|
+
| InspectorOutput
|
|
121
|
+
| GatekeeperDirectOutput
|
|
122
|
+
| NavigatorAdvice;
|
|
113
123
|
|
|
114
124
|
export function isTerminatingToolName(
|
|
115
125
|
name: string,
|
|
@@ -141,6 +151,10 @@ export function acceptedTextFor(toolName: TerminatingToolName): string {
|
|
|
141
151
|
return GLEANER_LEFT_ACCEPTED_TEXT;
|
|
142
152
|
case INSPECTOR_OUTPUT_TOOL_NAME:
|
|
143
153
|
return INSPECTOR_ACCEPTED_TEXT;
|
|
154
|
+
case GATEKEEPER_OUTPUT_TOOL_NAME:
|
|
155
|
+
return GATEKEEPER_ACCEPTED_TEXT;
|
|
156
|
+
case NAVIGATOR_OUTPUT_TOOL_NAME:
|
|
157
|
+
return NAVIGATOR_ACCEPTED_TEXT;
|
|
144
158
|
}
|
|
145
159
|
}
|
|
146
160
|
|
|
@@ -197,6 +211,8 @@ export function validateAcceptedDetails(
|
|
|
197
211
|
[COUNTERSIGN_OUTPUT_TOOL_NAME]: ["converged", "continue", "escalate"],
|
|
198
212
|
[GLEANER_LEFT_OUTPUT_TOOL_NAME]: ["completed"],
|
|
199
213
|
[INSPECTOR_OUTPUT_TOOL_NAME]: ["pass", "bounce"],
|
|
214
|
+
[GATEKEEPER_OUTPUT_TOOL_NAME]: ["dispatch", "pass"],
|
|
215
|
+
[NAVIGATOR_OUTPUT_TOOL_NAME]: ["advice"],
|
|
200
216
|
};
|
|
201
217
|
const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
|
|
202
218
|
const baseDiscriminator = discriminator;
|
|
@@ -233,6 +249,10 @@ export function validateAcceptedDetails(
|
|
|
233
249
|
return validateRecordedGleanerLeftOutput(details);
|
|
234
250
|
case INSPECTOR_OUTPUT_TOOL_NAME:
|
|
235
251
|
return validateRecordedInspectorOutput(details);
|
|
252
|
+
case GATEKEEPER_OUTPUT_TOOL_NAME:
|
|
253
|
+
return validateRecordedGatekeeperOutput(details);
|
|
254
|
+
case NAVIGATOR_OUTPUT_TOOL_NAME:
|
|
255
|
+
return validateRecordedNavigatorOutput(details);
|
|
236
256
|
}
|
|
237
257
|
} catch (error) {
|
|
238
258
|
if (error instanceof Error && error.constructor === Error) throw new AcceptedDetailsContractError(error.message, { cause: error });
|
|
@@ -279,7 +299,9 @@ export function acceptedFacts(toolName: TerminatingToolName, details: AcceptedDe
|
|
|
279
299
|
case DOCTOR_OUTPUT_TOOL_NAME:
|
|
280
300
|
case NOTARY_OUTPUT_TOOL_NAME:
|
|
281
301
|
case GLEANER_LEFT_OUTPUT_TOOL_NAME:
|
|
282
|
-
case INSPECTOR_OUTPUT_TOOL_NAME:
|
|
302
|
+
case INSPECTOR_OUTPUT_TOOL_NAME:
|
|
303
|
+
case GATEKEEPER_OUTPUT_TOOL_NAME:
|
|
304
|
+
case NAVIGATOR_OUTPUT_TOOL_NAME: return { status: (details as { status: string }).status };
|
|
283
305
|
case JUDGE_OUTPUT_TOOL_NAME: return { status: (details as { judgeStatus: string }).judgeStatus };
|
|
284
306
|
case COUNTERSIGN_OUTPUT_TOOL_NAME: return { status: (details as { countersignStatus: string }).countersignStatus };
|
|
285
307
|
case MERGER_OUTPUT_TOOL_NAME: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Composition-root unique authoritative public-role records (#509 / #524). */
|
|
2
2
|
import { COLLECTOR_OUTPUT_TOOL } from "./package-contracts/collector-output.ts";
|
|
3
|
+
import { GATEKEEPER_OUTPUT_TOOL_NAME } from "./package-contracts/gatekeeper-output.ts";
|
|
4
|
+
import { NAVIGATOR_OUTPUT_TOOL_NAME } from "./package-contracts/navigator-output.ts";
|
|
3
5
|
import { JUDGE_OUTPUT_TOOL_NAME } from "./package-contracts/judge-output.ts";
|
|
4
6
|
import { REVIEWER_OUTPUT_TOOL_NAME } from "./package-contracts/reviewer-output.ts";
|
|
5
7
|
import { CODER_OUTPUT_TOOL_NAME, FIXER_OUTPUT_TOOL_NAME } from "./package-contracts/worker-output.ts";
|
|
@@ -28,8 +30,7 @@ export const INSPECTOR_SESSION_MATERIALS = [
|
|
|
28
30
|
] as const;
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
|
-
* One record per public callable role
|
|
32
|
-
* live on the session-opening projection).
|
|
33
|
+
* One record per public callable role (#639: includes gatekeeper and navigator).
|
|
33
34
|
*/
|
|
34
35
|
export const PUBLIC_ROLE_RECORDS = [
|
|
35
36
|
{
|
|
@@ -162,6 +163,28 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
162
163
|
activationStage: "load-and-install",
|
|
163
164
|
sessionMaterials: INSPECTOR_SESSION_MATERIALS,
|
|
164
165
|
},
|
|
166
|
+
// #639: gatekeeper and navigator are roles like any other — public ak-role
|
|
167
|
+
// entries; automatic attendance (province dispatch, navigator sidecar) is
|
|
168
|
+
// unchanged and orthogonal to callability.
|
|
169
|
+
{
|
|
170
|
+
role: "gatekeeper",
|
|
171
|
+
phases: [null],
|
|
172
|
+
outputTool: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
173
|
+
inputFlag: undefined,
|
|
174
|
+
phaseFlag: undefined,
|
|
175
|
+
activationStage: "load-and-install",
|
|
176
|
+
// Province materials; officers reuse their own public records below.
|
|
177
|
+
sessionMaterials: ["CLAUDE.md", "souls/gatekeeper.md", "souls/quality-law.md", "souls/gate-output-guide.md"],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
role: "navigator",
|
|
181
|
+
phases: [null],
|
|
182
|
+
outputTool: NAVIGATOR_OUTPUT_TOOL_NAME,
|
|
183
|
+
inputFlag: undefined,
|
|
184
|
+
phaseFlag: undefined,
|
|
185
|
+
activationStage: "load-and-install",
|
|
186
|
+
sessionMaterials: ["CLAUDE.md", "souls/navigator.md"],
|
|
187
|
+
},
|
|
165
188
|
] as const;
|
|
166
189
|
|
|
167
190
|
export type PublicRoleRecord = (typeof PUBLIC_ROLE_RECORDS)[number];
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -142,6 +142,10 @@ function buildActivationFlagArgs(activation: RoleTurnActivation): string[] {
|
|
|
142
142
|
];
|
|
143
143
|
case "inspector":
|
|
144
144
|
return ["--ak-role", "inspector"];
|
|
145
|
+
case "gatekeeper":
|
|
146
|
+
return ["--ak-role", "gatekeeper"];
|
|
147
|
+
case "navigator":
|
|
148
|
+
return ["--ak-role", "navigator"];
|
|
145
149
|
default: {
|
|
146
150
|
const _exhaustive: never = activation;
|
|
147
151
|
return _exhaustive;
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -52,9 +52,11 @@ import {
|
|
|
52
52
|
parseGleanerLeftArgv,
|
|
53
53
|
parseDoctorArgv,
|
|
54
54
|
parseFixerArgv,
|
|
55
|
+
parseGatekeeperArgv,
|
|
55
56
|
parseJudgeArgv,
|
|
56
57
|
parseInspectorArgv,
|
|
57
58
|
parseMergerArgv,
|
|
59
|
+
parseNavigatorArgv,
|
|
58
60
|
parseNotaryArgv,
|
|
59
61
|
parseReviewerArgv,
|
|
60
62
|
recordLaunchedPiIdentity,
|
|
@@ -71,6 +73,7 @@ import {
|
|
|
71
73
|
type TypedOptionConsumer,
|
|
72
74
|
} from "./option-definitions.ts";
|
|
73
75
|
import { runPublicCoder, runPublicCoderResume } from "./coder-run.ts";
|
|
76
|
+
import { runPublicInstructionSeat } from "./instruction-seat-run.ts";
|
|
74
77
|
import { runPublicCollector } from "./collector-run.ts";
|
|
75
78
|
import { runPublicCountersign, runPublicCountersignResume } from "./countersign-run.ts";
|
|
76
79
|
import { runPublicGleanerLeft, runPublicGleanerLeftResume } from "./gleaner-left-run.ts";
|
|
@@ -90,7 +93,6 @@ import {
|
|
|
90
93
|
} from "./run-lifecycle.ts";
|
|
91
94
|
import {
|
|
92
95
|
INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
|
|
93
|
-
isAutomaticConfigurableSeat,
|
|
94
96
|
isPublicCallableRole,
|
|
95
97
|
isPublicCliSupportCommand,
|
|
96
98
|
isPublicConfigurableSeat,
|
|
@@ -128,6 +130,8 @@ export const PUBLIC_ROLE_ARGV = {
|
|
|
128
130
|
notary: { parse: parseNotaryArgv, options: optionsForOwner("notary") },
|
|
129
131
|
inspector: { parse: parseInspectorArgv, options: optionsForOwner("inspector") },
|
|
130
132
|
reviewer: { parse: parseReviewerArgv, options: optionsForOwner("reviewer") },
|
|
133
|
+
gatekeeper: { parse: parseGatekeeperArgv, options: optionsForOwner("gatekeeper") },
|
|
134
|
+
navigator: { parse: parseNavigatorArgv, options: optionsForOwner("navigator") },
|
|
131
135
|
/** Deterministic analysis seat (#336) — argv parse only; no LLM admission. */
|
|
132
136
|
analyst: { parse: parseAnalystArgv, options: optionsForOwner("analyst") },
|
|
133
137
|
} as const;
|
|
@@ -596,17 +600,12 @@ function requireLegalEngineName(name: string): string {
|
|
|
596
600
|
}
|
|
597
601
|
}
|
|
598
602
|
|
|
599
|
-
/** Callable seats own persistent call axes
|
|
603
|
+
/** Callable seats own persistent call axes (checked against the callable predicate). */
|
|
600
604
|
function requireCallableSeat(
|
|
601
605
|
seat: string,
|
|
602
606
|
axis: "engine" | "host",
|
|
603
607
|
verb: "set-engine" | "unset-engine" | "set-host" | "unset-host",
|
|
604
608
|
): asserts seat is PublicCallableRole {
|
|
605
|
-
if (isAutomaticConfigurableSeat(seat)) {
|
|
606
|
-
throw new CliUsageError(
|
|
607
|
-
`config ${verb} refuses ${seat}: no independent activation path; storing would be silently ineffective`,
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
609
|
if (!isPublicCallableRole(seat)) {
|
|
611
610
|
throw new CliUsageError(`unknown ${axis}-axis seat: ${seat}`);
|
|
612
611
|
}
|
|
@@ -765,12 +764,11 @@ function renderCommandHelp(command: string): string | undefined {
|
|
|
765
764
|
}
|
|
766
765
|
|
|
767
766
|
function renderRoles(seats: readonly EffectiveSeat[]): string {
|
|
768
|
-
const lines: string[] = ["seat\
|
|
767
|
+
const lines: string[] = ["seat\tsource\tmodel"];
|
|
769
768
|
for (const seat of seats) {
|
|
770
|
-
const kind = seat.automatic ? "automatic" : "callable";
|
|
771
769
|
const model =
|
|
772
770
|
seat.selection === undefined ? "-" : formatModelSpec(seat.selection);
|
|
773
|
-
lines.push(`${seat.seat}\t${
|
|
771
|
+
lines.push(`${seat.seat}\t${seat.source}\t${model}`);
|
|
774
772
|
}
|
|
775
773
|
return `${lines.join("\n")}\n`;
|
|
776
774
|
}
|
|
@@ -1569,6 +1567,35 @@ export async function runAkRole(
|
|
|
1569
1567
|
};
|
|
1570
1568
|
}
|
|
1571
1569
|
|
|
1570
|
+
// Gatekeeper/Navigator direct public run paths (#639) — instruction seats,
|
|
1571
|
+
// a role like any other; one parameterized branch for both.
|
|
1572
|
+
if (parsed.command === "gatekeeper" || parsed.command === "navigator") {
|
|
1573
|
+
const agentDir = resolveAgentDir(env, home);
|
|
1574
|
+
const cwd = env.cwd ?? process.cwd();
|
|
1575
|
+
const config = await loadAndValidateConfig(home, env.packageRoot);
|
|
1576
|
+
const credentials =
|
|
1577
|
+
env.credentials ?? (await loadCredentialProviders(agentDir));
|
|
1578
|
+
const seat = resolveEffectiveSeat(
|
|
1579
|
+
config,
|
|
1580
|
+
parsed.command,
|
|
1581
|
+
credentials,
|
|
1582
|
+
invocationFromParsed(parsed),
|
|
1583
|
+
);
|
|
1584
|
+
const result = await runPublicInstructionSeat(
|
|
1585
|
+
parsed.args,
|
|
1586
|
+
createRoleEnvironment(env, { role: parsed.command, home, agentDir, cwd, credentials, seat, config }),
|
|
1587
|
+
io,
|
|
1588
|
+
parsed.command,
|
|
1589
|
+
parsed.command === "gatekeeper"
|
|
1590
|
+
? PUBLIC_ROLE_ARGV.gatekeeper.parse
|
|
1591
|
+
: PUBLIC_ROLE_ARGV.navigator.parse,
|
|
1592
|
+
);
|
|
1593
|
+
return {
|
|
1594
|
+
exitCode: result.exitCode,
|
|
1595
|
+
...(result.terminal === undefined ? {} : { terminal: result.terminal }),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1572
1599
|
// Analyst public run path: deterministic analysis seat (#336 issue / #337 sweep).
|
|
1573
1600
|
// Not an LLM PUBLIC_CALLABLE_ROLE — registered only on PUBLIC_ROLE_ARGV (#176).
|
|
1574
1601
|
if (parsed.command === "analyst") {
|