@zq-silk/yui 0.6.2 → 0.6.3
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 +28 -4
- package/README.md +62 -24
- package/dist/agent/argumentPolicy.js +1 -1
- package/dist/agent/managedRuntimeEnvironment.js +1 -0
- package/dist/cli/commandCatalog.js +19 -9
- package/dist/cli/interactionPolicy.js +4 -2
- package/dist/cli.js +77 -32
- package/dist/commands/taskCommands.js +46 -11
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskRoleRuntimeStatus.js +170 -10
- package/dist/controller/agentRuntimeObserver.js +210 -0
- package/dist/controller/clientRuntime.js +3 -21
- package/dist/controller/controller.js +47 -7
- package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
- package/dist/controller/runtime.js +9 -3
- package/dist/controller/runtimeEventInbox.js +49 -295
- package/dist/controller/runtimeEventProcessor.js +184 -321
- package/dist/controller/runtimeHookRunFence.js +226 -0
- package/dist/controller/runtimeLaunchCoordinator.js +91 -26
- package/dist/controller/runtimeObservationHook.js +112 -0
- package/dist/core/controllerServer.js +5 -0
- package/dist/executor/agentAdapter.js +18 -3
- package/dist/executor/fileRoleLaunchPlanner.js +64 -15
- package/dist/executor/managedClaudeRunner.js +121 -0
- package/dist/observability/executionAudit.js +6 -3
- package/dist/repository/taskWorkspacePreparer.js +1 -4
- package/dist/run/providerRetryConfig.js +8 -3
- package/dist/runtime/agentDriver.js +229 -0
- package/dist/runtime/agentDriverObservation.js +57 -0
- package/dist/runtime/builtinAgentDrivers.js +235 -0
- package/dist/runtime/builtinTranscriptObserver.js +290 -0
- package/dist/runtime/builtinTranscriptUsage.js +97 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/ports.js +12 -1
- package/dist/runtime/runtimeObservation.js +297 -0
- package/dist/runtime/runtimeProjection.js +277 -0
- package/dist/runtime/sessionTerminationGuard.js +78 -22
- package/dist/runtime/tmuxAdapters.js +35 -0
- package/dist/scheduler/activeRoleRunDelivery.js +28 -13
- package/dist/scheduler/leaderWakeupProcessor.js +21 -2
- package/dist/scheduler/roleRunLiveness.js +2 -2
- package/dist/scheduler/roleRunStall.js +62 -114
- package/dist/storage/migration/productionRegistry.js +41 -0
- package/dist/storage/sqliteStore.js +3 -3
- package/dist/storage/storageVersions.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +0 -28
- package/dist/telemetry/telemetryCompaction.js +1 -0
- package/dist/telemetry/telemetryConfig.js +4 -5
- package/dist/tmux/tmuxManager.js +136 -22
- package/dist/web/assets/client/view.js +1 -1
- package/dist/web/tmuxWebTerminal.js +17 -12
- package/dist/web/webSnapshot.js +1 -1
- package/dist/worktree/managedWorkspace.js +14 -0
- package/i18n/README.zh-CN.md +7 -5
- package/package.json +1 -1
- package/dist/controller/claudeLifecycleHook.js +0 -203
- package/dist/controller/codexLifecycleHook.js +0 -108
- package/dist/controller/providerHookRunFence.js +0 -156
- package/dist/lifecycle/providerLifecycleMapping.js +0 -190
- package/dist/telemetry/telemetryRouter.js +0 -32
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { AgentDriverRegistry } from "./agentDriver.js";
|
|
2
|
+
import { claudeTranscriptObserver, codexTranscriptObserver, transcriptObserverSource } from "./builtinTranscriptObserver.js";
|
|
3
|
+
export const CODEX_DRIVER_ID = "openai/codex";
|
|
4
|
+
export const CLAUDE_CODE_DRIVER_ID = "anthropic/claude-code";
|
|
5
|
+
export function builtinDriverIdForAdapter(adapterId) {
|
|
6
|
+
return builtinAgentDriverRegistry().requireByAdapterId(adapterId).id;
|
|
7
|
+
}
|
|
8
|
+
const STRUCTURED_CLI_CAPABILITIES = Object.freeze({
|
|
9
|
+
surfaces: Object.freeze(["interactive-cli"]),
|
|
10
|
+
control: Object.freeze({
|
|
11
|
+
start: true,
|
|
12
|
+
resume: true,
|
|
13
|
+
sendTurn: true,
|
|
14
|
+
interrupt: true,
|
|
15
|
+
stop: true
|
|
16
|
+
}),
|
|
17
|
+
observation: Object.freeze({
|
|
18
|
+
sessionIdentity: "exact",
|
|
19
|
+
sessionBootstrap: "discovered",
|
|
20
|
+
preInputReadiness: "unavailable",
|
|
21
|
+
promptAcceptance: "exact",
|
|
22
|
+
turnLifecycle: "exact",
|
|
23
|
+
// Model activity is inferred from usage deltas; these are the operation
|
|
24
|
+
// boundaries the current Hook surfaces actually expose.
|
|
25
|
+
operations: Object.freeze(["tool", "subagent"]),
|
|
26
|
+
waiting: Object.freeze(["permission"]),
|
|
27
|
+
usage: "streaming-cumulative",
|
|
28
|
+
delivery: "ordered-best-effort"
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
export const BUILTIN_AGENT_DRIVERS = Object.freeze([
|
|
32
|
+
Object.freeze({
|
|
33
|
+
id: CLAUDE_CODE_DRIVER_ID,
|
|
34
|
+
label: "Claude Code",
|
|
35
|
+
protocolVersion: 1,
|
|
36
|
+
adapterId: "claude",
|
|
37
|
+
capabilities: Object.freeze({
|
|
38
|
+
...STRUCTURED_CLI_CAPABILITIES,
|
|
39
|
+
observation: Object.freeze({
|
|
40
|
+
...STRUCTURED_CLI_CAPABILITIES.observation,
|
|
41
|
+
sessionBootstrap: "preallocated",
|
|
42
|
+
preInputReadiness: "exact"
|
|
43
|
+
})
|
|
44
|
+
}),
|
|
45
|
+
runtime: Object.freeze({
|
|
46
|
+
nativeSessionId: ({ payload }) => (optionalIdentityFrom(payload, ["session_id"])),
|
|
47
|
+
nativeTurnId: ({ payload }) => (optionalIdentityFrom(payload, ["prompt_id", "turn_id"])),
|
|
48
|
+
mapHook: ({ hookEventName, payload, occurrenceId }) => (mapClaudeHook(hookEventName, payload, occurrenceId)),
|
|
49
|
+
classifyHook: ({ hookEventName, payload }) => Object.freeze({
|
|
50
|
+
...(hookEventName === "SessionStart" && payload.source === "startup"
|
|
51
|
+
? { startupSession: "preallocated" }
|
|
52
|
+
: {}),
|
|
53
|
+
terminal: isTerminalHook(hookEventName)
|
|
54
|
+
}),
|
|
55
|
+
observer: Object.freeze({
|
|
56
|
+
source: (input) => transcriptObserverSource(CLAUDE_CODE_DRIVER_ID, input),
|
|
57
|
+
sample: claudeTranscriptObserver
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
Object.freeze({
|
|
62
|
+
id: CODEX_DRIVER_ID,
|
|
63
|
+
label: "Codex",
|
|
64
|
+
protocolVersion: 1,
|
|
65
|
+
adapterId: "codex",
|
|
66
|
+
capabilities: STRUCTURED_CLI_CAPABILITIES,
|
|
67
|
+
runtime: Object.freeze({
|
|
68
|
+
nativeSessionId: ({ payload }) => (optionalIdentityFrom(payload, ["session_id"])),
|
|
69
|
+
nativeTurnId: ({ payload }) => (optionalIdentityFrom(payload, ["turn_id", "prompt_id"])),
|
|
70
|
+
mapHook: ({ hookEventName, payload, occurrenceId }) => (mapCodexHook(hookEventName, payload, occurrenceId)),
|
|
71
|
+
classifyHook: ({ hookEventName }) => Object.freeze({
|
|
72
|
+
...(hookEventName === "SessionStart"
|
|
73
|
+
? { startupSession: "discovered" }
|
|
74
|
+
: {}),
|
|
75
|
+
terminal: isTerminalHook(hookEventName)
|
|
76
|
+
}),
|
|
77
|
+
observer: Object.freeze({
|
|
78
|
+
source: (input) => transcriptObserverSource(CODEX_DRIVER_ID, input),
|
|
79
|
+
sample: codexTranscriptObserver
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
]);
|
|
84
|
+
export function builtinAgentDriverRegistry() {
|
|
85
|
+
const registry = new AgentDriverRegistry();
|
|
86
|
+
for (const descriptor of BUILTIN_AGENT_DRIVERS)
|
|
87
|
+
registry.register(descriptor);
|
|
88
|
+
return registry;
|
|
89
|
+
}
|
|
90
|
+
function mapClaudeHook(name, payload, occurrenceId) {
|
|
91
|
+
switch (name) {
|
|
92
|
+
case "SessionStart":
|
|
93
|
+
return payload.source === "startup"
|
|
94
|
+
? mapped("session.ready")
|
|
95
|
+
: mapped("session.started");
|
|
96
|
+
case "UserPromptSubmit":
|
|
97
|
+
return mapped("turn.accepted");
|
|
98
|
+
case "PreToolUse":
|
|
99
|
+
return operation("operation.started", "tool", toolId(payload));
|
|
100
|
+
case "PostToolUse":
|
|
101
|
+
return operation("operation.completed", "tool", toolId(payload));
|
|
102
|
+
case "PostToolUseFailure":
|
|
103
|
+
return operation("operation.failed", "tool", toolId(payload));
|
|
104
|
+
case "PermissionRequest":
|
|
105
|
+
return mapped("turn.waiting", {
|
|
106
|
+
reason: "permission",
|
|
107
|
+
waitId: optionalIdentityFrom(payload, ["tool_use_id", "call_id"]) ?? requireOccurrence(occurrenceId)
|
|
108
|
+
});
|
|
109
|
+
case "MessageDisplay": {
|
|
110
|
+
const messageId = optionalIdentityFrom(payload, ["message_id"]);
|
|
111
|
+
const index = typeof payload.index === "number" && Number.isSafeInteger(payload.index)
|
|
112
|
+
? String(payload.index)
|
|
113
|
+
: undefined;
|
|
114
|
+
return mapped("activity.observed", {
|
|
115
|
+
activity: "model",
|
|
116
|
+
activityId: messageId === undefined
|
|
117
|
+
? requireOccurrence(occurrenceId)
|
|
118
|
+
: `${messageId}:${index ?? "message"}`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
case "SubagentStart":
|
|
122
|
+
return operation("operation.started", "subagent", subagentId(payload));
|
|
123
|
+
case "SubagentStop":
|
|
124
|
+
return operation("operation.completed", "subagent", subagentId(payload));
|
|
125
|
+
case "Stop":
|
|
126
|
+
return mapped("turn.completed", optionalSummary(payload));
|
|
127
|
+
case "StopFailure":
|
|
128
|
+
return mapped("turn.failed", claudeFailure(payload));
|
|
129
|
+
case "SessionEnd":
|
|
130
|
+
return mapped("session.ended");
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Claude Code Driver does not support Hook event: ${name}.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function mapCodexHook(name, payload, occurrenceId) {
|
|
136
|
+
switch (name) {
|
|
137
|
+
case "SessionStart":
|
|
138
|
+
return mapped("session.started");
|
|
139
|
+
case "UserPromptSubmit":
|
|
140
|
+
return mapped("turn.accepted");
|
|
141
|
+
case "PreToolUse":
|
|
142
|
+
return operation("operation.started", "tool", toolId(payload));
|
|
143
|
+
case "PostToolUse":
|
|
144
|
+
return operation("operation.completed", "tool", toolId(payload));
|
|
145
|
+
case "PostToolUseFailure":
|
|
146
|
+
return operation("operation.failed", "tool", toolId(payload));
|
|
147
|
+
case "PermissionRequest":
|
|
148
|
+
return mapped("turn.waiting", {
|
|
149
|
+
reason: "permission",
|
|
150
|
+
waitId: optionalIdentityFrom(payload, ["tool_use_id", "call_id", "tool_call_id"])
|
|
151
|
+
?? requireOccurrence(occurrenceId)
|
|
152
|
+
});
|
|
153
|
+
case "SubagentStart":
|
|
154
|
+
return operation("operation.started", "subagent", subagentId(payload));
|
|
155
|
+
case "SubagentStop":
|
|
156
|
+
return operation("operation.completed", "subagent", subagentId(payload));
|
|
157
|
+
case "Stop":
|
|
158
|
+
return mapped("turn.completed", optionalSummary(payload));
|
|
159
|
+
case "SessionEnd":
|
|
160
|
+
return mapped("session.ended");
|
|
161
|
+
default:
|
|
162
|
+
throw new Error(`Codex Driver does not support Hook event: ${name}.`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function mapped(kind, payload = {}) {
|
|
166
|
+
return Object.freeze({ kind, payload: Object.freeze({ ...payload }) });
|
|
167
|
+
}
|
|
168
|
+
function operation(kind, operationKind, operationId) {
|
|
169
|
+
return mapped(kind, { operationId, operation: operationKind });
|
|
170
|
+
}
|
|
171
|
+
function toolId(payload) {
|
|
172
|
+
return firstIdentity(payload, ["tool_use_id", "call_id", "tool_call_id"], "Tool operation id");
|
|
173
|
+
}
|
|
174
|
+
function subagentId(payload) {
|
|
175
|
+
return firstIdentity(payload, ["agent_id", "subagent_id"], "Subagent operation id");
|
|
176
|
+
}
|
|
177
|
+
function firstIdentity(payload, fields, label) {
|
|
178
|
+
for (const field of fields) {
|
|
179
|
+
const value = payload[field];
|
|
180
|
+
if (typeof value === "string" && value.trim().length > 0 && !value.includes("\0")) {
|
|
181
|
+
return value.trim();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`${label} is required.`);
|
|
185
|
+
}
|
|
186
|
+
function optionalIdentityFrom(payload, fields) {
|
|
187
|
+
for (const field of fields) {
|
|
188
|
+
const value = payload[field];
|
|
189
|
+
if (typeof value === "string" && value.trim().length > 0 && !value.includes("\0")) {
|
|
190
|
+
return value.trim();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
function optionalSummary(payload, preferred = "last_assistant_message") {
|
|
196
|
+
for (const field of [preferred, "summary", "message"]) {
|
|
197
|
+
const value = payload[field];
|
|
198
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
199
|
+
return { summary: value.trim() };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
function claudeFailure(payload) {
|
|
205
|
+
const code = firstIdentity(payload, ["error"], "Claude StopFailure error");
|
|
206
|
+
const details = optionalText(payload.error_details);
|
|
207
|
+
const lastOutput = optionalText(payload.last_assistant_message);
|
|
208
|
+
return {
|
|
209
|
+
failure: {
|
|
210
|
+
code,
|
|
211
|
+
...(details === undefined ? {} : { details }),
|
|
212
|
+
...(lastOutput === undefined ? {} : { lastOutput })
|
|
213
|
+
},
|
|
214
|
+
summary: [
|
|
215
|
+
"Agent turn failed.",
|
|
216
|
+
`error: ${code}`,
|
|
217
|
+
...(details === undefined ? [] : [`details: ${details}`]),
|
|
218
|
+
...(lastOutput === undefined ? [] : [`last_output: ${lastOutput}`])
|
|
219
|
+
].join("\n")
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function optionalText(value) {
|
|
223
|
+
return typeof value === "string" && value.trim().length > 0
|
|
224
|
+
? value.trim()
|
|
225
|
+
: undefined;
|
|
226
|
+
}
|
|
227
|
+
function isTerminalHook(name) {
|
|
228
|
+
return name === "Stop" || name === "StopFailure" || name === "SessionEnd";
|
|
229
|
+
}
|
|
230
|
+
function requireOccurrence(value) {
|
|
231
|
+
if (value === undefined || value.trim().length === 0) {
|
|
232
|
+
throw new Error("Agent Driver Hook occurrence id is required.");
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { open, stat } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute } from "node:path";
|
|
4
|
+
const MAX_INITIAL_TAIL_BYTES = 1024 * 1024;
|
|
5
|
+
const MAX_SAMPLE_BYTES = 1024 * 1024;
|
|
6
|
+
const MAX_REMAINDER_BYTES = 64 * 1024;
|
|
7
|
+
const MAX_CLAUDE_MESSAGES = 4_096;
|
|
8
|
+
export function transcriptObserverSource(driverId, input) {
|
|
9
|
+
if (input.hookEventName !== "UserPromptSubmit")
|
|
10
|
+
return null;
|
|
11
|
+
const locator = input.payload.transcript_path;
|
|
12
|
+
if (typeof locator !== "string" || !isAbsolute(locator) || locator.includes("\0"))
|
|
13
|
+
return null;
|
|
14
|
+
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
|
+
const digest = createHash("sha256")
|
|
20
|
+
.update(JSON.stringify([driverId, sessionId, turnId, locator]))
|
|
21
|
+
.digest("hex");
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
sourceId: `transcript-${digest}`,
|
|
25
|
+
transport: "append-only-jsonl",
|
|
26
|
+
locator
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function codexTranscriptObserver(source, cursor) {
|
|
30
|
+
return sampleJsonl(source, cursor, parseCodexLines);
|
|
31
|
+
}
|
|
32
|
+
export async function claudeTranscriptObserver(source, cursor) {
|
|
33
|
+
return sampleJsonl(source, cursor, parseClaudeLines);
|
|
34
|
+
}
|
|
35
|
+
async function sampleJsonl(source, rawCursor, parse) {
|
|
36
|
+
const previous = normalizeCursor(rawCursor);
|
|
37
|
+
let metadata;
|
|
38
|
+
try {
|
|
39
|
+
metadata = await stat(source.locator);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return unavailable(previous, error);
|
|
43
|
+
}
|
|
44
|
+
if (!metadata.isFile())
|
|
45
|
+
return unavailable(previous, new Error("observer locator is not a file"));
|
|
46
|
+
const reset = previous !== undefined && metadata.size < previous.offset;
|
|
47
|
+
const initial = previous === undefined || reset;
|
|
48
|
+
const start = initial
|
|
49
|
+
? Math.max(0, metadata.size - MAX_INITIAL_TAIL_BYTES)
|
|
50
|
+
: previous.offset;
|
|
51
|
+
const length = Math.min(MAX_SAMPLE_BYTES, Math.max(0, metadata.size - start));
|
|
52
|
+
let bytes = Buffer.alloc(0);
|
|
53
|
+
try {
|
|
54
|
+
if (length > 0) {
|
|
55
|
+
const handle = await open(source.locator, "r");
|
|
56
|
+
try {
|
|
57
|
+
bytes = Buffer.alloc(length);
|
|
58
|
+
const read = await handle.read(bytes, 0, length, start);
|
|
59
|
+
bytes = bytes.subarray(0, read.bytesRead);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await handle.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return unavailable(previous, error);
|
|
68
|
+
}
|
|
69
|
+
let text = `${initial ? "" : previous?.remainder ?? ""}${bytes.toString("utf8")}`;
|
|
70
|
+
if (initial && start > 0) {
|
|
71
|
+
const firstLineEnd = text.indexOf("\n");
|
|
72
|
+
text = firstLineEnd < 0 ? "" : text.slice(firstLineEnd + 1);
|
|
73
|
+
}
|
|
74
|
+
const complete = text.endsWith("\n");
|
|
75
|
+
const split = text.split("\n");
|
|
76
|
+
const remainder = complete ? "" : split.pop() ?? "";
|
|
77
|
+
const boundedRemainder = Buffer.byteLength(remainder, "utf8") <= MAX_REMAINDER_BYTES
|
|
78
|
+
? remainder
|
|
79
|
+
: "";
|
|
80
|
+
const parsed = parse(split, initial ? {} : previous?.state ?? {});
|
|
81
|
+
const nextCursor = Object.freeze({
|
|
82
|
+
offset: start + bytes.length,
|
|
83
|
+
remainder: boundedRemainder,
|
|
84
|
+
state: parsed.state
|
|
85
|
+
});
|
|
86
|
+
const fellBehind = start + bytes.length < metadata.size;
|
|
87
|
+
const detail = parsed.degraded
|
|
88
|
+
?? (fellBehind ? "Transcript observer is catching up with a bounded read." : undefined)
|
|
89
|
+
?? (reset ? "Transcript was truncated; observer baseline was reset." : undefined)
|
|
90
|
+
?? (remainder !== boundedRemainder ? "Oversized partial transcript line was discarded." : undefined);
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
cursor: nextCursor,
|
|
93
|
+
status: detail === undefined ? "healthy" : "degraded",
|
|
94
|
+
...(detail === undefined ? {} : { detail }),
|
|
95
|
+
...(parsed.usage === undefined ? {} : { usage: parsed.usage }),
|
|
96
|
+
...(parsed.activityId === undefined ? {} : {
|
|
97
|
+
activity: "model",
|
|
98
|
+
activityId: parsed.activityId
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function parseCodexLines(lines, state) {
|
|
103
|
+
let usage = usageFrom(state.usage);
|
|
104
|
+
let activityId;
|
|
105
|
+
let malformed = 0;
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const entry = jsonObject(line);
|
|
108
|
+
if (entry === null) {
|
|
109
|
+
if (line.trim().length > 0)
|
|
110
|
+
malformed += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (entry.type !== "event_msg")
|
|
114
|
+
continue;
|
|
115
|
+
const payload = object(entry.payload);
|
|
116
|
+
if (payload?.type !== "token_count")
|
|
117
|
+
continue;
|
|
118
|
+
const candidate = normalizedCodexUsage(object(object(payload.info)?.total_token_usage));
|
|
119
|
+
if (candidate === undefined)
|
|
120
|
+
continue;
|
|
121
|
+
usage = candidate;
|
|
122
|
+
activityId = `usage:${candidate.inputTokens}:${candidate.outputTokens}`;
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
state: Object.freeze({ ...(usage === undefined ? {} : { usage }) }),
|
|
126
|
+
...(usage === undefined ? {} : { usage }),
|
|
127
|
+
...(activityId === undefined ? {} : { activityId }),
|
|
128
|
+
...(malformed === 0 ? {} : { degraded: `${malformed} malformed transcript line(s) ignored.` })
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function parseClaudeLines(lines, state) {
|
|
132
|
+
const messages = messageState(state.messages);
|
|
133
|
+
let activityId;
|
|
134
|
+
let malformed = 0;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const entry = jsonObject(line);
|
|
137
|
+
if (entry === null) {
|
|
138
|
+
if (line.trim().length > 0)
|
|
139
|
+
malformed += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (entry.type !== "assistant")
|
|
143
|
+
continue;
|
|
144
|
+
const message = object(entry.message);
|
|
145
|
+
const usage = normalizedClaudeUsage(object(message?.usage));
|
|
146
|
+
if (usage === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
const key = identity(message?.id) === undefined
|
|
149
|
+
? identity(entry.uuid) === undefined ? undefined : `entry:${identity(entry.uuid)}`
|
|
150
|
+
: `message:${identity(message?.id)}`;
|
|
151
|
+
if (key === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
messages[key] = usage;
|
|
154
|
+
activityId = `${key}:${usage.inputTokens}:${usage.outputTokens}`;
|
|
155
|
+
}
|
|
156
|
+
const keys = Object.keys(messages);
|
|
157
|
+
let degraded = malformed === 0 ? undefined : `${malformed} malformed transcript line(s) ignored.`;
|
|
158
|
+
if (keys.length > MAX_CLAUDE_MESSAGES) {
|
|
159
|
+
for (const key of keys.slice(0, keys.length - MAX_CLAUDE_MESSAGES))
|
|
160
|
+
delete messages[key];
|
|
161
|
+
degraded = "Claude transcript message baseline was bounded to the newest entries.";
|
|
162
|
+
}
|
|
163
|
+
const usage = sumUsage(Object.values(messages));
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
state: Object.freeze({ messages: Object.freeze(messages) }),
|
|
166
|
+
...(usage === undefined ? {} : { usage }),
|
|
167
|
+
...(activityId === undefined ? {} : { activityId }),
|
|
168
|
+
...(degraded === undefined ? {} : { degraded })
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function normalizeCursor(input) {
|
|
172
|
+
if (input === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
const offset = input.offset;
|
|
175
|
+
const remainder = input.remainder;
|
|
176
|
+
const state = input.state;
|
|
177
|
+
if (!Number.isSafeInteger(offset) || offset < 0
|
|
178
|
+
|| typeof remainder !== "string"
|
|
179
|
+
|| state === null || typeof state !== "object" || Array.isArray(state))
|
|
180
|
+
return undefined;
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
offset: offset,
|
|
183
|
+
remainder,
|
|
184
|
+
state: state
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function unavailable(cursor, error) {
|
|
188
|
+
return Object.freeze({
|
|
189
|
+
cursor: cursor ?? Object.freeze({ offset: 0, remainder: "", state: Object.freeze({}) }),
|
|
190
|
+
status: "unavailable",
|
|
191
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function normalizedCodexUsage(usage) {
|
|
195
|
+
const inputTokens = integer(usage?.input_tokens);
|
|
196
|
+
const outputTokens = integer(usage?.output_tokens);
|
|
197
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
198
|
+
return undefined;
|
|
199
|
+
const cachedInputTokens = integer(usage?.cached_input_tokens);
|
|
200
|
+
const reasoningTokens = integer(usage?.reasoning_output_tokens);
|
|
201
|
+
return Object.freeze({
|
|
202
|
+
inputTokens,
|
|
203
|
+
outputTokens,
|
|
204
|
+
...(cachedInputTokens === undefined ? {} : { cachedInputTokens }),
|
|
205
|
+
...(reasoningTokens === undefined ? {} : { reasoningTokens })
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function normalizedClaudeUsage(usage) {
|
|
209
|
+
const directInput = integer(usage?.input_tokens);
|
|
210
|
+
const outputTokens = integer(usage?.output_tokens);
|
|
211
|
+
if (directInput === undefined || outputTokens === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
const cacheRead = integer(usage?.cache_read_input_tokens) ?? 0;
|
|
214
|
+
const cacheCreated = integer(usage?.cache_creation_input_tokens) ?? 0;
|
|
215
|
+
const reasoningTokens = integer(object(usage?.output_tokens_details)?.thinking_tokens);
|
|
216
|
+
return Object.freeze({
|
|
217
|
+
inputTokens: directInput + cacheRead + cacheCreated,
|
|
218
|
+
outputTokens,
|
|
219
|
+
cachedInputTokens: cacheRead + cacheCreated,
|
|
220
|
+
...(reasoningTokens === undefined ? {} : { reasoningTokens })
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function messageState(value) {
|
|
224
|
+
const source = object(value) ?? {};
|
|
225
|
+
const result = {};
|
|
226
|
+
for (const [key, raw] of Object.entries(source)) {
|
|
227
|
+
const usage = usageFrom(raw);
|
|
228
|
+
if (usage !== undefined)
|
|
229
|
+
result[key] = usage;
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function usageFrom(value) {
|
|
234
|
+
const raw = object(value);
|
|
235
|
+
const inputTokens = integer(raw?.inputTokens);
|
|
236
|
+
const outputTokens = integer(raw?.outputTokens);
|
|
237
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
238
|
+
return undefined;
|
|
239
|
+
const cachedInputTokens = integer(raw?.cachedInputTokens);
|
|
240
|
+
const reasoningTokens = integer(raw?.reasoningTokens);
|
|
241
|
+
return Object.freeze({
|
|
242
|
+
inputTokens,
|
|
243
|
+
outputTokens,
|
|
244
|
+
...(cachedInputTokens === undefined ? {} : { cachedInputTokens }),
|
|
245
|
+
...(reasoningTokens === undefined ? {} : { reasoningTokens })
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function sumUsage(values) {
|
|
249
|
+
if (values.length === 0)
|
|
250
|
+
return undefined;
|
|
251
|
+
let inputTokens = 0;
|
|
252
|
+
let outputTokens = 0;
|
|
253
|
+
let cachedInputTokens = 0;
|
|
254
|
+
let reasoningTokens = 0;
|
|
255
|
+
for (const usage of values) {
|
|
256
|
+
inputTokens += usage.inputTokens;
|
|
257
|
+
outputTokens += usage.outputTokens;
|
|
258
|
+
cachedInputTokens += usage.cachedInputTokens ?? 0;
|
|
259
|
+
reasoningTokens += usage.reasoningTokens ?? 0;
|
|
260
|
+
}
|
|
261
|
+
return Object.freeze({
|
|
262
|
+
inputTokens,
|
|
263
|
+
outputTokens,
|
|
264
|
+
cachedInputTokens,
|
|
265
|
+
...(reasoningTokens === 0 ? {} : { reasoningTokens })
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function jsonObject(line) {
|
|
269
|
+
if (line.trim().length === 0)
|
|
270
|
+
return null;
|
|
271
|
+
try {
|
|
272
|
+
return object(JSON.parse(line));
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function object(value) {
|
|
279
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
280
|
+
? value
|
|
281
|
+
: null;
|
|
282
|
+
}
|
|
283
|
+
function integer(value) {
|
|
284
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
285
|
+
}
|
|
286
|
+
function identity(value) {
|
|
287
|
+
return typeof value === "string" && value.trim().length > 0 && !value.includes("\0")
|
|
288
|
+
? value.trim()
|
|
289
|
+
: undefined;
|
|
290
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function codexTranscriptUsage(transcript) {
|
|
2
|
+
let latest = null;
|
|
3
|
+
for (const line of transcript.split("\n")) {
|
|
4
|
+
const entry = parseLine(line);
|
|
5
|
+
if (entry?.type !== "event_msg")
|
|
6
|
+
continue;
|
|
7
|
+
const payload = object(entry.payload);
|
|
8
|
+
if (payload?.type !== "token_count")
|
|
9
|
+
continue;
|
|
10
|
+
const info = object(payload.info);
|
|
11
|
+
const usage = object(info?.total_token_usage);
|
|
12
|
+
const inputTokens = integer(usage?.input_tokens);
|
|
13
|
+
const outputTokens = integer(usage?.output_tokens);
|
|
14
|
+
if (inputTokens === null || outputTokens === null)
|
|
15
|
+
continue;
|
|
16
|
+
const cachedInputTokens = integer(usage?.cached_input_tokens);
|
|
17
|
+
const reasoningTokens = integer(usage?.reasoning_output_tokens);
|
|
18
|
+
latest = Object.freeze({
|
|
19
|
+
inputTokens,
|
|
20
|
+
outputTokens,
|
|
21
|
+
...(cachedInputTokens === null ? {} : { cachedInputTokens }),
|
|
22
|
+
...(reasoningTokens === null ? {} : { reasoningTokens })
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return latest;
|
|
26
|
+
}
|
|
27
|
+
export function claudeTranscriptUsage(transcript) {
|
|
28
|
+
const messages = new Map();
|
|
29
|
+
for (const line of transcript.split("\n")) {
|
|
30
|
+
const entry = parseLine(line);
|
|
31
|
+
if (entry?.type !== "assistant")
|
|
32
|
+
continue;
|
|
33
|
+
const message = object(entry.message);
|
|
34
|
+
const usage = object(message?.usage);
|
|
35
|
+
const directInput = integer(usage?.input_tokens);
|
|
36
|
+
const outputTokens = integer(usage?.output_tokens);
|
|
37
|
+
if (directInput === null || outputTokens === null)
|
|
38
|
+
continue;
|
|
39
|
+
const cacheRead = integer(usage?.cache_read_input_tokens) ?? 0;
|
|
40
|
+
const cacheCreated = integer(usage?.cache_creation_input_tokens) ?? 0;
|
|
41
|
+
const details = object(usage?.output_tokens_details);
|
|
42
|
+
const reasoningTokens = integer(details?.thinking_tokens);
|
|
43
|
+
const key = typeof message?.id === "string" && message.id.length > 0
|
|
44
|
+
? `message:${message.id}`
|
|
45
|
+
: typeof entry.uuid === "string" && entry.uuid.length > 0
|
|
46
|
+
? `entry:${entry.uuid}`
|
|
47
|
+
: null;
|
|
48
|
+
// A streaming transcript can repeat cumulative snapshots. Without a
|
|
49
|
+
// stable provider or entry identity, summing them would fabricate growth.
|
|
50
|
+
if (key === null)
|
|
51
|
+
continue;
|
|
52
|
+
messages.set(key, Object.freeze({
|
|
53
|
+
// Normalize inputTokens as the complete input total. cachedInputTokens is
|
|
54
|
+
// a breakdown and must not be added to it again by the projection.
|
|
55
|
+
inputTokens: directInput + cacheRead + cacheCreated,
|
|
56
|
+
outputTokens,
|
|
57
|
+
cachedInputTokens: cacheRead + cacheCreated,
|
|
58
|
+
...(reasoningTokens === null ? {} : { reasoningTokens })
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
if (messages.size === 0)
|
|
62
|
+
return null;
|
|
63
|
+
let inputTokens = 0;
|
|
64
|
+
let outputTokens = 0;
|
|
65
|
+
let cachedInputTokens = 0;
|
|
66
|
+
let reasoningTokens = 0;
|
|
67
|
+
for (const usage of messages.values()) {
|
|
68
|
+
inputTokens += usage.inputTokens;
|
|
69
|
+
outputTokens += usage.outputTokens;
|
|
70
|
+
cachedInputTokens += usage.cachedInputTokens ?? 0;
|
|
71
|
+
reasoningTokens += usage.reasoningTokens ?? 0;
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
inputTokens,
|
|
75
|
+
outputTokens,
|
|
76
|
+
cachedInputTokens,
|
|
77
|
+
...(reasoningTokens === 0 ? {} : { reasoningTokens })
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function parseLine(line) {
|
|
81
|
+
if (line.trim().length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
return object(JSON.parse(line));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function object(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
92
|
+
? value
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
95
|
+
function integer(value) {
|
|
96
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
97
|
+
}
|
|
@@ -280,9 +280,9 @@ export function assertExactTaskRuntimeState(runtime, store, options = {}) {
|
|
|
280
280
|
const sessionLaunch = runtime.launchId !== undefined
|
|
281
281
|
&& session?.launchId === runtime.launchId;
|
|
282
282
|
const executionRef = lifecycleMailbox?.processing?.executionRef;
|
|
283
|
-
const preallocated = options.
|
|
283
|
+
const preallocated = options.preallocatedDriverSessionReservation;
|
|
284
284
|
const exactPreallocatedReservation = preallocated !== undefined
|
|
285
|
-
&& runtime.adapterId ===
|
|
285
|
+
&& runtime.adapterId === preallocated.adapterId
|
|
286
286
|
&& runtime.runId !== undefined
|
|
287
287
|
&& runtime.launchId !== undefined
|
|
288
288
|
&& runtime.nativeSessionId !== undefined
|
package/dist/runtime/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
|
3
3
|
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
4
4
|
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
5
5
|
export { createPendingTurnCompletion, DEFAULT_RECENT_TURN_ID_LIMIT, hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
6
|
-
export { RuntimeLaunchError } from "./ports.js";
|
|
6
|
+
export { RuntimeHostContentionError, RuntimeLaunchError } from "./ports.js";
|
|
7
7
|
export { TmuxPromptPushAdapter, TmuxSessionHost } from "./tmuxAdapters.js";
|
|
8
8
|
export { FileTaskRuntimeIsolation, YUI_TASK_RUNTIME_ISOLATION_DESCRIPTOR, YUI_TASK_RUNTIME_SERVICE_NAMESPACE, assertTaskRuntimeIsolationPreflight, createTaskRuntimeIsolationDescriptor, parseTaskRuntimeIsolationDescriptor, planTaskRuntimeCleanup, taskRuntimeIsolationEnvironment, taskRuntimeIsolationFingerprint } from "./taskRuntimeIsolation.js";
|
|
9
9
|
export { createSessionOwnerIdentity, discoverProviderRootByLaunchEnv, isLinuxProcessLive, listLaunchFencedProcesses, listOwnedProcessTree, readLinuxProcessIdentity } from "./sessionOwnerIdentity.js";
|
package/dist/runtime/ports.js
CHANGED
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
export class RuntimeLaunchError extends Error {
|
|
3
3
|
retryable;
|
|
4
4
|
launchId;
|
|
5
|
+
reason;
|
|
5
6
|
name = "RuntimeLaunchError";
|
|
6
|
-
constructor(retryable, launchId, message) {
|
|
7
|
+
constructor(retryable, launchId, message, reason) {
|
|
7
8
|
super(message);
|
|
8
9
|
this.retryable = retryable;
|
|
9
10
|
this.launchId = launchId;
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** A host-side contention check that occurs before planning or process start. */
|
|
15
|
+
export class RuntimeHostContentionError extends Error {
|
|
16
|
+
reason;
|
|
17
|
+
name = "RuntimeHostContentionError";
|
|
18
|
+
constructor(reason, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.reason = reason;
|
|
10
21
|
}
|
|
11
22
|
}
|