@akagilnc/pi-workflow-roles 0.1.2020 → 0.1.2033
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compliance-transport.js +24 -19
- package/dist/evidence-child-executor.js +4 -0
- package/dist/package-owned-tool-idle.js +66 -4
- package/dist/public-cli/main.js +109 -121
- package/package.json +1 -1
- package/src/compliance-transport.ts +24 -19
- package/src/evidence-child-executor.ts +4 -0
- package/src/package-owned-tool-idle.ts +64 -7
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/coder-run.ts +5 -14
- package/src/public-cli/collector-run.ts +4 -13
- package/src/public-cli/config.ts +45 -7
- package/src/public-cli/doctor-run.ts +4 -13
- package/src/public-cli/fixer-run.ts +5 -14
- package/src/public-cli/invocation.ts +78 -7
- package/src/public-cli/judge-run.ts +5 -14
- package/src/public-cli/merger-run.ts +5 -14
- package/src/public-cli/registry.ts +2 -1
- package/src/public-cli/reviewer-run.ts +5 -14
- package/src/public-cli/run-lifecycle.ts +14 -1
- package/src/ticket-trajectory.ts +5 -1
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
6
|
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
7
|
* role failure, process termination, signal abort, config, or Pi built-in coverage.
|
|
8
|
+
*
|
|
9
|
+
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
|
+
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
|
+
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
8
12
|
*/
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
14
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
10
15
|
|
|
11
16
|
import {
|
|
12
17
|
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
13
18
|
createStreamIdleGuard,
|
|
19
|
+
type StreamIdleGuard,
|
|
14
20
|
} from "./stream-idle-guard.ts";
|
|
15
21
|
import { isProducingToolUpdate } from "./tool-execution-observation.ts";
|
|
16
22
|
|
|
@@ -19,6 +25,17 @@ export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_
|
|
|
19
25
|
|
|
20
26
|
const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
21
27
|
|
|
28
|
+
type PackageOwnedToolIdleSuspension = {
|
|
29
|
+
suspend(): void;
|
|
30
|
+
resume(): void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Active outer package-owned execute idle, if any. Nested tool executes install
|
|
35
|
+
* their own store; compliance audit only suspends the store visible at await time.
|
|
36
|
+
*/
|
|
37
|
+
const packageOwnedToolIdleScope = new AsyncLocalStorage<PackageOwnedToolIdleSuspension>();
|
|
38
|
+
|
|
22
39
|
export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
23
40
|
readonly code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
|
|
24
41
|
readonly idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
|
|
@@ -52,6 +69,24 @@ function isPackageOwnedToolActivityUpdate(partialResult: unknown): boolean {
|
|
|
52
69
|
return true;
|
|
53
70
|
}
|
|
54
71
|
|
|
72
|
+
/**
|
|
73
|
+
* #339: suspend the active package-owned execute idle for one real compliance
|
|
74
|
+
* audit await. Single-layer only — production has one runComplianceAudit seam.
|
|
75
|
+
* No-op outside a wrapped package-owned execute. No second timeout or retry.
|
|
76
|
+
*/
|
|
77
|
+
export async function withPackageOwnedToolIdleSuspended<T>(
|
|
78
|
+
run: () => Promise<T>,
|
|
79
|
+
): Promise<T> {
|
|
80
|
+
const scope = packageOwnedToolIdleScope.getStore();
|
|
81
|
+
if (scope === undefined) return run();
|
|
82
|
+
scope.suspend();
|
|
83
|
+
try {
|
|
84
|
+
return await run();
|
|
85
|
+
} finally {
|
|
86
|
+
scope.resume();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
/**
|
|
56
91
|
* Single shared execute wrapper for package-owned tool definitions.
|
|
57
92
|
* Idempotent: wrapping twice returns the same protected definition.
|
|
@@ -72,7 +107,8 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
72
107
|
const onUpdate = args[3] as ((partialResult: unknown) => void) | undefined;
|
|
73
108
|
return new Promise((resolve, reject) => {
|
|
74
109
|
let settled = false;
|
|
75
|
-
|
|
110
|
+
let suspended = false;
|
|
111
|
+
let idle: StreamIdleGuard = createStreamIdleGuard({
|
|
76
112
|
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
77
113
|
});
|
|
78
114
|
|
|
@@ -88,6 +124,25 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
88
124
|
};
|
|
89
125
|
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
90
126
|
|
|
127
|
+
const suspension: PackageOwnedToolIdleSuspension = {
|
|
128
|
+
suspend(): void {
|
|
129
|
+
if (settled || suspended) return;
|
|
130
|
+
suspended = true;
|
|
131
|
+
// ADR 0059 owns the audit interval — drop this layer until audit returns.
|
|
132
|
+
idle.signal.removeEventListener("abort", onIdle);
|
|
133
|
+
idle.dispose();
|
|
134
|
+
},
|
|
135
|
+
resume(): void {
|
|
136
|
+
if (settled || !suspended) return;
|
|
137
|
+
suspended = false;
|
|
138
|
+
// Fresh single-layer silence window for post-audit work (e.g. cleanup).
|
|
139
|
+
idle = createStreamIdleGuard({
|
|
140
|
+
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
141
|
+
});
|
|
142
|
+
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
91
146
|
const guardedOnUpdate = onUpdate === undefined
|
|
92
147
|
? undefined
|
|
93
148
|
: (partialResult: unknown) => {
|
|
@@ -101,12 +156,14 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
101
156
|
callArgs[2] = signal;
|
|
102
157
|
callArgs[3] = guardedOnUpdate;
|
|
103
158
|
|
|
104
|
-
void
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
159
|
+
void packageOwnedToolIdleScope.run(suspension, async () => {
|
|
160
|
+
try {
|
|
161
|
+
const result = await originalExecute(...callArgs);
|
|
162
|
+
settle(() => resolve(result));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
settle(() => reject(error));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
110
167
|
});
|
|
111
168
|
};
|
|
112
169
|
(wrappedExecute as { [WRAPPED]?: boolean })[WRAPPED] = true;
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
formatModelSpec,
|
|
11
11
|
loadCredentialProviders,
|
|
12
12
|
loadPublicCliConfig,
|
|
13
|
-
|
|
13
|
+
parsePersistentModelSpec,
|
|
14
14
|
resolveEffectiveSeat,
|
|
15
15
|
savePublicCliConfig,
|
|
16
16
|
setPersistentSeatConfig,
|
|
@@ -402,7 +402,7 @@ async function runConfigCommand(
|
|
|
402
402
|
if (!isPublicConfigurableSeat(seat)) {
|
|
403
403
|
throw new CliUsageError(`unknown configurable seat: ${seat}`);
|
|
404
404
|
}
|
|
405
|
-
config = setPersistentSeatConfig(config, seat,
|
|
405
|
+
config = setPersistentSeatConfig(config, seat, parsePersistentModelSpec(spec));
|
|
406
406
|
}
|
|
407
407
|
await savePublicCliConfig(config, home);
|
|
408
408
|
io.stdout(renderConfig(config));
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type AdmittedCoderInvocation,
|
|
25
25
|
} from "./invocation.ts";
|
|
26
26
|
import {
|
|
27
|
+
buildSeatModelCliArgs,
|
|
27
28
|
type CredentialProviders,
|
|
28
29
|
type SeatModelConfig,
|
|
29
30
|
} from "./config.ts";
|
|
@@ -84,17 +85,6 @@ export type CoderRunEnv = {
|
|
|
84
85
|
timeoutMs?: number;
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
function buildModelArgs(model: SeatModelConfig | undefined): string[] {
|
|
88
|
-
if (model === undefined) return [];
|
|
89
|
-
return [
|
|
90
|
-
"--provider",
|
|
91
|
-
model.provider,
|
|
92
|
-
"--model",
|
|
93
|
-
model.model,
|
|
94
|
-
"--thinking",
|
|
95
|
-
model.thinking,
|
|
96
|
-
];
|
|
97
|
-
}
|
|
98
88
|
|
|
99
89
|
/**
|
|
100
90
|
* Build Internal activation extra-args for an admitted Coder run.
|
|
@@ -136,7 +126,7 @@ export function buildCoderActivationExtraArgs(
|
|
|
136
126
|
admitted.taskPath,
|
|
137
127
|
"--mode",
|
|
138
128
|
"json",
|
|
139
|
-
...
|
|
129
|
+
...buildSeatModelCliArgs(options.model),
|
|
140
130
|
prompt,
|
|
141
131
|
];
|
|
142
132
|
}
|
|
@@ -179,7 +169,7 @@ export function buildCoderResumeActivationExtraArgs(
|
|
|
179
169
|
admitted.taskPath,
|
|
180
170
|
"--mode",
|
|
181
171
|
"json",
|
|
182
|
-
...
|
|
172
|
+
...buildSeatModelCliArgs(options.model),
|
|
183
173
|
RESUME_TRANSPORT_ENVELOPE,
|
|
184
174
|
];
|
|
185
175
|
}
|
|
@@ -304,7 +294,7 @@ async function dispatchAdmittedCoder(input: {
|
|
|
304
294
|
io,
|
|
305
295
|
);
|
|
306
296
|
}
|
|
307
|
-
await markRunRunning(admitted.runDirectory);
|
|
297
|
+
await markRunRunning(admitted.runDirectory, env.model);
|
|
308
298
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
309
299
|
|
|
310
300
|
const childEnv: NodeJS.ProcessEnv = {
|
|
@@ -434,6 +424,7 @@ export async function runPublicCoder(
|
|
|
434
424
|
attachmentPaths: parsed.attachmentPaths,
|
|
435
425
|
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
436
426
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
427
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
437
428
|
});
|
|
438
429
|
} catch (error) {
|
|
439
430
|
if (error instanceof CliUsageError) {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ParseCollectorArgvResult,
|
|
21
21
|
} from "./invocation.ts";
|
|
22
22
|
import {
|
|
23
|
+
buildSeatModelCliArgs,
|
|
23
24
|
type CredentialProviders,
|
|
24
25
|
type SeatModelConfig,
|
|
25
26
|
} from "./config.ts";
|
|
@@ -69,17 +70,6 @@ export type CollectorRunEnv = {
|
|
|
69
70
|
timeoutMs?: number;
|
|
70
71
|
};
|
|
71
72
|
|
|
72
|
-
function buildModelArgs(model: SeatModelConfig | undefined): string[] {
|
|
73
|
-
if (model === undefined) return [];
|
|
74
|
-
return [
|
|
75
|
-
"--provider",
|
|
76
|
-
model.provider,
|
|
77
|
-
"--model",
|
|
78
|
-
model.model,
|
|
79
|
-
"--thinking",
|
|
80
|
-
model.thinking,
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
73
|
|
|
84
74
|
/**
|
|
85
75
|
* Build Internal activation extra-args for an admitted Collector run.
|
|
@@ -114,7 +104,7 @@ export function buildCollectorActivationExtraArgs(
|
|
|
114
104
|
: ["--ak-collector-request-manifest", admitted.requestManifestPath]),
|
|
115
105
|
"--mode",
|
|
116
106
|
"json",
|
|
117
|
-
...
|
|
107
|
+
...buildSeatModelCliArgs(options.model),
|
|
118
108
|
prompt,
|
|
119
109
|
];
|
|
120
110
|
}
|
|
@@ -202,7 +192,7 @@ async function dispatchAdmittedCollector(input: {
|
|
|
202
192
|
io,
|
|
203
193
|
);
|
|
204
194
|
}
|
|
205
|
-
await markRunRunning(admitted.runDirectory);
|
|
195
|
+
await markRunRunning(admitted.runDirectory, env.model);
|
|
206
196
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
207
197
|
|
|
208
198
|
const childEnv: NodeJS.ProcessEnv = {
|
|
@@ -340,6 +330,7 @@ export async function runPublicCollector(
|
|
|
340
330
|
...(parsed.repo === undefined ? {} : { repo: parsed.repo }),
|
|
341
331
|
...(parsed.requestManifestPath === undefined ? {} : { requestManifestPath: parsed.requestManifestPath }),
|
|
342
332
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
333
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
343
334
|
});
|
|
344
335
|
} catch (error) {
|
|
345
336
|
if (error instanceof CliUsageError) {
|
package/src/public-cli/config.ts
CHANGED
|
@@ -108,12 +108,18 @@ export function parseModelSpec(
|
|
|
108
108
|
const thinkingSplit = trimmed.lastIndexOf(":");
|
|
109
109
|
let modelPart = trimmed;
|
|
110
110
|
let thinking: PublicThinkingLevel | undefined = fallbackThinking;
|
|
111
|
-
|
|
111
|
+
// #346: no colon → bare provider/model is legal. Colon present (any index,
|
|
112
|
+
// including 0) → suffix must be a typed PublicThinkingLevel (empty/unknown
|
|
113
|
+
// stay format rejects; never swallow ":…" into the model name).
|
|
114
|
+
if (thinkingSplit !== -1) {
|
|
112
115
|
const maybeThinking = trimmed.slice(thinkingSplit + 1);
|
|
113
|
-
if (THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (!THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`model specification must be provider/model[:thinking], got ${spec}`,
|
|
119
|
+
);
|
|
116
120
|
}
|
|
121
|
+
thinking = maybeThinking as PublicThinkingLevel;
|
|
122
|
+
modelPart = trimmed.slice(0, thinkingSplit);
|
|
117
123
|
}
|
|
118
124
|
const slash = modelPart.indexOf("/");
|
|
119
125
|
if (slash <= 0 || slash === modelPart.length - 1) {
|
|
@@ -123,16 +129,48 @@ export function parseModelSpec(
|
|
|
123
129
|
}
|
|
124
130
|
const provider = modelPart.slice(0, slash);
|
|
125
131
|
const model = modelPart.slice(slash + 1);
|
|
126
|
-
|
|
132
|
+
// #346: bare provider/model is legal on invocation — do not invent thinking.
|
|
133
|
+
// Persistent config set still requires thinking via parsePersistentModelSpec.
|
|
134
|
+
return thinking === undefined
|
|
135
|
+
? { provider, model }
|
|
136
|
+
: { provider, model, thinking };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Persistent seat config keeps the three-part provider/model:thinking grammar. */
|
|
140
|
+
export function parsePersistentModelSpec(spec: string): SeatModelConfig & {
|
|
141
|
+
thinking: PublicThinkingLevel;
|
|
142
|
+
} {
|
|
143
|
+
const parsed = parseModelSpec(spec);
|
|
144
|
+
if (parsed.thinking === undefined) {
|
|
127
145
|
throw new Error(
|
|
128
146
|
`model specification requires a thinking level (provider/model:thinking), got ${spec}`,
|
|
129
147
|
);
|
|
130
148
|
}
|
|
131
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
provider: parsed.provider,
|
|
151
|
+
model: parsed.model,
|
|
152
|
+
thinking: parsed.thinking,
|
|
153
|
+
};
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
export function formatModelSpec(selection: SeatModelConfig): string {
|
|
135
|
-
|
|
157
|
+
const base = `${selection.provider}/${selection.model}`;
|
|
158
|
+
return selection.thinking === undefined ? base : `${base}:${selection.thinking}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Single source: seat model selection → Pi argv at the public CLI execution seam.
|
|
163
|
+
* Bare provider/model omits --thinking so pi/model defaults apply; suffix passes through.
|
|
164
|
+
*/
|
|
165
|
+
export function buildSeatModelCliArgs(model: SeatModelConfig | undefined): string[] {
|
|
166
|
+
if (model === undefined) return [];
|
|
167
|
+
return [
|
|
168
|
+
"--provider",
|
|
169
|
+
model.provider,
|
|
170
|
+
"--model",
|
|
171
|
+
model.model,
|
|
172
|
+
...(model.thinking === undefined ? [] : ["--thinking", model.thinking]),
|
|
173
|
+
];
|
|
136
174
|
}
|
|
137
175
|
|
|
138
176
|
function parsePublicCliConfig(value: unknown): PublicCliConfig {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ParseDoctorArgvResult,
|
|
21
21
|
} from "./invocation.ts";
|
|
22
22
|
import {
|
|
23
|
+
buildSeatModelCliArgs,
|
|
23
24
|
type CredentialProviders,
|
|
24
25
|
type SeatModelConfig,
|
|
25
26
|
} from "./config.ts";
|
|
@@ -70,17 +71,6 @@ export type DoctorRunEnv = {
|
|
|
70
71
|
timeoutMs?: number;
|
|
71
72
|
};
|
|
72
73
|
|
|
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
74
|
|
|
85
75
|
/**
|
|
86
76
|
* Build Internal activation extra-args for an admitted Doctor run.
|
|
@@ -111,7 +101,7 @@ export function buildDoctorActivationExtraArgs(
|
|
|
111
101
|
admitted.caseRunsPath,
|
|
112
102
|
"--mode",
|
|
113
103
|
"json",
|
|
114
|
-
...
|
|
104
|
+
...buildSeatModelCliArgs(options.model),
|
|
115
105
|
prompt,
|
|
116
106
|
];
|
|
117
107
|
}
|
|
@@ -183,7 +173,7 @@ async function dispatchAdmittedDoctor(input: {
|
|
|
183
173
|
io,
|
|
184
174
|
);
|
|
185
175
|
}
|
|
186
|
-
await markRunRunning(admitted.runDirectory);
|
|
176
|
+
await markRunRunning(admitted.runDirectory, env.model);
|
|
187
177
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
188
178
|
|
|
189
179
|
const childEnv: NodeJS.ProcessEnv = {
|
|
@@ -319,6 +309,7 @@ export async function runPublicDoctor(
|
|
|
319
309
|
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
320
310
|
...(parsed.runs === undefined ? {} : { runs: parsed.runs }),
|
|
321
311
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
312
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
322
313
|
});
|
|
323
314
|
} catch (error) {
|
|
324
315
|
if (error instanceof CliUsageError) {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type AdmittedFixerInvocation,
|
|
27
27
|
} from "./invocation.ts";
|
|
28
28
|
import {
|
|
29
|
+
buildSeatModelCliArgs,
|
|
29
30
|
type CredentialProviders,
|
|
30
31
|
type SeatModelConfig,
|
|
31
32
|
} from "./config.ts";
|
|
@@ -87,17 +88,6 @@ export type FixerRunEnv = {
|
|
|
87
88
|
timeoutMs?: number;
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
function buildModelArgs(model: SeatModelConfig | undefined): string[] {
|
|
91
|
-
if (model === undefined) return [];
|
|
92
|
-
return [
|
|
93
|
-
"--provider",
|
|
94
|
-
model.provider,
|
|
95
|
-
"--model",
|
|
96
|
-
model.model,
|
|
97
|
-
"--thinking",
|
|
98
|
-
model.thinking,
|
|
99
|
-
];
|
|
100
|
-
}
|
|
101
91
|
|
|
102
92
|
/**
|
|
103
93
|
* Build Internal activation extra-args for an admitted Fixer run.
|
|
@@ -145,7 +135,7 @@ export function buildFixerActivationExtraArgs(
|
|
|
145
135
|
...prerequisiteArgs,
|
|
146
136
|
"--mode",
|
|
147
137
|
"json",
|
|
148
|
-
...
|
|
138
|
+
...buildSeatModelCliArgs(options.model),
|
|
149
139
|
prompt,
|
|
150
140
|
];
|
|
151
141
|
}
|
|
@@ -194,7 +184,7 @@ export function buildFixerResumeActivationExtraArgs(
|
|
|
194
184
|
...prerequisiteArgs,
|
|
195
185
|
"--mode",
|
|
196
186
|
"json",
|
|
197
|
-
...
|
|
187
|
+
...buildSeatModelCliArgs(options.model),
|
|
198
188
|
RESUME_TRANSPORT_ENVELOPE,
|
|
199
189
|
];
|
|
200
190
|
}
|
|
@@ -303,7 +293,7 @@ async function dispatchAdmittedFixer(input: {
|
|
|
303
293
|
io,
|
|
304
294
|
);
|
|
305
295
|
}
|
|
306
|
-
await markRunRunning(admitted.runDirectory);
|
|
296
|
+
await markRunRunning(admitted.runDirectory, env.model);
|
|
307
297
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
308
298
|
|
|
309
299
|
const childEnv: NodeJS.ProcessEnv = {
|
|
@@ -461,6 +451,7 @@ export async function runPublicFixer(
|
|
|
461
451
|
: { prerequisitesPath: parsed.prerequisitesPath }),
|
|
462
452
|
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
463
453
|
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
454
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
464
455
|
});
|
|
465
456
|
} catch (error) {
|
|
466
457
|
if (error instanceof CliUsageError) {
|
|
@@ -172,14 +172,39 @@ type RoleInvocationLedgerSource = Pick<
|
|
|
172
172
|
"runId" | "bookKey" | "projectRoot" | "runDirectory" | "sessionDirectory" | "sessionFile" | "correlationId" | "ticketNumber"
|
|
173
173
|
>;
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Effective provider/model selection recorded on the invocation identity page.
|
|
177
|
+
* thinking is present only when the caller/seat supplied it — bare model omits it.
|
|
178
|
+
*/
|
|
179
|
+
export type InvocationEffectiveModel = {
|
|
180
|
+
readonly provider: string;
|
|
181
|
+
readonly model: string;
|
|
182
|
+
readonly thinking?: string;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** Project effective model onto ledger fields; absent thinking stays absent. */
|
|
186
|
+
function effectiveModelLedgerFields(
|
|
187
|
+
model: InvocationEffectiveModel | undefined,
|
|
188
|
+
): Record<string, string> {
|
|
189
|
+
if (model === undefined) return {};
|
|
190
|
+
return {
|
|
191
|
+
provider: model.provider,
|
|
192
|
+
model: model.model,
|
|
193
|
+
...(model.thinking === undefined ? {} : { thinking: model.thinking }),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
175
197
|
/**
|
|
176
198
|
* Persist one `invocation.json` identity page for the public run.
|
|
177
199
|
* Admission is the sole source for every field; this is the only identity
|
|
178
200
|
* projection and callers never provide an independent ledger shape.
|
|
201
|
+
* When an effective model is known at admission, provider/model (and thinking
|
|
202
|
+
* only when supplied) are written onto the same page.
|
|
179
203
|
*/
|
|
180
204
|
async function writeRoleInvocationLedger(
|
|
181
205
|
source: RoleInvocationLedgerSource,
|
|
182
206
|
role: AdmittedRoleInvocation["role"],
|
|
207
|
+
effectiveModel?: InvocationEffectiveModel,
|
|
183
208
|
): Promise<void> {
|
|
184
209
|
const identity = {
|
|
185
210
|
role,
|
|
@@ -191,6 +216,7 @@ async function writeRoleInvocationLedger(
|
|
|
191
216
|
sessionFile: source.sessionFile,
|
|
192
217
|
...(source.correlationId === undefined ? {} : { correlationId: source.correlationId }),
|
|
193
218
|
...(source.ticketNumber === undefined ? {} : { ticketNumber: source.ticketNumber }),
|
|
219
|
+
...effectiveModelLedgerFields(effectiveModel),
|
|
194
220
|
};
|
|
195
221
|
await writeFile(
|
|
196
222
|
join(source.runDirectory, "invocation.json"),
|
|
@@ -199,6 +225,37 @@ async function writeRoleInvocationLedger(
|
|
|
199
225
|
);
|
|
200
226
|
}
|
|
201
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Merge the effective launch model onto the existing invocation identity page
|
|
230
|
+
* (resume / temporary override path — same field shape as admission write).
|
|
231
|
+
* Bare model clears any prior thinking key so absence stays honest.
|
|
232
|
+
*/
|
|
233
|
+
export async function recordEffectiveInvocationModel(
|
|
234
|
+
runDirectory: string,
|
|
235
|
+
model: InvocationEffectiveModel,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
const ledgerPath = join(runDirectory, "invocation.json");
|
|
238
|
+
const current = JSON.parse(await readFile(ledgerPath, "utf8")) as Record<
|
|
239
|
+
string,
|
|
240
|
+
unknown
|
|
241
|
+
>;
|
|
242
|
+
const next: Record<string, unknown> = {
|
|
243
|
+
...current,
|
|
244
|
+
provider: model.provider,
|
|
245
|
+
model: model.model,
|
|
246
|
+
};
|
|
247
|
+
if (model.thinking === undefined) {
|
|
248
|
+
delete next.thinking;
|
|
249
|
+
} else {
|
|
250
|
+
next.thinking = model.thinking;
|
|
251
|
+
}
|
|
252
|
+
await writeFile(
|
|
253
|
+
ledgerPath,
|
|
254
|
+
`${JSON.stringify(next, null, 2)}\n`,
|
|
255
|
+
"utf8",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
202
259
|
/** Merge observed launch-time fields into the single existing invocation.json identity page. */
|
|
203
260
|
async function mergeInvocationIdentityPage(
|
|
204
261
|
runDirectory: string,
|
|
@@ -686,6 +743,8 @@ export type AdmitJudgeInvocationOptions = {
|
|
|
686
743
|
project?: string;
|
|
687
744
|
/** Injectable clock/id for tests. */
|
|
688
745
|
createRunId?: () => string;
|
|
746
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
747
|
+
model?: InvocationEffectiveModel;
|
|
689
748
|
};
|
|
690
749
|
|
|
691
750
|
/**
|
|
@@ -736,7 +795,7 @@ export async function admitJudgeInvocation(
|
|
|
736
795
|
};
|
|
737
796
|
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
738
797
|
await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
|
|
739
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
798
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
740
799
|
|
|
741
800
|
return {
|
|
742
801
|
role: "judge",
|
|
@@ -811,6 +870,8 @@ export type AdmitCoderInvocationOptions = {
|
|
|
811
870
|
attachmentPaths: readonly string[];
|
|
812
871
|
project?: string;
|
|
813
872
|
createRunId?: () => string;
|
|
873
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
874
|
+
model?: InvocationEffectiveModel;
|
|
814
875
|
};
|
|
815
876
|
|
|
816
877
|
/**
|
|
@@ -874,7 +935,7 @@ export async function admitCoderInvocation(
|
|
|
874
935
|
};
|
|
875
936
|
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
876
937
|
await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
|
|
877
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
938
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
878
939
|
|
|
879
940
|
return {
|
|
880
941
|
role: "coder",
|
|
@@ -923,6 +984,8 @@ export type AdmitFixerInvocationOptions = {
|
|
|
923
984
|
prerequisitesPath?: string;
|
|
924
985
|
project?: string;
|
|
925
986
|
createRunId?: () => string;
|
|
987
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
988
|
+
model?: InvocationEffectiveModel;
|
|
926
989
|
};
|
|
927
990
|
|
|
928
991
|
/**
|
|
@@ -1026,7 +1089,7 @@ export async function admitFixerInvocation(
|
|
|
1026
1089
|
};
|
|
1027
1090
|
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1028
1091
|
await writeFile(admittedRequestPath, `${JSON.stringify(admitted, null, 2)}\n`, "utf8");
|
|
1029
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
1092
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
1030
1093
|
|
|
1031
1094
|
return {
|
|
1032
1095
|
role: "fixer",
|
|
@@ -1188,6 +1251,8 @@ export type AdmitCollectorInvocationOptions = {
|
|
|
1188
1251
|
/** Optional public request configuration; copied into the admitted run. */
|
|
1189
1252
|
requestManifestPath?: string;
|
|
1190
1253
|
createRunId?: () => string;
|
|
1254
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
1255
|
+
model?: InvocationEffectiveModel;
|
|
1191
1256
|
};
|
|
1192
1257
|
|
|
1193
1258
|
/**
|
|
@@ -1286,7 +1351,7 @@ export async function admitCollectorInvocation(
|
|
|
1286
1351
|
`${JSON.stringify(admitted, null, 2)}\n`,
|
|
1287
1352
|
"utf8",
|
|
1288
1353
|
);
|
|
1289
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
1354
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
1290
1355
|
|
|
1291
1356
|
return {
|
|
1292
1357
|
role: "collector",
|
|
@@ -1440,6 +1505,8 @@ export type AdmitDoctorInvocationOptions = {
|
|
|
1440
1505
|
attachmentPaths?: readonly string[];
|
|
1441
1506
|
project?: string;
|
|
1442
1507
|
createRunId?: () => string;
|
|
1508
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
1509
|
+
model?: InvocationEffectiveModel;
|
|
1443
1510
|
};
|
|
1444
1511
|
|
|
1445
1512
|
/**
|
|
@@ -1619,7 +1686,7 @@ export async function admitDoctorInvocation(
|
|
|
1619
1686
|
`${JSON.stringify(admitted, null, 2)}\n`,
|
|
1620
1687
|
"utf8",
|
|
1621
1688
|
);
|
|
1622
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
1689
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
1623
1690
|
|
|
1624
1691
|
return {
|
|
1625
1692
|
role: "doctor",
|
|
@@ -1730,6 +1797,8 @@ export type AdmitReviewerInvocationOptions = {
|
|
|
1730
1797
|
authorityRefs?: readonly string[];
|
|
1731
1798
|
project?: string;
|
|
1732
1799
|
createRunId?: () => string;
|
|
1800
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
1801
|
+
model?: InvocationEffectiveModel;
|
|
1733
1802
|
};
|
|
1734
1803
|
|
|
1735
1804
|
/**
|
|
@@ -1794,7 +1863,7 @@ export async function admitReviewerInvocation(
|
|
|
1794
1863
|
`${JSON.stringify(admitted, null, 2)}\n`,
|
|
1795
1864
|
"utf8",
|
|
1796
1865
|
);
|
|
1797
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
1866
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
1798
1867
|
|
|
1799
1868
|
return {
|
|
1800
1869
|
role: "reviewer",
|
|
@@ -1945,6 +2014,8 @@ export type AdmitMergerInvocationOptions = {
|
|
|
1945
2014
|
createRunId?: () => string;
|
|
1946
2015
|
/** Test seam; production binds createProductionMergerGitState(projectRoot). */
|
|
1947
2016
|
gitState?: MergerGitState;
|
|
2017
|
+
/** Effective model for this invocation — written onto invocation.json. */
|
|
2018
|
+
model?: InvocationEffectiveModel;
|
|
1948
2019
|
};
|
|
1949
2020
|
|
|
1950
2021
|
/**
|
|
@@ -2052,7 +2123,7 @@ export async function admitMergerInvocation(
|
|
|
2052
2123
|
`${JSON.stringify(admitted, null, 2)}\n`,
|
|
2053
2124
|
"utf8",
|
|
2054
2125
|
);
|
|
2055
|
-
await writeRoleInvocationLedger(admitted, admitted.role);
|
|
2126
|
+
await writeRoleInvocationLedger(admitted, admitted.role, options.model);
|
|
2056
2127
|
|
|
2057
2128
|
return {
|
|
2058
2129
|
role: "merger",
|