@zq-silk/yui 0.6.2 → 0.6.4

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.
Files changed (63) hide show
  1. package/ARCHITECTURE.md +28 -4
  2. package/README.md +60 -97
  3. package/dist/agent/argumentPolicy.js +1 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +1 -0
  5. package/dist/cli/commandCatalog.js +19 -9
  6. package/dist/cli/interactionPolicy.js +4 -2
  7. package/dist/cli.js +77 -32
  8. package/dist/commands/taskCommands.js +46 -11
  9. package/dist/commands/taskContextCommand.js +1 -1
  10. package/dist/commands/taskRoleRuntimeStatus.js +170 -10
  11. package/dist/controller/agentRuntimeObserver.js +210 -0
  12. package/dist/controller/clientRuntime.js +3 -21
  13. package/dist/controller/controller.js +47 -7
  14. package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
  15. package/dist/controller/runtime.js +9 -3
  16. package/dist/controller/runtimeEventInbox.js +49 -295
  17. package/dist/controller/runtimeEventProcessor.js +184 -321
  18. package/dist/controller/runtimeHookRunFence.js +226 -0
  19. package/dist/controller/runtimeLaunchCoordinator.js +91 -26
  20. package/dist/controller/runtimeObservationHook.js +112 -0
  21. package/dist/core/controllerServer.js +5 -0
  22. package/dist/executor/agentAdapter.js +18 -3
  23. package/dist/executor/fileRoleLaunchPlanner.js +64 -15
  24. package/dist/executor/managedClaudeRunner.js +121 -0
  25. package/dist/observability/executionAudit.js +6 -3
  26. package/dist/repository/taskWorkspacePreparer.js +1 -4
  27. package/dist/run/providerRetryConfig.js +8 -3
  28. package/dist/runtime/agentDriver.js +229 -0
  29. package/dist/runtime/agentDriverObservation.js +57 -0
  30. package/dist/runtime/builtinAgentDrivers.js +235 -0
  31. package/dist/runtime/builtinTranscriptObserver.js +290 -0
  32. package/dist/runtime/builtinTranscriptUsage.js +97 -0
  33. package/dist/runtime/exactControlPlane.js +2 -2
  34. package/dist/runtime/index.js +1 -1
  35. package/dist/runtime/ports.js +12 -1
  36. package/dist/runtime/runtimeObservation.js +297 -0
  37. package/dist/runtime/runtimeProjection.js +277 -0
  38. package/dist/runtime/sessionTerminationGuard.js +78 -22
  39. package/dist/runtime/tmuxAdapters.js +35 -0
  40. package/dist/scheduler/activeRoleRunDelivery.js +28 -13
  41. package/dist/scheduler/leaderWakeupProcessor.js +21 -2
  42. package/dist/scheduler/roleRunLiveness.js +2 -2
  43. package/dist/scheduler/roleRunStall.js +62 -114
  44. package/dist/storage/migration/productionRegistry.js +85 -0
  45. package/dist/storage/sqliteStore.js +3 -3
  46. package/dist/storage/storageVersions.js +1 -1
  47. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +2 -1
  48. package/dist/storage/upgrade/sqliteStateMigration.js +123 -0
  49. package/dist/telemetry/sqliteTelemetryStore.js +0 -28
  50. package/dist/telemetry/telemetryCompaction.js +1 -0
  51. package/dist/telemetry/telemetryConfig.js +4 -5
  52. package/dist/tmux/tmuxManager.js +136 -22
  53. package/dist/web/assets/client/view.js +1 -1
  54. package/dist/web/tmuxWebTerminal.js +17 -12
  55. package/dist/web/webSnapshot.js +1 -1
  56. package/dist/worktree/managedWorkspace.js +14 -0
  57. package/i18n/README.zh-CN.md +12 -7
  58. package/package.json +1 -1
  59. package/dist/controller/claudeLifecycleHook.js +0 -203
  60. package/dist/controller/codexLifecycleHook.js +0 -108
  61. package/dist/controller/providerHookRunFence.js +0 -156
  62. package/dist/lifecycle/providerLifecycleMapping.js +0 -190
  63. 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.preallocatedNativeSessionReservation;
283
+ const preallocated = options.preallocatedDriverSessionReservation;
284
284
  const exactPreallocatedReservation = preallocated !== undefined
285
- && runtime.adapterId === "claude"
285
+ && runtime.adapterId === preallocated.adapterId
286
286
  && runtime.runId !== undefined
287
287
  && runtime.launchId !== undefined
288
288
  && runtime.nativeSessionId !== undefined
@@ -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";
@@ -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
  }