@rynx-ai/runtime 0.1.0 → 0.1.10-beta.2

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 (56) hide show
  1. package/dist/claude/executor.d.ts +3 -5
  2. package/dist/claude/executor.js +3 -5
  3. package/dist/claude/native-bridge.d.ts +74 -17
  4. package/dist/claude/native-bridge.js +225 -30
  5. package/dist/claude/native-hook-main.js +327 -38
  6. package/dist/claude/native-hooks.d.ts +3 -2
  7. package/dist/claude/native-hooks.js +15 -6
  8. package/dist/claude/native-integration.d.ts +123 -16
  9. package/dist/claude/native-integration.js +624 -81
  10. package/dist/claude/settings.d.ts +8 -0
  11. package/dist/claude/settings.js +50 -0
  12. package/dist/claude/transcript.d.ts +2 -2
  13. package/dist/claude/transcript.js +14 -3
  14. package/dist/codex/rollout-synth.d.ts +8 -3
  15. package/dist/codex/rollout-synth.js +65 -32
  16. package/dist/codex-app-server/client.d.ts +27 -40
  17. package/dist/codex-app-server/client.js +1134 -99
  18. package/dist/codex-app-server/forwarder.d.ts +36 -10
  19. package/dist/codex-app-server/forwarder.js +146 -28
  20. package/dist/codex-app-server/mapping.d.ts +1 -1
  21. package/dist/codex-app-server/mapping.js +64 -5
  22. package/dist/codex-app-server/protocol.d.ts +269 -4
  23. package/dist/codex-app-server/transport.d.ts +20 -5
  24. package/dist/codex-app-server/transport.js +93 -40
  25. package/dist/codex-app-server/ws-channel.d.ts +3 -3
  26. package/dist/codex-app-server/ws-channel.js +23 -7
  27. package/dist/codex-child-env.js +33 -0
  28. package/dist/codex-home.d.ts +16 -6
  29. package/dist/codex-home.js +46 -15
  30. package/dist/codex-session-store.d.ts +2 -1
  31. package/dist/host.d.ts +38 -38
  32. package/dist/host.js +626 -121
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.js +1 -1
  35. package/dist/input-resources.d.ts +13 -0
  36. package/dist/input-resources.js +67 -0
  37. package/dist/interactions.d.ts +61 -0
  38. package/dist/interactions.js +236 -0
  39. package/dist/models-catalog.d.ts +5 -13
  40. package/dist/models-catalog.js +60 -9
  41. package/dist/runner/child.d.ts +9 -1
  42. package/dist/runner/child.js +100 -19
  43. package/dist/runner/manager.d.ts +79 -11
  44. package/dist/runner/manager.js +423 -43
  45. package/dist/runner/protocol.d.ts +30 -11
  46. package/dist/runner-main.js +9 -6
  47. package/dist/runtime-status.js +1 -1
  48. package/dist/terminal/claude-tui.d.ts +8 -3
  49. package/dist/terminal/claude-tui.js +6 -2
  50. package/dist/terminal/codex-tui.d.ts +3 -3
  51. package/dist/terminal/codex-tui.js +1 -1
  52. package/dist/terminal/registry.d.ts +1 -1
  53. package/dist/terminal/registry.js +1 -1
  54. package/dist/terminal/tmux.d.ts +6 -6
  55. package/dist/terminal/tmux.js +10 -10
  56. package/package.json +8 -3
@@ -1,4 +1,5 @@
1
- import type { AgentEvent, TerminalCommandData, TodoItem } from "@rynx-ai/core";
1
+ import type { AgentEvent, SessionInteractionResolution, TerminalCommandData, TodoItem } from "@rynx-ai/core";
2
+ import type { ResolveInteractionResult, RuntimeInteractionEvent } from "../interactions.js";
2
3
  /** Mirror sink — the same shape as `CodexForwarderSink` so the host reuses one
3
4
  * per-turn normalizer wiring for both runtimes. */
