@rynx-ai/runtime 0.1.0

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 (69) hide show
  1. package/dist/claude/executor.d.ts +17 -0
  2. package/dist/claude/executor.js +28 -0
  3. package/dist/claude/models.d.ts +10 -0
  4. package/dist/claude/models.js +33 -0
  5. package/dist/claude/native-bridge.d.ts +133 -0
  6. package/dist/claude/native-bridge.js +299 -0
  7. package/dist/claude/native-hook-main.d.ts +2 -0
  8. package/dist/claude/native-hook-main.js +74 -0
  9. package/dist/claude/native-hooks.d.ts +41 -0
  10. package/dist/claude/native-hooks.js +73 -0
  11. package/dist/claude/native-integration.d.ts +213 -0
  12. package/dist/claude/native-integration.js +665 -0
  13. package/dist/claude/native-message-display-main.d.ts +2 -0
  14. package/dist/claude/native-message-display-main.js +51 -0
  15. package/dist/claude/native-status-main.d.ts +2 -0
  16. package/dist/claude/native-status-main.js +105 -0
  17. package/dist/claude/status.d.ts +23 -0
  18. package/dist/claude/status.js +118 -0
  19. package/dist/claude/transcript.d.ts +79 -0
  20. package/dist/claude/transcript.js +272 -0
  21. package/dist/claude/trust.d.ts +6 -0
  22. package/dist/claude/trust.js +85 -0
  23. package/dist/codex/rollout-synth.d.ts +37 -0
  24. package/dist/codex/rollout-synth.js +212 -0
  25. package/dist/codex-app-server/client.d.ts +138 -0
  26. package/dist/codex-app-server/client.js +341 -0
  27. package/dist/codex-app-server/forwarder.d.ts +92 -0
  28. package/dist/codex-app-server/forwarder.js +188 -0
  29. package/dist/codex-app-server/mapping.d.ts +19 -0
  30. package/dist/codex-app-server/mapping.js +189 -0
  31. package/dist/codex-app-server/protocol.d.ts +472 -0
  32. package/dist/codex-app-server/protocol.js +12 -0
  33. package/dist/codex-app-server/transport.d.ts +139 -0
  34. package/dist/codex-app-server/transport.js +422 -0
  35. package/dist/codex-app-server/ws-channel.d.ts +72 -0
  36. package/dist/codex-app-server/ws-channel.js +233 -0
  37. package/dist/codex-child-env.d.ts +1 -0
  38. package/dist/codex-child-env.js +27 -0
  39. package/dist/codex-home.d.ts +47 -0
  40. package/dist/codex-home.js +135 -0
  41. package/dist/codex-session-store.d.ts +42 -0
  42. package/dist/codex-session-store.js +126 -0
  43. package/dist/host.d.ts +324 -0
  44. package/dist/host.js +1323 -0
  45. package/dist/index.d.ts +18 -0
  46. package/dist/index.js +17 -0
  47. package/dist/models-catalog.d.ts +18 -0
  48. package/dist/models-catalog.js +27 -0
  49. package/dist/runner/child.d.ts +58 -0
  50. package/dist/runner/child.js +268 -0
  51. package/dist/runner/manager.d.ts +175 -0
  52. package/dist/runner/manager.js +458 -0
  53. package/dist/runner/protocol.d.ts +195 -0
  54. package/dist/runner/protocol.js +41 -0
  55. package/dist/runner/transport.d.ts +36 -0
  56. package/dist/runner/transport.js +72 -0
  57. package/dist/runner-main.d.ts +2 -0
  58. package/dist/runner-main.js +61 -0
  59. package/dist/runtime-status.d.ts +16 -0
  60. package/dist/runtime-status.js +80 -0
  61. package/dist/terminal/claude-tui.d.ts +27 -0
  62. package/dist/terminal/claude-tui.js +13 -0
  63. package/dist/terminal/codex-tui.d.ts +54 -0
  64. package/dist/terminal/codex-tui.js +26 -0
  65. package/dist/terminal/registry.d.ts +42 -0
  66. package/dist/terminal/registry.js +70 -0
  67. package/dist/terminal/tmux.d.ts +150 -0
  68. package/dist/terminal/tmux.js +364 -0
  69. package/package.json +32 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pre-accept Claude Code's first-run TUI gates so a daemon-spawned `claude`
