@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
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build the single explicit settings document for a rynx-managed Claude
3
+ * Session. Host/project settings still provide auth, env and permission policy,
4
+ * but their plugins and hooks are excluded: rynx must be the only owner of
5
+ * PermissionRequest and AskUserQuestion, and Agent Skills arrive only through
6
+ * the selected `--plugin-dir`.
7
+ */
8
+ export declare function buildManagedClaudeSettings(cwd: string, rynxSettings: object, configDir?: string): Record<string, unknown>;
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ function readSettings(path) {
8
+ try {
9
+ const value = JSON.parse(readFileSync(path, "utf8"));
10
+ return isRecord(value) ? value : {};
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ }
16
+ function mergeSettings(base, override) {
17
+ const merged = { ...base };
18
+ for (const [key, value] of Object.entries(override)) {
19
+ const current = merged[key];
20
+ merged[key] = isRecord(current) && isRecord(value)
21
+ ? mergeSettings(current, value)
22
+ : value;
23
+ }
24
+ return merged;
25
+ }
26
+ /**
27
+ * Build the single explicit settings document for a rynx-managed Claude
28
+ * Session. Host/project settings still provide auth, env and permission policy,
29
+ * but their plugins and hooks are excluded: rynx must be the only owner of
30
+ * PermissionRequest and AskUserQuestion, and Agent Skills arrive only through
31
+ * the selected `--plugin-dir`.
32
+ */
33
+ export function buildManagedClaudeSettings(cwd, rynxSettings, configDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude")) {
34
+ const sources = [
35
+ join(configDir, "settings.json"),
36
+ join(cwd, ".claude", "settings.json"),
37
+ join(cwd, ".claude", "settings.local.json"),
38
+ ];
39
+ let inherited = {};
40
+ for (const source of sources)
41
+ inherited = mergeSettings(inherited, readSettings(source));
42
+ // These sources can register competing interaction owners or host Skills.
43
+ // Rynx injects its own hooks/status line below and selected Skills separately.
44
+ delete inherited.hooks;
45
+ delete inherited.statusLine;
46
+ delete inherited.disableAllHooks;
47
+ delete inherited.enabledPlugins;
48
+ delete inherited.extraKnownMarketplaces;
49
+ return mergeSettings(inherited, rynxSettings);
50
+ }
@@ -45,10 +45,10 @@ export declare function parseTranscriptRecord(record: unknown, opts?: ParseTrans
45
45
  /**
46
46
  * Whether a transcript is a `/fork` (branch) of another session: claude stamps a
47
47
  * `forkedFrom: { sessionId }` marker in an early record pointing at the source
48
- * session (omnigent's `transcript_has_forked_from_marker`). Used to distinguish a
48
+ * session (reference implementation's `transcript_has_forked_from_marker`). Used to distinguish a
49
49
  * fork from an ordinary `resume` (both arrive as `SessionStart source="resume"`).
50
50
  * Scans only the head of the file (the marker lands up front). NOTE: unverified on
51
- * this host — no local transcript carries the marker — so it follows omnigent's shape.
51
+ * this host — no local transcript carries the marker — so it follows reference implementation's shape.
52
52
  */
53
53
  export declare function transcriptHasForkedFrom(path: string, currentSessionId?: string): boolean;
54
54
  /**
@@ -2,7 +2,7 @@
2
2
  * Claude Code transcript reader (Phase E, claude-native). Claude Code appends a
3
3
  * JSONL transcript per session at
4
4
  * `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`; the interactive TUI's
5
- * stdout is a full-screen UI, so — like omnigent's claude-native — we take the
5
+ * stdout is a full-screen UI, so — like reference implementation's claude-native — we take the
6
6
  * structured story from this file, not the PTY. Each `user`/`assistant` record
7
7
  * wraps an Anthropic message (`{ role, content: [blocks] }`), so we map its
8
8
  * blocks to the same typed {@link AgentEvent}s the headless executor emits.
@@ -121,6 +121,17 @@ export function parseTranscriptRecord(record, opts) {
121
121
  return [];
122
122
  const out = [];
123
123
  if (rec.type === "assistant" && message.role === "assistant") {
124
+ // Claude writes this zero-token synthetic filler after an accidental empty
125
+ // submit. It is transcript bookkeeping, not an assistant response. Filter
126
+ // by provider provenance and exact shape here, before normalization loses
127
+ // `model: "<synthetic>"`; a UI string filter could hide legitimate output.
128
+ if (!rec.isApiErrorMessage &&
129
+ message.model === "<synthetic>" &&
130
+ message.content.length === 1 &&
131
+ message.content[0]?.type === "text" &&
132
+ message.content[0].text === "No response requested.") {
133
+ return [];
134
+ }
124
135
  const itemId = message.id;
125
136
  const texts = [];
126
137
  for (const block of message.content) {
@@ -173,10 +184,10 @@ function isObject(value) {
173
184
  /**
174
185
  * Whether a transcript is a `/fork` (branch) of another session: claude stamps a
175
186
  * `forkedFrom: { sessionId }` marker in an early record pointing at the source
176
- * session (omnigent's `transcript_has_forked_from_marker`). Used to distinguish a
187
+ * session (reference implementation's `transcript_has_forked_from_marker`). Used to distinguish a
177
188
  * fork from an ordinary `resume` (both arrive as `SessionStart source="resume"`).
178
189
  * Scans only the head of the file (the marker lands up front). NOTE: unverified on
179
- * this host — no local transcript carries the marker — so it follows omnigent's shape.
190
+ * this host — no local transcript carries the marker — so it follows reference implementation's shape.
180
191
  */
181
192
  export function transcriptHasForkedFrom(path, currentSessionId) {
182
193
  let text;
@@ -1,4 +1,5 @@
1
- import type { SessionItem } from "@rynx-ai/core";
1
+ import { type SessionItem } from "@rynx-ai/core";
2
+ import { type CodexLineageRuntime } from "../codex-home.js";
2
3
  export interface SynthesizeRolloutOptions {
3
4
  threadId: string;
4
5
  cwd: string;
@@ -8,6 +9,10 @@ export interface SynthesizeRolloutOptions {
8
9
  sessionId?: string;
9
10
  /** Override the private CODEX_HOME (tests, or a pre-resolved home). */
10
11
  codexHome?: string;
12
+ /** Codex-lineage runtime whose private home/session layout is being written. */
13
+ runtime?: CodexLineageRuntime;
14
+ /** Runtime-neutral alias for `codexHome`; preferred for Traex callers. */
15
+ runtimeHome?: string;
11
16
  /** codex CLI version for `session_meta` (informational for ≥0.133; presence
12
17
  * matters). */
13
18
  cliVersion?: string;
@@ -22,8 +27,8 @@ interface RolloutRecord {
22
27
  type: "session_meta" | "turn_context" | "response_item" | "event_msg";
23
28
  payload: Record<string, unknown>;
24
29
  }
25
- /** Locate an existing rollout for `threadId` under `<codexHome>/sessions/**`. */
26
- export declare function findCodexRollout(codexHome: string, threadId: string): string | null;
30
+ /** Locate an existing rollout for `threadId` under the runtime's sessions root. */
31
+ export declare function findCodexRollout(runtimeHome: string, threadId: string, runtime?: CodexLineageRuntime): string | null;
27
32
  /** Build the ordered rollout records (session_meta first, then per-turn
28
33
  * turn_context + per-item response_item + per-message event_msg). */
29
34
  export declare function buildRolloutRecords(opts: SynthesizeRolloutOptions): RolloutRecord[];
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Synthesize a codex rollout file from rynx's canonical session log, so
3
3
  * `codex --remote resume <threadId>` works when the local rollout is missing
4
- * (fork / worktree / cross-machine). Ports omnigent's
4
+ * (fork / worktree / cross-machine). Ports reference implementation's
5
5
  * `_ensure_local_codex_resume_rollout`, adapted to rynx: the daemon (control-api)
6
6
  * has BOTH the session items (`SessionLogStore`) and — since the private CODEX_HOME
7
7
  * is a deterministic uid-scoped path — the app-server's rollout dir, so this runs
@@ -13,14 +13,19 @@
13
13
  * (codex ≥0.136 renders an empty thread without them). `turn_context` groups items
14
14
  * into turns.
15
15
  */
16
+ import { createHash } from "node:crypto";
16
17
  import { copyFileSync, mkdirSync, readdirSync, renameSync, writeFileSync } from "node:fs";
17
18
  import { join, relative } from "node:path";
18
- import { codexHomePath, legacyCodexHomePath } from "../codex-home.js";
19
+ import { getRuntimeProfile, resolveRuntimeHome, rynxHome, } from "@rynx-ai/core";
20
+ import { legacyCodexHomePath, runtimeHomePath, } from "../codex-home.js";
19
21
  /** codex validates the thread id straight into a filename + resume arg. */
20
22
  const THREAD_ID_RE = /^[0-9a-fA-F-]+$/;
21
- /** Locate an existing rollout for `threadId` under `<codexHome>/sessions/**`. */
22
- export function findCodexRollout(codexHome, threadId) {
23
- const root = join(codexHome, "sessions");
23
+ function sessionsRoot(runtimeHome, runtime) {
24
+ return join(runtimeHome, ...getRuntimeProfile(runtime).sessionsSubpath);
25
+ }
26
+ /** Locate an existing rollout for `threadId` under the runtime's sessions root. */
27
+ export function findCodexRollout(runtimeHome, threadId, runtime = "codex") {
28
+ const root = sessionsRoot(runtimeHome, runtime);
24
29
  const suffix = `-${threadId}.jsonl`;
25
30
  const stack = [root];
26
31
  while (stack.length) {
@@ -49,18 +54,21 @@ export function findCodexRollout(codexHome, threadId) {
49
54
  * expects) so the per-session app-server can resume it. Best-effort; gated by
50
55
  * `RYNX_CODEX_HOME_LEGACY_FALLBACK` (default on; set `0`/`false` to disable).
51
56
  */
52
- function adoptLegacyRollout(codexHome, threadId) {
53
- const flag = process.env.RYNX_CODEX_HOME_LEGACY_FALLBACK?.trim().toLowerCase();
57
+ function adoptLegacyRollout(runtimeHome, threadId, runtime) {
58
+ const flag = (process.env.RYNX_RUNTIME_HOME_LEGACY_FALLBACK
59
+ ?? process.env.RYNX_CODEX_HOME_LEGACY_FALLBACK)?.trim().toLowerCase();
54
60
  if (flag === "0" || flag === "false")
55
61
  return false;
56
- const legacy = legacyCodexHomePath();
57
- if (legacy === codexHome)
62
+ const legacy = runtime === "codex"
63
+ ? legacyCodexHomePath()
64
+ : resolveRuntimeHome(getRuntimeProfile(runtime));
65
+ if (legacy === runtimeHome)
58
66
  return false;
59
- const src = findCodexRollout(legacy, threadId);
67
+ const src = findCodexRollout(legacy, threadId, runtime);
60
68
  if (!src)
61
69
  return false;
62
- const rel = relative(join(legacy, "sessions"), src);
63
- const dst = join(codexHome, "sessions", rel);
70
+ const rel = relative(sessionsRoot(legacy, runtime), src);
71
+ const dst = join(sessionsRoot(runtimeHome, runtime), rel);
64
72
  try {
65
73
  mkdirSync(join(dst, ".."), { recursive: true, mode: 0o700 });
66
74
  copyFileSync(src, dst);
@@ -79,7 +87,10 @@ function turnIdOf(responseId) {
79
87
  return responseId.startsWith("resp_codex_") ? responseId.slice("resp_codex_".length) : responseId;
80
88
  }
81
89
  function textOf(item) {
82
- return item.data.content.map((p) => p.text).join("");
90
+ return item.data.content
91
+ .filter((part) => "text" in part)
92
+ .map((part) => part.text)
93
+ .join("");
83
94
  }
84
95
  /** Convert one canonical {@link SessionItem} to its codex `response_item` payload,
85
96
  * or null for types codex doesn't carry (reasoning/terminal_command/error). */
@@ -87,9 +98,9 @@ function responseItemPayload(item) {
87
98
  switch (item.type) {
88
99
  case "message": {
89
100
  const apiType = item.data.role === "assistant" ? "output_text" : "input_text";
90
- const content = item.data.content
91
- .filter((p) => p.text)
92
- .map((p) => ({ type: apiType, text: p.text }));
101
+ const content = item.data.content.flatMap((part) => "text" in part && part.text
102
+ ? [{ type: apiType, text: part.text }]
103
+ : []);
93
104
  if (content.length === 0)
94
105
  return null;
95
106
  return { type: "message", role: item.data.role, content };
@@ -108,23 +119,41 @@ function responseItemPayload(item) {
108
119
  }
109
120
  }
110
121
  /** The `event_msg` mirror for a message (required for a VISIBLE turn on codex ≥0.136). */
111
- function eventMsgPayload(item) {
122
+ function eventMsgPayload(item, sessionId) {
112
123
  const message = textOf(item).trim();
113
- if (!message)
124
+ const localImages = item.data.role === "user" && sessionId
125
+ ? item.data.content.flatMap((part) => part.type === "input_image" ? [resourcePath(sessionId, part)] : [])
126
+ : [];
127
+ if (!message && localImages.length === 0)
114
128
  return null;
115
129
  if (item.data.role === "user") {
116
- return { type: "user_message", message, images: [], local_images: [], text_elements: [] };
130
+ return {
131
+ type: "user_message",
132
+ message,
133
+ images: [],
134
+ local_images: localImages,
135
+ text_elements: [],
136
+ };
117
137
  }
118
138
  if (item.data.role === "assistant") {
119
139
  return { type: "agent_message", message, phase: "final_answer", memory_citation: null };
120
140
  }
121
141
  return null;
122
142
  }
143
+ function resourcePath(sessionId, part) {
144
+ const sessionKey = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
145
+ const extension = part.mediaType === "image/png"
146
+ ? "png"
147
+ : part.mediaType === "image/jpeg"
148
+ ? "jpg"
149
+ : "webp";
150
+ return join(rynxHome(), "resources", sessionKey, `${part.resourceId}.${extension}`);
151
+ }
123
152
  /** Build the ordered rollout records (session_meta first, then per-turn
124
153
  * turn_context + per-item response_item + per-message event_msg). */
125
154
  export function buildRolloutRecords(opts) {
126
155
  const now = opts.now ?? (() => Date.now());
127
- const modelProvider = opts.modelProvider ?? "openai";
156
+ const modelProvider = opts.modelProvider ?? (opts.runtime === "traex" ? "trae" : "openai");
128
157
  const cliVersion = opts.cliVersion ?? "0.0.0";
129
158
  const metaTs = iso(opts.items[0]?.createdAt ?? now());
130
159
  const records = [
@@ -147,7 +176,10 @@ export function buildRolloutRecords(opts) {
147
176
  const seenTurns = new Set();
148
177
  for (const item of opts.items) {
149
178
  const payload = responseItemPayload(item);
150
- if (!payload)
179
+ const eventPayload = item.type === "message"
180
+ ? eventMsgPayload(item, opts.sessionId)
181
+ : null;
182
+ if (!payload && !eventPayload)
151
183
  continue;
152
184
  const ts = iso(item.createdAt);
153
185
  const turnId = turnIdOf(item.responseId);
@@ -159,12 +191,10 @@ export function buildRolloutRecords(opts) {
159
191
  payload: { turn_id: turnId, cwd: opts.cwd, approval_policy: "on-request" },
160
192
  });
161
193
  }
162
- records.push({ timestamp: ts, type: "response_item", payload });
163
- if (item.type === "message") {
164
- const evt = eventMsgPayload(item);
165
- if (evt)
166
- records.push({ timestamp: ts, type: "event_msg", payload: evt });
167
- }
194
+ if (payload)
195
+ records.push({ timestamp: ts, type: "response_item", payload });
196
+ if (eventPayload)
197
+ records.push({ timestamp: ts, type: "event_msg", payload: eventPayload });
168
198
  }
169
199
  return records;
170
200
  }
@@ -177,14 +207,17 @@ export function buildRolloutRecords(opts) {
177
207
  export function ensureCodexResumeRollout(opts) {
178
208
  if (!THREAD_ID_RE.test(opts.threadId))
179
209
  return "skipped";
180
- const codexHome = opts.codexHome ?? (opts.sessionId ? codexHomePath(opts.sessionId) : undefined);
181
- if (!codexHome)
210
+ const runtime = opts.runtime ?? "codex";
211
+ const runtimeHome = opts.runtimeHome
212
+ ?? opts.codexHome
213
+ ?? (opts.sessionId ? runtimeHomePath(opts.sessionId, runtime) : undefined);
214
+ if (!runtimeHome)
182
215
  return "skipped"; // no session context → can't locate the home
183
- if (findCodexRollout(codexHome, opts.threadId))
216
+ if (findCodexRollout(runtimeHome, opts.threadId, runtime))
184
217
  return "exists";
185
218
  // Back-compat: a pre-per-session rollout may live under the OLD shared home. Copy
186
219
  // it forward into this session's home so the per-session app-server can resume it.
187
- if (adoptLegacyRollout(codexHome, opts.threadId))
220
+ if (adoptLegacyRollout(runtimeHome, opts.threadId, runtime))
188
221
  return "exists";
189
222
  const records = buildRolloutRecords(opts);
190
223
  // Nothing but the session_meta header → no history to carry; skip (a fresh
@@ -197,7 +230,7 @@ export function ensureCodexResumeRollout(opts) {
197
230
  const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
198
231
  const dd = String(d.getUTCDate()).padStart(2, "0");
199
232
  const stamp = iso(d.getTime()).slice(0, 19).replace(/:/g, "-"); // YYYY-MM-DDTHH-MM-SS
200
- const dir = join(codexHome, "sessions", yyyy, mm, dd);
233
+ const dir = join(sessionsRoot(runtimeHome, runtime), yyyy, mm, dd);
201
234
  const target = join(dir, `rollout-${stamp}-${opts.threadId}.jsonl`);
202
235
  const tmp = `${target}.tmp`;
203
236
  try {
@@ -1,5 +1,7 @@
1
+ import type { RuntimeUserInput, SessionInteractionResolution } from "@rynx-ai/core";
1
2
  import type { AskForApproval, ClientInfo, CollaborationModeListResponse, GetAuthStatusParams, GetAuthStatusResponse, InitializeResponse, ModelListParams, ModelListResponse, ReviewStartParams, ReviewStartResponse, SandboxMode, ThreadForkParams, ThreadGoalClearParams, ThreadGoalGetParams, ThreadGoalGetResponse, ThreadGoalSetParams, ThreadListParams, ThreadListResponse, ResumedThread, ThreadResumeParams, ThreadSettingsUpdateParams, ThreadStartParams, TurnInterruptParams, TurnStartParams, TurnSteerParams, UserInput } from "./protocol.js";
2
3
  import { CodexAppServerTransport, type CodexAppServerProcessSpawner, type RpcChannel, type TransportLogger } from "./transport.js";
4
+ import type { ResolveInteractionResult, RuntimeInteractionListener } from "../interactions.js";
3
5
  export type ApprovalDecisionPolicy = "auto-approve-session" | "auto-decline" | "auto-cancel";
4
6
  export type CodexNotificationListener = (method: string, params: unknown) => void;
5
7
  export interface CodexAppServerClientOptions {
@@ -16,36 +18,21 @@ export interface CodexAppServerClientOptions {
16
18
  * prompting a human.
17
19
  */
18
20
  approvalDecisionPolicy?: ApprovalDecisionPolicy;
19
- /**
20
- * Surface approvals to a user instead of auto-deciding. When true and an
21
- * approval listener is set, each request blocks until {@link
22
- * CodexAppServerClient.resolveApproval} is called (or a timeout falls back to
23
- * {@link approvalDecisionPolicy}).
24
- */
25
- interactiveApprovals?: boolean;
26
- }
27
- /** A codex approval decision (superset of exec + patch decision enums). */
28
- export type ApprovalDecision = "acceptForSession" | "accept" | "decline" | "cancel";
29
- /** A pending approval surfaced to the user, awaiting {@link CodexAppServerClient.resolveApproval}. */
30
- export interface ApprovalRequest {
31
- approvalId: string;
32
- kind: "exec" | "patch";
33
- command?: string;
34
- cwd?: string;
35
- diff?: string;
36
21
  }
37
22
  export declare class CodexAppServerClient {
38
23
  readonly transport: CodexAppServerTransport;
39
24
  private readonly logger;
40
25
  private readonly clientInfo;
41
26
  private readonly approvalDecisionPolicy;
42
- private readonly interactiveApprovals;
43
27
  private readonly channel;
44
28
  private readonly notificationSubscribers;
45
- private approvalListener;
46
- private readonly pendingApprovals;
29
+ private interactionListener;
30
+ private connectionListener;
31
+ private connectionState;
32
+ private readonly pendingInteractions;
33
+ private readonly settledInteractions;
47
34
  private initializeResponse;
48
- constructor({ spawner, channel, logger, clientInfo, approvalDecisionPolicy, interactiveApprovals, }: CodexAppServerClientOptions);
35
+ constructor({ spawner, channel, logger, clientInfo, approvalDecisionPolicy, }: CodexAppServerClientOptions);
49
36
  /**
50
37
  * The multi-client endpoint a `codex --remote` TUI can attach to, when this
51
38
  * client runs over a {@link WsRpcChannel}. `undefined` for the default stdio
@@ -101,27 +88,26 @@ export declare class CodexAppServerClient {
101
88
  private dispatchNotification;
102
89
  private handleServerRequest;
103
90
  /**
104
- * Register the listener that surfaces interactive approval requests. Set by
105
- * the executor for the duration of a run so requests reach its event stream
106
- * (and thus the web / Lark approval card).
107
- */
108
- setApprovalRequestListener(listener: ((request: ApprovalRequest) => void) | null): void;
109
- /**
110
- * Deliver a user's decision for a pending interactive approval. Returns false
111
- * if the approval id is unknown (already resolved, timed out, or auto-decided).
112
- */
113
- resolveApproval(approvalId: string, decision: ApprovalDecision): boolean;
114
- /**
115
- * Interactive approval path: surface the request and block the codex
116
- * server-request until the user decides (or the timeout falls back to the
117
- * auto policy). When interactive approvals are off, decide immediately.
91
+ * Register the provider-neutral interaction listener. A request is inserted
92
+ * into the pending map before the listener is invoked, so a synchronous
93
+ * resolver still wins correctly.
118
94
  */
119
- private handleApproval;
95
+ setInteractionListener(listener: RuntimeInteractionListener | null): void;
96
+ /** Observe the underlying app-server connection independently from any one
97
+ * interaction. A disconnected client that never received the duplicate
98
+ * native request still has to count as unavailable during host failover. */
99
+ setConnectionListener(listener: ((state: "connected" | "disconnected") => void) | null): void;
100
+ private setConnectionState;
101
+ resolveInteraction(interactionId: string, resolution: SessionInteractionResolution): ResolveInteractionResult;
102
+ /** Cancel every pending request owned by this connection without replying. */
103
+ cancelInteractions(reason?: string): void;
104
+ private handleInteraction;
120
105
  private autoApprovalDecision;
121
- private respondCommandApproval;
122
- private respondFileChangeApproval;
123
- private declineDecisionForApprovals;
124
- private respondNotSupported;
106
+ private resolveByProvider;
107
+ private handleServerRequestResponseDelivery;
108
+ private cancelPendingInteractions;
109
+ private emitInteraction;
110
+ private rememberSettled;
125
111
  }
126
112
  export interface BuildSandboxPolicyOptions {
127
113
  mode: SandboxMode;
@@ -135,4 +121,5 @@ export interface BuildSandboxPolicyOptions {
135
121
  */
136
122
  export declare function buildSandboxPolicy(options: BuildSandboxPolicyOptions): Record<string, unknown>;
137
123
  export declare function buildTextUserInput(message: string): UserInput[];
124
+ export declare function buildRuntimeUserInput(input: RuntimeUserInput): UserInput[];
138
125
  export type { AskForApproval };