@akagilnc/pi-workflow-roles 0.1.3414 → 0.1.3445
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/archivist-record-entry.js +21 -12
- package/dist/archivist-record-topology.js +20 -0
- package/dist/auditor-dossier-tool.js +67 -76
- package/dist/canonical-json.js +66 -69
- package/dist/compliance-transport.js +99 -86
- package/dist/countersign-contracts.js +23 -25
- package/dist/doctor-contracts.js +169 -145
- package/dist/engine-detour-tool.js +83 -87
- package/dist/engine-detour.js +148 -183
- package/dist/evidence-child-executor.js +12 -9
- package/dist/exact-utf8.js +11 -10
- package/dist/git-object-id.js +10 -5
- package/dist/gleaner-left-contracts.js +33 -35
- package/dist/grok/production-host.js +239 -154
- package/dist/inspector-contracts.js +28 -31
- package/dist/institutional-resolution.js +101 -104
- package/dist/ledger-session-read.js +241 -0
- package/dist/merger-contracts.js +66 -56
- package/dist/navigator-attendance.js +0 -6
- package/dist/navigator-session-contracts.js +6 -10
- package/dist/notary-contracts.js +91 -100
- package/dist/open-tool-schema.js +32 -31
- package/dist/package-contracts/collector-output.js +56 -43
- package/dist/package-contracts/fixer-output.js +52 -50
- package/dist/package-contracts/fixer-packet.js +67 -64
- package/dist/package-contracts/gatekeeper-output.js +48 -56
- package/dist/package-contracts/judge-output.js +23 -20
- package/dist/package-contracts/navigator-output.js +35 -40
- package/dist/package-contracts/reviewer-output.js +94 -110
- package/dist/package-contracts/terminating-infrastructure.js +62 -90
- package/dist/package-contracts/worker-output.js +30 -11
- package/dist/package-resources/engine-material.js +58 -94
- package/dist/pi/in-process-session.js +2 -12
- package/dist/public-cli/config.js +403 -485
- package/dist/public-cli/main.js +1226 -561
- package/dist/public-cli/registry.js +107 -114
- package/dist/receipt-delivery-policy.js +74 -76
- package/dist/session-opening-materials.js +44 -47
- package/dist/sha256.js +5 -3
- package/dist/sitian-appender.js +204 -210
- package/dist/sitian-contracts.js +15 -19
- package/dist/sitian-facade.js +5 -10
- package/dist/sitian-reader.js +37 -46
- package/dist/stream-idle-guard.js +63 -71
- package/dist/ticket-seat-memory.js +244 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +41 -0
- package/src/analyst-gate-cycles-read.ts +152 -83
- package/src/analyst-ledger.ts +170 -43
- package/src/archivist-record-entry.ts +32 -11
- package/src/archivist-record-topology.ts +39 -0
- package/src/evidence-child-executor.ts +18 -10
- package/src/grok/production-host.ts +44 -9
- package/src/host-contracts.ts +18 -0
- package/src/host-transition-prior-native.ts +8 -1
- package/src/ledger-session-read.ts +56 -0
- package/src/navigator-attendance.ts +0 -6
- package/src/navigator-session-contracts.ts +15 -14
- package/src/pi/durable-principal.ts +6 -0
- package/src/pi/in-process-session.ts +10 -17
- package/src/public-cli/cli.ts +2 -14
- package/src/public-cli/config.ts +6 -28
- package/src/public-cli/inspector-run.ts +21 -10
- package/src/public-cli/invocation.ts +8 -25
- package/src/public-cli/notary-run.ts +21 -4
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/post-admission.ts +333 -42
- package/src/public-cli/registry.ts +2 -8
- package/src/public-cli/run-lifecycle.ts +1 -3
- package/src/public-cli/settlement.ts +266 -81
- package/src/session-opening-materials.ts +4 -2
- package/src/ticket-seat-memory.ts +454 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure archivist record placement topology (ADR 0048 / 0065).
|
|
3
|
+
* No SessionManager / pi-coding-agent — cold public-bin and ledger discovery
|
|
4
|
+
* may import this without pulling open-session runtime into the graph.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
10
|
+
import {
|
|
11
|
+
activationBookDirectory,
|
|
12
|
+
resolveActivationLedgerHome,
|
|
13
|
+
resolveActivationLedgerHomeForPath,
|
|
14
|
+
} from "./activation-ledger-topology.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sole subject-keyed nest path under the ledger book (ADR 0048 / 0065).
|
|
18
|
+
* Callers must not re-hash subject or re-join book/kind/digest themselves.
|
|
19
|
+
*/
|
|
20
|
+
export function subjectKeyedRecordDirectory(input: {
|
|
21
|
+
readonly cwd: string;
|
|
22
|
+
readonly kind: string;
|
|
23
|
+
readonly subject: string;
|
|
24
|
+
/** Parent session file — home path-derives from it when present. */
|
|
25
|
+
readonly parentSessionFile?: string;
|
|
26
|
+
/** Explicit process home when no parent file is available (discovery-only). */
|
|
27
|
+
readonly home?: string;
|
|
28
|
+
}): string {
|
|
29
|
+
const ledgerHome =
|
|
30
|
+
input.parentSessionFile !== undefined && input.parentSessionFile.length > 0
|
|
31
|
+
? resolveActivationLedgerHomeForPath(input.parentSessionFile)
|
|
32
|
+
: resolveActivationLedgerHome(input.home);
|
|
33
|
+
const digest = createHash("sha256").update(input.subject).digest("hex").slice(0, 32);
|
|
34
|
+
return join(
|
|
35
|
+
activationBookDirectory(ledgerHome, resolveBookKeyFromGit(input.cwd)),
|
|
36
|
+
input.kind,
|
|
37
|
+
digest,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -26,6 +26,12 @@ import {
|
|
|
26
26
|
} from "./compliance-transport.ts";
|
|
27
27
|
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
28
28
|
import { sitianReport } from "./sitian-facade.ts";
|
|
29
|
+
import {
|
|
30
|
+
isTicketSeatMemorySeat,
|
|
31
|
+
readRunTicketNumber,
|
|
32
|
+
ticketSeatMemorySubject,
|
|
33
|
+
TICKET_SEAT_MEMORY_KIND,
|
|
34
|
+
} from "./ticket-seat-memory.ts";
|
|
29
35
|
import { createEngineDetourToolDefinition } from "./engine-detour-tool.ts";
|
|
30
36
|
import { engineNameFromEnv } from "./engine-detour.ts";
|
|
31
37
|
import {
|
|
@@ -564,9 +570,16 @@ export async function executeAuditorChild(
|
|
|
564
570
|
const parentHeader = parentSessionManager?.getHeader?.();
|
|
565
571
|
const parentSessionFile = parentSessionManager?.getSessionFile?.();
|
|
566
572
|
const parentAttemptEntryId = parentSessionManager?.getLeafId?.();
|
|
573
|
+
// #636: ticket+seat subject resumes the same nest (navigator subject path); no ticket → fresh nest.
|
|
574
|
+
const ticketNumber = await readRunTicketNumber(runDirectory);
|
|
575
|
+
const memorySubject =
|
|
576
|
+
ticketNumber !== undefined && isTicketSeatMemorySeat(seat)
|
|
577
|
+
? ticketSeatMemorySubject(ticketNumber, seat)
|
|
578
|
+
: undefined;
|
|
567
579
|
const auditorSessionManager: SessionManager = createRecordSession({
|
|
568
580
|
cwd,
|
|
569
|
-
kind:
|
|
581
|
+
kind: TICKET_SEAT_MEMORY_KIND,
|
|
582
|
+
...(memorySubject === undefined ? {} : { subject: memorySubject }),
|
|
570
583
|
...(parentSessionManager === undefined ? {} : { parent: parentSessionManager }),
|
|
571
584
|
});
|
|
572
585
|
|
|
@@ -962,7 +975,7 @@ export function createNativeNavigatorSessionFactory(
|
|
|
962
975
|
return async ({ context, subject, modelSettingPath, tool }) => {
|
|
963
976
|
const resolved = await resolveNavigatorSeatSelection(context, modelSettingPath, defaultModelSettingPath);
|
|
964
977
|
let selection = resolved.selection;
|
|
965
|
-
let thinkingLevel = resolved.thinkingLevel;
|
|
978
|
+
let thinkingLevel: string | undefined = resolved.thinkingLevel;
|
|
966
979
|
let configuredLabel = resolved.configuredLabel;
|
|
967
980
|
|
|
968
981
|
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
@@ -1104,7 +1117,7 @@ export function createNativeNavigatorSessionFactory(
|
|
|
1104
1117
|
}
|
|
1105
1118
|
},
|
|
1106
1119
|
entries: () => sessionManager.getEntries(),
|
|
1107
|
-
setModel: async (next
|
|
1120
|
+
setModel: async (next) => {
|
|
1108
1121
|
let nextParsed: ReturnType<typeof parseNavigatorModelSetting>;
|
|
1109
1122
|
try {
|
|
1110
1123
|
nextParsed = parseNavigatorModelSetting(next);
|
|
@@ -1120,16 +1133,11 @@ export function createNativeNavigatorSessionFactory(
|
|
|
1120
1133
|
`Navigator model switch requires a new session: ${configuredLabel} → ${next}`,
|
|
1121
1134
|
);
|
|
1122
1135
|
}
|
|
1123
|
-
|
|
1124
|
-
throw new NavigatorUnavailableError(
|
|
1125
|
-
"thinking",
|
|
1126
|
-
`Navigator thinking level ${nextThinking} is unavailable for ${next}`,
|
|
1127
|
-
);
|
|
1128
|
-
}
|
|
1136
|
+
// Thinking is opaque pass-through; no stick/availability re-check.
|
|
1129
1137
|
selection = {
|
|
1130
1138
|
provider: nextParsed.provider,
|
|
1131
1139
|
model: nextParsed.model,
|
|
1132
|
-
thinking: nextParsed.thinkingLevel,
|
|
1140
|
+
...(nextParsed.thinkingLevel === undefined ? {} : { thinking: nextParsed.thinkingLevel }),
|
|
1133
1141
|
};
|
|
1134
1142
|
thinkingLevel = nextParsed.thinkingLevel;
|
|
1135
1143
|
configuredLabel = next;
|
|
@@ -167,18 +167,25 @@ export async function bindProductionGrokIsolation(
|
|
|
167
167
|
* while preserving the session dossier under runDirectory/grok-home. Success,
|
|
168
168
|
* typed-result, and throw paths all clean up; cleanup failure and primary+cleanup
|
|
169
169
|
* both surface.
|
|
170
|
+
*
|
|
171
|
+
* `noteOpened` fires only after bind succeeds — the actual open event, distinct
|
|
172
|
+
* from turn return/throw — so last-host ownership can retain the opened run
|
|
173
|
+
* even when the body or cleanup later throws (#636).
|
|
170
174
|
*/
|
|
171
175
|
export async function withProductionGrokIsolation<T>(
|
|
172
176
|
runDirectory: string,
|
|
173
177
|
operatorHome: string,
|
|
174
178
|
packageRoot: string,
|
|
175
179
|
run: (binding: ProductionGrokIsolationBinding) => Promise<T>,
|
|
180
|
+
noteOpened?: (runDirectory: string) => void,
|
|
176
181
|
): Promise<T> {
|
|
177
182
|
let binding: ProductionGrokIsolationBinding | undefined;
|
|
178
183
|
let primaryFailure: ProductionGrokPrimaryFailure = NO_PRODUCTION_GROK_PRIMARY_FAILURE;
|
|
179
184
|
let value!: T;
|
|
180
185
|
try {
|
|
181
186
|
binding = await bindProductionGrokIsolation(runDirectory, operatorHome, packageRoot);
|
|
187
|
+
// Open fact: bind succeeded under runDirectory. Not deferred to turn outcome.
|
|
188
|
+
noteOpened?.(runDirectory);
|
|
182
189
|
value = await run(binding);
|
|
183
190
|
} catch (error) {
|
|
184
191
|
primaryFailure = { present: true, value: error };
|
|
@@ -278,6 +285,24 @@ async function recordGrokCapabilities(
|
|
|
278
285
|
);
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Which run directory owns GROK_HOME for this turn (#636 / ADR 0077).
|
|
290
|
+
* Same-host ticket-seat resume reopens the prior native home; otherwise the live run.
|
|
291
|
+
* Private to the production host — not a test seam.
|
|
292
|
+
*/
|
|
293
|
+
function resolveProductionGrokIsolationRunDirectory(
|
|
294
|
+
request: Pick<RoleTurnRequest, "runDirectory" | "continuation" | "nativeHomeRunDirectory">,
|
|
295
|
+
): string {
|
|
296
|
+
if (
|
|
297
|
+
request.continuation.kind === "resume" &&
|
|
298
|
+
typeof request.nativeHomeRunDirectory === "string" &&
|
|
299
|
+
request.nativeHomeRunDirectory.length > 0
|
|
300
|
+
) {
|
|
301
|
+
return request.nativeHomeRunDirectory;
|
|
302
|
+
}
|
|
303
|
+
return request.runDirectory;
|
|
304
|
+
}
|
|
305
|
+
|
|
281
306
|
/**
|
|
282
307
|
* Assemble the production grok-build RoleTurnHost from the S6 true adapter.
|
|
283
308
|
*/
|
|
@@ -317,16 +342,26 @@ export function createProductionGrokRoleTurnHost(options: ProductionGrokHostOpti
|
|
|
317
342
|
|
|
318
343
|
return {
|
|
319
344
|
executeTurn(request) {
|
|
345
|
+
// #636 same-host ticket-seat resume: reopen the prior run's grok-home so
|
|
346
|
+
// session/load hits the ACP storage that bound the shared principal.
|
|
347
|
+
// Fresh runs and cross-host arrivals keep isolating under request.runDirectory.
|
|
348
|
+
const isolationRunDirectory = resolveProductionGrokIsolationRunDirectory(request);
|
|
320
349
|
const execution = serial.then(() =>
|
|
321
|
-
withProductionGrokIsolation(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
350
|
+
withProductionGrokIsolation(
|
|
351
|
+
isolationRunDirectory,
|
|
352
|
+
request.home,
|
|
353
|
+
packageRoot,
|
|
354
|
+
async (binding) => {
|
|
355
|
+
turn = binding;
|
|
356
|
+
try {
|
|
357
|
+
// S6 seatbelt hangs on request.home — same isolated root as GROK_HOME.
|
|
358
|
+
return await inner.executeTurn({ ...request, home: binding.controlledHome });
|
|
359
|
+
} finally {
|
|
360
|
+
turn = undefined;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
request.noteNativeHomeOpened,
|
|
364
|
+
),
|
|
330
365
|
);
|
|
331
366
|
serial = execution.then(
|
|
332
367
|
() => undefined,
|
package/src/host-contracts.ts
CHANGED
|
@@ -165,6 +165,19 @@ export type RoleTurnRequest = {
|
|
|
165
165
|
readonly timeoutMs?: number;
|
|
166
166
|
/** Set by post-admission only on a real host switch; never on same-host resume. */
|
|
167
167
|
readonly hostTransition?: RoleTurnHostTransition;
|
|
168
|
+
/**
|
|
169
|
+
* Grok native isolation run for ticket-seat resume (#636 / ADR 0079):
|
|
170
|
+
* reopen grok-home under this prior run (ACP storage). Covers same-host,
|
|
171
|
+
* same-run retry after a new run, and return-to-Grok after another host.
|
|
172
|
+
* Absent → isolate under runDirectory. Never a cross-host byte handoff.
|
|
173
|
+
*/
|
|
174
|
+
readonly nativeHomeRunDirectory?: string;
|
|
175
|
+
/**
|
|
176
|
+
* Production Grok isolation notes the actual open event here after bind succeeds
|
|
177
|
+
* (#636). Open is not the same event as turn return/throw — post-admission uses
|
|
178
|
+
* this fact for last-host ownership instead of inferring open from the turn outcome.
|
|
179
|
+
*/
|
|
180
|
+
readonly noteNativeHomeOpened?: (runDirectory: string) => void;
|
|
168
181
|
};
|
|
169
182
|
|
|
170
183
|
/** Turn result — only fields upper layers currently consume. */
|
|
@@ -203,6 +216,11 @@ export type NewDurablePrincipalRequest = {
|
|
|
203
216
|
/** Host authority for issuing, checking, and temporarily decoding durable principals. */
|
|
204
217
|
export interface DurablePrincipalAuthority {
|
|
205
218
|
issue(request: NewDurablePrincipalRequest): DurablePrincipal;
|
|
219
|
+
/**
|
|
220
|
+
* Host-owned seal of already-placed coordinates into a durable principal wire
|
|
221
|
+
* object. Public layers must not forge opaque principal shapes (#636).
|
|
222
|
+
*/
|
|
223
|
+
seal(coordinates: DurablePrincipalCoordinates): DurablePrincipal;
|
|
206
224
|
isAvailable(principal: DurablePrincipal): Promise<boolean>;
|
|
207
225
|
decode(principal: unknown): DurablePrincipalCoordinates;
|
|
208
226
|
}
|
|
@@ -71,12 +71,18 @@ export async function listGrokNativeRecordPaths(runDirectory: string): Promise<s
|
|
|
71
71
|
* Project one hostTransition only for a real switch between known hosts.
|
|
72
72
|
* Unknown host names → undefined (no inject). Empty native volume still
|
|
73
73
|
* yields a typed switch (empty path list).
|
|
74
|
+
*
|
|
75
|
+
* Grok native home lives under the run that wrote it. Ticket-seat memory
|
|
76
|
+
* cross-run switches pass `previousRunDirectory` from last-host metadata so
|
|
77
|
+
* the scan does not look at an empty fresh run (#636 / #617 DK-4).
|
|
74
78
|
*/
|
|
75
79
|
export async function projectHostTransitionPriorNative(input: {
|
|
76
80
|
readonly previousHost: string;
|
|
77
81
|
readonly liveHost: string;
|
|
78
82
|
readonly runDirectory: string;
|
|
79
83
|
readonly piSessionFile: string;
|
|
84
|
+
/** Prior run that held Grok native records when principal continued across runs. */
|
|
85
|
+
readonly previousRunDirectory?: string;
|
|
80
86
|
}): Promise<RoleTurnHostTransition | undefined> {
|
|
81
87
|
if (input.previousHost === input.liveHost) return undefined;
|
|
82
88
|
if (!isKnownRoleTurnHost(input.previousHost) || !isKnownRoleTurnHost(input.liveHost)) {
|
|
@@ -90,7 +96,8 @@ export async function projectHostTransitionPriorNative(input: {
|
|
|
90
96
|
};
|
|
91
97
|
}
|
|
92
98
|
// previousHost === "grok-build": DK-7 path handoff only — do not read bytes.
|
|
93
|
-
const
|
|
99
|
+
const grokRoot = input.previousRunDirectory ?? input.runDirectory;
|
|
100
|
+
const paths = await listGrokNativeRecordPaths(grokRoot);
|
|
94
101
|
return {
|
|
95
102
|
previousHost: "grok-build",
|
|
96
103
|
priorNativePaths: paths,
|
|
@@ -258,3 +258,59 @@ export function extractSessionToolIntervals(
|
|
|
258
258
|
: { ...base, endedAt: interval.endedAt };
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* One binding-owned interval inside a continuous volume (#636):
|
|
264
|
+
* from the nearest preceding binding at-or-before `anchorIndex` through the
|
|
265
|
+
* row before the next binding (or EOF). Gate/settlement/analyst share this
|
|
266
|
+
* slice — do not fork a second interval scan.
|
|
267
|
+
*
|
|
268
|
+
* `closed` is true iff a later binding ended the interval before EOF of the
|
|
269
|
+
* provided rows. On a damaged prefix parse, closed intervals are fully owned
|
|
270
|
+
* by their run; open intervals (through prefix EOF) may have lost rows to the
|
|
271
|
+
* bad line and stay honest-missing for full span/tools.
|
|
272
|
+
*/
|
|
273
|
+
export type BindingInterval = {
|
|
274
|
+
readonly rows: readonly LedgerSessionRow[];
|
|
275
|
+
readonly closed: boolean;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export function intervalRowsAroundAnchor(
|
|
279
|
+
rows: readonly LedgerSessionRow[],
|
|
280
|
+
anchorIndex: number,
|
|
281
|
+
isBindingRow: (row: LedgerSessionRow) => boolean,
|
|
282
|
+
): BindingInterval {
|
|
283
|
+
let start = 0;
|
|
284
|
+
for (let i = anchorIndex; i >= 0; i -= 1) {
|
|
285
|
+
if (isBindingRow(rows[i]!)) {
|
|
286
|
+
start = i;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
let end = rows.length;
|
|
291
|
+
for (let i = Math.max(anchorIndex, start) + 1; i < rows.length; i += 1) {
|
|
292
|
+
if (isBindingRow(rows[i]!)) {
|
|
293
|
+
end = i;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return { rows: rows.slice(start, end), closed: end < rows.length };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Interval owned by the first binding row that satisfies `matches`.
|
|
302
|
+
* `undefined` when the volume carries no matching binding — callers must not
|
|
303
|
+
* silently treat the whole continuous volume as that owner.
|
|
304
|
+
*/
|
|
305
|
+
export function intervalRowsForMatchingBinding(
|
|
306
|
+
rows: readonly LedgerSessionRow[],
|
|
307
|
+
isBindingRow: (row: LedgerSessionRow) => boolean,
|
|
308
|
+
matches: (row: LedgerSessionRow) => boolean,
|
|
309
|
+
): BindingInterval | undefined {
|
|
310
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
311
|
+
const row = rows[i]!;
|
|
312
|
+
if (!isBindingRow(row) || !matches(row)) continue;
|
|
313
|
+
return intervalRowsAroundAnchor(rows, i, isBindingRow);
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
@@ -648,9 +648,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
648
648
|
try {
|
|
649
649
|
await created.setModel?.(modelSetting, model.thinkingLevel);
|
|
650
650
|
if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
|
|
651
|
-
if (created.getThinkingLevel?.() !== undefined && created.getThinkingLevel() !== model.thinkingLevel) {
|
|
652
|
-
throw new NavigatorUnavailableError("thinking", `Navigator thinking level ${model.thinkingLevel} is unavailable for ${modelSetting}`);
|
|
653
|
-
}
|
|
654
651
|
created.appendEntry(INVOCATION_ENTRY, { invocationId, role: options.role, phase: options.phase, subjectKey });
|
|
655
652
|
if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
|
|
656
653
|
session = created;
|
|
@@ -665,9 +662,6 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
|
|
|
665
662
|
} else {
|
|
666
663
|
try {
|
|
667
664
|
await session.setModel?.(modelSetting, model.thinkingLevel);
|
|
668
|
-
if (session.getThinkingLevel?.() !== undefined && session.getThinkingLevel() !== model.thinkingLevel) {
|
|
669
|
-
throw new NavigatorUnavailableError("thinking", `Navigator thinking level ${model.thinkingLevel} is unavailable for ${modelSetting}`);
|
|
670
|
-
}
|
|
671
665
|
} catch (error) {
|
|
672
666
|
// Contract: README.md#Navigator-attendance — resumed-session configuration failures remain typed unavailable and retain the original cause.
|
|
673
667
|
throw error instanceof NavigatorUnavailableError ? error : navigatorUnavailableError("session", error);
|
|
@@ -162,8 +162,8 @@ export type NavigatorPreparationSession = {
|
|
|
162
162
|
appendEntry(customType: string, data: unknown): void;
|
|
163
163
|
entries(): readonly unknown[];
|
|
164
164
|
providerFailure?(): NavigatorProviderFailureFact | undefined;
|
|
165
|
-
setModel?(model: string, thinkingLevel
|
|
166
|
-
getThinkingLevel?(): string;
|
|
165
|
+
setModel?(model: string, thinkingLevel?: string): Promise<void>;
|
|
166
|
+
getThinkingLevel?(): string | undefined;
|
|
167
167
|
recordPointer(): string;
|
|
168
168
|
dispose(): void | Promise<void>;
|
|
169
169
|
};
|
|
@@ -204,22 +204,22 @@ export async function writeNavigatorModelSetting(model: string, path = navigator
|
|
|
204
204
|
export function parseNavigatorModelSetting(value: string): {
|
|
205
205
|
provider: string;
|
|
206
206
|
model: string;
|
|
207
|
-
thinkingLevel
|
|
207
|
+
thinkingLevel?: string;
|
|
208
208
|
} {
|
|
209
209
|
const slash = value.indexOf("/");
|
|
210
210
|
if (slash <= 0 || slash === value.length - 1) {
|
|
211
|
-
throw new Error("Navigator model setting must be provider/model[:
|
|
211
|
+
throw new Error("Navigator model setting must be provider/model[:thinking]");
|
|
212
212
|
}
|
|
213
213
|
const provider = value.slice(0, slash);
|
|
214
214
|
const modelWithThinking = value.slice(slash + 1);
|
|
215
215
|
const colon = modelWithThinking.lastIndexOf(":");
|
|
216
216
|
const suffix = colon < 0 ? undefined : modelWithThinking.slice(colon + 1);
|
|
217
|
-
if (suffix !== undefined && suffix !== "max") {
|
|
218
|
-
throw new Error("Navigator model setting must use :max or omit the thinking suffix");
|
|
219
|
-
}
|
|
220
217
|
const model = colon < 0 ? modelWithThinking : modelWithThinking.slice(0, colon);
|
|
221
218
|
if (model === "") throw new Error("Navigator model setting must include a model");
|
|
222
|
-
|
|
219
|
+
// Suffix is opaque pass-through; bare provider/model omits thinkingLevel (Pi default).
|
|
220
|
+
return suffix === undefined
|
|
221
|
+
? { provider, model }
|
|
222
|
+
: { provider, model, thinkingLevel: suffix };
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
@@ -230,20 +230,21 @@ export async function resolveNavigatorSeatSelection(
|
|
|
230
230
|
context: HostContext,
|
|
231
231
|
modelSettingPath: string | undefined,
|
|
232
232
|
defaultModelSettingPath: string,
|
|
233
|
-
): Promise<{ selection: HostInstitutionalModelSelection; configuredLabel: string; thinkingLevel
|
|
233
|
+
): Promise<{ selection: HostInstitutionalModelSelection; configuredLabel: string; thinkingLevel?: string }> {
|
|
234
234
|
const runDirectory = auditorRunDirectory(context);
|
|
235
235
|
if (runDirectory !== undefined) {
|
|
236
236
|
try {
|
|
237
237
|
const selection = await readInstitutionalSeatSelection(runDirectory, "navigator");
|
|
238
|
-
const thinkingLevel = selection.thinking === "max" ? "max" : "off";
|
|
239
238
|
return {
|
|
240
239
|
selection: {
|
|
241
240
|
provider: selection.provider,
|
|
242
241
|
model: selection.model,
|
|
243
242
|
...(selection.thinking === undefined ? {} : { thinking: selection.thinking }),
|
|
244
243
|
},
|
|
245
|
-
configuredLabel:
|
|
246
|
-
|
|
244
|
+
configuredLabel: selection.thinking === undefined
|
|
245
|
+
? `${selection.provider}/${selection.model}`
|
|
246
|
+
: `${selection.provider}/${selection.model}:${selection.thinking}`,
|
|
247
|
+
...(selection.thinking === undefined ? {} : { thinkingLevel: selection.thinking }),
|
|
247
248
|
};
|
|
248
249
|
} catch (error) {
|
|
249
250
|
if (
|
|
@@ -274,9 +275,9 @@ export async function resolveNavigatorSeatSelection(
|
|
|
274
275
|
selection: {
|
|
275
276
|
provider: parsed.provider,
|
|
276
277
|
model: parsed.model,
|
|
277
|
-
thinking: parsed.thinkingLevel,
|
|
278
|
+
...(parsed.thinkingLevel === undefined ? {} : { thinking: parsed.thinkingLevel }),
|
|
278
279
|
},
|
|
279
280
|
configuredLabel: configured,
|
|
280
|
-
thinkingLevel: parsed.thinkingLevel,
|
|
281
|
+
...(parsed.thinkingLevel === undefined ? {} : { thinkingLevel: parsed.thinkingLevel }),
|
|
281
282
|
};
|
|
282
283
|
}
|
|
@@ -55,6 +55,12 @@ export const piDurablePrincipalAuthority: DurablePrincipalAuthority = {
|
|
|
55
55
|
sessionFile: coordinates.sessionFile,
|
|
56
56
|
});
|
|
57
57
|
},
|
|
58
|
+
seal(coordinates: DurablePrincipalCoordinates): DurablePrincipal {
|
|
59
|
+
return encode({
|
|
60
|
+
sessionDirectory: coordinates.sessionDirectory,
|
|
61
|
+
sessionFile: coordinates.sessionFile,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
58
64
|
decode(value: unknown): DurablePrincipalCoordinates {
|
|
59
65
|
const record = value as Partial<DurablePrincipalCoordinates> | null;
|
|
60
66
|
if (record === null || typeof record !== "object") {
|
|
@@ -237,7 +237,7 @@ export async function openPiInstitutionalSession(
|
|
|
237
237
|
? childRegistry.find(selection.provider, selection.model)
|
|
238
238
|
: undefined;
|
|
239
239
|
|
|
240
|
-
const withTypedReason = (error: Error, reason: "auth" | "model"
|
|
240
|
+
const withTypedReason = (error: Error, reason: "auth" | "model"): Error =>
|
|
241
241
|
Object.assign(error, { reason });
|
|
242
242
|
|
|
243
243
|
// Existing Navigator/model contract (pre-#590): unknown provider or model is
|
|
@@ -254,13 +254,15 @@ export async function openPiInstitutionalSession(
|
|
|
254
254
|
const fallbackApi = providerDefaultModel?.api
|
|
255
255
|
?? (childProvider as any)?.api
|
|
256
256
|
?? (selection.provider === "openai-codex" ? "openai-codex-responses" : "openai-completions");
|
|
257
|
+
// Fallback model facts come from the provider surface only — never derive
|
|
258
|
+
// reasoning (or any capability) from the thinking string (#683 pass-through).
|
|
257
259
|
const modelToUse = foundModel ?? {
|
|
258
260
|
id: selection.model,
|
|
259
261
|
name: selection.model,
|
|
260
262
|
api: fallbackApi as any,
|
|
261
263
|
provider: selection.provider,
|
|
262
264
|
baseUrl: providerDefaultModel?.baseUrl ?? "",
|
|
263
|
-
reasoning:
|
|
265
|
+
reasoning: providerDefaultModel?.reasoning ?? false,
|
|
264
266
|
input: ["text"],
|
|
265
267
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
266
268
|
contextWindow: 128000,
|
|
@@ -631,15 +633,15 @@ export async function openPiInstitutionalSession(
|
|
|
631
633
|
}
|
|
632
634
|
}
|
|
633
635
|
|
|
634
|
-
// 7. Create AgentSession
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
// typed unavailable/thinking (pre-#590 attendance setModel check).
|
|
638
|
-
const requestedThinking = options.selection.thinking === "max" ? "max" : "off";
|
|
636
|
+
// 7. Create AgentSession — thinking is opaque pass-through. Absent selection
|
|
637
|
+
// omits thinkingLevel (Pi default). Pi clamps unsupported levels itself;
|
|
638
|
+
// we do not re-check or invent defaults.
|
|
639
639
|
const { session } = await createAgentSession({
|
|
640
640
|
cwd: options.cwd,
|
|
641
641
|
model: effectiveModel,
|
|
642
|
-
|
|
642
|
+
...(options.selection.thinking === undefined
|
|
643
|
+
? {}
|
|
644
|
+
: { thinkingLevel: options.selection.thinking as any }),
|
|
643
645
|
modelRuntime: runtime,
|
|
644
646
|
sessionManager,
|
|
645
647
|
settingsManager: settings,
|
|
@@ -649,15 +651,6 @@ export async function openPiInstitutionalSession(
|
|
|
649
651
|
...(options.toolsAllowlist === undefined ? {} : { tools: options.toolsAllowlist as string[] }),
|
|
650
652
|
...(customTools.length === 0 ? {} : { customTools }),
|
|
651
653
|
});
|
|
652
|
-
if (requestedThinking === "max" && session.thinkingLevel !== "max") {
|
|
653
|
-
session.dispose();
|
|
654
|
-
throw withTypedReason(
|
|
655
|
-
new Error(
|
|
656
|
-
`${label} thinking level max is unavailable for ${selection.provider}/${selection.model}`,
|
|
657
|
-
),
|
|
658
|
-
"thinking",
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
654
|
|
|
662
655
|
// 8. Event subscriptions
|
|
663
656
|
const listeners = new Set<(event: HostInstitutionalSessionEvent) => void>();
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -461,16 +461,6 @@ function cliResultFromRoleRun(result: {
|
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
const THINKING_LEVELS = new Set([
|
|
465
|
-
"off",
|
|
466
|
-
"minimal",
|
|
467
|
-
"low",
|
|
468
|
-
"medium",
|
|
469
|
-
"high",
|
|
470
|
-
"xhigh",
|
|
471
|
-
"max",
|
|
472
|
-
]);
|
|
473
|
-
|
|
474
464
|
function defaultIo(): CliIo {
|
|
475
465
|
return {
|
|
476
466
|
stdout: (text) => {
|
|
@@ -505,10 +495,8 @@ type ParsedGlobal = {
|
|
|
505
495
|
};
|
|
506
496
|
|
|
507
497
|
function parseThinking(value: string): PublicThinkingLevel {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
return value as PublicThinkingLevel;
|
|
498
|
+
// #683: opaque pass-through — no local whitelist.
|
|
499
|
+
return value;
|
|
512
500
|
}
|
|
513
501
|
|
|
514
502
|
function parseArgv(argv: readonly string[]): ParsedGlobal {
|
package/src/public-cli/config.ts
CHANGED
|
@@ -102,16 +102,6 @@ export type InvocationModelOverride = {
|
|
|
102
102
|
host?: string;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
-
export const THINKING_LEVELS = new Set<PublicThinkingLevel>([
|
|
106
|
-
"off",
|
|
107
|
-
"minimal",
|
|
108
|
-
"low",
|
|
109
|
-
"medium",
|
|
110
|
-
"high",
|
|
111
|
-
"xhigh",
|
|
112
|
-
"max",
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
105
|
export function publicCliConfigPath(home: string): string {
|
|
116
106
|
if (typeof home !== "string" || home.trim() === "") {
|
|
117
107
|
throw new Error("home must be explicitly provided");
|
|
@@ -336,17 +326,10 @@ export function parseModelSpec(
|
|
|
336
326
|
const thinkingSplit = trimmed.lastIndexOf(":");
|
|
337
327
|
let modelPart = trimmed;
|
|
338
328
|
let thinking: PublicThinkingLevel | undefined = fallbackThinking;
|
|
339
|
-
// #346: no colon → bare provider/model is legal. Colon present
|
|
340
|
-
//
|
|
341
|
-
// stay format rejects; never swallow ":…" into the model name).
|
|
329
|
+
// #346/#683: no colon → bare provider/model is legal. Colon present → suffix
|
|
330
|
+
// is opaque thinking pass-through (no local whitelist); never swallow into model.
|
|
342
331
|
if (thinkingSplit !== -1) {
|
|
343
|
-
|
|
344
|
-
if (!THINKING_LEVELS.has(maybeThinking as PublicThinkingLevel)) {
|
|
345
|
-
throw new Error(
|
|
346
|
-
`model specification must be provider/model[:thinking], got ${spec}`,
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
thinking = maybeThinking as PublicThinkingLevel;
|
|
332
|
+
thinking = trimmed.slice(thinkingSplit + 1);
|
|
350
333
|
modelPart = trimmed.slice(0, thinkingSplit);
|
|
351
334
|
}
|
|
352
335
|
const slash = modelPart.indexOf("/");
|
|
@@ -507,14 +490,9 @@ function parseSeatModelConfig(value: unknown, seat: string): PersistentSeatConfi
|
|
|
507
490
|
if (typeof raw.model !== "string" || raw.model.trim() === "") {
|
|
508
491
|
throw new Error(`config seat ${seat} requires model`);
|
|
509
492
|
}
|
|
510
|
-
// #384: thinking is optional
|
|
511
|
-
if (raw.thinking !== undefined) {
|
|
512
|
-
|
|
513
|
-
typeof raw.thinking !== "string" ||
|
|
514
|
-
!THINKING_LEVELS.has(raw.thinking as PublicThinkingLevel)
|
|
515
|
-
) {
|
|
516
|
-
throw new Error(`config seat ${seat} requires a valid thinking level`);
|
|
517
|
-
}
|
|
493
|
+
// #384/#683: thinking is optional opaque pass-through; when present must be a string.
|
|
494
|
+
if (raw.thinking !== undefined && typeof raw.thinking !== "string") {
|
|
495
|
+
throw new Error(`config seat ${seat} thinking must be a string`);
|
|
518
496
|
}
|
|
519
497
|
const parsed: PersistentSeatConfig = {
|
|
520
498
|
provider: raw.provider as string,
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
|
|
8
8
|
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
|
|
9
|
+
import { rebindAdmittedToTicketSeatMemory } from "../ticket-seat-memory.ts";
|
|
9
10
|
import { CliUsageError } from "./cli-errors.ts";
|
|
11
|
+
import { resolveSeatTicketBinding } from "./seat-ticket-binding.ts";
|
|
10
12
|
import {
|
|
11
13
|
admitInspectorInvocation,
|
|
12
14
|
buildInspectorTransportPrompt,
|
|
@@ -89,8 +91,26 @@ export async function runPublicInspector(
|
|
|
89
91
|
throw error;
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
// #635 seat self-ticket then #636 ticket+seat memory principal (before admitted mark).
|
|
95
|
+
await resolveSeatTicketBinding(admitted, env);
|
|
96
|
+
const memory = await rebindAdmittedToTicketSeatMemory({
|
|
97
|
+
admitted,
|
|
98
|
+
seat: "inspector",
|
|
99
|
+
principalAuthority: env.principalAuthority,
|
|
100
|
+
});
|
|
92
101
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
93
102
|
|
|
103
|
+
// Same ticket nest already present → native host resume (not a fresh initial).
|
|
104
|
+
// Prompt is the same officer transport; only continuation kind flips (#636 / ADR 0079).
|
|
105
|
+
const engineMaterial = engineSessionMaterialFromOptions({
|
|
106
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
107
|
+
packageRoot: env.packageRoot,
|
|
108
|
+
});
|
|
109
|
+
const continuation = {
|
|
110
|
+
kind: memory?.resumed === true ? ("resume" as const) : ("initial" as const),
|
|
111
|
+
prompt: buildInspectorTransportPrompt(admitted, engineMaterial),
|
|
112
|
+
};
|
|
113
|
+
|
|
94
114
|
const turnRequest = buildInspectorTurnRequest(admitted, {
|
|
95
115
|
packageRoot: env.packageRoot,
|
|
96
116
|
home: env.home,
|
|
@@ -101,16 +121,7 @@ export async function runPublicInspector(
|
|
|
101
121
|
...(env.correlationId === undefined || env.correlationId.trim() === ""
|
|
102
122
|
? {}
|
|
103
123
|
: { correlationId: env.correlationId }),
|
|
104
|
-
continuation
|
|
105
|
-
kind: "initial",
|
|
106
|
-
prompt: buildInspectorTransportPrompt(
|
|
107
|
-
admitted,
|
|
108
|
-
engineSessionMaterialFromOptions({
|
|
109
|
-
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
110
|
-
packageRoot: env.packageRoot,
|
|
111
|
-
}),
|
|
112
|
-
),
|
|
113
|
-
},
|
|
124
|
+
continuation,
|
|
114
125
|
});
|
|
115
126
|
|
|
116
127
|
return await runPostAdmissionOneShot({
|