@akagilnc/pi-workflow-roles 0.1.1751
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/LICENSE +202 -0
- package/README.md +104 -0
- package/README.zh-CN.md +133 -0
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/dist/activation-ledger-git.js +68 -0
- package/dist/activation-ledger-session.js +120 -0
- package/dist/activation-ledger-topology.js +239 -0
- package/dist/activation-reconciliation.js +61 -0
- package/dist/audit-escalation.js +108 -0
- package/dist/auditor-dossier-tool.js +35 -0
- package/dist/canonical-json.js +78 -0
- package/dist/compliance-transport.js +77 -0
- package/dist/doctor-contracts.js +172 -0
- package/dist/dossier-resolution.js +103 -0
- package/dist/evidence-child-executor.js +661 -0
- package/dist/exact-utf8.js +12 -0
- package/dist/git-object-id.js +7 -0
- package/dist/in-process-session.js +50 -0
- package/dist/merger-contracts.js +76 -0
- package/dist/navigator-attendance.js +995 -0
- package/dist/navigator-invocation-identity.js +220 -0
- package/dist/open-tool-schema.js +39 -0
- package/dist/package-contracts/collector-output.js +50 -0
- package/dist/package-contracts/fixer-output.js +72 -0
- package/dist/package-contracts/fixer-packet.js +77 -0
- package/dist/package-contracts/judge-output.js +17 -0
- package/dist/package-contracts/reviewer-output.js +82 -0
- package/dist/package-contracts/terminating-tools.js +173 -0
- package/dist/package-contracts/worker-output.js +13 -0
- package/dist/package-owned-tool-idle.js +104 -0
- package/dist/packaged-role-registry.js +34 -0
- package/dist/public-cli/main.js +23867 -0
- package/dist/public-command-renderer.js +20 -0
- package/dist/reviewer-agent.js +93 -0
- package/dist/reviewer-child-executor.js +23 -0
- package/dist/reviewer-construction.js +95 -0
- package/dist/reviewer-dispatch.js +77 -0
- package/dist/reviewer-execution-ledger.js +160 -0
- package/dist/reviewer-failure-diagnostic.js +17 -0
- package/dist/reviewer-git-snapshot.js +38 -0
- package/dist/reviewer-pinned-git.js +146 -0
- package/dist/reviewer-preflight-error.js +15 -0
- package/dist/reviewer-prompt-identity.js +10 -0
- package/dist/reviewer-scope-prompt.js +21 -0
- package/dist/reviewer-workspace.js +151 -0
- package/dist/sha256.js +5 -0
- package/dist/sitian-record-entry.js +33 -0
- package/dist/stderr-jsonl.js +26 -0
- package/dist/stream-idle-guard.js +75 -0
- package/dist/tool-execution-observation.js +141 -0
- package/dist/uuidv7.js +21 -0
- package/dist/work-subject-identity.js +53 -0
- package/extensions/role-runtime.ts +303 -0
- package/package.json +69 -0
- package/packets/fixer-prerequisites.json +6 -0
- package/packets/fixer-repair.md +5 -0
- package/packets/judge-apply.md +77 -0
- package/packets/judge-authority.md +64 -0
- package/packets/judge-plan.md +55 -0
- package/packets/judge-review.md +49 -0
- package/packets/judge-submission.md +34 -0
- package/resources/methods/code-review/SKILL.md +92 -0
- package/resources/methods/code-review/agents/openai.yaml +3 -0
- package/resources/methods/code-review/provenance.json +26 -0
- package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
- package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
- package/resources/methods/diagnosing-bugs/provenance.json +31 -0
- package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
- package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
- package/resources/methods/tdd/SKILL.md +38 -0
- package/resources/methods/tdd/agents/openai.yaml +3 -0
- package/resources/methods/tdd/mocking.md +59 -0
- package/resources/methods/tdd/provenance.json +36 -0
- package/resources/methods/tdd/tests.md +77 -0
- package/resources/navigator-route-playbook.md +32 -0
- package/schemas/tool-execution-observation.schema.json +107 -0
- package/scripts/build-package.mjs +65 -0
- package/scripts/generate-tool-execution-observation-schema.ts +7 -0
- package/souls/coder.md +10 -0
- package/souls/collector.md +11 -0
- package/souls/doctor-auditor.md +23 -0
- package/souls/doctor.md +8 -0
- package/souls/fixer-auditor.md +33 -0
- package/souls/fixer.md +13 -0
- package/souls/judge-auditor.md +33 -0
- package/souls/judge.md +74 -0
- package/souls/merger.md +5 -0
- package/souls/navigator.md +5 -0
- package/souls/reviewer-auditor.md +25 -0
- package/souls/reviewer.md +11 -0
- package/src/activation-ledger-git.ts +96 -0
- package/src/activation-ledger-session.ts +188 -0
- package/src/activation-ledger-topology.ts +301 -0
- package/src/activation-ledger.ts +240 -0
- package/src/activation-reconciliation.ts +163 -0
- package/src/activation-trace.ts +38 -0
- package/src/audit-escalation.ts +177 -0
- package/src/auditor-dossier-tool.ts +48 -0
- package/src/auditor-soul.ts +28 -0
- package/src/canonical-json.ts +74 -0
- package/src/canonical-skill-binding.ts +107 -0
- package/src/collector-config.ts +89 -0
- package/src/collector-evidence.ts +461 -0
- package/src/collector-github.ts +656 -0
- package/src/collector-identity.ts +161 -0
- package/src/collector-ledger.ts +827 -0
- package/src/collector-receipt.ts +87 -0
- package/src/collector-role.ts +592 -0
- package/src/collector-tool-schemas.ts +19 -0
- package/src/compliance-transport.ts +130 -0
- package/src/doctor-auditor.ts +53 -0
- package/src/doctor-contracts.ts +166 -0
- package/src/doctor-evidence.ts +47 -0
- package/src/doctor-role.ts +18 -0
- package/src/dossier-resolution.ts +137 -0
- package/src/evidence-child-executor.ts +775 -0
- package/src/exact-utf8.ts +9 -0
- package/src/factory-board.ts +1822 -0
- package/src/git-object-id.ts +11 -0
- package/src/human-format.ts +65 -0
- package/src/in-process-session.ts +78 -0
- package/src/judge-auditor.ts +55 -0
- package/src/judge-recording-anti-forge.ts +53 -0
- package/src/judge-role.ts +160 -0
- package/src/merger-contracts.ts +71 -0
- package/src/merger-git-state.ts +76 -0
- package/src/merger-role.ts +60 -0
- package/src/navigator-attendance.ts +1254 -0
- package/src/navigator-invocation-identity.ts +446 -0
- package/src/open-tool-schema.ts +46 -0
- package/src/package-contracts/collector-output.ts +109 -0
- package/src/package-contracts/fixer-output.ts +81 -0
- package/src/package-contracts/fixer-packet.ts +93 -0
- package/src/package-contracts/judge-output.ts +39 -0
- package/src/package-contracts/reviewer-output.ts +115 -0
- package/src/package-contracts/terminating-tools.ts +259 -0
- package/src/package-contracts/worker-output.ts +36 -0
- package/src/package-owned-tool-idle.ts +134 -0
- package/src/package-resources/method-skill-binding.ts +87 -0
- package/src/package-resources/method-skill.ts +358 -0
- package/src/packaged-role-registry.ts +36 -0
- package/src/public-cli/cli-errors.ts +11 -0
- package/src/public-cli/cli-io.ts +4 -0
- package/src/public-cli/cli.ts +912 -0
- package/src/public-cli/coder-run.ts +575 -0
- package/src/public-cli/collector-run.ts +375 -0
- package/src/public-cli/command-renderer.ts +8 -0
- package/src/public-cli/config.ts +346 -0
- package/src/public-cli/doctor-run.ts +355 -0
- package/src/public-cli/explicit-internal.ts +274 -0
- package/src/public-cli/fixer-run.ts +587 -0
- package/src/public-cli/host-pi-runtime.ts +112 -0
- package/src/public-cli/invocation.ts +1958 -0
- package/src/public-cli/judge-run.ts +507 -0
- package/src/public-cli/main.ts +15 -0
- package/src/public-cli/merger-run.ts +681 -0
- package/src/public-cli/public-run-credentials.ts +71 -0
- package/src/public-cli/registry.ts +153 -0
- package/src/public-cli/reviewer-run.ts +561 -0
- package/src/public-cli/run-lifecycle.ts +884 -0
- package/src/public-cli/settlement.ts +3765 -0
- package/src/public-cli/terminal.ts +325 -0
- package/src/public-command-renderer.ts +43 -0
- package/src/reviewer-agent.ts +94 -0
- package/src/reviewer-auditor.ts +53 -0
- package/src/reviewer-child-executor.ts +31 -0
- package/src/reviewer-construction.ts +137 -0
- package/src/reviewer-dispatch.ts +94 -0
- package/src/reviewer-execution-ledger.ts +206 -0
- package/src/reviewer-failure-diagnostic.ts +18 -0
- package/src/reviewer-git-snapshot.ts +53 -0
- package/src/reviewer-pinned-git.ts +144 -0
- package/src/reviewer-preflight-error.ts +14 -0
- package/src/reviewer-prompt-identity.ts +17 -0
- package/src/reviewer-role.ts +193 -0
- package/src/reviewer-scope-prompt.ts +24 -0
- package/src/reviewer-settlement.ts +63 -0
- package/src/reviewer-workspace.ts +111 -0
- package/src/role-runtime.ts +884 -0
- package/src/sha256.ts +6 -0
- package/src/sitian-record-entry.ts +57 -0
- package/src/stderr-jsonl.ts +28 -0
- package/src/stream-idle-guard.ts +98 -0
- package/src/ticket-snapshot.ts +662 -0
- package/src/ticket-trajectory.ts +1000 -0
- package/src/tool-execution-observation.ts +168 -0
- package/src/uuidv7.ts +1 -0
- package/src/work-subject-identity.ts +94 -0
- package/src/worker-role.ts +434 -0
- package/src/worker-submission-gates.ts +225 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent and effective per-seat model/thinking configuration for ak-role.
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
AUTOMATIC_NAVIGATOR_SEAT,
|
|
10
|
+
PUBLIC_CALLABLE_ROLES,
|
|
11
|
+
PUBLIC_CONFIGURABLE_SEATS,
|
|
12
|
+
type ModelRef,
|
|
13
|
+
type PublicConfigurableSeat,
|
|
14
|
+
type PublicThinkingLevel,
|
|
15
|
+
publicStartupCandidates,
|
|
16
|
+
} from "./registry.ts";
|
|
17
|
+
|
|
18
|
+
export type CredentialProviders = {
|
|
19
|
+
"openai-codex": boolean;
|
|
20
|
+
xai: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type SeatModelConfig = ModelRef;
|
|
24
|
+
|
|
25
|
+
export type PublicCliConfig = {
|
|
26
|
+
seats: Partial<Record<PublicConfigurableSeat, SeatModelConfig>>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type EffectiveSource = "persistent" | "startup" | "invocation" | "unconfigured";
|
|
30
|
+
|
|
31
|
+
export type EffectiveSeat = {
|
|
32
|
+
seat: PublicConfigurableSeat;
|
|
33
|
+
/** True when the seat is automatic Navigator attendance rather than caller-selected. */
|
|
34
|
+
automatic: boolean;
|
|
35
|
+
source: EffectiveSource;
|
|
36
|
+
selection?: SeatModelConfig;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type InvocationModelOverride = {
|
|
40
|
+
model?: string;
|
|
41
|
+
thinking?: PublicThinkingLevel;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const THINKING_LEVELS = new Set<PublicThinkingLevel>([
|
|
45
|
+
"off",
|
|
46
|
+
"minimal",
|
|
47
|
+
"low",
|
|
48
|
+
"medium",
|
|
49
|
+
"high",
|
|
50
|
+
"xhigh",
|
|
51
|
+
"max",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
export function publicCliConfigPath(home: string = homedir()): string {
|
|
55
|
+
return join(home, ".ak-roles", "public-cli.json");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadPublicCliConfig(
|
|
59
|
+
home: string = homedir(),
|
|
60
|
+
): Promise<PublicCliConfig> {
|
|
61
|
+
const path = publicCliConfigPath(home);
|
|
62
|
+
try {
|
|
63
|
+
const raw = await readFile(path, "utf8");
|
|
64
|
+
return parsePublicCliConfig(JSON.parse(raw));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (
|
|
67
|
+
error instanceof Error &&
|
|
68
|
+
"code" in error &&
|
|
69
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
70
|
+
) {
|
|
71
|
+
return { seats: {} };
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function savePublicCliConfig(
|
|
78
|
+
config: PublicCliConfig,
|
|
79
|
+
home: string = homedir(),
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const path = publicCliConfigPath(home);
|
|
82
|
+
await mkdir(dirname(path), { recursive: true });
|
|
83
|
+
const normalized = parsePublicCliConfig(config);
|
|
84
|
+
await writeFile(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function setPersistentSeatConfig(
|
|
88
|
+
config: PublicCliConfig,
|
|
89
|
+
seat: PublicConfigurableSeat,
|
|
90
|
+
selection: SeatModelConfig,
|
|
91
|
+
): PublicCliConfig {
|
|
92
|
+
return {
|
|
93
|
+
seats: {
|
|
94
|
+
...config.seats,
|
|
95
|
+
[seat]: { ...selection },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function parseModelSpec(
|
|
101
|
+
spec: string,
|
|
102
|
+
fallbackThinking?: PublicThinkingLevel,
|
|
103
|
+
): SeatModelConfig {
|
|
104
|
+
const trimmed = spec.trim();
|
|
105
|
+
if (!trimmed) {
|
|
106
|
+
throw new Error("model specification must be non-empty");
|
|
107
|
+
}
|
|
108
|
+
const thinkingSplit = trimmed.lastIndexOf(":");
|
|
109
|
+
let modelPart = trimmed;
|
|
110
|
+
let thinking: PublicThinkingLevel | undefined = fallbackThinking;
|
|
111
|
+
if (thinkingSplit > 0) {
|
|
112
|
+
const maybeThinking = trimmed.slice(thinkingSplit + 1);
|
|
113
|
+
if (THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
|
|
114
|
+
thinking = maybeThinking as PublicThinkingLevel;
|
|
115
|
+
modelPart = trimmed.slice(0, thinkingSplit);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const slash = modelPart.indexOf("/");
|
|
119
|
+
if (slash <= 0 || slash === modelPart.length - 1) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`model specification must be provider/model[:thinking], got ${spec}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const provider = modelPart.slice(0, slash);
|
|
125
|
+
const model = modelPart.slice(slash + 1);
|
|
126
|
+
if (!thinking) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`model specification requires a thinking level (provider/model:thinking), got ${spec}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return { provider, model, thinking };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function formatModelSpec(selection: SeatModelConfig): string {
|
|
135
|
+
return `${selection.provider}/${selection.model}:${selection.thinking}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parsePublicCliConfig(value: unknown): PublicCliConfig {
|
|
139
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
140
|
+
throw new Error("public CLI config must be an object");
|
|
141
|
+
}
|
|
142
|
+
const record = value as { seats?: unknown };
|
|
143
|
+
if (record.seats === undefined) {
|
|
144
|
+
return { seats: {} };
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
record.seats === null ||
|
|
148
|
+
typeof record.seats !== "object" ||
|
|
149
|
+
Array.isArray(record.seats)
|
|
150
|
+
) {
|
|
151
|
+
throw new Error("public CLI config.seats must be an object");
|
|
152
|
+
}
|
|
153
|
+
const seats: PublicCliConfig["seats"] = {};
|
|
154
|
+
for (const [key, raw] of Object.entries(
|
|
155
|
+
record.seats as Record<string, unknown>,
|
|
156
|
+
)) {
|
|
157
|
+
if (!(PUBLIC_CONFIGURABLE_SEATS as readonly string[]).includes(key)) {
|
|
158
|
+
throw new Error(`unknown configurable seat in config: ${key}`);
|
|
159
|
+
}
|
|
160
|
+
seats[key as PublicConfigurableSeat] = parseSeatModelConfig(raw, key);
|
|
161
|
+
}
|
|
162
|
+
return { seats };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseSeatModelConfig(value: unknown, seat: string): SeatModelConfig {
|
|
166
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
167
|
+
throw new Error(`config seat ${seat} must be an object`);
|
|
168
|
+
}
|
|
169
|
+
const raw = value as Record<string, unknown>;
|
|
170
|
+
if (typeof raw.provider !== "string" || raw.provider.trim() === "") {
|
|
171
|
+
throw new Error(`config seat ${seat} requires provider`);
|
|
172
|
+
}
|
|
173
|
+
if (typeof raw.model !== "string" || raw.model.trim() === "") {
|
|
174
|
+
throw new Error(`config seat ${seat} requires model`);
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
typeof raw.thinking !== "string" ||
|
|
178
|
+
!THINKING_LEVELS.has(raw.thinking as PublicThinkingLevel)
|
|
179
|
+
) {
|
|
180
|
+
throw new Error(`config seat ${seat} requires a valid thinking level`);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
provider: raw.provider,
|
|
184
|
+
model: raw.model,
|
|
185
|
+
thinking: raw.thinking as PublicThinkingLevel,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function providerConfigured(
|
|
190
|
+
credentials: CredentialProviders,
|
|
191
|
+
provider: string,
|
|
192
|
+
): boolean {
|
|
193
|
+
if (provider === "openai-codex") return credentials["openai-codex"] === true;
|
|
194
|
+
if (provider === "xai") return credentials.xai === true;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Typed provider-credential absence for the public seat providers ak-role owns.
|
|
200
|
+
* Presence is read from auth.json shape (CredentialProviders), never from stderr prose.
|
|
201
|
+
* Returns undefined for unknown/custom providers (not this package's credential map).
|
|
202
|
+
*/
|
|
203
|
+
export function missingPublicProviderCredential(
|
|
204
|
+
provider: string,
|
|
205
|
+
credentials: CredentialProviders,
|
|
206
|
+
): provider is "openai-codex" | "xai" {
|
|
207
|
+
if (provider !== "openai-codex" && provider !== "xai") return false;
|
|
208
|
+
return !providerConfigured(credentials, provider);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function pickStartupCandidate(
|
|
212
|
+
seat: PublicConfigurableSeat,
|
|
213
|
+
credentials: CredentialProviders,
|
|
214
|
+
): SeatModelConfig | undefined {
|
|
215
|
+
for (const candidate of publicStartupCandidates(seat)) {
|
|
216
|
+
if (providerConfigured(credentials, candidate.provider)) {
|
|
217
|
+
return { ...candidate };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function resolveBaseSeat(
|
|
224
|
+
config: PublicCliConfig,
|
|
225
|
+
seat: PublicConfigurableSeat,
|
|
226
|
+
credentials: CredentialProviders,
|
|
227
|
+
): EffectiveSeat {
|
|
228
|
+
const automatic = seat === AUTOMATIC_NAVIGATOR_SEAT;
|
|
229
|
+
const persistent = config.seats[seat];
|
|
230
|
+
if (persistent !== undefined) {
|
|
231
|
+
return {
|
|
232
|
+
seat,
|
|
233
|
+
automatic,
|
|
234
|
+
source: "persistent",
|
|
235
|
+
selection: { ...persistent },
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const startup = pickStartupCandidate(seat, credentials);
|
|
239
|
+
if (startup !== undefined) {
|
|
240
|
+
return {
|
|
241
|
+
seat,
|
|
242
|
+
automatic,
|
|
243
|
+
source: "startup",
|
|
244
|
+
selection: startup,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return { seat, automatic, source: "unconfigured" };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function resolveEffectiveSeat(
|
|
251
|
+
config: PublicCliConfig,
|
|
252
|
+
seat: PublicConfigurableSeat,
|
|
253
|
+
credentials: CredentialProviders,
|
|
254
|
+
invocation?: InvocationModelOverride,
|
|
255
|
+
): EffectiveSeat {
|
|
256
|
+
const automatic = seat === AUTOMATIC_NAVIGATOR_SEAT;
|
|
257
|
+
const hasInvocation =
|
|
258
|
+
invocation !== undefined &&
|
|
259
|
+
(invocation.model !== undefined || invocation.thinking !== undefined);
|
|
260
|
+
if (!hasInvocation || invocation === undefined) {
|
|
261
|
+
return resolveBaseSeat(config, seat, credentials);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (invocation.model !== undefined) {
|
|
265
|
+
const spec =
|
|
266
|
+
invocation.model.includes(":") || invocation.thinking === undefined
|
|
267
|
+
? invocation.model
|
|
268
|
+
: `${invocation.model}:${invocation.thinking}`;
|
|
269
|
+
return {
|
|
270
|
+
seat,
|
|
271
|
+
automatic,
|
|
272
|
+
source: "invocation",
|
|
273
|
+
selection: parseModelSpec(spec),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const base = resolveBaseSeat(config, seat, credentials);
|
|
278
|
+
if (base.selection === undefined || invocation.thinking === undefined) {
|
|
279
|
+
return { seat, automatic, source: "unconfigured" };
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
seat,
|
|
283
|
+
automatic,
|
|
284
|
+
source: "invocation",
|
|
285
|
+
selection: { ...base.selection, thinking: invocation.thinking },
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function effectiveSeatConfigurations(
|
|
290
|
+
config: PublicCliConfig,
|
|
291
|
+
credentials: CredentialProviders,
|
|
292
|
+
invocation?: InvocationModelOverride,
|
|
293
|
+
): EffectiveSeat[] {
|
|
294
|
+
return PUBLIC_CONFIGURABLE_SEATS.map((seat) =>
|
|
295
|
+
resolveEffectiveSeat(config, seat, credentials, invocation),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Callable roles first (package order), then automatic Navigator. */
|
|
300
|
+
export function listRolesForDisplay(
|
|
301
|
+
config: PublicCliConfig,
|
|
302
|
+
credentials: CredentialProviders,
|
|
303
|
+
invocation?: InvocationModelOverride,
|
|
304
|
+
): EffectiveSeat[] {
|
|
305
|
+
const all = effectiveSeatConfigurations(config, credentials, invocation);
|
|
306
|
+
const callable = PUBLIC_CALLABLE_ROLES.map(
|
|
307
|
+
(role) => all.find((entry) => entry.seat === role)!,
|
|
308
|
+
);
|
|
309
|
+
const navigator = all.find((entry) => entry.seat === AUTOMATIC_NAVIGATOR_SEAT)!;
|
|
310
|
+
return [...callable, navigator];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Read configured credential presence from a Pi auth.json document.
|
|
315
|
+
* Only presence of a provider key matters for startup selection (#11).
|
|
316
|
+
*/
|
|
317
|
+
export function credentialProvidersFromAuthData(
|
|
318
|
+
data: unknown,
|
|
319
|
+
): CredentialProviders {
|
|
320
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
321
|
+
return { "openai-codex": false, xai: false };
|
|
322
|
+
}
|
|
323
|
+
const record = data as Record<string, unknown>;
|
|
324
|
+
return {
|
|
325
|
+
"openai-codex": Object.prototype.hasOwnProperty.call(record, "openai-codex"),
|
|
326
|
+
xai: Object.prototype.hasOwnProperty.call(record, "xai"),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function loadCredentialProviders(
|
|
331
|
+
agentDir: string,
|
|
332
|
+
): Promise<CredentialProviders> {
|
|
333
|
+
try {
|
|
334
|
+
const raw = await readFile(join(agentDir, "auth.json"), "utf8");
|
|
335
|
+
return credentialProvidersFromAuthData(JSON.parse(raw));
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (
|
|
338
|
+
error instanceof Error &&
|
|
339
|
+
"code" in error &&
|
|
340
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
341
|
+
) {
|
|
342
|
+
return { "openai-codex": false, xai: false };
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Doctor Role run: admit Issue → retained case via #78 → explicit
|
|
3
|
+
* Internal activate → settle Terminal result (#113). One-shot; no resume path.
|
|
4
|
+
* Failure settlement reuses the #107 shared owner.
|
|
5
|
+
*/
|
|
6
|
+
import { writeFile } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
runExplicitInternalActivation,
|
|
11
|
+
type ExplicitInternalKnownFailure,
|
|
12
|
+
type ExplicitInternalPiRunner,
|
|
13
|
+
type ExplicitInternalPiResult,
|
|
14
|
+
} from "./explicit-internal.ts";
|
|
15
|
+
import { CliUsageError } from "./cli-errors.ts";
|
|
16
|
+
import {
|
|
17
|
+
admitDoctorInvocation,
|
|
18
|
+
buildDoctorTransportPrompt,
|
|
19
|
+
type AdmittedDoctorInvocation,
|
|
20
|
+
type ParseDoctorArgvResult,
|
|
21
|
+
} from "./invocation.ts";
|
|
22
|
+
import {
|
|
23
|
+
type CredentialProviders,
|
|
24
|
+
type SeatModelConfig,
|
|
25
|
+
} from "./config.ts";
|
|
26
|
+
import {
|
|
27
|
+
missingCredentialPreDispatchFailure,
|
|
28
|
+
postRunMissingCredentialFailure,
|
|
29
|
+
} from "./public-run-credentials.ts";
|
|
30
|
+
import {
|
|
31
|
+
acquireRunWriterLease,
|
|
32
|
+
clearTypedProviderHttpObservation,
|
|
33
|
+
markRunAdmitted,
|
|
34
|
+
markRunRunning,
|
|
35
|
+
markRunTerminal,
|
|
36
|
+
RunWriterLeaseHeldError,
|
|
37
|
+
type RunWriterLease,
|
|
38
|
+
} from "./run-lifecycle.ts";
|
|
39
|
+
import {
|
|
40
|
+
classifyPostAdmissionFailure,
|
|
41
|
+
exitCodeForTerminalOutcome,
|
|
42
|
+
formatTerminalResult,
|
|
43
|
+
inspectJudgeSession,
|
|
44
|
+
isLawfulTypedTerminalOutcome,
|
|
45
|
+
presentFailureTerminal,
|
|
46
|
+
presentStructuralRejection,
|
|
47
|
+
resolveAuditedRunnerKnownFailure,
|
|
48
|
+
explicitInternalKnownFailureClassificationInput,
|
|
49
|
+
settleFailureTerminalResult,
|
|
50
|
+
trySettleDoctorTerminalResult,
|
|
51
|
+
trySettleComplianceAuditIncompleteTerminalResult,
|
|
52
|
+
} from "./settlement.ts";
|
|
53
|
+
import type { CliIo } from "./cli-io.ts";
|
|
54
|
+
import type {
|
|
55
|
+
ControlledFailureCause,
|
|
56
|
+
TerminalResult,
|
|
57
|
+
} from "./terminal.ts";
|
|
58
|
+
|
|
59
|
+
export type DoctorRunEnv = {
|
|
60
|
+
home: string;
|
|
61
|
+
agentDir: string;
|
|
62
|
+
packageRoot: string;
|
|
63
|
+
cwd: string;
|
|
64
|
+
correlationId?: string;
|
|
65
|
+
piRunner?: ExplicitInternalPiRunner;
|
|
66
|
+
model?: SeatModelConfig;
|
|
67
|
+
credentials?: CredentialProviders;
|
|
68
|
+
createRunId?: () => string;
|
|
69
|
+
extraPiArgs?: readonly string[];
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function buildModelArgs(model: SeatModelConfig | undefined): string[] {
|
|
74
|
+
if (model === undefined) return [];
|
|
75
|
+
return [
|
|
76
|
+
"--provider",
|
|
77
|
+
model.provider,
|
|
78
|
+
"--model",
|
|
79
|
+
model.model,
|
|
80
|
+
"--thinking",
|
|
81
|
+
model.thinking,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build Internal activation extra-args for an admitted Doctor run.
|
|
87
|
+
* Always --no-skills (Doctor forbids every Skill). Session under #78 book.
|
|
88
|
+
* Case path is the retained runs root — never a legacy case packet.
|
|
89
|
+
*/
|
|
90
|
+
export function buildDoctorActivationExtraArgs(
|
|
91
|
+
admitted: AdmittedDoctorInvocation,
|
|
92
|
+
options: {
|
|
93
|
+
model?: SeatModelConfig;
|
|
94
|
+
extraPiArgs?: readonly string[];
|
|
95
|
+
} = {},
|
|
96
|
+
): string[] {
|
|
97
|
+
const prompt = buildDoctorTransportPrompt(admitted);
|
|
98
|
+
return [
|
|
99
|
+
"--no-skills",
|
|
100
|
+
"--no-prompt-templates",
|
|
101
|
+
"--no-themes",
|
|
102
|
+
"--no-context-files",
|
|
103
|
+
"--session",
|
|
104
|
+
admitted.sessionFile,
|
|
105
|
+
"--session-dir",
|
|
106
|
+
admitted.sessionDirectory,
|
|
107
|
+
...(options.extraPiArgs ?? []),
|
|
108
|
+
"--ak-role",
|
|
109
|
+
"doctor",
|
|
110
|
+
"--ak-doctor-case",
|
|
111
|
+
admitted.caseRunsPath,
|
|
112
|
+
"--mode",
|
|
113
|
+
"json",
|
|
114
|
+
...buildModelArgs(options.model),
|
|
115
|
+
prompt,
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function presentControlledFailure(
|
|
120
|
+
admitted: AdmittedDoctorInvocation,
|
|
121
|
+
failureInput: {
|
|
122
|
+
timedOut: boolean;
|
|
123
|
+
code: number | null;
|
|
124
|
+
stderr: string;
|
|
125
|
+
thrown?: unknown;
|
|
126
|
+
knownFailure?: ExplicitInternalKnownFailure;
|
|
127
|
+
},
|
|
128
|
+
io: CliIo,
|
|
129
|
+
): Promise<{
|
|
130
|
+
exitCode: number;
|
|
131
|
+
admitted: AdmittedDoctorInvocation;
|
|
132
|
+
terminal: TerminalResult;
|
|
133
|
+
}> {
|
|
134
|
+
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
135
|
+
const session =
|
|
136
|
+
!hasThrown &&
|
|
137
|
+
!failureInput.timedOut &&
|
|
138
|
+
failureInput.knownFailure === undefined
|
|
139
|
+
? await inspectJudgeSession(admitted.sessionFile)
|
|
140
|
+
: undefined;
|
|
141
|
+
const failure = classifyPostAdmissionFailure({
|
|
142
|
+
timedOut: failureInput.timedOut,
|
|
143
|
+
code: failureInput.code,
|
|
144
|
+
stderr: failureInput.stderr,
|
|
145
|
+
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
146
|
+
...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
|
|
147
|
+
...(session === undefined ? {} : { session }),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Doctor does not support resume — always terminal.
|
|
151
|
+
await markRunTerminal(admitted.runDirectory).catch(() => undefined);
|
|
152
|
+
|
|
153
|
+
const terminal = await settleFailureTerminalResult(admitted, failure);
|
|
154
|
+
presentFailureTerminal(terminal, io);
|
|
155
|
+
return {
|
|
156
|
+
exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
|
|
157
|
+
admitted,
|
|
158
|
+
terminal,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function dispatchAdmittedDoctor(input: {
|
|
163
|
+
admitted: AdmittedDoctorInvocation;
|
|
164
|
+
env: DoctorRunEnv;
|
|
165
|
+
io: CliIo;
|
|
166
|
+
extraArgs: string[];
|
|
167
|
+
lease: RunWriterLease;
|
|
168
|
+
}): Promise<{
|
|
169
|
+
exitCode: number;
|
|
170
|
+
admitted: AdmittedDoctorInvocation;
|
|
171
|
+
terminal?: TerminalResult;
|
|
172
|
+
}> {
|
|
173
|
+
const { admitted, env, io, extraArgs, lease } = input;
|
|
174
|
+
try {
|
|
175
|
+
const missingCredential = missingCredentialPreDispatchFailure(
|
|
176
|
+
env.model,
|
|
177
|
+
env.credentials,
|
|
178
|
+
);
|
|
179
|
+
if (missingCredential !== undefined) {
|
|
180
|
+
return await presentControlledFailure(
|
|
181
|
+
admitted,
|
|
182
|
+
missingCredential,
|
|
183
|
+
io,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
await markRunRunning(admitted.runDirectory);
|
|
187
|
+
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
188
|
+
|
|
189
|
+
const childEnv: NodeJS.ProcessEnv = {
|
|
190
|
+
...process.env,
|
|
191
|
+
HOME: env.home,
|
|
192
|
+
PI_CODING_AGENT_DIR: env.agentDir,
|
|
193
|
+
AK_ROLE_RUN_DIR: admitted.runDirectory,
|
|
194
|
+
};
|
|
195
|
+
if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
|
|
196
|
+
childEnv.AK_CORRELATION_ID = env.correlationId;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let result: ExplicitInternalPiResult;
|
|
200
|
+
try {
|
|
201
|
+
result = await runExplicitInternalActivation({
|
|
202
|
+
packageRoot: env.packageRoot,
|
|
203
|
+
extraArgs,
|
|
204
|
+
cwd: admitted.projectRoot,
|
|
205
|
+
home: env.home,
|
|
206
|
+
agentDir: env.agentDir,
|
|
207
|
+
env: childEnv,
|
|
208
|
+
timeoutMs: env.timeoutMs,
|
|
209
|
+
...(env.piRunner === undefined ? {} : { runner: env.piRunner }),
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return await presentControlledFailure(
|
|
213
|
+
admitted,
|
|
214
|
+
{
|
|
215
|
+
timedOut: false,
|
|
216
|
+
code: null,
|
|
217
|
+
stderr: "",
|
|
218
|
+
thrown: error,
|
|
219
|
+
},
|
|
220
|
+
io,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
await writeFile(
|
|
226
|
+
join(admitted.runDirectory, "stderr.log"),
|
|
227
|
+
result.stderr,
|
|
228
|
+
"utf8",
|
|
229
|
+
);
|
|
230
|
+
} catch {
|
|
231
|
+
// continue to lawful / controlled-failure settlement
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let lawful: TerminalResult | undefined;
|
|
235
|
+
try {
|
|
236
|
+
lawful = await trySettleDoctorTerminalResult(admitted);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return await presentControlledFailure(
|
|
239
|
+
admitted,
|
|
240
|
+
{
|
|
241
|
+
timedOut: false,
|
|
242
|
+
code: result.code,
|
|
243
|
+
stderr: result.stderr,
|
|
244
|
+
thrown: error,
|
|
245
|
+
},
|
|
246
|
+
io,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (lawful !== undefined && isLawfulTypedTerminalOutcome(lawful.roleOutcome)) {
|
|
250
|
+
await markRunTerminal(admitted.runDirectory).catch(() => undefined);
|
|
251
|
+
io.stdout(formatTerminalResult(lawful));
|
|
252
|
+
return {
|
|
253
|
+
exitCode: exitCodeForTerminalOutcome(lawful.roleOutcome),
|
|
254
|
+
admitted,
|
|
255
|
+
terminal: lawful,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const auditIncomplete = await trySettleComplianceAuditIncompleteTerminalResult(admitted);
|
|
260
|
+
if (auditIncomplete !== undefined) {
|
|
261
|
+
await markRunTerminal(admitted.runDirectory).catch(() => undefined);
|
|
262
|
+
if (auditIncomplete.roleOutcome.kind === "failure") {
|
|
263
|
+
presentFailureTerminal(auditIncomplete, io);
|
|
264
|
+
} else {
|
|
265
|
+
io.stdout(formatTerminalResult(auditIncomplete));
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
exitCode: exitCodeForTerminalOutcome(auditIncomplete.roleOutcome),
|
|
269
|
+
admitted,
|
|
270
|
+
terminal: auditIncomplete,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const credentialFailure = postRunMissingCredentialFailure(
|
|
275
|
+
result,
|
|
276
|
+
env.model,
|
|
277
|
+
env.credentials,
|
|
278
|
+
);
|
|
279
|
+
const knownFailure = await resolveAuditedRunnerKnownFailure({
|
|
280
|
+
runner: result.knownFailure,
|
|
281
|
+
sessionFile: admitted.sessionFile,
|
|
282
|
+
credential: credentialFailure,
|
|
283
|
+
});
|
|
284
|
+
return await presentControlledFailure(
|
|
285
|
+
admitted,
|
|
286
|
+
{
|
|
287
|
+
timedOut: result.timedOut,
|
|
288
|
+
code: result.code,
|
|
289
|
+
stderr: result.stderr,
|
|
290
|
+
...(knownFailure === undefined ? {} : { knownFailure }),
|
|
291
|
+
},
|
|
292
|
+
io,
|
|
293
|
+
);
|
|
294
|
+
} finally {
|
|
295
|
+
await lease.release();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function runPublicDoctor(
|
|
300
|
+
argv: readonly string[],
|
|
301
|
+
env: DoctorRunEnv,
|
|
302
|
+
io: CliIo,
|
|
303
|
+
parseDoctorArgv: (args: readonly string[]) => ParseDoctorArgvResult,
|
|
304
|
+
): Promise<{
|
|
305
|
+
exitCode: number;
|
|
306
|
+
admitted?: AdmittedDoctorInvocation;
|
|
307
|
+
terminal?: TerminalResult;
|
|
308
|
+
}> {
|
|
309
|
+
let admitted: AdmittedDoctorInvocation;
|
|
310
|
+
try {
|
|
311
|
+
const parsed = parseDoctorArgv(argv);
|
|
312
|
+
admitted = await admitDoctorInvocation({
|
|
313
|
+
home: env.home,
|
|
314
|
+
cwd: env.cwd,
|
|
315
|
+
issueNumber: parsed.issueNumber,
|
|
316
|
+
instruction: parsed.instruction,
|
|
317
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
318
|
+
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
319
|
+
...(parsed.runs === undefined ? {} : { runs: parsed.runs }),
|
|
320
|
+
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof CliUsageError) {
|
|
324
|
+
presentStructuralRejection(error, io);
|
|
325
|
+
return { exitCode: 2 };
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await markRunAdmitted(admitted);
|
|
331
|
+
|
|
332
|
+
let lease: RunWriterLease;
|
|
333
|
+
try {
|
|
334
|
+
lease = await acquireRunWriterLease(admitted.runDirectory);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (error instanceof RunWriterLeaseHeldError) {
|
|
337
|
+
presentStructuralRejection(error, io);
|
|
338
|
+
return { exitCode: 2 };
|
|
339
|
+
}
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const extraArgs = buildDoctorActivationExtraArgs(admitted, {
|
|
344
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
345
|
+
...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return await dispatchAdmittedDoctor({
|
|
349
|
+
admitted,
|
|
350
|
+
env,
|
|
351
|
+
io,
|
|
352
|
+
extraArgs,
|
|
353
|
+
lease,
|
|
354
|
+
});
|
|
355
|
+
}
|