4
5
  export interface ClaudeForwarderSink {
@@ -14,6 +15,9 @@ export interface ClaudeForwarderSink {
14
15
  onTodos(todos: TodoItem[]): void;
15
16
  /** One mapped event within the current turn. */
16
17
  onEvent(event: AgentEvent): void;
18
+ /** A provider-neutral question/permission requested or settled while this
19
+ * Turn remains active. */
20
+ onInteraction(event: RuntimeInteractionEvent): void;
17
21
  /** The current turn finished (a new user prompt, or the inactivity backstop).
18
22
  * `usage` carries the latest statusLine context/cost snapshot, when captured. */
19
23
  onTurnEnd(usage?: Record<string, unknown>): void;
@@ -51,6 +55,10 @@ export interface ClaudeLiveSessionOptions {
51
55
  * flush still join the turn (Stop and the flush race). */
52
56
  stopGraceMs?: number;
53
57
  now?: () => number;
58
+ /** Process-liveness probe for the blocking native hook (test override). */
59
+ isProcessAlive?: (pid: number) => boolean;
60
+ /** Native lease timestamp reader (test override). */
61
+ interactionLeaseUpdatedAt?: (interactionId: string) => number | undefined;
54
62
  }
55
63
  export declare class ClaudeLiveSession {
56
64
  private readonly bridgeDir;
@@ -59,9 +67,13 @@ export declare class ClaudeLiveSession {
59
67
  private readonly idleCloseMs;
60
68
  private readonly stopGraceMs;
61
69
  private readonly now;
70
+ private readonly isProcessAlive;
71
+ private readonly leaseUpdatedAt;
62
72
  private started;
63
73
  private stopped;
64
74
  private hooksOffset;
75
+ private interactionsOffset;
76
+ private interactionAcksOffset;
65
77
  private transcriptOffset;
66
78
  /** Secondary dedup: source ids (record uuids) already forwarded, so a re-read
67
79
  * (fingerprint reset / mid-poll death) doesn't re-emit. Persisted in the
@@ -76,11 +88,40 @@ export declare class ClaudeLiveSession {
76
88
  private readonly seenClaudeSessionIds;
77
89
  private turnOpen;
78
90
  private currentTurnId?;
91
+ /** The Turn opened from a pre-transcript interaction. Its unique provisional
92
+ * id keeps responses distinct until the real prompt uuid can be adopted. */
93
+ private syntheticTurn;
79
94
  private lastActivityAt;
80
95
  /** When the Stop hook fired (null = not yet) — drives the grace-period close. */
81
96
  private stopPendingAt;
82
97
  /** Open tool-call ids (start seen, end not yet) — suppresses the idle backstop. */
83
98
  private readonly openToolIds;
99
+ /** Transcript-backed acknowledgements for web→TUI input. A direct idle
100
+ * submit is recorded as `promptSource:"typed"`; a submit while Claude is busy
101
+ * is recorded immediately as `queue-operation:enqueue` and only later
102
+ * promoted to `promptSource:"queued"`. The promotion is intentionally not a
103
+ * second acknowledgement. */
104
+ private submissionSequence;
105
+ private readonly submissionObservations;
106
+ /** Enqueued type-ahead inputs awaiting their eventual role:user promotion.
107
+ * Claude has used both `promptSource:"queued"` and `"sdk"` for that later
108
+ * record, so content correlation is the stable discriminator. */
109
+ private readonly pendingQueuedSubmissions;
110
+ /** Blocking native questions/permissions. Their hook subprocess is still
111
+ * executing, so the Turn remains running and cannot be idle-closed. */
112
+ private readonly pendingInteractions;
113
+ private readonly settledInteractions;
114
+ /** Claimed response files may still contain the provider-bound, unredacted
115
+ * answer. Keep tracking them after canonical settlement until ACK/hook cleanup. */
116
+ private readonly claimedInteractions;
117
+ private readonly scheduledClaimScrubs;
118
+ /** Stop is flushed after interactions in the same poll, preventing a
119
+ * request+Stop race from briefly reporting this executing Turn as idle. */
120
+ private stopSignalPending;
121
+ /** StopFailure is likewise flushed after interactions. A blocking hook can
122
+ * append its request immediately before the failure hook lands; closing here
123
+ * would let the later interaction poll reopen an already-failed Turn. */
124
+ private turnFailurePending;
84
125
  /** Sub-agent (Task) ids already forwarded — a Task tool_result is processed once. */
85
126
  private readonly seenSubagents;
86
127
  /** The agent's task list, keyed by task id in creation order (claude
@@ -90,14 +131,19 @@ export declare class ClaudeLiveSession {
90
131
  * a turn's usage on close. undefined until the statusLine hook first fires. */
91
132
  private latestStatus?;
92
133
  private deltasOffset;
93
- /** MessageDisplay message ids in first-seen order, FIFO-mapped onto the
134
+ /** Finalized MessageDisplay ids in completion order, FIFO-mapped onto the
94
135
  * transcript's assistant-text records so a streamed message and its final
95
136
  * item share an itemId (message_id is absent from the transcript). */
96
137
  private readonly messageIdQueue;
97
- private readonly seenMessageIds;
138
+ private readonly finalizedMessageIds;
139
+ /** Transcript messages that arrived before their MessageDisplay stream. A
140
+ * matching late final consumes both sides so its id cannot shift the FIFO
141
+ * used by the next assistant message. */
142
+ private readonly unmatchedTranscriptMessages;
143
+ private readonly streamedMessageText;
98
144
  constructor(opts: ClaudeLiveSessionOptions);
99
145
  /**
100
- * Restore the durable forwarder cursor for `transcriptPath` (omnigent's
146
+ * Restore the durable forwarder cursor for `transcriptPath` (reference implementation's
101
147
  * `_validated_transcript_state`). A cursor for a DIFFERENT file is ignored (a new
102
148
  * session starts at 0). A matching cursor whose fingerprint still validates
103
149
  * resumes at its `byteOffset`; a MISMATCH (the file was truncated/replaced) skips
@@ -109,14 +155,35 @@ export declare class ClaudeLiveSession {
109
155
  private persistForwardState;
110
156
  /** True once SessionStart bound the transcript (or a resume path was given). */
111
157
  isReady(): boolean;
158
+ /** Drain records that predate a new injection, then return a monotonic
159
+ * checkpoint. This prevents a lagging, older identical prompt from
160
+ * acknowledging the new submit. */
161
+ beginSubmissionObservation(): number;
162
+ /** Whether Claude durably accepted `text` after `checkpoint`. Polling the
163
+ * transcript here makes submit confirmation independent of the background
164
+ * forwarder interval and covers both idle and type-ahead submissions. */
165
+ hasObservedSubmissionAfter(checkpoint: number, text: string): boolean;
166
+ private rememberSubmission;
167
+ private rememberQueuedSubmission;
168
+ private consumeQueuedPromotion;
112
169
  start(): void;
113
170
  stop(): void;
171
+ /** Phase two of runner shutdown. The caller must stop the Claude terminal
172
+ * first so no hook can still be opening an atomically claimed answer. This is
173
+ * synchronous because the runner process exits immediately afterwards. */
174
+ finalizeStop(): void;
114
175
  /** One poll cycle (hooks → transcript → idle backstop). Exposed for tests to
115
176
  * drive deterministically; the async {@link loop} just calls it on an interval. */
116
177
  tick(): void;
117
178
  private loop;
118
179
  private pollHooks;
119
180
  private handleHook;
181
+ /** Process a provider failure only after this tick has discovered every
182
+ * blocking request already appended by its hook subprocess. */
183
+ private flushTurnFailure;
184
+ /** Emit the Stop idle signal only after this tick has discovered native
185
+ * interactions. A pending question/permission is active execution, not idle. */
186
+ private flushStopSignal;
120
187
  /** SessionStart drives discovery (first) and rotation (a later one with a NEW
121
188
  * session id + transcript). `/clear` → source="clear"; `/fork` → source="resume"
122
189
  * into an unseen id with a forkedFrom marker. Any other new-transcript
@@ -127,6 +194,39 @@ export declare class ClaudeLiveSession {
127
194
  * rotation, so their cursors are NOT reset). */
128
195
  private repointTranscript;
129
196
  private pollTranscript;
197
+ /** Tail blocking hook requests after transcript records, so an interaction
198
+ * emitted in the same poll attaches to the user Turn that caused it. */
199
+ private pollInteractions;
200
+ private drainInteractionCommits;
201
+ /** A hook acknowledgement is the commit point: it is appended after the hook
202
+ * consumes the one-shot response and before it returns the native verdict. */
203
+ private pollInteractionAcks;
204
+ /** A claimed response is already consumed by the native hook. This fallback
205
+ * converges the interaction even if the hook exits before appending its
206
+ * redacted ACK. */
207
+ private pollInteractionClaims;
208
+ /** A request cannot remain actionable after its blocking hook exits. A short
209
+ * post-submit timeout also converges requests created by older hooks that did
210
+ * not publish a waiter pid, and scrubs any unacknowledged secret response. */
211
+ private pollAbandonedInteractions;
212
+ /** Deliver an answer to the blocked hook. The bridge file contains the real
213
+ * answer; only the canonical resolved event receives a secret-redacted copy. */
214
+ resolveInteraction(interactionId: string, resolution: SessionInteractionResolution): ResolveInteractionResult;
215
+ private cancelPendingInteractions;
216
+ /** Terminal cancellation may race the hook's atomic result claim. A claim
217
+ * always wins; otherwise the cancellation file becomes the hook's verdict. */
218
+ private cancelOrSettlePendingInteraction;
219
+ private settleClaimedInteraction;
220
+ /** A claim is the hook's private handoff file and can contain secret answers.
221
+ * ACK/finally normally removes it; process death or an expired lease is the
222
+ * crash fallback after the canonical interaction has already settled. */
223
+ private pollClaimedInteractionCleanup;
224
+ /** Once this forwarder stops it can no longer poll a later hook crash. Keep a
225
+ * short unref'd scrub deadline so unredacted claim material cannot persist for
226
+ * the lifetime of the daemon. */
227
+ private scheduleClaimedInteractionScrubs;
228
+ private settleCancelledInteraction;
229
+ private rememberSettled;
130
230
  private handleRecord;
131
231
  /** Fold a `TaskCreate` (new pending task) or `TaskUpdate` (status/subject/delete)
132
232
  * record into the task list; on any change emit the whole list as a snapshot. */
@@ -140,6 +240,7 @@ export declare class ClaudeLiveSession {
140
240
  * events. Guarded on an open turn — deltas belong to the turn the user record
141
241
  * opened; the offset advances only once processed. */
142
242
  private pollDeltas;
243
+ private finalizeMessageDisplay;
143
244
  /** Refresh the latest statusLine context/cost snapshot (a last-writer-wins file
144
245
  * the statusLine hook overwrites on every TUI render). */
145
246
  private pollStatus;
@@ -149,7 +250,8 @@ export declare class ClaudeLiveSession {
149
250
  private statusUsage;
150
251
  /** Remap an assistant-text `message_completed` onto the FIFO-matched
151
252
  * MessageDisplay message_id, so its streamed deltas and this final item share
152
- * an itemId. No queued delta (MessageDisplay didn't fire) → keep the transcript id. */
253
+ * an itemId. No queued delta yet → keep the transcript id and remember its
254
+ * text so a late MessageDisplay final can be reconciled backward. */
153
255
  private remapMessageItem;
154
256
  private trackTool;
155
257
  private ensureTurn;
@@ -172,6 +274,7 @@ export declare class ClaudeLiveSession {
172
274
  isTurnOpen(): boolean;
173
275
  private closeTurn;
174
276
  private closeTurnError;
277
+ private resetMessageCorrelation;
175
278
  private maybeIdleClose;
176
279
  }
177
280
  /** The tmux-pane operations claude-native injection needs (a subset of
@@ -182,16 +285,20 @@ export interface TerminalInjector {
182
285
  clearInputLine(): void;
183
286
  paste(text: string): void;
184
287
  sendEnter(): void;
185
- /** Interrupt the running turn (Escape) — the web Stop button (omnigent). */
288
+ /** Interrupt the running turn (Escape) — the web Stop button (reference implementation). */
186
289
  interrupt(): void;
187
290
  }
188
291
  export interface InjectViaTerminalOptions {
189
292
  promptGlyph?: string;
190
293
  promptTimeoutMs?: number;
191
- pasteCommitMs?: number;
192
294
  settleMs?: number;
193
- submitVerifyMs?: number;
194
- submitRetryMs?: number;
295
+ /** Transcript-backed proof that Claude accepted this exact input. When
296
+ * absent, injection keeps the legacy one-Enter best-effort contract. */
297
+ submissionObserved?: () => boolean;
298
+ /** Per-attempt wait for a transcript acknowledgement. */
299
+ submitConfirmMs?: number;
300
+ /** Total Enter attempts when transcript confirmation is available. */
301
+ maxSubmitAttempts?: number;
195
302
  pollMs?: number;
196
303
  now?: () => number;
197
304
  sleep?: (ms: number) => Promise<void>;
@@ -200,14 +307,14 @@ export interface InjectViaTerminalOptions {
200
307
  signal?: AbortSignal;
201
308
  }
202
309
  /**
203
- * Deliver `text` into a claude TUI pane, the omnigent recipe:
204
- * ready-gate (poll for `❯`) → clear leftover → bracketed paste (+ trailing
205
- * newline) wait for the draft to land → settle → submit Enter → verify the
206
- * draft left the box (re-send Enter while it hasn't).
310
+ * Deliver `text` into a claude TUI pane, the reference implementation recipe:
311
+ * ready-gate (poll for `❯`) → clear leftover → bracketed paste the draft
312
+ * → settle → submit Enter → confirm from Claude's transcript.
207
313
  *
208
- * THROWS if the prompt never appears within the ready-gate window (omnigent
314
+ * THROWS if the prompt never appears within the ready-gate window (reference implementation
209
315
  * `_wait_for_claude_prompt_ready` RAISE) — a not-ready pane is a hard error the
210
- * caller reports, NOT a signal to fall through to a second output path. Returns
211
- * `true` once the message is submitted (best-effort even if submit-verify times out).
316
+ * caller reports, NOT a signal to fall through to a second output path. With a
317
+ * transcript observer, Enter is retried only when Claude has durably recorded
318
+ * neither a direct user prompt nor a type-ahead enqueue.
212
319
  */
213
320
  export declare function injectViaTerminal(injector: TerminalInjector, text: string, opts?: InjectViaTerminalOptions): Promise<boolean>;