@akagilnc/pi-workflow-roles 0.1.5303 → 0.1.5312
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/acp-host/description.js +0 -11
- package/dist/acp-host/production-host.js +577 -604
- package/dist/analyst-ledger.js +16 -1
- package/dist/analyst-metric-families/acceptance-success-rework.js +16 -15
- package/dist/analyst-metric-families/b2-frame-buckets-actions.js +3 -8
- package/dist/analyst-metric-families/gate-cycles.js +2 -8
- package/dist/analyst-metric-families/leg-wall-clock.js +4 -13
- package/dist/analyst-metric-families/round-timeline.js +1 -4
- package/dist/analyst-model-groups.js +1 -7
- package/dist/analyst-page.js +4 -13
- package/dist/headless-host/description.js +0 -11
- package/dist/headless-host/production-host.js +679 -718
- package/dist/host-descriptions.js +13 -0
- package/dist/prepared-role-turn.js +4 -0
- package/dist/public-cli/machine-method-skills.js +5 -5
- package/dist/public-cli/main.js +291 -313
- package/package.json +1 -1
- package/src/acp-host/description.ts +2 -12
- package/src/acp-host/production-host.ts +3 -2
- package/src/acp-host/role-turn-host.ts +7 -42
- package/src/analyst-ledger.ts +20 -1
- package/src/analyst-metric-families/acceptance-success-rework.ts +5 -15
- package/src/analyst-metric-families/b2-frame-buckets-actions.ts +3 -7
- package/src/analyst-metric-families/gate-cycles.ts +2 -8
- package/src/analyst-metric-families/leg-wall-clock.ts +4 -16
- package/src/analyst-metric-families/round-timeline.ts +1 -5
- package/src/analyst-model-groups.ts +1 -10
- package/src/analyst-page.ts +4 -16
- package/src/external-host-turn-loop.ts +36 -16
- package/src/headless-host/description.ts +3 -18
- package/src/headless-host/production-host.ts +3 -2
- package/src/headless-host/role-turn-host.ts +10 -68
- package/src/host-descriptions.ts +18 -0
- package/src/prepared-role-turn.ts +8 -0
- package/src/public-cli/machine-method-skills.ts +5 -5
package/package.json
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
* optional seat-profile soul — is data here; the lifecycle in role-turn-host.ts
|
|
5
5
|
* stays one copy (#732).
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import type { HostIdentityDescription } from "../host-descriptions.ts";
|
|
9
8
|
import type { SeatProfileSoul } from "./seat-profile-soul.ts";
|
|
10
9
|
|
|
11
|
-
export type AcpHostDescription = Readonly<{
|
|
12
|
-
/** Binary path segments relative to the operator home. */
|
|
13
|
-
binaryFromHome: readonly string[];
|
|
10
|
+
export type AcpHostDescription = HostIdentityDescription & Readonly<{
|
|
14
11
|
argv: Readonly<{
|
|
15
12
|
prefix: readonly string[];
|
|
16
13
|
suffix: readonly string[];
|
|
@@ -26,8 +23,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
26
23
|
* `provider:model` (hermes).
|
|
27
24
|
*/
|
|
28
25
|
modelPassing: "argv" | "set_model";
|
|
29
|
-
/** Durable ACP binding filename written beside the session principal. */
|
|
30
|
-
sessionBindingFile: string;
|
|
31
26
|
/**
|
|
32
27
|
* When set, the production factory ensures a seat profile whose SOUL.md is a
|
|
33
28
|
* symlink to the packaged role soul, and prefixes argv with `flag <name>`.
|
|
@@ -36,11 +31,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
36
31
|
seatProfileSoul?: SeatProfileSoul;
|
|
37
32
|
}>;
|
|
38
33
|
|
|
39
|
-
/** Absolute agent binary for one operator home. */
|
|
40
|
-
export function resolveAcpBinary(description: AcpHostDescription, operatorHome: string): string {
|
|
41
|
-
return join(operatorHome, ...description.binaryFromHome);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
/** Stdio argv: optional profile flag, thinking flag, prefix, model, suffix. */
|
|
45
35
|
export function acpStdioArgs(
|
|
46
36
|
description: AcpHostDescription,
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
|
|
11
11
|
import type { DurablePrincipalAuthority, RoleTurnHost, RoleTurnRequest } from "../host-contracts.ts";
|
|
12
|
+
import { resolveHostBinary } from "../host-descriptions.ts";
|
|
12
13
|
import { prepareRoleEnvelope } from "../role-envelope.ts";
|
|
13
14
|
import type { RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
14
15
|
import { createRoleRuntimeDependencies } from "../role-runtime-dependencies.ts";
|
|
15
16
|
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
16
|
-
import { acpStdioArgs,
|
|
17
|
+
import { acpStdioArgs, type AcpHostDescription } from "./description.ts";
|
|
17
18
|
import {
|
|
18
19
|
connectAcpStdio,
|
|
19
20
|
createAcpRoleTurnHost,
|
|
@@ -74,7 +75,7 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
74
75
|
role: request.activation.role,
|
|
75
76
|
});
|
|
76
77
|
return connectAcpStdio({
|
|
77
|
-
binary:
|
|
78
|
+
binary: resolveHostBinary(description, request.home),
|
|
78
79
|
args: acpStdioArgs(
|
|
79
80
|
description,
|
|
80
81
|
request.model,
|
|
@@ -2,11 +2,13 @@ import { join } from "node:path";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
|
|
5
|
-
import type { RoleTurnHost,
|
|
5
|
+
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
9
|
+
externalHostFailure as failure,
|
|
9
10
|
raceAgainstHostAbort,
|
|
11
|
+
withExternalHostCleanupFailure,
|
|
10
12
|
} from "../external-host-turn-loop.ts";
|
|
11
13
|
|
|
12
14
|
import {
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
} from "../package-contracts/navigator-output.ts";
|
|
20
22
|
import {
|
|
21
23
|
renderSystemPromptOverride,
|
|
24
|
+
resolveBoundHostSessionId,
|
|
22
25
|
type PreparedRoleTurn,
|
|
23
26
|
type SessionIdentityAuthority,
|
|
24
27
|
} from "../prepared-role-turn.ts";
|
|
@@ -96,41 +99,6 @@ export type AcpRoleTurnHostConfig = Readonly<{
|
|
|
96
99
|
prepare(request: RoleTurnRequest): Promise<PreparedRoleTurn>;
|
|
97
100
|
}>;
|
|
98
101
|
|
|
99
|
-
function failure(
|
|
100
|
-
cause: "activation" | "session" | "output",
|
|
101
|
-
name: string,
|
|
102
|
-
code: string,
|
|
103
|
-
details?: Readonly<Record<string, unknown>>,
|
|
104
|
-
diagnostic?: string,
|
|
105
|
-
): RoleTurnResult {
|
|
106
|
-
return {
|
|
107
|
-
code: null,
|
|
108
|
-
stderr: "",
|
|
109
|
-
timedOut: false,
|
|
110
|
-
knownFailure: {
|
|
111
|
-
cause,
|
|
112
|
-
identity: { name, code },
|
|
113
|
-
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
114
|
-
...(details === undefined ? {} : { details }),
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Success→dispose failure; existing failure keeps primary cause + cleanup detail. */
|
|
120
|
-
function withCleanupFailure(outcome: RoleTurnResult, cleanupError: unknown): RoleTurnResult {
|
|
121
|
-
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
122
|
-
if (outcome.knownFailure === undefined) {
|
|
123
|
-
return failure("session", "AcpDisposeFailure", "dispose-failed", { cleanupError: message }, message);
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
...outcome,
|
|
127
|
-
knownFailure: {
|
|
128
|
-
...outcome.knownFailure,
|
|
129
|
-
details: { ...(outcome.knownFailure.details ?? {}), cleanupError: message },
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
102
|
type RpcReply = { readonly id?: unknown; readonly method?: unknown; readonly params?: unknown; readonly result?: unknown; readonly error?: unknown };
|
|
135
103
|
|
|
136
104
|
function acpError(code: string, message: string, cause?: unknown): Error & { readonly code: string } {
|
|
@@ -259,7 +227,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
259
227
|
let sessionOpened = false;
|
|
260
228
|
let accepted = false;
|
|
261
229
|
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
262
|
-
// Mutable so dispose failure can outrank a clean turn
|
|
230
|
+
// Mutable so dispose failure can outrank a clean turn.
|
|
263
231
|
let outcome: RoleTurnResult = failure("session", "AcpNoOutcome", "no-outcome");
|
|
264
232
|
try {
|
|
265
233
|
if (prepared.mcpServers.length === 0) {
|
|
@@ -324,10 +292,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
324
292
|
};
|
|
325
293
|
let sessionReady = true;
|
|
326
294
|
if (request.continuation.kind === "resume") {
|
|
327
|
-
const
|
|
328
|
-
const boundSessionId = explicitHostSessionId !== undefined && explicitHostSessionId !== ""
|
|
329
|
-
? explicitHostSessionId
|
|
330
|
-
: await config.sessionIdentity.load(request.principal);
|
|
295
|
+
const boundSessionId = await resolveBoundHostSessionId(request, config.sessionIdentity);
|
|
331
296
|
if (boundSessionId !== undefined && boundSessionId !== "") {
|
|
332
297
|
sessionId = boundSessionId;
|
|
333
298
|
sessionId = await loadSession(boundSessionId);
|
|
@@ -446,7 +411,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
446
411
|
try {
|
|
447
412
|
await prepared.dispose?.();
|
|
448
413
|
} catch (cleanupError) {
|
|
449
|
-
outcome =
|
|
414
|
+
outcome = withExternalHostCleanupFailure(outcome, cleanupError, "AcpDisposeFailure");
|
|
450
415
|
}
|
|
451
416
|
}
|
|
452
417
|
return outcome;
|
package/src/analyst-ledger.ts
CHANGED
|
@@ -319,7 +319,7 @@ function admitFrameSpanFromRows(rows: readonly LedgerSessionRow[]): {
|
|
|
319
319
|
}
|
|
320
320
|
return {
|
|
321
321
|
models,
|
|
322
|
-
frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt },
|
|
322
|
+
frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt, wallMs: endedMs - startedMs },
|
|
323
323
|
partialFirstFrameAt: { status: "present", at: span.startedAt },
|
|
324
324
|
partialLastFrameAt: { status: "present", at: span.endedAt },
|
|
325
325
|
};
|
|
@@ -329,6 +329,8 @@ function admitFrameSpanFromRows(rows: readonly LedgerSessionRow[]): {
|
|
|
329
329
|
export type AnalystRunFrameSpan = {
|
|
330
330
|
readonly startedAt: string;
|
|
331
331
|
readonly endedAt: string;
|
|
332
|
+
/** Validated wall clock, computed once when the session span is admitted. */
|
|
333
|
+
readonly wallMs: number;
|
|
332
334
|
};
|
|
333
335
|
|
|
334
336
|
/**
|
|
@@ -371,6 +373,23 @@ export type AnalystReadableRunFacts = {
|
|
|
371
373
|
readonly gateCycles: readonly AnalystGateCycleRound[];
|
|
372
374
|
};
|
|
373
375
|
|
|
376
|
+
/** Total wall clock for exactly the readable runs supplied by the caller. */
|
|
377
|
+
export function sumAnalystRunWallMs(runs: readonly AnalystReadableRunFacts[]): number {
|
|
378
|
+
let total = 0;
|
|
379
|
+
for (const run of runs) total += run.frameSpan.wallMs;
|
|
380
|
+
return total;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Shared identity order for readable legs and their page/metric projections. */
|
|
384
|
+
export function compareAnalystLegs(
|
|
385
|
+
a: Pick<AnalystReadableRunFacts, "book" | "role" | "runId">,
|
|
386
|
+
b: Pick<AnalystReadableRunFacts, "book" | "role" | "runId">,
|
|
387
|
+
): number {
|
|
388
|
+
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
389
|
+
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
390
|
+
return a.runId.localeCompare(b.runId);
|
|
391
|
+
}
|
|
392
|
+
|
|
374
393
|
export type AnalystScopedRunScan = {
|
|
375
394
|
/** Readable in-scope runs with retained typed facts (A2). */
|
|
376
395
|
readonly runs: readonly AnalystReadableRunFacts[];
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - success rate den = success-eligible accepted legs (no-receipt out; planned out)
|
|
10
10
|
* - planned = plan-duty acceptance; never success numerator or denominator
|
|
11
11
|
*/
|
|
12
|
-
import type
|
|
12
|
+
import { compareAnalystLegs, sumAnalystRunWallMs, type AnalystReadableRunFacts, type AnalystRunTerminalFace } from "../analyst-ledger.ts";
|
|
13
13
|
import { medianNumber } from "../analyst-median.ts";
|
|
14
14
|
import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
|
|
15
15
|
import { packagedAnalystTerminal } from "../packaged-role-registry.ts";
|
|
@@ -99,10 +99,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
99
99
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function wallMsFromSpan(span: { readonly startedAt: string; readonly endedAt: string }): number {
|
|
103
|
-
return Date.parse(span.endedAt) - Date.parse(span.startedAt);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
102
|
/** Locate collector groups array: receipt.groups or top-level groups. */
|
|
107
103
|
function findCollectorGroups(body: Record<string, unknown>): unknown {
|
|
108
104
|
if (Array.isArray(body.groups)) return body.groups;
|
|
@@ -235,7 +231,7 @@ function projectLegs(runs: readonly AnalystReadableRunFacts[]): AnalystAcceptanc
|
|
|
235
231
|
book: run.book,
|
|
236
232
|
role: run.role,
|
|
237
233
|
startedAt: run.frameSpan.startedAt,
|
|
238
|
-
wallMs:
|
|
234
|
+
wallMs: run.frameSpan.wallMs,
|
|
239
235
|
terminalLabel: mapped.terminalLabel,
|
|
240
236
|
accepted: mapped.accepted,
|
|
241
237
|
success: mapped.success,
|
|
@@ -247,11 +243,7 @@ function projectLegs(runs: readonly AnalystReadableRunFacts[]): AnalystAcceptanc
|
|
|
247
243
|
}
|
|
248
244
|
|
|
249
245
|
// Stable page order: book, role, runId (match A1 leg sort).
|
|
250
|
-
return legs.sort(
|
|
251
|
-
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
252
|
-
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
253
|
-
return a.runId.localeCompare(b.runId);
|
|
254
|
-
});
|
|
246
|
+
return legs.sort(compareAnalystLegs);
|
|
255
247
|
}
|
|
256
248
|
|
|
257
249
|
function aggregateByRole(legs: readonly AnalystAcceptanceLeg[]): AnalystRoleAcceptanceStats[] {
|
|
@@ -302,12 +294,10 @@ function aggregateByRole(legs: readonly AnalystAcceptanceLeg[]): AnalystRoleAcce
|
|
|
302
294
|
});
|
|
303
295
|
}
|
|
304
296
|
|
|
305
|
-
function reworkLens(legs: readonly AnalystAcceptanceLeg[]): AnalystReworkLens {
|
|
297
|
+
function reworkLens(legs: readonly AnalystAcceptanceLeg[], totalWallMs: number): AnalystReworkLens {
|
|
306
298
|
let reworkWallMs = 0;
|
|
307
|
-
let totalWallMs = 0;
|
|
308
299
|
let reworkLegCount = 0;
|
|
309
300
|
for (const leg of legs) {
|
|
310
|
-
totalWallMs += leg.wallMs;
|
|
311
301
|
if (leg.rework) {
|
|
312
302
|
reworkWallMs += leg.wallMs;
|
|
313
303
|
reworkLegCount += 1;
|
|
@@ -331,7 +321,7 @@ export function buildAcceptanceSuccessReworkSection(
|
|
|
331
321
|
kind: "analyst-acceptance-success-rework",
|
|
332
322
|
legs,
|
|
333
323
|
byRole: aggregateByRole(legs),
|
|
334
|
-
rework: reworkLens(legs),
|
|
324
|
+
rework: reworkLens(legs, sumAnalystRunWallMs(runs)),
|
|
335
325
|
};
|
|
336
326
|
}
|
|
337
327
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { SessionToolInterval } from "../ledger-session-read.ts";
|
|
18
18
|
import { medianNumber } from "../analyst-median.ts";
|
|
19
|
-
import type
|
|
19
|
+
import { compareAnalystLegs, type AnalystReadableRunFacts } from "../analyst-ledger.ts";
|
|
20
20
|
import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
|
|
21
21
|
|
|
22
22
|
export type AnalystB2ToolAction = {
|
|
@@ -221,7 +221,7 @@ export function computeAnalystB2RunMetrics(
|
|
|
221
221
|
): AnalystB2RunMetrics {
|
|
222
222
|
const frameStartMs = timestampMs(facts.frameSpan.startedAt);
|
|
223
223
|
const frameEndMs = timestampMs(facts.frameSpan.endedAt);
|
|
224
|
-
const wallMs =
|
|
224
|
+
const wallMs = facts.frameSpan.wallMs;
|
|
225
225
|
|
|
226
226
|
// Single frame-bounded core: clip → union once → bucket/complement/actions.
|
|
227
227
|
const tools = clipToolsToFrame(closedTools(facts.toolIntervals), frameStartMs, frameEndMs);
|
|
@@ -258,11 +258,7 @@ const b2FrameBucketsActionsFamily: AnalystMetricFamilyModule = {
|
|
|
258
258
|
if (input.runs.length === 0) return undefined;
|
|
259
259
|
const runs = [...input.runs]
|
|
260
260
|
.map(computeAnalystB2RunMetrics)
|
|
261
|
-
.sort(
|
|
262
|
-
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
263
|
-
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
264
|
-
return a.runId.localeCompare(b.runId);
|
|
265
|
-
});
|
|
261
|
+
.sort(compareAnalystLegs);
|
|
266
262
|
const section: AnalystB2FrameBucketsActionsSection = {
|
|
267
263
|
kind: "analyst-b2-frame-buckets-actions",
|
|
268
264
|
runs,
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
AnalystGateCycleOrigin,
|
|
10
10
|
AnalystGateCycleRound,
|
|
11
11
|
} from "../analyst-gate-cycles-read.ts";
|
|
12
|
-
import type
|
|
12
|
+
import { compareAnalystLegs, type AnalystReadableRunFacts } from "../analyst-ledger.ts";
|
|
13
13
|
import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
|
|
14
14
|
|
|
15
15
|
export type AnalystGateCyclesRoundRow = {
|
|
@@ -73,12 +73,6 @@ function projectLeg(facts: AnalystReadableRunFacts): AnalystGateCyclesLeg {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function compareLegs(a: AnalystGateCyclesLeg, b: AnalystGateCyclesLeg): number {
|
|
77
|
-
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
78
|
-
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
79
|
-
return a.runId.localeCompare(b.runId);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
76
|
type OfficerAccum = {
|
|
83
77
|
rounds: number;
|
|
84
78
|
bounceCount: number;
|
|
@@ -132,7 +126,7 @@ const gateCyclesFamily: AnalystMetricFamilyModule = {
|
|
|
132
126
|
id: "gate-cycles",
|
|
133
127
|
contribute(input) {
|
|
134
128
|
if (input.runs.length === 0) return undefined;
|
|
135
|
-
const legs = input.runs.map(projectLeg).sort(
|
|
129
|
+
const legs = input.runs.map(projectLeg).sort(compareAnalystLegs);
|
|
136
130
|
const section: AnalystGateCyclesSection = {
|
|
137
131
|
kind: "analyst-gate-cycles",
|
|
138
132
|
legs,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Registers by file drop under analyst-metric-families/ (A2 discovery).
|
|
7
7
|
*/
|
|
8
8
|
import { medianNumber } from "../analyst-median.ts";
|
|
9
|
-
import type
|
|
9
|
+
import { compareAnalystLegs, sumAnalystRunWallMs, type AnalystReadableRunFacts } from "../analyst-ledger.ts";
|
|
10
10
|
import type { AnalystMetricFamilyModule } from "../analyst-metric-family.ts";
|
|
11
11
|
|
|
12
12
|
/** One readable leg's session-frame wall clock (first usable → last usable). */
|
|
@@ -31,19 +31,12 @@ export type AnalystLegWallClockSection = {
|
|
|
31
31
|
readonly totalElapsedMs: number;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
function frameSpanWallMs(span: {
|
|
35
|
-
readonly startedAt: string;
|
|
36
|
-
readonly endedAt: string;
|
|
37
|
-
}): number {
|
|
38
|
-
return Date.parse(span.endedAt) - Date.parse(span.startedAt);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
function projectEntry(facts: AnalystReadableRunFacts): AnalystLegWallClockEntry {
|
|
42
35
|
return {
|
|
43
36
|
runId: facts.runId,
|
|
44
37
|
book: facts.book,
|
|
45
38
|
role: facts.role,
|
|
46
|
-
wallMs:
|
|
39
|
+
wallMs: facts.frameSpan.wallMs,
|
|
47
40
|
};
|
|
48
41
|
}
|
|
49
42
|
|
|
@@ -52,9 +45,7 @@ function compareRankingDesc(
|
|
|
52
45
|
b: AnalystLegWallClockEntry,
|
|
53
46
|
): number {
|
|
54
47
|
if (b.wallMs !== a.wallMs) return b.wallMs - a.wallMs;
|
|
55
|
-
|
|
56
|
-
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
57
|
-
return a.runId.localeCompare(b.runId);
|
|
48
|
+
return compareAnalystLegs(a, b);
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
/** Discovered by analyst-metric-families loader (default export). */
|
|
@@ -74,14 +65,11 @@ const legWallClockFamily: AnalystMetricFamilyModule = {
|
|
|
74
65
|
return undefined;
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
let totalElapsedMs = 0;
|
|
78
|
-
for (const wallMs of walls) totalElapsedMs += wallMs;
|
|
79
|
-
|
|
80
68
|
const section: AnalystLegWallClockSection = {
|
|
81
69
|
kind: "analyst-leg-wall-clock",
|
|
82
70
|
ranking,
|
|
83
71
|
medianWallMs,
|
|
84
|
-
totalElapsedMs,
|
|
72
|
+
totalElapsedMs: sumAnalystRunWallMs(input.runs),
|
|
85
73
|
};
|
|
86
74
|
return { legWallClock: section };
|
|
87
75
|
},
|
|
@@ -70,10 +70,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
70
70
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function wallMsFromSpan(startedAt: string, endedAt: string): number {
|
|
74
|
-
return Date.parse(endedAt) - Date.parse(startedAt);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
73
|
function readClassCount(body: Record<string, unknown>): number | undefined {
|
|
78
74
|
if (!isRecord(body.outcome)) return undefined;
|
|
79
75
|
if (!isRecord(body.outcome.decisiveFacts)) return undefined;
|
|
@@ -113,7 +109,7 @@ function projectRunRow(facts: AnalystReadableRunFacts): AnalystRoundTimelineRunR
|
|
|
113
109
|
role: facts.role,
|
|
114
110
|
startedAt,
|
|
115
111
|
endedAt,
|
|
116
|
-
wallMs:
|
|
112
|
+
wallMs: facts.frameSpan.wallMs,
|
|
117
113
|
terminal: projectTerminal(facts),
|
|
118
114
|
};
|
|
119
115
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { buildAcceptanceSuccessReworkSection } from "./analyst-metric-families/acceptance-success-rework.ts";
|
|
12
12
|
import type { AnalystReadableRunFacts } from "./analyst-ledger.ts";
|
|
13
13
|
import { medianNumber } from "./analyst-median.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { sortUnreadable, type AnalystUnreadableRun } from "./analyst-page.ts";
|
|
15
15
|
|
|
16
16
|
/** One raw model group with rates over legs in that group. */
|
|
17
17
|
export type AnalystModelGroupRow = {
|
|
@@ -74,15 +74,6 @@ function displayNameFor(
|
|
|
74
74
|
return aliased === undefined ? rawGroupKey : aliased;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function sortUnreadable(
|
|
78
|
-
unreadable: readonly AnalystUnreadableRun[],
|
|
79
|
-
): AnalystUnreadableRun[] {
|
|
80
|
-
return [...unreadable].sort((a, b) => {
|
|
81
|
-
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
82
|
-
return a.runId.localeCompare(b.runId);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
77
|
/**
|
|
87
78
|
* Legs with no usable session model identity cannot join any model group.
|
|
88
79
|
* Reuse the typed unreadable path (locatable missing-source) — not an empty-key group.
|
package/src/analyst-page.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
ensureRealDirectoryTree,
|
|
16
16
|
physicalPathIdentity,
|
|
17
17
|
} from "./activation-ledger-topology.ts";
|
|
18
|
-
import type
|
|
18
|
+
import { compareAnalystLegs, sumAnalystRunWallMs, type AnalystReadableRunFacts } from "./analyst-ledger.ts";
|
|
19
19
|
import { loadAnalystIssueMetricFamilies } from "./analyst-metric-families.ts";
|
|
20
20
|
import { composeAnalystMetricFamilySections } from "./analyst-metric-family.ts";
|
|
21
21
|
|
|
@@ -187,14 +187,10 @@ export function analystIssuePageAddressFromPage(
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function sortLegs(legs: readonly AnalystLegEntry[]): AnalystLegEntry[] {
|
|
190
|
-
return [...legs].sort(
|
|
191
|
-
if (a.book !== b.book) return a.book.localeCompare(b.book);
|
|
192
|
-
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
193
|
-
return a.runId.localeCompare(b.runId);
|
|
194
|
-
});
|
|
190
|
+
return [...legs].sort(compareAnalystLegs);
|
|
195
191
|
}
|
|
196
192
|
|
|
197
|
-
function sortUnreadable(
|
|
193
|
+
export function sortUnreadable(
|
|
198
194
|
unreadable: readonly AnalystUnreadableRun[],
|
|
199
195
|
): AnalystUnreadableRun[] {
|
|
200
196
|
return [...unreadable].sort((a, b) => {
|
|
@@ -260,13 +256,6 @@ export function computeAnalystMsPerKLines(
|
|
|
260
256
|
};
|
|
261
257
|
}
|
|
262
258
|
|
|
263
|
-
function frameSpanWallMs(span: {
|
|
264
|
-
readonly startedAt: string;
|
|
265
|
-
readonly endedAt: string;
|
|
266
|
-
}): number {
|
|
267
|
-
return Date.parse(span.endedAt) - Date.parse(span.startedAt);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
259
|
/**
|
|
271
260
|
* Σ readable wall clocks + max end-frame across ALL runs (C1 efficiency / index).
|
|
272
261
|
* Unreadable runs never enter totalElapsedMs; their available lastFrameAt still
|
|
@@ -279,10 +268,9 @@ export function summarizeAnalystRunEfficiency(
|
|
|
279
268
|
readonly totalElapsedMs: number;
|
|
280
269
|
readonly lastActivityAt: AnalystOptionalTimestamp;
|
|
281
270
|
} {
|
|
282
|
-
|
|
271
|
+
const totalElapsedMs = sumAnalystRunWallMs(runs);
|
|
283
272
|
let latestEndedAt: string | undefined;
|
|
284
273
|
for (const run of runs) {
|
|
285
|
-
totalElapsedMs += frameSpanWallMs(run.frameSpan);
|
|
286
274
|
const endedAt = run.frameSpan.endedAt;
|
|
287
275
|
if (latestEndedAt === undefined || endedAt > latestEndedAt) {
|
|
288
276
|
latestEndedAt = endedAt;
|
|
@@ -9,25 +9,11 @@ import type {
|
|
|
9
9
|
RoleTurnRequest,
|
|
10
10
|
RoleTurnResult,
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
|
+
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
12
13
|
|
|
13
14
|
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
14
15
|
|
|
15
|
-
export type ExternalPreparedTurn =
|
|
16
|
-
readonly prompt: string;
|
|
17
|
-
readonly abortSignal?: AbortSignal;
|
|
18
|
-
closeRound(): Promise<
|
|
19
|
-
| { readonly accepted: true }
|
|
20
|
-
| {
|
|
21
|
-
readonly accepted: false;
|
|
22
|
-
readonly retry: {
|
|
23
|
-
readonly code: string;
|
|
24
|
-
readonly toolCallIds: readonly string[];
|
|
25
|
-
readonly message: string;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
| { readonly accepted: false; readonly failure: RoleTurnKnownFailure }
|
|
29
|
-
>;
|
|
30
|
-
}>;
|
|
16
|
+
export type ExternalPreparedTurn = Pick<PreparedRoleTurn, "prompt" | "abortSignal" | "closeRound">;
|
|
31
17
|
|
|
32
18
|
export type ExternalHostRoundOutcome =
|
|
33
19
|
| { readonly status: "delivered"; readonly stderr?: string }
|
|
@@ -96,6 +82,40 @@ export function raceAgainstHostAbort<T>(
|
|
|
96
82
|
});
|
|
97
83
|
}
|
|
98
84
|
|
|
85
|
+
export function externalHostFailure(
|
|
86
|
+
cause: NonNullable<RoleTurnKnownFailure["cause"]>,
|
|
87
|
+
name: string,
|
|
88
|
+
code: string,
|
|
89
|
+
details?: Readonly<Record<string, unknown>>,
|
|
90
|
+
diagnostic?: string,
|
|
91
|
+
): RoleTurnResult {
|
|
92
|
+
return asFailure({
|
|
93
|
+
cause,
|
|
94
|
+
identity: { name, code },
|
|
95
|
+
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
96
|
+
...(details === undefined ? {} : { details }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A dispose failure replaces success; a primary failure retains its cause. */
|
|
101
|
+
export function withExternalHostCleanupFailure(
|
|
102
|
+
outcome: RoleTurnResult,
|
|
103
|
+
cleanupError: unknown,
|
|
104
|
+
name: string,
|
|
105
|
+
): RoleTurnResult {
|
|
106
|
+
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
107
|
+
if (outcome.knownFailure === undefined) {
|
|
108
|
+
return externalHostFailure("session", name, "dispose-failed", { cleanupError: message }, message);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...outcome,
|
|
112
|
+
knownFailure: {
|
|
113
|
+
...outcome.knownFailure,
|
|
114
|
+
details: { ...(outcome.knownFailure.details ?? {}), cleanupError: message },
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
function asFailure(knownFailure: RoleTurnKnownFailure, stderr = ""): RoleTurnResult {
|
|
100
120
|
return { code: null, stderr, timedOut: false, knownFailure };
|
|
101
121
|
}
|
|
@@ -4,17 +4,10 @@
|
|
|
4
4
|
* Claude print-mode and codex exec differ enough that #752 host-specific
|
|
5
5
|
* assembly lives here as sibling helpers — not a third unified abstraction.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type HeadlessHostBase = Readonly<{
|
|
10
|
-
/** Binary path segments relative to the operator home. */
|
|
11
|
-
binaryFromHome: readonly string[];
|
|
12
|
-
/** Durable session-id binding filename beside the session principal. */
|
|
13
|
-
sessionBindingFile: string;
|
|
14
|
-
}>;
|
|
7
|
+
import type { HostIdentityDescription } from "../host-descriptions.ts";
|
|
15
8
|
|
|
16
9
|
/** Claude Code print-mode (#645). */
|
|
17
|
-
export type ClaudePrintHostDescription =
|
|
10
|
+
export type ClaudePrintHostDescription = HostIdentityDescription & Readonly<{
|
|
18
11
|
protocol: "claude-print";
|
|
19
12
|
/**
|
|
20
13
|
* Host-native print-mode flags that never change per turn (no prompt).
|
|
@@ -48,7 +41,7 @@ export type ClaudePrintHostDescription = HeadlessHostBase & Readonly<{
|
|
|
48
41
|
* Protocol differences (JSONL, resume subcommand, schema file, `-c` MCP) stay
|
|
49
42
|
* in codex-specific helpers — description only carries identity + binary path.
|
|
50
43
|
*/
|
|
51
|
-
export type CodexExecHostDescription =
|
|
44
|
+
export type CodexExecHostDescription = HostIdentityDescription & Readonly<{
|
|
52
45
|
protocol: "codex-exec";
|
|
53
46
|
}>;
|
|
54
47
|
|
|
@@ -66,14 +59,6 @@ export function isCodexExecDescription(
|
|
|
66
59
|
return description.protocol === "codex-exec";
|
|
67
60
|
}
|
|
68
61
|
|
|
69
|
-
/** Absolute agent binary for one operator home. */
|
|
70
|
-
export function resolveHeadlessBinary(
|
|
71
|
-
description: HeadlessHostDescription,
|
|
72
|
-
operatorHome: string,
|
|
73
|
-
): string {
|
|
74
|
-
return join(operatorHome, ...description.binaryFromHome);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
62
|
/**
|
|
78
63
|
* Build one Claude print-mode argv for a single process turn.
|
|
79
64
|
* Shape: `<promptFlag> <fixedArgs…> <system/schema/mcp/model/effort/session…>`.
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
|
|
14
14
|
import type { DurablePrincipalAuthority, RoleTurnHost } from "../host-contracts.ts";
|
|
15
|
+
import { resolveHostBinary } from "../host-descriptions.ts";
|
|
15
16
|
import { createRoleRuntimeDependencies } from "../role-runtime-dependencies.ts";
|
|
16
17
|
import { prepareRoleEnvelope } from "../role-envelope.ts";
|
|
17
18
|
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
18
|
-
import {
|
|
19
|
+
import type { HeadlessHostDescription } from "./description.ts";
|
|
19
20
|
import { createHeadlessRoleTurnHost } from "./role-turn-host.ts";
|
|
20
21
|
|
|
21
22
|
export type ProductionHeadlessHostOptions = Readonly<{
|
|
@@ -46,7 +47,7 @@ export function createProductionHeadlessRoleTurnHost(
|
|
|
46
47
|
description,
|
|
47
48
|
hostName,
|
|
48
49
|
sessionIdentity,
|
|
49
|
-
binary:
|
|
50
|
+
binary: resolveHostBinary(description, operatorHome),
|
|
50
51
|
env: {
|
|
51
52
|
...process.env,
|
|
52
53
|
AK_PACKAGE_ROOT: packageRoot,
|