@rynx-ai/runtime 0.1.11-beta.4 → 0.1.11-beta.41
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/executor.d.ts +19 -5
- package/dist/claude/executor.js +56 -12
- package/dist/claude/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.d.ts +103 -1
- package/dist/claude/native-bridge.js +445 -30
- package/dist/claude/native-hook-main.js +81 -1
- package/dist/claude/native-hooks.js +7 -0
- package/dist/claude/native-integration.d.ts +178 -26
- package/dist/claude/native-integration.js +1528 -170
- package/dist/claude/session-status.d.ts +39 -0
- package/dist/claude/session-status.js +163 -0
- 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 +158 -47
- package/dist/codex-app-server/client.d.ts +10 -6
- package/dist/codex-app-server/client.js +67 -15
- package/dist/codex-app-server/forwarder.d.ts +92 -3
- package/dist/codex-app-server/forwarder.js +532 -57
- package/dist/codex-app-server/mapping.d.ts +3 -6
- package/dist/codex-app-server/mapping.js +206 -36
- package/dist/codex-app-server/mcp-startup.d.ts +13 -0
- package/dist/codex-app-server/mcp-startup.js +63 -0
- package/dist/codex-app-server/process-registry.d.ts +36 -0
- package/dist/codex-app-server/process-registry.js +320 -0
- package/dist/codex-app-server/protocol.d.ts +64 -7
- package/dist/codex-app-server/ws-channel.d.ts +7 -0
- package/dist/codex-app-server/ws-channel.js +104 -28
- package/dist/codex-home.d.ts +35 -3
- package/dist/codex-home.js +323 -18
- package/dist/codex-session-store.d.ts +23 -0
- package/dist/codex-session-store.js +21 -0
- package/dist/host.d.ts +103 -46
- package/dist/host.js +1988 -634
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/input-resources.d.ts +4 -0
- package/dist/input-resources.js +21 -5
- package/dist/models-catalog.d.ts +2 -1
- package/dist/models-catalog.js +94 -6
- package/dist/runner/child.d.ts +97 -28
- package/dist/runner/child.js +1486 -100
- package/dist/runner/manager.d.ts +110 -29
- package/dist/runner/manager.js +1481 -246
- package/dist/runner/protocol.d.ts +212 -24
- package/dist/runner/protocol.js +5 -0
- package/dist/runner/startup-policy.d.ts +7 -0
- package/dist/runner/startup-policy.js +10 -0
- package/dist/runner/transport.d.ts +18 -2
- package/dist/runner/transport.js +82 -3
- package/dist/runner-main.js +8 -3
- package/dist/terminal/claude-tui.d.ts +3 -1
- package/dist/terminal/claude-tui.js +3 -1
- package/dist/terminal/codex-tui.d.ts +4 -0
- package/dist/terminal/codex-tui.js +5 -0
- package/dist/terminal/control-parser.d.ts +39 -0
- package/dist/terminal/control-parser.js +172 -0
- package/dist/terminal/registry.d.ts +18 -15
- package/dist/terminal/registry.js +44 -23
- package/dist/terminal/spool.d.ts +47 -0
- package/dist/terminal/spool.js +231 -0
- package/dist/terminal/tmux.d.ts +126 -74
- package/dist/terminal/tmux.js +807 -211
- package/package.json +4 -4
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* interaction hooks rendezvous with the runner through the session bridge. */
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
5
|
import { realpathSync } from "node:fs";
|
|
6
|
-
import { claimInteractionResult, recordInteractionAck, recordHookEvent, recordInteractionRequest, removeClaimedInteractionResult, removeInteractionLease, touchInteractionLease, } from "./native-bridge.js";
|
|
6
|
+
import { claimInteractionResult, annotateClaudeResumeContext, recordInteractionAck, recordHookEvent, recordInteractionRequest, removeClaimedInteractionResult, removeInteractionLease, touchInteractionLease, } from "./native-bridge.js";
|
|
7
|
+
import { waitForTranscriptForkSignal, } from "./transcript.js";
|
|
7
8
|
import { boundInteractionRequest, redactInteractionResolution, } from "../interactions.js";
|
|
8
9
|
function argValue(argv, flag) {
|
|
9
10
|
const i = argv.indexOf(flag);
|
|
@@ -23,6 +24,21 @@ function asRecord(value) {
|
|
|
23
24
|
function asString(value) {
|
|
24
25
|
return typeof value === "string" && value ? value : undefined;
|
|
25
26
|
}
|
|
27
|
+
async function annotateClaudeForkSignal(bridgeDir, payload) {
|
|
28
|
+
annotateClaudeResumeContext(bridgeDir, payload);
|
|
29
|
+
if (payload.hook_event_name !== "SessionStart" ||
|
|
30
|
+
payload.source !== "resume" ||
|
|
31
|
+
payload.rynx_claude_session_was_seen === true)
|
|
32
|
+
return;
|
|
33
|
+
const transcriptPath = asString(payload.transcript_path);
|
|
34
|
+
const sessionId = asString(payload.session_id);
|
|
35
|
+
const sourceSessionId = asString(payload.rynx_previous_claude_session_id);
|
|
36
|
+
if (!transcriptPath || !sessionId || !sourceSessionId)
|
|
37
|
+
return;
|
|
38
|
+
const recordedAt = Date.now();
|
|
39
|
+
if (await waitForTranscriptForkSignal(transcriptPath, sessionId, sourceSessionId, recordedAt))
|
|
40
|
+
payload.rynx_fork_detected = true;
|
|
41
|
+
}
|
|
26
42
|
function interactionId(payload) {
|
|
27
43
|
// PermissionRequest does not carry a tool_use_id. A per-process nonce keeps
|
|
28
44
|
// repeated or concurrent identical prompts from collapsing into one bridge id.
|
|
@@ -176,6 +192,43 @@ function permissionRequest(id, payload, toolInput, suggestions) {
|
|
|
176
192
|
const toolName = asString(payload.tool_name) ?? "tool";
|
|
177
193
|
const command = asString(toolInput.command) ?? asString(toolInput.file_path);
|
|
178
194
|
const cwd = asString(payload.cwd);
|
|
195
|
+
if (toolName === "ExitPlanMode") {
|
|
196
|
+
const plan = asString(toolInput.plan) ?? "Plan details were not provided by Claude.";
|
|
197
|
+
return {
|
|
198
|
+
interactionId: id,
|
|
199
|
+
kind: "permission",
|
|
200
|
+
title: "Plan review",
|
|
201
|
+
fields: [{
|
|
202
|
+
id: "feedback",
|
|
203
|
+
type: "text",
|
|
204
|
+
label: "What should change about the plan?",
|
|
205
|
+
required: false,
|
|
206
|
+
multiline: true,
|
|
207
|
+
placeholder: "Revision feedback (optional)",
|
|
208
|
+
}],
|
|
209
|
+
actions: [
|
|
210
|
+
{
|
|
211
|
+
id: "allow_auto",
|
|
212
|
+
label: "Yes, and use auto mode",
|
|
213
|
+
style: "primary",
|
|
214
|
+
requiresAnswers: false,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "allow_manual",
|
|
218
|
+
label: "Yes, manually approve edits",
|
|
219
|
+
requiresAnswers: false,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "deny_feedback",
|
|
223
|
+
label: "Reject with feedback",
|
|
224
|
+
style: "danger",
|
|
225
|
+
requiresAnswers: false,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
context: { toolName, summary: plan },
|
|
229
|
+
createdAt: Date.now(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
179
232
|
return {
|
|
180
233
|
interactionId: id,
|
|
181
234
|
kind: "permission",
|
|
@@ -261,6 +314,32 @@ function nativeVerdict(hookKind, payload, result, suggestions) {
|
|
|
261
314
|
},
|
|
262
315
|
};
|
|
263
316
|
}
|
|
317
|
+
if (toolName === "ExitPlanMode") {
|
|
318
|
+
const feedback = answerText(resolution.answers?.feedback).trim();
|
|
319
|
+
const behavior = resolution.actionId === "allow_auto" ||
|
|
320
|
+
resolution.actionId === "allow_manual"
|
|
321
|
+
? "allow"
|
|
322
|
+
: "deny";
|
|
323
|
+
return {
|
|
324
|
+
hookSpecificOutput: {
|
|
325
|
+
hookEventName: "PermissionRequest",
|
|
326
|
+
decision: {
|
|
327
|
+
behavior,
|
|
328
|
+
...(behavior === "deny" && feedback ? { message: feedback } : {}),
|
|
329
|
+
...(behavior === "allow"
|
|
330
|
+
? {
|
|
331
|
+
updatedInput: toolInput,
|
|
332
|
+
updatedPermissions: [{
|
|
333
|
+
type: "setMode",
|
|
334
|
+
mode: resolution.actionId === "allow_auto" ? "auto" : "default",
|
|
335
|
+
destination: "session",
|
|
336
|
+
}],
|
|
337
|
+
}
|
|
338
|
+
: {}),
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
264
343
|
const suggestionMatch = /^allow_suggestion_(\d+)$/.exec(resolution.actionId);
|
|
265
344
|
const suggestionIndex = suggestionMatch ? Number(suggestionMatch[1]) : -1;
|
|
266
345
|
const selectedSuggestion = Number.isSafeInteger(suggestionIndex)
|
|
@@ -301,6 +380,7 @@ async function main() {
|
|
|
301
380
|
}
|
|
302
381
|
if (!hookKind) {
|
|
303
382
|
try {
|
|
383
|
+
await annotateClaudeForkSignal(bridgeDir, payload);
|
|
304
384
|
recordHookEvent(bridgeDir, payload);
|
|
305
385
|
}
|
|
306
386
|
catch {
|
|
@@ -48,6 +48,13 @@ export function buildClaudeHookSettings(options) {
|
|
|
48
48
|
SessionStart: [{ hooks: [observerHook] }],
|
|
49
49
|
Stop: [{ hooks: [observerHook] }],
|
|
50
50
|
StopFailure: [{ hooks: [observerHook] }],
|
|
51
|
+
TaskCreated: [{ hooks: [observerHook] }],
|
|
52
|
+
TaskCompleted: [{ hooks: [observerHook] }],
|
|
53
|
+
PostToolUse: [
|
|
54
|
+
{ matcher: "TodoWrite", hooks: [observerHook] },
|
|
55
|
+
{ matcher: "TaskUpdate", hooks: [observerHook] },
|
|
56
|
+
],
|
|
57
|
+
PreCompact: [{ hooks: [observerHook] }],
|
|
51
58
|
};
|
|
52
59
|
if (options.permissionMode === "bypassPermissions") {
|
|
53
60
|
const ask = shJoin([node, entry, "ask-user-question", "--bridge-dir", options.bridgeDir]);
|
|
@@ -1,18 +1,49 @@
|
|
|
1
1
|
import type { AgentEvent, SessionInteractionResolution, TerminalCommandData, TodoItem } from "@rynx-ai/core";
|
|
2
2
|
import type { ResolveInteractionResult, RuntimeInteractionEvent } from "../interactions.js";
|
|
3
|
+
import { type ClaudeRunnerStatus } from "./session-status.js";
|
|
3
4
|
/** Mirror sink — the same shape as `CodexForwarderSink` so the host reuses one
|
|
4
5
|
* per-turn normalizer wiring for both runtimes. */
|
|
5
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[]>;
|
|
6
20
|
/** A turn began; `turnId` (the user record uuid) derives a stable responseId. */
|
|
7
21
|
onTurnStart(turnId?: string): void;
|
|
8
22
|
/** The user's prompt text (a `role:user` conversation message — not identity). */
|
|
9
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;
|
|
10
36
|
/** A local shell command the user ran in the TUI (claude `!` bash mode), framed
|
|
11
37
|
* as its own mini-turn (an `onTurnStart`/`onTurnEnd` pair brackets this call). */
|
|
12
38
|
onTerminalCommand(cmd: TerminalCommandData): void;
|
|
13
39
|
/** The agent's task list changed (claude `TaskCreate`/`TaskUpdate`) — the WHOLE
|
|
14
40
|
* current list (a session-level snapshot, not tied to a turn). */
|
|
15
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>;
|
|
16
47
|
/** One mapped event within the current turn. */
|
|
17
48
|
onEvent(event: AgentEvent): void;
|
|
18
49
|
/** A provider-neutral question/permission requested or settled while this
|
|
@@ -20,15 +51,23 @@ export interface ClaudeForwarderSink {
|
|
|
20
51
|
onInteraction(event: RuntimeInteractionEvent): void;
|
|
21
52
|
/** The current turn finished (a new user prompt, or the inactivity backstop).
|
|
22
53
|
* `usage` carries the latest statusLine context/cost snapshot, when captured. */
|
|
23
|
-
onTurnEnd(usage?: Record<string, unknown
|
|
24
|
-
/** The
|
|
54
|
+
onTurnEnd(usage?: Record<string, unknown>, backgroundTaskCount?: number, reason?: "rotation"): void;
|
|
55
|
+
/** The current turn ended because the user explicitly interrupted it. */
|
|
56
|
+
onTurnInterrupted?(usage?: Record<string, unknown>): void;
|
|
57
|
+
/** Escape was sent for the open Turn. Publish cancelled UI state immediately;
|
|
58
|
+
* the final close remains delayed so a late transcript record can join it. */
|
|
59
|
+
onTurnInterruptRequested?(): void;
|
|
60
|
+
/** The runtime status changed according to Claude's session metadata. */
|
|
61
|
+
onStatus?(status: ClaudeRunnerStatus, blockedOn?: string): void;
|
|
62
|
+
/** The runtime went idle on the legacy hook fallback — surface idle WITHOUT finalizing
|
|
25
63
|
* the turn. claude fires Stop around the same time it flushes the final
|
|
26
64
|
* assistant record and the two orderings race; finalizing here would split a
|
|
27
65
|
* late assistant record into its own turn. The turn is finalized by the next
|
|
28
66
|
* user prompt or the inactivity backstop, so a late record still joins it. */
|
|
29
|
-
onIdle(): void
|
|
30
|
-
/**
|
|
31
|
-
|
|
67
|
+
onIdle(backgroundTaskCount?: number, sourceId?: string): void | Promise<void>;
|
|
68
|
+
/** Claude's current Turn or native runtime failed (StopFailure/pane exit).
|
|
69
|
+
* May fire between Turns so Session-level failure can retire stale liveness. */
|
|
70
|
+
onTurnError(error: Error, sourceId?: string): void | Promise<void>;
|
|
32
71
|
/** Fired once SessionStart reveals claude's session id + transcript path, so
|
|
33
72
|
* the host can persist the id and release its readiness gate. */
|
|
34
73
|
onSessionDiscovered?(claudeSessionId: string, transcriptPath: string): void;
|
|
@@ -36,10 +75,10 @@ export interface ClaudeForwarderSink {
|
|
|
36
75
|
* binding. The host keeps the original binding and fails readiness. */
|
|
37
76
|
onSessionResumeError?(error: Error): void;
|
|
38
77
|
/** The claude session rotated (`/clear` → fresh, `/fork` → derived): a new
|
|
39
|
-
* SessionStart reported a new session id + transcript. The
|
|
40
|
-
*
|
|
41
|
-
* host
|
|
42
|
-
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>;
|
|
43
82
|
}
|
|
44
83
|
export interface ClaudeLiveSessionOptions {
|
|
45
84
|
/** The session's bridge dir (`hooks.jsonl` lives here). */
|
|
@@ -55,6 +94,10 @@ export interface ClaudeLiveSessionOptions {
|
|
|
55
94
|
* matching forwarder cursor exists, follow only records appended after the
|
|
56
95
|
* SessionStart discovery instead of mirroring native history a second time. */
|
|
57
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;
|
|
58
101
|
/** Poll interval (ms). The files are append-only, so polling is simplest. */
|
|
59
102
|
pollMs?: number;
|
|
60
103
|
/** Inactivity (ms) FALLBACK close for a turn whose Stop hook never fired. */
|
|
@@ -79,10 +122,28 @@ export declare class ClaudeLiveSession {
|
|
|
79
122
|
private readonly leaseUpdatedAt;
|
|
80
123
|
private started;
|
|
81
124
|
private stopped;
|
|
125
|
+
private supervisorTask?;
|
|
126
|
+
private releaseSleep?;
|
|
82
127
|
private hooksOffset;
|
|
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?;
|
|
83
137
|
private interactionsOffset;
|
|
84
138
|
private interactionAcksOffset;
|
|
85
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;
|
|
86
147
|
/** Secondary dedup: source ids (record uuids) already forwarded, so a re-read
|
|
87
148
|
* (fingerprint reset / mid-poll death) doesn't re-emit. Persisted in the
|
|
88
149
|
* forwarder state; bounded there. */
|
|
@@ -94,17 +155,40 @@ export declare class ClaudeLiveSession {
|
|
|
94
155
|
/** Persisted native id requested through `claude --resume`. */
|
|
95
156
|
private readonly expectedClaudeSessionId?;
|
|
96
157
|
private readonly resumeAtEndOnDiscovery;
|
|
158
|
+
private readonly initialTranscriptOffsetOnDiscovery;
|
|
97
159
|
/** Every claude session uuid seen — a resume into an UNSEEN id + a forkedFrom
|
|
98
160
|
* marker signals a `/fork` (vs. resuming a known branch). */
|
|
99
161
|
private readonly seenClaudeSessionIds;
|
|
100
162
|
private turnOpen;
|
|
101
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?;
|
|
172
|
+
/** The open turn received an explicit Escape/Stop and must close cancelled. */
|
|
173
|
+
private turnInterrupted;
|
|
102
174
|
/** The Turn opened from a pre-transcript interaction. Its unique provisional
|
|
103
175
|
* id keeps responses distinct until the real prompt uuid can be adopted. */
|
|
104
176
|
private syntheticTurn;
|
|
105
177
|
private lastActivityAt;
|
|
106
178
|
/** When the Stop hook fired (null = not yet) — drives the grace-period close. */
|
|
107
179
|
private stopPendingAt;
|
|
180
|
+
/** Authoritative live background-shell count from the pending Stop hook. */
|
|
181
|
+
private stopBackgroundTaskCount;
|
|
182
|
+
/** Whether the pending Stop count already rode an immediate idle edge. */
|
|
183
|
+
private stopBackgroundTaskCountDelivered;
|
|
184
|
+
/** An idle status from Claude's session metadata. The short delay keeps a
|
|
185
|
+
* final transcript record in the response without letting stale tool state
|
|
186
|
+
* override the provider's terminal status. */
|
|
187
|
+
private providerIdleAt;
|
|
188
|
+
private statusPoller?;
|
|
189
|
+
/** A failed hook is authoritative until the next running edge. Claude writes
|
|
190
|
+
* idle after failures too, so that trailing file update must not erase it. */
|
|
191
|
+
private providerFailureSticky;
|
|
108
192
|
/** Open tool-call ids (start seen, end not yet) — suppresses the idle backstop. */
|
|
109
193
|
private readonly openToolIds;
|
|
110
194
|
/** Transcript-backed acknowledgements for web→TUI input. A direct idle
|
|
@@ -133,8 +217,17 @@ export declare class ClaudeLiveSession {
|
|
|
133
217
|
* append its request immediately before the failure hook lands; closing here
|
|
134
218
|
* would let the later interaction poll reopen an already-failed Turn. */
|
|
135
219
|
private turnFailurePending;
|
|
136
|
-
/**
|
|
137
|
-
|
|
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?;
|
|
138
231
|
/** The agent's task list, keyed by task id in creation order (claude
|
|
139
232
|
* `TaskCreate`/`TaskUpdate`). Emitted whole as a snapshot on any change. */
|
|
140
233
|
private readonly todos;
|
|
@@ -142,6 +235,7 @@ export declare class ClaudeLiveSession {
|
|
|
142
235
|
* a turn's usage on close. undefined until the statusLine hook first fires. */
|
|
143
236
|
private latestStatus?;
|
|
144
237
|
private deltasOffset;
|
|
238
|
+
private readonly seenDeltaKeys;
|
|
145
239
|
/** Finalized MessageDisplay ids in completion order, FIFO-mapped onto the
|
|
146
240
|
* transcript's assistant-text records so a streamed message and its final
|
|
147
241
|
* item share an itemId (message_id is absent from the transcript). */
|
|
@@ -179,22 +273,54 @@ export declare class ClaudeLiveSession {
|
|
|
179
273
|
private consumeQueuedPromotion;
|
|
180
274
|
start(): void;
|
|
181
275
|
stop(): void;
|
|
276
|
+
/** Wait for the supervised polling task to retire. Terminal teardown starts
|
|
277
|
+
* synchronously in {@link stop}; callers use this bounded join before their
|
|
278
|
+
* runner process exits. */
|
|
279
|
+
waitForStop(timeoutMs?: number): Promise<void>;
|
|
182
280
|
/** Phase two of runner shutdown. The caller must stop the Claude terminal
|
|
183
281
|
* first so no hook can still be opening an atomically claimed answer. This is
|
|
184
282
|
* synchronous because the runner process exits immediately afterwards. */
|
|
185
283
|
finalizeStop(): void;
|
|
186
|
-
/** One poll cycle (
|
|
284
|
+
/** One poll cycle (rotation/PreCompact prescan → transcript → hooks → live
|
|
285
|
+
* lanes → idle backstop). Exposed for tests to
|
|
187
286
|
* drive deterministically; the async {@link loop} just calls it on an interval. */
|
|
188
287
|
tick(): void;
|
|
288
|
+
/** Bind Claude's per-process session metadata after the terminal exists. */
|
|
289
|
+
attachStatusSource({ panePid, configDir, }: {
|
|
290
|
+
panePid: () => number | undefined;
|
|
291
|
+
configDir?: string;
|
|
292
|
+
}): void;
|
|
293
|
+
private supervise;
|
|
189
294
|
private loop;
|
|
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;
|
|
190
304
|
private pollHooks;
|
|
305
|
+
private isSessionRotationHook;
|
|
306
|
+
private isForkHookRecord;
|
|
307
|
+
private persistHookCursor;
|
|
191
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;
|
|
192
317
|
/** Process a provider failure only after this tick has discovered every
|
|
193
318
|
* blocking request already appended by its hook subprocess. */
|
|
194
319
|
private flushTurnFailure;
|
|
195
320
|
/** Emit the Stop idle signal only after this tick has discovered native
|
|
196
321
|
* interactions. A pending question/permission is active execution, not idle. */
|
|
197
322
|
private flushStopSignal;
|
|
323
|
+
private finishTerminalHookDelivery;
|
|
198
324
|
/** SessionStart drives discovery (first) and rotation (a later one with a NEW
|
|
199
325
|
* session id + transcript). `/clear` → source="clear"; `/fork` → source="resume"
|
|
200
326
|
* into an unseen id with a forkedFrom marker. Any other new-transcript
|
|
@@ -204,7 +330,18 @@ export declare class ClaudeLiveSession {
|
|
|
204
330
|
* files — hooks/deltas/status — are shared by the same claude process across a
|
|
205
331
|
* rotation, so their cursors are NOT reset). */
|
|
206
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;
|
|
207
339
|
private pollTranscript;
|
|
340
|
+
private rememberSourceId;
|
|
341
|
+
private restoreSubagentForwardStates;
|
|
342
|
+
private persistSubagentForwardStates;
|
|
343
|
+
private pollNativeSubagents;
|
|
344
|
+
private pollNativeSubagent;
|
|
208
345
|
/** Tail blocking hook requests after transcript records, so an interaction
|
|
209
346
|
* emitted in the same poll attaches to the user Turn that caused it. */
|
|
210
347
|
private pollInteractions;
|
|
@@ -238,15 +375,16 @@ export declare class ClaudeLiveSession {
|
|
|
238
375
|
private scheduleClaimedInteractionScrubs;
|
|
239
376
|
private settleCancelledInteraction;
|
|
240
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;
|
|
241
382
|
private handleRecord;
|
|
383
|
+
private emitUserMessageRecord;
|
|
384
|
+
private handleCompactSummary;
|
|
242
385
|
/** Fold a `TaskCreate` (new pending task) or `TaskUpdate` (status/subject/delete)
|
|
243
386
|
* record into the task list; on any change emit the whole list as a snapshot. */
|
|
244
387
|
private maybeUpdateTodos;
|
|
245
|
-
/** On a `Task` tool_result, replay the sub-agent's own transcript
|
|
246
|
-
* (`subagents/agent-<agentId>.jsonl`) as events tagged with the parent Task
|
|
247
|
-
* tool-use id, so the canonical layer nests them under that call. Fired once
|
|
248
|
-
* per sub-agent (at tool_result time the file is complete — no live race). */
|
|
249
|
-
private maybeForwardSubagent;
|
|
250
388
|
/** Tail streamed assistant-text chunks (MessageDisplay) into live `token`
|
|
251
389
|
* events. Guarded on an open turn — deltas belong to the turn the user record
|
|
252
390
|
* opened; the offset advances only once processed. */
|
|
@@ -255,6 +393,8 @@ export declare class ClaudeLiveSession {
|
|
|
255
393
|
/** Refresh the latest statusLine context/cost snapshot (a last-writer-wins file
|
|
256
394
|
* the statusLine hook overwrites on every TUI render). */
|
|
257
395
|
private pollStatus;
|
|
396
|
+
private pollSessionStatus;
|
|
397
|
+
private handleSessionStatus;
|
|
258
398
|
/** A usage record from the latest statusLine snapshot (snake_case, the keys the
|
|
259
399
|
* normalizer's `turn_completed` + the web `readUsageTokens` read), or undefined
|
|
260
400
|
* if the statusLine hook has not fired yet. */
|
|
@@ -265,10 +405,12 @@ export declare class ClaudeLiveSession {
|
|
|
265
405
|
* text so a late MessageDisplay final can be reconciled backward. */
|
|
266
406
|
private remapMessageItem;
|
|
267
407
|
private trackTool;
|
|
408
|
+
private rememberNativeSubagentParentResponse;
|
|
268
409
|
private ensureTurn;
|
|
269
|
-
/** Mirror a
|
|
270
|
-
*
|
|
271
|
-
*
|
|
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. */
|
|
272
414
|
private emitTerminalCommand;
|
|
273
415
|
/** The web Stop button interrupted this session (host sent Escape). claude
|
|
274
416
|
* records the interrupt in its own transcript but may not fire a Stop hook when
|
|
@@ -283,6 +425,10 @@ export declare class ClaudeLiveSession {
|
|
|
283
425
|
* sent an Escape — an Escape into idle claude submits an empty turn, which
|
|
284
426
|
* claude answers with a stray "No response requested." bubble. */
|
|
285
427
|
isTurnOpen(): boolean;
|
|
428
|
+
/** Fail an open turn exactly once when its native terminal/runner disappears. */
|
|
429
|
+
failOpenTurn(error: Error): boolean;
|
|
430
|
+
/** Retire process-scoped metadata before classifying a terminal exit. */
|
|
431
|
+
noteTerminalExit(error: Error): boolean;
|
|
286
432
|
private closeTurn;
|
|
287
433
|
private closeTurnError;
|
|
288
434
|
private resetMessageCorrelation;
|
|
@@ -292,6 +438,8 @@ export declare class ClaudeLiveSession {
|
|
|
292
438
|
* {@link import("../terminal/tmux.js").TmuxTerminal}). The runner-child hands
|
|
293
439
|
* this to the host after launching the pane, since the host doesn't own tmux. */
|
|
294
440
|
export interface TerminalInjector {
|
|
441
|
+
/** PID of the process owning the tmux pane, when available. */
|
|
442
|
+
panePid?(): number | undefined;
|
|
295
443
|
capturePane(): string;
|
|
296
444
|
clearInputLine(): void;
|
|
297
445
|
paste(text: string): void;
|
|
@@ -303,12 +451,16 @@ export interface InjectViaTerminalOptions {
|
|
|
303
451
|
promptGlyph?: string;
|
|
304
452
|
promptTimeoutMs?: number;
|
|
305
453
|
settleMs?: number;
|
|
454
|
+
/** Wait for the pasted draft to become visible before the first Enter. */
|
|
455
|
+
pasteCommitMs?: number;
|
|
306
456
|
/** Transcript-backed proof that Claude accepted this exact input. When
|
|
307
|
-
*
|
|
457
|
+
* present it takes precedence over pane heuristics. */
|
|
308
458
|
submissionObserved?: () => boolean;
|
|
309
|
-
/**
|
|
459
|
+
/** Total post-Enter verification budget. */
|
|
310
460
|
submitConfirmMs?: number;
|
|
311
|
-
/**
|
|
461
|
+
/** Minimum delay between Enter retries while the draft remains visible. */
|
|
462
|
+
submitRetryMs?: number;
|
|
463
|
+
/** Optional safety cap for tests/callers; the time budget remains authoritative. */
|
|
312
464
|
maxSubmitAttempts?: number;
|
|
313
465
|
pollMs?: number;
|
|
314
466
|
now?: () => number;
|
|
@@ -324,8 +476,8 @@ export interface InjectViaTerminalOptions {
|
|
|
324
476
|
*
|
|
325
477
|
* THROWS if the prompt never appears within the ready-gate window (reference implementation
|
|
326
478
|
* `_wait_for_claude_prompt_ready` RAISE) — a not-ready pane is a hard error the
|
|
327
|
-
* caller reports, NOT a signal to fall through to a second output path.
|
|
328
|
-
*
|
|
329
|
-
* neither a direct user prompt nor a type-ahead enqueue.
|
|
479
|
+
* caller reports, NOT a signal to fall through to a second output path. Enter
|
|
480
|
+
* is retried only while the exact draft remains visible and Claude has durably
|
|
481
|
+
* recorded neither a direct user prompt nor a type-ahead enqueue.
|
|
330
482
|
*/
|
|
331
483
|
export declare function injectViaTerminal(injector: TerminalInjector, text: string, opts?: InjectViaTerminalOptions): Promise<boolean>;
|