@zq-silk/yui 0.11.3 → 0.12.0
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/ARCHITECTURE.md +23 -4
- package/README.md +36 -13
- package/dist/cli/commandCatalog.js +2 -4
- package/dist/cli.js +105 -43
- package/dist/commands/configCommands.js +12 -45
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +154 -171
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +55 -11
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +24 -0
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +2 -2
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +1 -1
- package/dist/controller/controller.js +8 -12
- package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
- package/dist/controller/runtime.js +3 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +4 -4
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +36 -16
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +1 -2
- package/dist/lifecycle/taskRoleSessionReset.js +5 -13
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtimeObservation.js +13 -0
- package/dist/runtime/runtimeProjection.js +16 -25
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/scheduler/activeRoleRunDelivery.js +4 -4
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +3 -5
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +0 -14
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +18 -8
- package/dist/storage/sqliteStore.js +24 -20
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -24
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +4 -0
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +53 -16
- package/skills/yui-operator/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +28 -7
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -62,7 +62,7 @@ export function classifyReviewRoundOutcome(round, evidence) {
|
|
|
62
62
|
reason: corroborationFailure
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
/** True only when a Round
|
|
65
|
+
/** True only when a Round contains semantic Reviewer evidence. */
|
|
66
66
|
export function isSemanticReviewRound(round, evidence) {
|
|
67
67
|
return classifyReviewRoundOutcome(round, evidence)?.kind === "semantic";
|
|
68
68
|
}
|
|
@@ -120,7 +120,7 @@ export function finishReviewRound(round, status, summary, now, result = {}) {
|
|
|
120
120
|
return terminal;
|
|
121
121
|
}
|
|
122
122
|
// Fail closed: a completed delta Round without an explicit, valid
|
|
123
|
-
// disposition
|
|
123
|
+
// disposition remains non-accepting for Leader routing. Uncertainty never accepts.
|
|
124
124
|
const disposition = result.deltaDisposition ?? "requires-full-review";
|
|
125
125
|
const reasoning = result.deltaReasoning ?? result.report ?? summary;
|
|
126
126
|
return validateReviewRound({
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { nextPendingBatch, mailboxHasWork } from "../coordination/workMailbox.js";
|
|
2
|
+
import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
3
|
+
/** One authoritative read-only projection for a Task Reviewer Role slot. */
|
|
4
|
+
export function projectReviewerAvailability(store, taskId, reviewerRoleName) {
|
|
5
|
+
const active = store.getActiveAgentRun(taskId, reviewerRoleName)
|
|
6
|
+
?? store.listAgentRuns(taskId).find((run) => (run.roleName === reviewerRoleName && run.status === "active"));
|
|
7
|
+
if (active !== null && active !== undefined) {
|
|
8
|
+
return {
|
|
9
|
+
kind: "busy",
|
|
10
|
+
reviewerRoleName,
|
|
11
|
+
phase: "active-run",
|
|
12
|
+
activeRunId: active.id,
|
|
13
|
+
...(active.reviewRoundId === undefined
|
|
14
|
+
? {}
|
|
15
|
+
: { activeReviewRoundId: active.reviewRoundId }),
|
|
16
|
+
startedAt: active.createdAt,
|
|
17
|
+
retryable: true,
|
|
18
|
+
retryAfterSeconds: 5
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const activeRound = store.listReviewRounds(taskId).find((round) => ((round.scope ?? "work-item") === "task"
|
|
22
|
+
&& (round.status === "pending" || round.status === "running")
|
|
23
|
+
&& (round.reviewerRoleName === reviewerRoleName
|
|
24
|
+
|| round.executionGroup?.lanes.some(({ roleName }) => roleName === reviewerRoleName) === true)));
|
|
25
|
+
if (activeRound !== undefined) {
|
|
26
|
+
return {
|
|
27
|
+
kind: "busy",
|
|
28
|
+
reviewerRoleName,
|
|
29
|
+
phase: "review-slot",
|
|
30
|
+
activeReviewRoundId: activeRound.id,
|
|
31
|
+
startedAt: activeRound.createdAt,
|
|
32
|
+
retryable: true,
|
|
33
|
+
retryAfterSeconds: 5
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const reviewerMailbox = store.getWorkMailbox({
|
|
37
|
+
kind: "role",
|
|
38
|
+
taskId,
|
|
39
|
+
roleName: reviewerRoleName
|
|
40
|
+
});
|
|
41
|
+
if (reviewerMailbox !== null && mailboxHasWork(reviewerMailbox)) {
|
|
42
|
+
return mailboxBusy(reviewerRoleName, "mailbox", reviewerMailbox);
|
|
43
|
+
}
|
|
44
|
+
const runtimeMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
45
|
+
scope: "task",
|
|
46
|
+
taskId,
|
|
47
|
+
roleName: reviewerRoleName
|
|
48
|
+
}));
|
|
49
|
+
if (runtimeMailbox !== null && mailboxHasWork(runtimeMailbox)) {
|
|
50
|
+
return mailboxBusy(reviewerRoleName, "runtime-lifecycle", runtimeMailbox);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kind: "available",
|
|
54
|
+
reviewerRoleName,
|
|
55
|
+
retryable: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function mailboxBusy(reviewerRoleName, phase, mailbox) {
|
|
59
|
+
const pending = nextPendingBatch(mailbox);
|
|
60
|
+
const startedAt = mailbox.processing?.startedAt ?? pending?.firstQueuedAt;
|
|
61
|
+
return {
|
|
62
|
+
kind: "busy",
|
|
63
|
+
reviewerRoleName,
|
|
64
|
+
phase,
|
|
65
|
+
...(startedAt === undefined ? {} : { startedAt }),
|
|
66
|
+
retryable: true,
|
|
67
|
+
retryAfterSeconds: 5
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -33,18 +33,24 @@ export function mapAgentDriverUsage(input, usage) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
function observation(input, mapped) {
|
|
36
|
-
const
|
|
36
|
+
const requestOccurrenceId = mapped.kind === "activity.observed"
|
|
37
|
+
&& mapped.payload.usage?.semantics === "request-context"
|
|
38
|
+
? requireRequestOccurrenceId(mapped.payload.activityId)
|
|
39
|
+
: undefined;
|
|
40
|
+
const eventId = hookEventId(input, mapped, requestOccurrenceId);
|
|
37
41
|
const fence = { ...input.fence, ...mapped.fence };
|
|
38
42
|
return createRuntimeObservation({
|
|
39
43
|
schemaVersion: 2,
|
|
40
44
|
eventId,
|
|
41
|
-
semanticKey:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
semanticKey: requestOccurrenceId === undefined
|
|
46
|
+
? runtimeObservationSemanticKey({
|
|
47
|
+
eventId,
|
|
48
|
+
kind: mapped.kind,
|
|
49
|
+
fence,
|
|
50
|
+
...(input.sequence === undefined ? {} : { sequence: input.sequence }),
|
|
51
|
+
payload: mapped.payload
|
|
52
|
+
})
|
|
53
|
+
: `provider-request:${eventId}`,
|
|
48
54
|
kind: mapped.kind,
|
|
49
55
|
authority: "provider-structured",
|
|
50
56
|
receivedAt: input.receivedAt,
|
|
@@ -60,13 +66,21 @@ function requireMatchingDriver(input) {
|
|
|
60
66
|
throw new Error("Agent Driver Hook fence does not match the selected Driver.");
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
|
-
function hookEventId(input, mapped) {
|
|
69
|
+
function hookEventId(input, mapped, requestOccurrenceId) {
|
|
64
70
|
const digest = createHash("sha256").update(JSON.stringify([
|
|
65
71
|
input.driver.id,
|
|
66
72
|
input.hookEventName,
|
|
67
73
|
input.fence,
|
|
68
74
|
mapped.kind,
|
|
69
|
-
|
|
75
|
+
...(requestOccurrenceId === undefined
|
|
76
|
+
? [mapped.payload]
|
|
77
|
+
: [{ requestOccurrenceId }])
|
|
70
78
|
])).digest("hex");
|
|
71
79
|
return `runtime-observation-${digest}`;
|
|
72
80
|
}
|
|
81
|
+
function requireRequestOccurrenceId(value) {
|
|
82
|
+
if (value === undefined || value.trim().length === 0 || value.includes("\0")) {
|
|
83
|
+
throw new Error("Request-context usage requires a stable activity id.");
|
|
84
|
+
}
|
|
85
|
+
return value.trim();
|
|
86
|
+
}
|
|
@@ -51,8 +51,8 @@ const STRUCTURED_CLI_CAPABILITIES = Object.freeze({
|
|
|
51
51
|
preInputReadiness: "unavailable",
|
|
52
52
|
promptAcceptance: "exact",
|
|
53
53
|
turnLifecycle: "exact",
|
|
54
|
-
//
|
|
55
|
-
//
|
|
54
|
+
// Transcript sources may emit an explicit activity identity separately
|
|
55
|
+
// from usage snapshots. Numeric token values never decide runtime health.
|
|
56
56
|
operations: Object.freeze(["tool", "subagent"]),
|
|
57
57
|
waiting: Object.freeze(["permission"]),
|
|
58
58
|
usage: "streaming-cumulative",
|
|
@@ -70,7 +70,8 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
|
|
|
70
70
|
observation: Object.freeze({
|
|
71
71
|
...STRUCTURED_CLI_CAPABILITIES.observation,
|
|
72
72
|
sessionBootstrap: "preallocated",
|
|
73
|
-
preInputReadiness: "exact"
|
|
73
|
+
preInputReadiness: "exact",
|
|
74
|
+
usage: "event-snapshot"
|
|
74
75
|
})
|
|
75
76
|
}),
|
|
76
77
|
runtime: Object.freeze({
|
|
@@ -5,6 +5,7 @@ const MAX_INITIAL_TAIL_BYTES = 1024 * 1024;
|
|
|
5
5
|
const MAX_SAMPLE_BYTES = 1024 * 1024;
|
|
6
6
|
const MAX_REMAINDER_BYTES = 64 * 1024;
|
|
7
7
|
const MAX_CLAUDE_MESSAGES = 4_096;
|
|
8
|
+
const INITIAL_CONTINUITY_EPOCH = "initial";
|
|
8
9
|
export function transcriptObserverSource(driverId, input) {
|
|
9
10
|
if (input.hookEventName !== "UserPromptSubmit")
|
|
10
11
|
return null;
|
|
@@ -12,12 +13,8 @@ export function transcriptObserverSource(driverId, input) {
|
|
|
12
13
|
if (typeof locator !== "string" || !isAbsolute(locator) || locator.includes("\0"))
|
|
13
14
|
return null;
|
|
14
15
|
const sessionId = identity(input.payload.session_id) ?? "session";
|
|
15
|
-
const turnId = identity(input.payload.prompt_id)
|
|
16
|
-
?? identity(input.payload.turn_id)
|
|
17
|
-
?? input.occurrenceId
|
|
18
|
-
?? "turn";
|
|
19
16
|
const digest = createHash("sha256")
|
|
20
|
-
.update(JSON.stringify([driverId, sessionId,
|
|
17
|
+
.update(JSON.stringify([driverId, sessionId, locator]))
|
|
21
18
|
.digest("hex");
|
|
22
19
|
return Object.freeze({
|
|
23
20
|
schemaVersion: 1,
|
|
@@ -26,13 +23,13 @@ export function transcriptObserverSource(driverId, input) {
|
|
|
26
23
|
locator
|
|
27
24
|
});
|
|
28
25
|
}
|
|
29
|
-
export async function codexTranscriptObserver(source, cursor) {
|
|
30
|
-
return sampleJsonl(source, cursor, parseCodexLines);
|
|
26
|
+
export async function codexTranscriptObserver(source, cursor, resume) {
|
|
27
|
+
return sampleJsonl(source, cursor, resume, parseCodexLines);
|
|
31
28
|
}
|
|
32
|
-
export async function claudeTranscriptObserver(source, cursor) {
|
|
33
|
-
return sampleJsonl(source, cursor, parseClaudeLines);
|
|
29
|
+
export async function claudeTranscriptObserver(source, cursor, resume) {
|
|
30
|
+
return sampleJsonl(source, cursor, resume, parseClaudeLines);
|
|
34
31
|
}
|
|
35
|
-
async function sampleJsonl(source, rawCursor, parse) {
|
|
32
|
+
async function sampleJsonl(source, rawCursor, resume, parse) {
|
|
36
33
|
const previous = normalizeCursor(rawCursor);
|
|
37
34
|
let metadata;
|
|
38
35
|
try {
|
|
@@ -43,8 +40,33 @@ async function sampleJsonl(source, rawCursor, parse) {
|
|
|
43
40
|
}
|
|
44
41
|
if (!metadata.isFile())
|
|
45
42
|
return unavailable(previous, new Error("observer locator is not a file"));
|
|
46
|
-
const
|
|
43
|
+
const fileFingerprint = transcriptFileFingerprint(metadata);
|
|
44
|
+
const checkpoint = previous === undefined
|
|
45
|
+
? transcriptCheckpoint(resume?.latestCheckpoint, resume?.latestOccurrenceId)
|
|
46
|
+
: undefined;
|
|
47
|
+
let checkpointMatches = false;
|
|
48
|
+
if (previous === undefined && checkpoint !== undefined) {
|
|
49
|
+
try {
|
|
50
|
+
checkpointMatches = await matchesTranscriptCheckpoint(source.locator, metadata, fileFingerprint, checkpoint);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return unavailable(previous, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const reset = previous !== undefined && (previous.fileFingerprint === undefined
|
|
57
|
+
|| previous.fileFingerprint !== fileFingerprint
|
|
58
|
+
|| metadata.size < previous.offset
|
|
59
|
+
|| (metadata.size === previous.offset && (previous.fileCtimeMs === undefined
|
|
60
|
+
|| previous.fileCtimeMs !== metadata.ctimeMs)));
|
|
61
|
+
const offlineReset = previous === undefined
|
|
62
|
+
&& checkpoint !== undefined
|
|
63
|
+
&& !checkpointMatches;
|
|
47
64
|
const initial = previous === undefined || reset;
|
|
65
|
+
const continuityEpoch = reset || offlineReset
|
|
66
|
+
? rotateContinuityEpoch(previous?.continuityEpoch ?? checkpoint?.continuityEpoch ?? INITIAL_CONTINUITY_EPOCH, fileFingerprint)
|
|
67
|
+
: previous?.continuityEpoch
|
|
68
|
+
?? checkpoint?.continuityEpoch
|
|
69
|
+
?? initialContinuityEpoch(fileFingerprint);
|
|
48
70
|
const start = initial
|
|
49
71
|
? Math.max(0, metadata.size - MAX_INITIAL_TAIL_BYTES)
|
|
50
72
|
: previous.offset;
|
|
@@ -67,32 +89,80 @@ async function sampleJsonl(source, rawCursor, parse) {
|
|
|
67
89
|
return unavailable(previous, error);
|
|
68
90
|
}
|
|
69
91
|
let text = `${initial ? "" : previous?.remainder ?? ""}${bytes.toString("utf8")}`;
|
|
92
|
+
let textOffset = initial
|
|
93
|
+
? start
|
|
94
|
+
: previous.offset - Buffer.byteLength(previous.remainder, "utf8");
|
|
70
95
|
if (initial && start > 0) {
|
|
71
96
|
const firstLineEnd = text.indexOf("\n");
|
|
72
|
-
|
|
97
|
+
if (firstLineEnd < 0) {
|
|
98
|
+
textOffset += Buffer.byteLength(text, "utf8");
|
|
99
|
+
text = "";
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const discarded = text.slice(0, firstLineEnd + 1);
|
|
103
|
+
textOffset += Buffer.byteLength(discarded, "utf8");
|
|
104
|
+
text = text.slice(firstLineEnd + 1);
|
|
105
|
+
}
|
|
73
106
|
}
|
|
74
107
|
const complete = text.endsWith("\n");
|
|
75
108
|
const split = text.split("\n");
|
|
76
109
|
const remainder = complete ? "" : split.pop() ?? "";
|
|
110
|
+
if (complete)
|
|
111
|
+
split.pop();
|
|
112
|
+
let lineOffset = textOffset;
|
|
113
|
+
const lines = split.map((content) => {
|
|
114
|
+
const line = Object.freeze({
|
|
115
|
+
content,
|
|
116
|
+
offset: lineOffset,
|
|
117
|
+
continuityEpoch,
|
|
118
|
+
fileFingerprint,
|
|
119
|
+
observedFileSize: metadata.size,
|
|
120
|
+
observedFileCtimeMs: metadata.ctimeMs
|
|
121
|
+
});
|
|
122
|
+
lineOffset += Buffer.byteLength(content, "utf8") + 1;
|
|
123
|
+
return line;
|
|
124
|
+
});
|
|
77
125
|
const boundedRemainder = Buffer.byteLength(remainder, "utf8") <= MAX_REMAINDER_BYTES
|
|
78
126
|
? remainder
|
|
79
127
|
: "";
|
|
80
|
-
const parsed = parse(
|
|
128
|
+
const parsed = parse(lines, initial ? {} : previous?.state ?? {});
|
|
129
|
+
const clippedBaseline = initial && start > 0;
|
|
130
|
+
const discontinuousBaseline = clippedBaseline || reset || offlineReset;
|
|
131
|
+
// A verified checkpoint inside this bounded read proves continuity only for
|
|
132
|
+
// the occurrences that follow it. Everything earlier remains partial.
|
|
133
|
+
const recoveredCheckpointIndex = checkpointMatches && checkpoint !== undefined
|
|
134
|
+
? parsed.usages.findIndex(({ occurrenceId }) => occurrenceId === checkpoint.occurrenceId)
|
|
135
|
+
: -1;
|
|
136
|
+
const usages = discontinuousBaseline
|
|
137
|
+
? parsed.usages.map((occurrence, index) => (recoveredCheckpointIndex >= 0 && index > recoveredCheckpointIndex
|
|
138
|
+
? occurrence
|
|
139
|
+
: Object.freeze({
|
|
140
|
+
...occurrence,
|
|
141
|
+
observationQuality: "partial"
|
|
142
|
+
})))
|
|
143
|
+
: parsed.usages;
|
|
81
144
|
const nextCursor = Object.freeze({
|
|
82
145
|
offset: start + bytes.length,
|
|
83
146
|
remainder: boundedRemainder,
|
|
84
|
-
state: parsed.state
|
|
147
|
+
state: parsed.state,
|
|
148
|
+
continuityEpoch,
|
|
149
|
+
fileFingerprint,
|
|
150
|
+
fileCtimeMs: metadata.ctimeMs
|
|
85
151
|
});
|
|
86
152
|
const fellBehind = start + bytes.length < metadata.size;
|
|
87
153
|
const detail = parsed.degraded
|
|
154
|
+
?? (clippedBaseline
|
|
155
|
+
? "Initial transcript history was clipped; request-boundary evidence is partial."
|
|
156
|
+
: undefined)
|
|
88
157
|
?? (fellBehind ? "Transcript observer is catching up with a bounded read." : undefined)
|
|
158
|
+
?? (offlineReset ? "Transcript changed while the observer was offline; baseline was reset." : undefined)
|
|
89
159
|
?? (reset ? "Transcript was truncated; observer baseline was reset." : undefined)
|
|
90
160
|
?? (remainder !== boundedRemainder ? "Oversized partial transcript line was discarded." : undefined);
|
|
91
161
|
return Object.freeze({
|
|
92
162
|
cursor: nextCursor,
|
|
93
163
|
status: detail === undefined ? "healthy" : "degraded",
|
|
94
164
|
...(detail === undefined ? {} : { detail }),
|
|
95
|
-
...(
|
|
165
|
+
...(usages.length === 0 ? {} : { usages: Object.freeze(usages) }),
|
|
96
166
|
...(parsed.activityId === undefined ? {} : {
|
|
97
167
|
activity: "model",
|
|
98
168
|
activityId: parsed.activityId
|
|
@@ -101,12 +171,17 @@ async function sampleJsonl(source, rawCursor, parse) {
|
|
|
101
171
|
}
|
|
102
172
|
function parseCodexLines(lines, state) {
|
|
103
173
|
let usage = usageFrom(state.usage);
|
|
104
|
-
|
|
174
|
+
// Keep every recognized-but-incomplete request boundary durable. Reuse the
|
|
175
|
+
// last complete cumulative total as a partial marker until the next exact
|
|
176
|
+
// occurrence restores the numeric counter.
|
|
177
|
+
let usageContinuityGap = state.usageContinuityGap === true;
|
|
178
|
+
const usages = [];
|
|
105
179
|
let malformed = 0;
|
|
180
|
+
let incompleteUsage = 0;
|
|
106
181
|
for (const line of lines) {
|
|
107
|
-
const entry = jsonObject(line);
|
|
182
|
+
const entry = jsonObject(line.content);
|
|
108
183
|
if (entry === null) {
|
|
109
|
-
if (line.trim().length > 0)
|
|
184
|
+
if (line.content.trim().length > 0)
|
|
110
185
|
malformed += 1;
|
|
111
186
|
continue;
|
|
112
187
|
}
|
|
@@ -115,27 +190,51 @@ function parseCodexLines(lines, state) {
|
|
|
115
190
|
const payload = object(entry.payload);
|
|
116
191
|
if (payload?.type !== "token_count")
|
|
117
192
|
continue;
|
|
193
|
+
// Codex also emits token_count records that carry rate limits only. They
|
|
194
|
+
// contain no usage occurrence and therefore create no request boundary.
|
|
195
|
+
if (payload.info === null)
|
|
196
|
+
continue;
|
|
118
197
|
const candidate = normalizedCodexUsage(object(object(payload.info)?.total_token_usage));
|
|
119
|
-
if (candidate === undefined)
|
|
198
|
+
if (candidate === undefined) {
|
|
199
|
+
usages.push(Object.freeze({
|
|
200
|
+
...transcriptOccurrence(line),
|
|
201
|
+
observationQuality: "partial",
|
|
202
|
+
...(usage === undefined ? {} : { usage })
|
|
203
|
+
}));
|
|
204
|
+
usageContinuityGap = true;
|
|
205
|
+
incompleteUsage += 1;
|
|
120
206
|
continue;
|
|
207
|
+
}
|
|
121
208
|
usage = candidate;
|
|
122
|
-
|
|
209
|
+
usages.push(Object.freeze({
|
|
210
|
+
...transcriptOccurrence(line),
|
|
211
|
+
...(usageContinuityGap ? { observationQuality: "partial" } : {}),
|
|
212
|
+
usage: candidate
|
|
213
|
+
}));
|
|
214
|
+
usageContinuityGap = false;
|
|
123
215
|
}
|
|
216
|
+
const degraded = transcriptParseDetail(malformed, incompleteUsage);
|
|
124
217
|
return Object.freeze({
|
|
125
|
-
state: Object.freeze({
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
218
|
+
state: Object.freeze({
|
|
219
|
+
...(usage === undefined ? {} : { usage }),
|
|
220
|
+
...(usageContinuityGap ? { usageContinuityGap: true } : {})
|
|
221
|
+
}),
|
|
222
|
+
usages: Object.freeze(usages),
|
|
223
|
+
...(degraded === undefined ? {} : { degraded })
|
|
129
224
|
});
|
|
130
225
|
}
|
|
131
226
|
function parseClaudeLines(lines, state) {
|
|
132
227
|
const messages = messageState(state.messages);
|
|
228
|
+
let usageContinuityGap = state.usageContinuityGap === true;
|
|
229
|
+
const usages = [];
|
|
133
230
|
let activityId;
|
|
134
231
|
let malformed = 0;
|
|
232
|
+
let incompleteUsage = 0;
|
|
233
|
+
let bounded = false;
|
|
135
234
|
for (const line of lines) {
|
|
136
|
-
const entry = jsonObject(line);
|
|
235
|
+
const entry = jsonObject(line.content);
|
|
137
236
|
if (entry === null) {
|
|
138
|
-
if (line.trim().length > 0)
|
|
237
|
+
if (line.content.trim().length > 0)
|
|
139
238
|
malformed += 1;
|
|
140
239
|
continue;
|
|
141
240
|
}
|
|
@@ -143,50 +242,209 @@ function parseClaudeLines(lines, state) {
|
|
|
143
242
|
continue;
|
|
144
243
|
const message = object(entry.message);
|
|
145
244
|
const usage = normalizedClaudeUsage(object(message?.usage));
|
|
146
|
-
if (usage === undefined)
|
|
245
|
+
if (usage === undefined) {
|
|
246
|
+
usages.push(Object.freeze({
|
|
247
|
+
...transcriptOccurrence(line),
|
|
248
|
+
observationQuality: "partial"
|
|
249
|
+
}));
|
|
250
|
+
usageContinuityGap = true;
|
|
251
|
+
incompleteUsage += 1;
|
|
147
252
|
continue;
|
|
253
|
+
}
|
|
148
254
|
const key = identity(message?.id) === undefined
|
|
149
255
|
? identity(entry.uuid) === undefined ? undefined : `entry:${identity(entry.uuid)}`
|
|
150
256
|
: `message:${identity(message?.id)}`;
|
|
151
|
-
if (key === undefined)
|
|
257
|
+
if (key === undefined) {
|
|
258
|
+
usages.push(Object.freeze({
|
|
259
|
+
...transcriptOccurrence(line),
|
|
260
|
+
observationQuality: "partial"
|
|
261
|
+
}));
|
|
262
|
+
usageContinuityGap = true;
|
|
263
|
+
incompleteUsage += 1;
|
|
152
264
|
continue;
|
|
265
|
+
}
|
|
153
266
|
messages[key] = usage;
|
|
154
|
-
activityId =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
267
|
+
activityId = key;
|
|
268
|
+
const keys = Object.keys(messages);
|
|
269
|
+
if (keys.length > MAX_CLAUDE_MESSAGES) {
|
|
270
|
+
for (const oldKey of keys.slice(0, keys.length - MAX_CLAUDE_MESSAGES)) {
|
|
271
|
+
delete messages[oldKey];
|
|
272
|
+
}
|
|
273
|
+
bounded = true;
|
|
274
|
+
}
|
|
275
|
+
usages.push(Object.freeze({
|
|
276
|
+
...transcriptOccurrence(line),
|
|
277
|
+
activityId: key,
|
|
278
|
+
...(usageContinuityGap ? { observationQuality: "partial" } : {}),
|
|
279
|
+
usage
|
|
280
|
+
}));
|
|
281
|
+
usageContinuityGap = false;
|
|
162
282
|
}
|
|
163
|
-
const
|
|
283
|
+
const degraded = transcriptParseDetail(malformed, incompleteUsage, bounded);
|
|
164
284
|
return Object.freeze({
|
|
165
|
-
state: Object.freeze({
|
|
166
|
-
|
|
285
|
+
state: Object.freeze({
|
|
286
|
+
messages: Object.freeze(messages),
|
|
287
|
+
...(usageContinuityGap ? { usageContinuityGap: true } : {})
|
|
288
|
+
}),
|
|
289
|
+
usages: Object.freeze(usages),
|
|
167
290
|
...(activityId === undefined ? {} : { activityId }),
|
|
168
291
|
...(degraded === undefined ? {} : { degraded })
|
|
169
292
|
});
|
|
170
293
|
}
|
|
294
|
+
function transcriptParseDetail(malformed, incompleteUsage, bounded = false) {
|
|
295
|
+
const details = [];
|
|
296
|
+
if (malformed > 0)
|
|
297
|
+
details.push(`${malformed} malformed transcript line(s) ignored.`);
|
|
298
|
+
if (incompleteUsage > 0) {
|
|
299
|
+
details.push(`${incompleteUsage} incomplete usage record(s) created a request-boundary gap.`);
|
|
300
|
+
}
|
|
301
|
+
if (bounded)
|
|
302
|
+
details.push("Claude transcript message baseline was bounded to the newest entries.");
|
|
303
|
+
return details.length === 0 ? undefined : details.join(" ");
|
|
304
|
+
}
|
|
305
|
+
function transcriptOccurrence(line) {
|
|
306
|
+
// The durable semantic key retains this bounded checkpoint. A Controller
|
|
307
|
+
// restart can therefore reuse the epoch only when the sampled file is still
|
|
308
|
+
// the same continuity, without persisting the process-local cursor itself.
|
|
309
|
+
const digest = createHash("sha256").update(line.content).digest("hex");
|
|
310
|
+
const occurrenceId = `transcript-v1:${Buffer.from(JSON.stringify({
|
|
311
|
+
continuityEpoch: line.continuityEpoch,
|
|
312
|
+
fileFingerprint: line.fileFingerprint,
|
|
313
|
+
offset: line.offset,
|
|
314
|
+
digest
|
|
315
|
+
}), "utf8").toString("base64url")}`;
|
|
316
|
+
const resumeCheckpoint = `transcript-checkpoint-v1:${Buffer.from(JSON.stringify({
|
|
317
|
+
occurrenceId,
|
|
318
|
+
continuityEpoch: line.continuityEpoch,
|
|
319
|
+
fileFingerprint: line.fileFingerprint,
|
|
320
|
+
offset: line.offset,
|
|
321
|
+
length: Buffer.byteLength(line.content, "utf8"),
|
|
322
|
+
digest,
|
|
323
|
+
observedFileSize: line.observedFileSize,
|
|
324
|
+
observedFileCtimeMs: line.observedFileCtimeMs
|
|
325
|
+
}), "utf8").toString("base64url")}`;
|
|
326
|
+
return Object.freeze({ occurrenceId, resumeCheckpoint });
|
|
327
|
+
}
|
|
328
|
+
function initialContinuityEpoch(fileFingerprint) {
|
|
329
|
+
return continuityEpoch("initial", fileFingerprint);
|
|
330
|
+
}
|
|
331
|
+
function rotateContinuityEpoch(previous, fileFingerprint) {
|
|
332
|
+
return continuityEpoch(previous, fileFingerprint);
|
|
333
|
+
}
|
|
334
|
+
function continuityEpoch(previous, fileFingerprint) {
|
|
335
|
+
return `sha256:${createHash("sha256")
|
|
336
|
+
.update(JSON.stringify([previous, fileFingerprint]))
|
|
337
|
+
.digest("hex")}`;
|
|
338
|
+
}
|
|
339
|
+
function transcriptFileFingerprint(metadata) {
|
|
340
|
+
return `sha256:${createHash("sha256").update(JSON.stringify([
|
|
341
|
+
metadata.dev,
|
|
342
|
+
metadata.ino
|
|
343
|
+
])).digest("hex")}`;
|
|
344
|
+
}
|
|
345
|
+
async function matchesTranscriptCheckpoint(locator, metadata, fileFingerprint, checkpoint) {
|
|
346
|
+
if (checkpoint.fileFingerprint !== fileFingerprint
|
|
347
|
+
|| metadata.size < checkpoint.observedFileSize)
|
|
348
|
+
return false;
|
|
349
|
+
if (metadata.size === checkpoint.observedFileSize) {
|
|
350
|
+
return metadata.ctimeMs === checkpoint.observedFileCtimeMs;
|
|
351
|
+
}
|
|
352
|
+
if (checkpoint.offset + checkpoint.length + 1 > metadata.size)
|
|
353
|
+
return false;
|
|
354
|
+
const handle = await open(locator, "r");
|
|
355
|
+
try {
|
|
356
|
+
const bytes = Buffer.alloc(checkpoint.length + 1);
|
|
357
|
+
const read = await handle.read(bytes, 0, bytes.length, checkpoint.offset);
|
|
358
|
+
return read.bytesRead === bytes.length
|
|
359
|
+
&& bytes.at(-1) === 0x0a
|
|
360
|
+
&& createHash("sha256")
|
|
361
|
+
.update(bytes.subarray(0, checkpoint.length))
|
|
362
|
+
.digest("hex") === checkpoint.digest;
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
await handle.close();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function transcriptCheckpoint(raw, occurrenceId) {
|
|
369
|
+
const prefix = "transcript-checkpoint-v1:";
|
|
370
|
+
if (raw === undefined || occurrenceId === undefined || !raw.startsWith(prefix))
|
|
371
|
+
return undefined;
|
|
372
|
+
try {
|
|
373
|
+
const value = JSON.parse(Buffer.from(raw.slice(prefix.length), "base64url").toString("utf8"));
|
|
374
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
375
|
+
return undefined;
|
|
376
|
+
const checkpoint = value;
|
|
377
|
+
return checkpoint.occurrenceId !== occurrenceId
|
|
378
|
+
|| typeof checkpoint.continuityEpoch !== "string"
|
|
379
|
+
|| checkpoint.continuityEpoch.length === 0
|
|
380
|
+
|| typeof checkpoint.fileFingerprint !== "string"
|
|
381
|
+
|| checkpoint.fileFingerprint.length === 0
|
|
382
|
+
|| !Number.isSafeInteger(checkpoint.offset)
|
|
383
|
+
|| checkpoint.offset < 0
|
|
384
|
+
|| !Number.isSafeInteger(checkpoint.length)
|
|
385
|
+
|| checkpoint.length < 0
|
|
386
|
+
|| typeof checkpoint.digest !== "string"
|
|
387
|
+
|| checkpoint.digest.length === 0
|
|
388
|
+
|| !Number.isSafeInteger(checkpoint.observedFileSize)
|
|
389
|
+
|| checkpoint.observedFileSize < 0
|
|
390
|
+
|| typeof checkpoint.observedFileCtimeMs !== "number"
|
|
391
|
+
|| !Number.isFinite(checkpoint.observedFileCtimeMs)
|
|
392
|
+
|| checkpoint.observedFileCtimeMs < 0
|
|
393
|
+
? undefined
|
|
394
|
+
: Object.freeze({
|
|
395
|
+
occurrenceId,
|
|
396
|
+
continuityEpoch: checkpoint.continuityEpoch,
|
|
397
|
+
fileFingerprint: checkpoint.fileFingerprint,
|
|
398
|
+
offset: checkpoint.offset,
|
|
399
|
+
length: checkpoint.length,
|
|
400
|
+
digest: checkpoint.digest,
|
|
401
|
+
observedFileSize: checkpoint.observedFileSize,
|
|
402
|
+
observedFileCtimeMs: checkpoint.observedFileCtimeMs
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
171
409
|
function normalizeCursor(input) {
|
|
172
410
|
if (input === undefined)
|
|
173
411
|
return undefined;
|
|
174
412
|
const offset = input.offset;
|
|
175
413
|
const remainder = input.remainder;
|
|
176
414
|
const state = input.state;
|
|
415
|
+
const continuityEpoch = input.continuityEpoch;
|
|
416
|
+
const fileFingerprint = input.fileFingerprint;
|
|
417
|
+
const fileCtimeMs = input.fileCtimeMs;
|
|
177
418
|
if (!Number.isSafeInteger(offset) || offset < 0
|
|
178
419
|
|| typeof remainder !== "string"
|
|
179
|
-
|| state === null || typeof state !== "object" || Array.isArray(state)
|
|
420
|
+
|| state === null || typeof state !== "object" || Array.isArray(state)
|
|
421
|
+
|| (continuityEpoch !== undefined
|
|
422
|
+
&& (typeof continuityEpoch !== "string" || continuityEpoch.length === 0))
|
|
423
|
+
|| (fileFingerprint !== undefined
|
|
424
|
+
&& (typeof fileFingerprint !== "string" || fileFingerprint.length === 0))
|
|
425
|
+
|| (fileCtimeMs !== undefined
|
|
426
|
+
&& (typeof fileCtimeMs !== "number" || !Number.isFinite(fileCtimeMs) || fileCtimeMs < 0))) {
|
|
180
427
|
return undefined;
|
|
428
|
+
}
|
|
181
429
|
return Object.freeze({
|
|
182
430
|
offset: offset,
|
|
183
431
|
remainder,
|
|
184
|
-
state: state
|
|
432
|
+
state: state,
|
|
433
|
+
continuityEpoch: typeof continuityEpoch === "string"
|
|
434
|
+
? continuityEpoch
|
|
435
|
+
: INITIAL_CONTINUITY_EPOCH,
|
|
436
|
+
...(typeof fileFingerprint === "string" ? { fileFingerprint } : {}),
|
|
437
|
+
...(typeof fileCtimeMs === "number" ? { fileCtimeMs } : {})
|
|
185
438
|
});
|
|
186
439
|
}
|
|
187
440
|
function unavailable(cursor, error) {
|
|
188
441
|
return Object.freeze({
|
|
189
|
-
cursor: cursor ?? Object.freeze({
|
|
442
|
+
cursor: cursor ?? Object.freeze({
|
|
443
|
+
offset: 0,
|
|
444
|
+
remainder: "",
|
|
445
|
+
state: Object.freeze({}),
|
|
446
|
+
continuityEpoch: INITIAL_CONTINUITY_EPOCH
|
|
447
|
+
}),
|
|
190
448
|
status: "unavailable",
|
|
191
449
|
detail: error instanceof Error ? error.message : String(error)
|
|
192
450
|
});
|
|
@@ -215,7 +473,7 @@ function normalizedClaudeUsage(usage) {
|
|
|
215
473
|
const cacheCreated = integer(usage?.cache_creation_input_tokens) ?? 0;
|
|
216
474
|
const reasoningTokens = integer(object(usage?.output_tokens_details)?.thinking_tokens);
|
|
217
475
|
return Object.freeze({
|
|
218
|
-
semantics: "
|
|
476
|
+
semantics: "request-context",
|
|
219
477
|
inputTokens: directInput + cacheRead + cacheCreated,
|
|
220
478
|
outputTokens,
|
|
221
479
|
cachedInputTokens: cacheRead + cacheCreated,
|
|
@@ -248,27 +506,6 @@ function usageFrom(value) {
|
|
|
248
506
|
...(reasoningTokens === undefined ? {} : { reasoningTokens })
|
|
249
507
|
});
|
|
250
508
|
}
|
|
251
|
-
function sumUsage(values) {
|
|
252
|
-
if (values.length === 0)
|
|
253
|
-
return undefined;
|
|
254
|
-
let inputTokens = 0;
|
|
255
|
-
let outputTokens = 0;
|
|
256
|
-
let cachedInputTokens = 0;
|
|
257
|
-
let reasoningTokens = 0;
|
|
258
|
-
for (const usage of values) {
|
|
259
|
-
inputTokens += usage.inputTokens;
|
|
260
|
-
outputTokens += usage.outputTokens;
|
|
261
|
-
cachedInputTokens += usage.cachedInputTokens ?? 0;
|
|
262
|
-
reasoningTokens += usage.reasoningTokens ?? 0;
|
|
263
|
-
}
|
|
264
|
-
return Object.freeze({
|
|
265
|
-
semantics: "cumulative-session",
|
|
266
|
-
inputTokens,
|
|
267
|
-
outputTokens,
|
|
268
|
-
cachedInputTokens,
|
|
269
|
-
...(reasoningTokens === 0 ? {} : { reasoningTokens })
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
509
|
function jsonObject(line) {
|
|
273
510
|
if (line.trim().length === 0)
|
|
274
511
|
return null;
|