@rynx-ai/runtime 0.1.11-beta.37 → 0.1.11-beta.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude/native-bridge.d.ts +85 -0
- package/dist/claude/native-bridge.js +335 -17
- package/dist/claude/native-hook-main.js +18 -1
- package/dist/claude/native-hooks.js +7 -0
- package/dist/claude/native-integration.d.ts +120 -18
- package/dist/claude/native-integration.js +1200 -161
- package/dist/claude/transcript-clone.d.ts +18 -0
- package/dist/claude/transcript-clone.js +497 -0
- package/dist/claude/transcript.d.ts +27 -4
- package/dist/claude/transcript.js +131 -30
- package/dist/codex-session-store.d.ts +23 -0
- package/dist/codex-session-store.js +21 -0
- package/dist/host.d.ts +31 -2
- package/dist/host.js +551 -72
- package/dist/runner/child.d.ts +29 -5
- package/dist/runner/child.js +635 -54
- package/dist/runner/manager.d.ts +25 -0
- package/dist/runner/manager.js +805 -115
- package/dist/runner/protocol.d.ts +76 -3
- package/dist/runner/transport.d.ts +9 -0
- package/dist/runner/transport.js +39 -12
- package/package.json +2 -2
|
@@ -4,16 +4,46 @@ import { type ClaudeRunnerStatus } from "./session-status.js";
|
|
|
4
4
|
/** Mirror sink — the same shape as `CodexForwarderSink` so the host reuses one
|
|
5
5
|
* per-turn normalizer wiring for both runtimes. */
|
|
6
6
|
export interface ClaudeForwarderSink {
|
|
7
|
+
/** Begin one transcript source record. The host derives per-item source keys
|
|
8
|
+
* from this stable base while still running the normalizer for already-seen
|
|
9
|
+
* items so later records recover the same turn/tool state. */
|
|
10
|
+
onTranscriptRecordStart?(sourceBase: string, isHandled: (sourceId: string) => boolean, delivery?: {
|
|
11
|
+
lane?: string;
|
|
12
|
+
deadLetterPath?: string;
|
|
13
|
+
/** Persist an individual handled item before the containing record's
|
|
14
|
+
* byte cursor is allowed to advance. */
|
|
15
|
+
onHandled?: (sourceId: string) => void;
|
|
16
|
+
}): void;
|
|
17
|
+
/** Resolve only after every newly emitted durable item from the current
|
|
18
|
+
* record has reached a handled delivery outcome. Returns all per-item keys. */
|
|
19
|
+
onTranscriptRecordEnd?(): Promise<readonly string[]>;
|
|
7
20
|
/** A turn began; `turnId` (the user record uuid) derives a stable responseId. */
|
|
8
21
|
onTurnStart(turnId?: string): void;
|
|
9
22
|
/** The user's prompt text (a `role:user` conversation message — not identity). */
|
|
10
23
|
onUserMessage(text: string): void;
|
|
24
|
+
/** Provider-generated task notification persisted as hidden model context.
|
|
25
|
+
* It owns an independent response identity and never changes the parent Turn. */
|
|
26
|
+
onMetaUserMessage?(text: string, sourceKey?: string, parentToolCallId?: string): void;
|
|
27
|
+
/** Claude Code native sub-agent task prompt, nested under the parent Task
|
|
28
|
+
* tool call instead of opening a parent Session turn. */
|
|
29
|
+
onSubagentUserMessage?(text: string, parentToolCallId: string): void;
|
|
30
|
+
/** A native sub-agent's top-level `!cmd`, nested under its parent Task. */
|
|
31
|
+
onSubagentTerminalCommand?(cmd: TerminalCommandData, parentToolCallId: string): void;
|
|
32
|
+
/** Resolve/publish the canonical parent Turn for a native sub-agent. This
|
|
33
|
+
* mapping is persisted beside the independent child cursor. */
|
|
34
|
+
resolveSubagentParentResponseId?(parentToolCallId: string): string | undefined;
|
|
35
|
+
onSubagentParentResponse?(parentToolCallId: string, responseId: string): void;
|
|
11
36
|
/** A local shell command the user ran in the TUI (claude `!` bash mode), framed
|
|
12
37
|
* as its own mini-turn (an `onTurnStart`/`onTurnEnd` pair brackets this call). */
|
|
13
38
|
onTerminalCommand(cmd: TerminalCommandData): void;
|
|
14
39
|
/** The agent's task list changed (claude `TaskCreate`/`TaskUpdate`) — the WHOLE
|
|
15
40
|
* current list (a session-level snapshot, not tied to a turn). */
|
|
16
41
|
onTodos(todos: TodoItem[]): void;
|
|
42
|
+
/** Best-effort spinner edges around Claude's native compaction. */
|
|
43
|
+
onCompactionStatus?(status: "in_progress" | "completed" | "failed"): void;
|
|
44
|
+
/** Persist one compaction boundary. `true` means confirmed/ambiguous-handled;
|
|
45
|
+
* `false` means a bounded secondary hook delivery was dropped. */
|
|
46
|
+
onCompactionBoundary?(summary: string, sequence: number, source: "transcript" | "hook"): boolean | Promise<boolean>;
|
|
17
47
|
/** One mapped event within the current turn. */
|
|
18
48
|
onEvent(event: AgentEvent): void;
|
|
19
49
|
/** A provider-neutral question/permission requested or settled while this
|
|
@@ -21,7 +51,7 @@ export interface ClaudeForwarderSink {
|
|
|
21
51
|
onInteraction(event: RuntimeInteractionEvent): void;
|
|
22
52
|
/** The current turn finished (a new user prompt, or the inactivity backstop).
|
|
23
53
|
* `usage` carries the latest statusLine context/cost snapshot, when captured. */
|
|
24
|
-
onTurnEnd(usage?: Record<string, unknown>, backgroundTaskCount?: number): void;
|
|
54
|
+
onTurnEnd(usage?: Record<string, unknown>, backgroundTaskCount?: number, reason?: "rotation"): void;
|
|
25
55
|
/** The current turn ended because the user explicitly interrupted it. */
|
|
26
56
|
onTurnInterrupted?(usage?: Record<string, unknown>): void;
|
|
27
57
|
/** Escape was sent for the open Turn. Publish cancelled UI state immediately;
|
|
@@ -34,10 +64,10 @@ export interface ClaudeForwarderSink {
|
|
|
34
64
|
* assistant record and the two orderings race; finalizing here would split a
|
|
35
65
|
* late assistant record into its own turn. The turn is finalized by the next
|
|
36
66
|
* user prompt or the inactivity backstop, so a late record still joins it. */
|
|
37
|
-
onIdle(backgroundTaskCount?: number): void
|
|
67
|
+
onIdle(backgroundTaskCount?: number, sourceId?: string): void | Promise<void>;
|
|
38
68
|
/** Claude's current Turn or native runtime failed (StopFailure/pane exit).
|
|
39
69
|
* May fire between Turns so Session-level failure can retire stale liveness. */
|
|
40
|
-
onTurnError(error: Error): void
|
|
70
|
+
onTurnError(error: Error, sourceId?: string): void | Promise<void>;
|
|
41
71
|
/** Fired once SessionStart reveals claude's session id + transcript path, so
|
|
42
72
|
* the host can persist the id and release its readiness gate. */
|
|
43
73
|
onSessionDiscovered?(claudeSessionId: string, transcriptPath: string): void;
|
|
@@ -45,10 +75,10 @@ export interface ClaudeForwarderSink {
|
|
|
45
75
|
* binding. The host keeps the original binding and fails readiness. */
|
|
46
76
|
onSessionResumeError?(error: Error): void;
|
|
47
77
|
/** The claude session rotated (`/clear` → fresh, `/fork` → derived): a new
|
|
48
|
-
* SessionStart reported a new session id + transcript. The
|
|
49
|
-
*
|
|
50
|
-
* host
|
|
51
|
-
onSessionRotated?(kind: "clear" | "fork", claudeSessionId: string, transcriptPath: string): void
|
|
78
|
+
* SessionStart reported a new session id + transcript. The old Response is
|
|
79
|
+
* closed first; the forwarder re-points only after this publication barrier
|
|
80
|
+
* resolves, so the host can mint a fresh rynx session and ACK mirror retarget. */
|
|
81
|
+
onSessionRotated?(kind: "clear" | "fork", claudeSessionId: string, transcriptPath: string, initialTranscriptOffset: number): void | Promise<void>;
|
|
52
82
|
}
|
|
53
83
|
export interface ClaudeLiveSessionOptions {
|
|
54
84
|
/** The session's bridge dir (`hooks.jsonl` lives here). */
|
|
@@ -64,6 +94,10 @@ export interface ClaudeLiveSessionOptions {
|
|
|
64
94
|
* matching forwarder cursor exists, follow only records appended after the
|
|
65
95
|
* SessionStart discovery instead of mirroring native history a second time. */
|
|
66
96
|
resumeAtEndOnDiscovery?: boolean;
|
|
97
|
+
/** Exact native-rotation boundary persisted by the host. It takes
|
|
98
|
+
* precedence over resumeAtEndOnDiscovery so a delayed restart cannot skip
|
|
99
|
+
* target records written after SessionStart. */
|
|
100
|
+
initialTranscriptOffsetOnDiscovery?: number;
|
|
67
101
|
/** Poll interval (ms). The files are append-only, so polling is simplest. */
|
|
68
102
|
pollMs?: number;
|
|
69
103
|
/** Inactivity (ms) FALLBACK close for a turn whose Stop hook never fired. */
|
|
@@ -92,9 +126,24 @@ export declare class ClaudeLiveSession {
|
|
|
92
126
|
private releaseSleep?;
|
|
93
127
|
private hooksOffset;
|
|
94
128
|
private hookEventCursor;
|
|
129
|
+
/** Rotation publication is a checkpoint barrier. No source is polled under
|
|
130
|
+
* either logical Session until the daemon confirms the new binding. */
|
|
131
|
+
private rotationPending;
|
|
132
|
+
/** A non-rotation async hook (currently compaction completion) holds only the
|
|
133
|
+
* hook cursor and parent transcript. Native sub-agents, deltas, interactions,
|
|
134
|
+
* and live status continue to converge while its delivery retries. */
|
|
135
|
+
private compactionHookPending;
|
|
136
|
+
private pendingCompactionHook?;
|
|
95
137
|
private interactionsOffset;
|
|
96
138
|
private interactionAcksOffset;
|
|
97
139
|
private transcriptOffset;
|
|
140
|
+
private committedTranscriptOffset;
|
|
141
|
+
private transcriptLineCursor;
|
|
142
|
+
private committedTranscriptLineCursor;
|
|
143
|
+
private transcriptDeliveryPending;
|
|
144
|
+
/** A transcript isCompactSummary boundary is currently in its durable lane.
|
|
145
|
+
* The completion-hook fallback must wait so the real summary remains primary. */
|
|
146
|
+
private transcriptCompactionPending;
|
|
98
147
|
/** Secondary dedup: source ids (record uuids) already forwarded, so a re-read
|
|
99
148
|
* (fingerprint reset / mid-poll death) doesn't re-emit. Persisted in the
|
|
100
149
|
* forwarder state; bounded there. */
|
|
@@ -106,11 +155,20 @@ export declare class ClaudeLiveSession {
|
|
|
106
155
|
/** Persisted native id requested through `claude --resume`. */
|
|
107
156
|
private readonly expectedClaudeSessionId?;
|
|
108
157
|
private readonly resumeAtEndOnDiscovery;
|
|
158
|
+
private readonly initialTranscriptOffsetOnDiscovery;
|
|
109
159
|
/** Every claude session uuid seen — a resume into an UNSEEN id + a forkedFrom
|
|
110
160
|
* marker signals a `/fork` (vs. resuming a known branch). */
|
|
111
161
|
private readonly seenClaudeSessionIds;
|
|
112
162
|
private turnOpen;
|
|
113
163
|
private currentTurnId?;
|
|
164
|
+
/** Input half of a split Claude `!cmd`, plus the turn id its later output
|
|
165
|
+
* must reuse. Kept across a terminal Stop edge like the current terminal
|
|
166
|
+
* response id. */
|
|
167
|
+
private activeTerminalCommand?;
|
|
168
|
+
/** Split-command state at the latest individually handled source boundary.
|
|
169
|
+
* Parsing may run ahead through later records, so durable state must not use
|
|
170
|
+
* the mutable live value until those later items are ACKed too. */
|
|
171
|
+
private committedTerminalCommand?;
|
|
114
172
|
/** The open turn received an explicit Escape/Stop and must close cancelled. */
|
|
115
173
|
private turnInterrupted;
|
|
116
174
|
/** The Turn opened from a pre-transcript interaction. Its unique provisional
|
|
@@ -159,8 +217,17 @@ export declare class ClaudeLiveSession {
|
|
|
159
217
|
* append its request immediately before the failure hook lands; closing here
|
|
160
218
|
* would let the later interaction poll reopen an already-failed Turn. */
|
|
161
219
|
private turnFailurePending;
|
|
162
|
-
/**
|
|
163
|
-
|
|
220
|
+
/** Stop/StopFailure owns the hook cursor until its terminal status reaches a
|
|
221
|
+
* handled delivery outcome. This preserves replay on runner restart. */
|
|
222
|
+
private pendingTerminalHook?;
|
|
223
|
+
private terminalHookDelivery?;
|
|
224
|
+
private pendingTranscriptCompactions;
|
|
225
|
+
/** Claude Code native Task/Agent sidechains. Each child owns an independent
|
|
226
|
+
* cursor/retry lane so one unavailable delivery cannot block its siblings or
|
|
227
|
+
* the parent transcript. */
|
|
228
|
+
private readonly nativeSubagents;
|
|
229
|
+
private readonly nativeSubagentParentResponses;
|
|
230
|
+
private subagentStateParentPath?;
|
|
164
231
|
/** The agent's task list, keyed by task id in creation order (claude
|
|
165
232
|
* `TaskCreate`/`TaskUpdate`). Emitted whole as a snapshot on any change. */
|
|
166
233
|
private readonly todos;
|
|
@@ -168,6 +235,7 @@ export declare class ClaudeLiveSession {
|
|
|
168
235
|
* a turn's usage on close. undefined until the statusLine hook first fires. */
|
|
169
236
|
private latestStatus?;
|
|
170
237
|
private deltasOffset;
|
|
238
|
+
private readonly seenDeltaKeys;
|
|
171
239
|
/** Finalized MessageDisplay ids in completion order, FIFO-mapped onto the
|
|
172
240
|
* transcript's assistant-text records so a streamed message and its final
|
|
173
241
|
* item share an itemId (message_id is absent from the transcript). */
|
|
@@ -213,7 +281,8 @@ export declare class ClaudeLiveSession {
|
|
|
213
281
|
* first so no hook can still be opening an atomically claimed answer. This is
|
|
214
282
|
* synchronous because the runner process exits immediately afterwards. */
|
|
215
283
|
finalizeStop(): void;
|
|
216
|
-
/** One poll cycle (
|
|
284
|
+
/** One poll cycle (rotation/PreCompact prescan → transcript → hooks → live
|
|
285
|
+
* lanes → idle backstop). Exposed for tests to
|
|
217
286
|
* drive deterministically; the async {@link loop} just calls it on an interval. */
|
|
218
287
|
tick(): void;
|
|
219
288
|
/** Bind Claude's per-process session metadata after the terminal exists. */
|
|
@@ -224,15 +293,34 @@ export declare class ClaudeLiveSession {
|
|
|
224
293
|
private supervise;
|
|
225
294
|
private loop;
|
|
226
295
|
private sleepUntilWake;
|
|
296
|
+
/** Rotation is a control edge, not an ordinary hook-stream item. Scan past a
|
|
297
|
+
* held Stop/compaction cursor so an old retry can never strand the physical
|
|
298
|
+
* Claude process on a new Session while Rynx still targets the old one. */
|
|
299
|
+
private prescanSessionRotation;
|
|
300
|
+
/** Mint compaction tokens before transcript parsing without advancing the
|
|
301
|
+
* hook cursor. The ordinary hook pass later observes the same cursor-keyed
|
|
302
|
+
* edge and converges on the already-created token. */
|
|
303
|
+
private prescanPrecompactEdges;
|
|
227
304
|
private pollHooks;
|
|
305
|
+
private isSessionRotationHook;
|
|
306
|
+
private isForkHookRecord;
|
|
228
307
|
private persistHookCursor;
|
|
229
308
|
private handleHook;
|
|
309
|
+
private handleTodoHook;
|
|
310
|
+
private compactionGeneration;
|
|
311
|
+
private readCompactionState;
|
|
312
|
+
private notePrecompact;
|
|
313
|
+
private compactionPendingMatches;
|
|
314
|
+
private markCompactionPersisted;
|
|
315
|
+
private claimHookCompaction;
|
|
316
|
+
private handleCompactionCompletionHook;
|
|
230
317
|
/** Process a provider failure only after this tick has discovered every
|
|
231
318
|
* blocking request already appended by its hook subprocess. */
|
|
232
319
|
private flushTurnFailure;
|
|
233
320
|
/** Emit the Stop idle signal only after this tick has discovered native
|
|
234
321
|
* interactions. A pending question/permission is active execution, not idle. */
|
|
235
322
|
private flushStopSignal;
|
|
323
|
+
private finishTerminalHookDelivery;
|
|
236
324
|
/** SessionStart drives discovery (first) and rotation (a later one with a NEW
|
|
237
325
|
* session id + transcript). `/clear` → source="clear"; `/fork` → source="resume"
|
|
238
326
|
* into an unseen id with a forkedFrom marker. Any other new-transcript
|
|
@@ -242,7 +330,18 @@ export declare class ClaudeLiveSession {
|
|
|
242
330
|
* files — hooks/deltas/status — are shared by the same claude process across a
|
|
243
331
|
* rotation, so their cursors are NOT reset). */
|
|
244
332
|
private repointTranscript;
|
|
333
|
+
/** Revalidate the committed cursor every poll, not only after process restart.
|
|
334
|
+
* Claude may replace/truncate a transcript in place. Treat a stale
|
|
335
|
+
* nonzero fingerprint as a new file and skips to its current EOF while
|
|
336
|
+
* preserving the bounded seen set. A temporarily missing file keeps the
|
|
337
|
+
* cursor unchanged. */
|
|
338
|
+
private validateLiveTranscriptCursor;
|
|
245
339
|
private pollTranscript;
|
|
340
|
+
private rememberSourceId;
|
|
341
|
+
private restoreSubagentForwardStates;
|
|
342
|
+
private persistSubagentForwardStates;
|
|
343
|
+
private pollNativeSubagents;
|
|
344
|
+
private pollNativeSubagent;
|
|
246
345
|
/** Tail blocking hook requests after transcript records, so an interaction
|
|
247
346
|
* emitted in the same poll attaches to the user Turn that caused it. */
|
|
248
347
|
private pollInteractions;
|
|
@@ -276,15 +375,16 @@ export declare class ClaudeLiveSession {
|
|
|
276
375
|
private scheduleClaimedInteractionScrubs;
|
|
277
376
|
private settleCancelledInteraction;
|
|
278
377
|
private rememberSettled;
|
|
378
|
+
/** State change represented by this record once its durable terminal item is
|
|
379
|
+
* handled. Parsing can run through multiple records before their deliveries
|
|
380
|
+
* settle, so this checkpoint is intentionally separate from the live state. */
|
|
381
|
+
private terminalCommandCheckpointForRecord;
|
|
279
382
|
private handleRecord;
|
|
383
|
+
private emitUserMessageRecord;
|
|
384
|
+
private handleCompactSummary;
|
|
280
385
|
/** Fold a `TaskCreate` (new pending task) or `TaskUpdate` (status/subject/delete)
|
|
281
386
|
* record into the task list; on any change emit the whole list as a snapshot. */
|
|
282
387
|
private maybeUpdateTodos;
|
|
283
|
-
/** On a `Task` tool_result, replay the sub-agent's own transcript
|
|
284
|
-
* (`subagents/agent-<agentId>.jsonl`) as events tagged with the parent Task
|
|
285
|
-
* tool-use id, so the canonical layer nests them under that call. Fired once
|
|
286
|
-
* per sub-agent (at tool_result time the file is complete — no live race). */
|
|
287
|
-
private maybeForwardSubagent;
|
|
288
388
|
/** Tail streamed assistant-text chunks (MessageDisplay) into live `token`
|
|
289
389
|
* events. Guarded on an open turn — deltas belong to the turn the user record
|
|
290
390
|
* opened; the offset advances only once processed. */
|
|
@@ -305,10 +405,12 @@ export declare class ClaudeLiveSession {
|
|
|
305
405
|
* text so a late MessageDisplay final can be reconciled backward. */
|
|
306
406
|
private remapMessageItem;
|
|
307
407
|
private trackTool;
|
|
408
|
+
private rememberNativeSubagentParentResponse;
|
|
308
409
|
private ensureTurn;
|
|
309
|
-
/** Mirror a
|
|
310
|
-
*
|
|
311
|
-
*
|
|
410
|
+
/** Mirror a Claude `!cmd` as one logical mini-turn even when input and output
|
|
411
|
+
* are separate transcript records. The output repeats the remembered command
|
|
412
|
+
* in Rynx's combined terminal item shape; the transcript projection merges
|
|
413
|
+
* that completion into the preceding command row. */
|
|
312
414
|
private emitTerminalCommand;
|
|
313
415
|
/** The web Stop button interrupted this session (host sent Escape). claude
|
|
314
416
|
* records the interrupt in its own transcript but may not fire a Stop hook when
|