3
+ * never blocks on a prompt nobody is at the terminal to answer.
4
+ *
5
+ * On a fresh context Claude blocks on prompts that do NOT fire any hook (so we
6
+ * can't auto-answer them in-band):
7
+ * - global onboarding (theme/login) → top-level `hasCompletedOnboarding`
8
+ * - per-dir "trust this folder?" → `projects[<abs cwd>].hasTrustDialogAccepted`
9
+ * - "Allow external CLAUDE.md imports?" (the one that bit us — the global
10
+ * `~/.claude/CLAUDE.md` imports files outside the cwd) →
11
+ * `projects[<abs cwd>].hasClaudeMdExternalIncludesApproved`
12
+ *
13
+ * We seed just these keys idempotently and preserve all other `~/.claude.json`
14
+ * state (the user's onboarding choices, project history, MCP config, OAuth
15
+ * account). This does NOT skip per-tool permission prompts — those are hookable
16
+ * and handled elsewhere; only the unhookable startup gates are pre-accepted.
17
+ *
18
+ * Concurrency: a read-modify-write of a file Claude also rewrites. We run it
19
+ * once before spawning (Claude isn't writing for this session yet) and replace
20
+ * atomically; a last-writer-wins race only re-shows a prompt, which a relaunch
21
+ * clears.
22
+ */
23
+ import { chmodSync, existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
24
+ import { homedir } from "node:os";
25
+ import { basename, dirname, join, resolve } from "node:path";
26
+ const PROJECT_TRUST_KEYS = {
27
+ hasTrustDialogAccepted: true,
28
+ hasClaudeMdExternalIncludesApproved: true,
29
+ hasClaudeMdExternalIncludesWarningShown: true,
30
+ };
31
+ function claudeConfigJsonPath() {
32
+ const dir = process.env.CLAUDE_CONFIG_DIR?.trim();
33
+ return dir ? join(dir, ".claude.json") : join(homedir(), ".claude.json");
34
+ }
35
+ function isPlainObject(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ /**
39
+ * Ensure `cwd` is pre-trusted in `~/.claude.json`. Idempotent; throws (rather
40
+ * than clobbering) if an existing config isn't shaped as expected. Opt out with
41
+ * `RYNX_CLAUDE_PRESEED_TRUST=0`.
42
+ */
43
+ export function ensureProjectTrusted(cwd, configPath = claudeConfigJsonPath()) {
44
+ const flag = process.env.RYNX_CLAUDE_PRESEED_TRUST;
45
+ if (flag === "0" || flag === "false")
46
+ return;
47
+ let data = {};
48
+ if (existsSync(configPath)) {
49
+ const parsed = JSON.parse(readFileSync(configPath, "utf8"));
50
+ if (!isPlainObject(parsed)) {
51
+ throw new Error(`${configPath} is not a JSON object; refusing to overwrite.`);
52
+ }
53
+ data = parsed;
54
+ }
55
+ let changed = false;
56
+ if (data.hasCompletedOnboarding !== true) {
57
+ data.hasCompletedOnboarding = true;
58
+ changed = true;
59
+ }
60
+ if (data.projects === undefined)
61
+ data.projects = {};
62
+ if (!isPlainObject(data.projects)) {
63
+ throw new Error(`${configPath} 'projects' is not a JSON object; refusing to overwrite.`);
64
+ }
65
+ const projects = data.projects;
66
+ const key = resolve(cwd);
67
+ if (projects[key] === undefined)
68
+ projects[key] = {};
69
+ if (!isPlainObject(projects[key])) {
70
+ throw new Error(`${configPath} projects[${key}] is not a JSON object; refusing to overwrite.`);
71
+ }
72
+ const entry = projects[key];
73
+ for (const [k, v] of Object.entries(PROJECT_TRUST_KEYS)) {
74
+ if (entry[k] !== v) {
75
+ entry[k] = v;
76
+ changed = true;
77
+ }
78
+ }
79
+ if (!changed)
80
+ return;
81
+ const tmp = join(dirname(configPath), `.${basename(configPath)}.${process.pid}.tmp`);
82
+ writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
83
+ chmodSync(tmp, 0o600); // pin owner-only even under a permissive umask
84
+ renameSync(tmp, configPath);
85
+ }
@@ -0,0 +1,37 @@
1
+ import type { SessionItem } from "@rynx-ai/core";
2
+ export interface SynthesizeRolloutOptions {
3
+ threadId: string;
4
+ cwd: string;
5
+ items: SessionItem[];
6
+ /** The rynx session (localThreadId) — resolves the per-session private CODEX_HOME
7
+ * (`codexHomePath(sessionId)`). Required unless `codexHome` is passed directly. */
8
+ sessionId?: string;
9
+ /** Override the private CODEX_HOME (tests, or a pre-resolved home). */
10
+ codexHome?: string;
11
+ /** codex CLI version for `session_meta` (informational for ≥0.133; presence
12
+ * matters). */
13
+ cliVersion?: string;
14
+ /** `session_meta.model_provider` — an empty/unresolvable value silently drops the
15
+ * carried history on resume. Defaults to `openai`. */
16
+ modelProvider?: string;
17
+ /** Clock injection (tests). */
18
+ now?: () => number;
19
+ }
20
+ interface RolloutRecord {
21
+ timestamp: string;
22
+ type: "session_meta" | "turn_context" | "response_item" | "event_msg";
23
+ payload: Record<string, unknown>;
24
+ }
25
+ /** Locate an existing rollout for `threadId` under `<codexHome>/sessions/**`. */
26
+ export declare function findCodexRollout(codexHome: string, threadId: string): string | null;
27
+ /** Build the ordered rollout records (session_meta first, then per-turn
28
+ * turn_context + per-item response_item + per-message event_msg). */
29
+ export declare function buildRolloutRecords(opts: SynthesizeRolloutOptions): RolloutRecord[];
30
+ /**
31
+ * Ensure a resumable codex rollout exists for `threadId`. No-op if one is already
32
+ * present (codex owns its own runtime rollouts). Otherwise synthesize one from the
33
+ * session items and write it atomically. Returns `"exists"`, `"written"`, or
34
+ * `"skipped"` (invalid thread id / nothing to carry). Best-effort: never throws.
35
+ */
36
+ export declare function ensureCodexResumeRollout(opts: SynthesizeRolloutOptions): "exists" | "written" | "skipped";
37
+ export {};
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Synthesize a codex rollout file from rynx's canonical session log, so
3
+ * `codex --remote resume <threadId>` works when the local rollout is missing
4
+ * (fork / worktree / cross-machine). Ports omnigent's
5
+ * `_ensure_local_codex_resume_rollout`, adapted to rynx: the daemon (control-api)
6
+ * has BOTH the session items (`SessionLogStore`) and — since the private CODEX_HOME
7
+ * is a deterministic uid-scoped path — the app-server's rollout dir, so this runs
8
+ * entirely daemon-side with no runner round-trip.
9
+ *
10
+ * The record shapes match codex 0.142.5 (empirically captured). The minimal set
11
+ * codex needs to resume with VISIBLE turns is: a `session_meta` first line, then
12
+ * per item a `response_item`, and — critically — an `event_msg` mirror per message
13
+ * (codex ≥0.136 renders an empty thread without them). `turn_context` groups items
14
+ * into turns.
15
+ */
16
+ import { copyFileSync, mkdirSync, readdirSync, renameSync, writeFileSync } from "node:fs";
17
+ import { join, relative } from "node:path";
18
+ import { codexHomePath, legacyCodexHomePath } from "../codex-home.js";
19
+ /** codex validates the thread id straight into a filename + resume arg. */
20
+ 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");
24
+ const suffix = `-${threadId}.jsonl`;
25
+ const stack = [root];
26
+ while (stack.length) {
27
+ const dir = stack.pop();
28
+ let entries;
29
+ try {
30
+ entries = readdirSync(dir, { withFileTypes: true });
31
+ }
32
+ catch {
33
+ continue; // missing dir — fine
34
+ }
35
+ for (const e of entries) {
36
+ const full = join(dir, e.name);
37
+ if (e.isDirectory())
38
+ stack.push(full);
39
+ else if (e.name.startsWith("rollout-") && e.name.endsWith(suffix))
40
+ return full;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Back-compat: before per-session homes, rollouts lived under one shared
47
+ * {@link legacyCodexHomePath}. If a session's rollout is only there, copy it forward
48
+ * into this session's home (preserving the `sessions/YYYY/MM/DD/` layout codex
49
+ * expects) so the per-session app-server can resume it. Best-effort; gated by
50
+ * `RYNX_CODEX_HOME_LEGACY_FALLBACK` (default on; set `0`/`false` to disable).
51
+ */
52
+ function adoptLegacyRollout(codexHome, threadId) {
53
+ const flag = process.env.RYNX_CODEX_HOME_LEGACY_FALLBACK?.trim().toLowerCase();
54
+ if (flag === "0" || flag === "false")
55
+ return false;
56
+ const legacy = legacyCodexHomePath();
57
+ if (legacy === codexHome)
58
+ return false;
59
+ const src = findCodexRollout(legacy, threadId);
60
+ if (!src)
61
+ return false;
62
+ const rel = relative(join(legacy, "sessions"), src);
63
+ const dst = join(codexHome, "sessions", rel);
64
+ try {
65
+ mkdirSync(join(dst, ".."), { recursive: true, mode: 0o700 });
66
+ copyFileSync(src, dst);
67
+ return true;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ function iso(ms) {
74
+ return new Date(ms).toISOString();
75
+ }
76
+ /** `resp_codex_<turnId>` → `<turnId>` (rynx derives the responseId from the codex
77
+ * turn id); anything else is used verbatim as a stable turn key. */
78
+ function turnIdOf(responseId) {
79
+ return responseId.startsWith("resp_codex_") ? responseId.slice("resp_codex_".length) : responseId;
80
+ }
81
+ function textOf(item) {
82
+ return item.data.content.map((p) => p.text).join("");
83
+ }
84
+ /** Convert one canonical {@link SessionItem} to its codex `response_item` payload,
85
+ * or null for types codex doesn't carry (reasoning/terminal_command/error). */
86
+ function responseItemPayload(item) {
87
+ switch (item.type) {
88
+ case "message": {
89
+ 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 }));
93
+ if (content.length === 0)
94
+ return null;
95
+ return { type: "message", role: item.data.role, content };
96
+ }
97
+ case "function_call":
98
+ return {
99
+ type: "function_call",
100
+ name: item.data.name,
101
+ call_id: item.data.callId,
102
+ arguments: item.data.arguments,
103
+ };
104
+ case "function_call_output":
105
+ return { type: "function_call_output", call_id: item.data.callId, output: item.data.output };
106
+ default:
107
+ return null;
108
+ }
109
+ }
110
+ /** The `event_msg` mirror for a message (required for a VISIBLE turn on codex ≥0.136). */
111
+ function eventMsgPayload(item) {
112
+ const message = textOf(item).trim();
113
+ if (!message)
114
+ return null;
115
+ if (item.data.role === "user") {
116
+ return { type: "user_message", message, images: [], local_images: [], text_elements: [] };
117
+ }
118
+ if (item.data.role === "assistant") {
119
+ return { type: "agent_message", message, phase: "final_answer", memory_citation: null };
120
+ }
121
+ return null;
122
+ }
123
+ /** Build the ordered rollout records (session_meta first, then per-turn
124
+ * turn_context + per-item response_item + per-message event_msg). */
125
+ export function buildRolloutRecords(opts) {
126
+ const now = opts.now ?? (() => Date.now());
127
+ const modelProvider = opts.modelProvider ?? "openai";
128
+ const cliVersion = opts.cliVersion ?? "0.0.0";
129
+ const metaTs = iso(opts.items[0]?.createdAt ?? now());
130
+ const records = [
131
+ {
132
+ timestamp: metaTs,
133
+ type: "session_meta",
134
+ payload: {
135
+ id: opts.threadId,
136
+ session_id: opts.threadId,
137
+ timestamp: metaTs,
138
+ cwd: opts.cwd,
139
+ originator: "rynx",
140
+ cli_version: cliVersion,
141
+ source: "rynx",
142
+ thread_source: "user",
143
+ model_provider: modelProvider,
144
+ },
145
+ },
146
+ ];
147
+ const seenTurns = new Set();
148
+ for (const item of opts.items) {
149
+ const payload = responseItemPayload(item);
150
+ if (!payload)
151
+ continue;
152
+ const ts = iso(item.createdAt);
153
+ const turnId = turnIdOf(item.responseId);
154
+ if (!seenTurns.has(turnId)) {
155
+ seenTurns.add(turnId);
156
+ records.push({
157
+ timestamp: ts,
158
+ type: "turn_context",
159
+ payload: { turn_id: turnId, cwd: opts.cwd, approval_policy: "on-request" },
160
+ });
161
+ }
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
+ }
168
+ }
169
+ return records;
170
+ }
171
+ /**
172
+ * Ensure a resumable codex rollout exists for `threadId`. No-op if one is already
173
+ * present (codex owns its own runtime rollouts). Otherwise synthesize one from the
174
+ * session items and write it atomically. Returns `"exists"`, `"written"`, or
175
+ * `"skipped"` (invalid thread id / nothing to carry). Best-effort: never throws.
176
+ */
177
+ export function ensureCodexResumeRollout(opts) {
178
+ if (!THREAD_ID_RE.test(opts.threadId))
179
+ return "skipped";
180
+ const codexHome = opts.codexHome ?? (opts.sessionId ? codexHomePath(opts.sessionId) : undefined);
181
+ if (!codexHome)
182
+ return "skipped"; // no session context → can't locate the home
183
+ if (findCodexRollout(codexHome, opts.threadId))
184
+ return "exists";
185
+ // Back-compat: a pre-per-session rollout may live under the OLD shared home. Copy
186
+ // it forward into this session's home so the per-session app-server can resume it.
187
+ if (adoptLegacyRollout(codexHome, opts.threadId))
188
+ return "exists";
189
+ const records = buildRolloutRecords(opts);
190
+ // Nothing but the session_meta header → no history to carry; skip (a fresh
191
+ // thread resumes fine without a rollout once it has run a turn).
192
+ if (records.length <= 1)
193
+ return "skipped";
194
+ const now = opts.now ?? (() => Date.now());
195
+ const d = new Date(now());
196
+ const yyyy = String(d.getUTCFullYear());
197
+ const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
198
+ const dd = String(d.getUTCDate()).padStart(2, "0");
199
+ const stamp = iso(d.getTime()).slice(0, 19).replace(/:/g, "-"); // YYYY-MM-DDTHH-MM-SS
200
+ const dir = join(codexHome, "sessions", yyyy, mm, dd);
201
+ const target = join(dir, `rollout-${stamp}-${opts.threadId}.jsonl`);
202
+ const tmp = `${target}.tmp`;
203
+ try {
204
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
205
+ writeFileSync(tmp, records.map((r) => JSON.stringify(r)).join("\n") + "\n");
206
+ renameSync(tmp, target);
207
+ return "written";
208
+ }
209
+ catch {
210
+ return "skipped"; // best-effort: a failed synth just leaves resume to fail as before
211
+ }
212
+ }
@@ -0,0 +1,138 @@
1
+ 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
+ import { CodexAppServerTransport, type CodexAppServerProcessSpawner, type RpcChannel, type TransportLogger } from "./transport.js";
3
+ export type ApprovalDecisionPolicy = "auto-approve-session" | "auto-decline" | "auto-cancel";
4
+ export type CodexNotificationListener = (method: string, params: unknown) => void;
5
+ export interface CodexAppServerClientOptions {
6
+ /** Default stdio transport. Mutually exclusive with {@link channel}. */
7
+ spawner?: CodexAppServerProcessSpawner;
8
+ /** Alternate I/O channel (e.g. a {@link WsRpcChannel} for co-drive). */
9
+ channel?: RpcChannel;
10
+ logger?: TransportLogger;
11
+ clientInfo?: ClientInfo;
12
+ /**
13
+ * What to do when the server asks for a command / file approval. Defaults
14
+ * to `auto-approve-session`, which matches the existing bridge behaviour
15
+ * of running everything inside `--sandbox workspace-write` without ever
16
+ * prompting a human.
17
+ */
18
+ 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
+ }
37
+ export declare class CodexAppServerClient {
38
+ readonly transport: CodexAppServerTransport;
39
+ private readonly logger;
40
+ private readonly clientInfo;
41
+ private readonly approvalDecisionPolicy;
42
+ private readonly interactiveApprovals;
43
+ private readonly channel;
44
+ private readonly notificationSubscribers;
45
+ private approvalListener;
46
+ private readonly pendingApprovals;
47
+ private initializeResponse;
48
+ constructor({ spawner, channel, logger, clientInfo, approvalDecisionPolicy, interactiveApprovals, }: CodexAppServerClientOptions);
49
+ /**
50
+ * The multi-client endpoint a `codex --remote` TUI can attach to, when this
51
+ * client runs over a {@link WsRpcChannel}. `undefined` for the default stdio
52
+ * transport (single client — not co-drivable). Available after the transport
53
+ * has started (post `ensureInitialized`).
54
+ */
55
+ terminalRemoteUrl(): string | undefined;
56
+ ensureInitialized(): Promise<InitializeResponse>;
57
+ getAuthStatus(params?: GetAuthStatusParams): Promise<GetAuthStatusResponse>;
58
+ threadStart(params: ThreadStartParams): Promise<{
59
+ threadId: string;
60
+ }>;
61
+ threadResume(params: ThreadResumeParams): Promise<{
62
+ threadId: string;
63
+ thread: ResumedThread;
64
+ }>;
65
+ turnStart(params: TurnStartParams): Promise<{
66
+ turnId: string;
67
+ }>;
68
+ turnInterrupt(params: TurnInterruptParams): Promise<void>;
69
+ /**
70
+ * Inject additional user input into the currently running turn. Returns the
71
+ * active turn id (may differ from `expectedTurnId`). Fails if no matching
72
+ * active turn exists, so callers should treat errors as best-effort.
73
+ */
74
+ turnSteer(params: TurnSteerParams): Promise<{
75
+ turnId: string;
76
+ }>;
77
+ /** List local conversation/thread history (newest first by default). */
78
+ threadList(params?: ThreadListParams): Promise<ThreadListResponse>;
79
+ /** Fork an existing thread into a new one, optionally overriding settings. */
80
+ threadFork(params: ThreadForkParams): Promise<{
81
+ thread: {
82
+ id: string;
83
+ };
84
+ } & Record<string, unknown>>;
85
+ /** Update per-thread settings (model / mode / personality / sandbox …). */
86
+ threadSettingsUpdate(params: ThreadSettingsUpdateParams): Promise<Record<string, unknown>>;
87
+ /** List the models the local Codex install exposes. */
88
+ modelList(params?: ModelListParams): Promise<ModelListResponse>;
89
+ /** List the available collaboration-mode presets (experimental). */
90
+ collaborationModeList(): Promise<CollaborationModeListResponse>;
91
+ /** Set (or update) the thread's tracked goal. */
92
+ threadGoalSet(params: ThreadGoalSetParams): Promise<Record<string, unknown>>;
93
+ /** Read the thread's current goal. */
94
+ threadGoalGet(params: ThreadGoalGetParams): Promise<ThreadGoalGetResponse>;
95
+ /** Clear the thread's tracked goal. */
96
+ threadGoalClear(params: ThreadGoalClearParams): Promise<Record<string, unknown>>;
97
+ /** Start a non-interactive code review on the thread. */
98
+ reviewStart(params: ReviewStartParams): Promise<ReviewStartResponse>;
99
+ onNotification(listener: CodexNotificationListener): () => void;
100
+ stop(): Promise<void>;
101
+ private dispatchNotification;
102
+ private handleServerRequest;
103
+ /**
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.
118
+ */
119
+ private handleApproval;
120
+ private autoApprovalDecision;
121
+ private respondCommandApproval;
122
+ private respondFileChangeApproval;
123
+ private declineDecisionForApprovals;
124
+ private respondNotSupported;
125
+ }
126
+ export interface BuildSandboxPolicyOptions {
127
+ mode: SandboxMode;
128
+ cwd: string;
129
+ allowedRoots: string[];
130
+ }
131
+ /**
132
+ * Construct a SandboxPolicy compatible with the App Server turn/start params
133
+ * from the env-configured mode and the allowlist of writable roots. The
134
+ * shape mirrors `SandboxPolicy` in `codex-app-server` generated types.
135
+ */
136
+ export declare function buildSandboxPolicy(options: BuildSandboxPolicyOptions): Record<string, unknown>;
137
+ export declare function buildTextUserInput(message: string): UserInput[];
138
+ export type { AskForApproval };