@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
package/dist/host.d.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { type AgentSpec, type ResolvedExecutionBudget, type SessionEvent } from "@rynx-ai/core";
2
+ import { type AgentRuntimeId } from "@rynx-ai/core";
3
+ import { type AppConfig } from "@rynx-ai/core";
4
+ import { createCodexChildEnv } from "./codex-child-env.js";
5
+ import type { InjectOutcome } from "./runner/protocol.js";
6
+ import { CodexAppServerClient } from "./codex-app-server/client.js";
7
+ import type { ModelListResponse, ThreadGoal } from "./codex-app-server/protocol.js";
8
+ import { type TerminalInjector } from "./claude/native-integration.js";
9
+ import { FileCodexSessionStore, resolveCodexSessionStorePath, type CodexSessionRecord, type CodexSessionStore } from "./codex-session-store.js";
10
+ import { AgentRuntimeError as CodexRuntimeError } from "@rynx-ai/core";
11
+ export { FileCodexSessionStore, resolveCodexSessionStorePath, type CodexSessionRecord, type CodexSessionStore, };
12
+ export interface CodexRuntimeStatus {
13
+ codex_available: boolean;
14
+ logged_in: boolean;
15
+ auth_mode: string | null;
16
+ session_store_writable: boolean;
17
+ codex_sessions_accessible: boolean;
18
+ issues: string[];
19
+ }
20
+ export interface CommandChunk {
21
+ stream: "stdout" | "stderr";
22
+ line: string;
23
+ }
24
+ export interface CommandCompletion {
25
+ exitCode: number;
26
+ stdout: string;
27
+ stderr: string;
28
+ }
29
+ export interface RunningCommand {
30
+ stream: AsyncGenerator<CommandChunk>;
31
+ completion: Promise<CommandCompletion>;
32
+ }
33
+ export interface CodexCommandRunner {
34
+ start(options: {
35
+ args: string[];
36
+ cwd: string;
37
+ signal?: AbortSignal;
38
+ }): RunningCommand;
39
+ capture(options: {
40
+ args: string[];
41
+ cwd?: string;
42
+ }): Promise<CommandCompletion>;
43
+ }
44
+ export { CodexRuntimeError };
45
+ export declare class SpawnCodexCommandRunner implements CodexCommandRunner {
46
+ private readonly cliPath;
47
+ private readonly killGraceMs;
48
+ constructor(cliPath: string, { killGraceMs }?: {
49
+ killGraceMs?: number;
50
+ });
51
+ start({ args, cwd, signal, }: {
52
+ args: string[];
53
+ cwd: string;
54
+ signal?: AbortSignal;
55
+ }): RunningCommand;
56
+ capture({ args, cwd, }: {
57
+ args: string[];
58
+ cwd?: string;
59
+ }): Promise<CommandCompletion>;
60
+ }
61
+ /**
62
+ * Result of a capability that operates on a specific Lark session's active
63
+ * Codex thread. `no_active_thread` means the session hasn't run a turn yet (no
64
+ * Codex thread bound); `unsupported` means the backend can't answer (e.g. the
65
+ * legacy `exec` backend).
66
+ */
67
+ export type CapabilityResult<T = void> = {
68
+ ok: true;
69
+ data: T;
70
+ } | {
71
+ ok: false;
72
+ reason: "unsupported" | "no_active_thread" | "error";
73
+ message?: string;
74
+ };
75
+ /**
76
+ * Read-only / per-thread Codex capabilities surfaced to the Lark bot for
77
+ * slash-commands. List capabilities resolve to `null` on the legacy `exec`
78
+ * backend; per-thread capabilities return a `CapabilityResult` so the bot can
79
+ * tell "no session yet" apart from "unsupported".
80
+ */
81
+ export interface CodexCapabilities {
82
+ listModels(runtime?: AgentRuntimeId): Promise<ModelListResponse | null>;
83
+ getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
84
+ setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
85
+ clearGoal(localThreadId: string): Promise<CapabilityResult>;
86
+ /**
87
+ * Fork the Codex thread bound to `currentLocalThreadId` and bind the forked
88
+ * thread (carrying full context) to `newLocalThreadId`, so a new Lark
89
+ * conversation resumes from it. Used by `/fork`.
90
+ */
91
+ forkSession(currentLocalThreadId: string, newLocalThreadId: string): Promise<CapabilityResult>;
92
+ }
93
+ /**
94
+ * Per-session claude-native live handle. claude has no app-server, so the
95
+ * forwarder tails the bridge (`hooks.jsonl`) + the transcript JSONL instead of
96
+ * subscribing to RPC. Injection is tmux send-keys into the pane, whose injector
97
+ * the runner-child attaches via {@link LocalAgentHost.attachTerminalInjector}.
98
+ */
99
+ /** Re-target the runner-child's mirror to a freshly minted rynx session (claude
100
+ * `/clear`·`/fork` rotation) and record the terminal transfer with the daemon.
101
+ * The runner-child owns the mirror emitter + transport, so it supplies this. */
102
+ export type RetargetMirror = (newSessionId: string, meta: {
103
+ kind: "clear" | "fork";
104
+ agent?: string;
105
+ model?: string;
106
+ cwd?: string;
107
+ parentSessionId?: string;
108
+ }) => void;
109
+ export interface LiveSessionOpts {
110
+ runtime?: AgentRuntimeId;
111
+ /** Preset agent id (or inline {@link LiveSessionOpts.agentSpec}), so the live
112
+ * launch applies the agent's model / skills / instructions — not just the
113
+ * runtime. Absent ⇒ falls back to the config defaults (prior behavior). */
114
+ agentName?: string;
115
+ agentSpec?: AgentSpec;
116
+ retargetMirror?: RetargetMirror;
117
+ }
118
+ /**
119
+ * The `OPENAI_*` retry env for a codex-lineage app-server spawn, or `undefined`
120
+ * when the budget declares no retry or the runtime is claude. claude applies
121
+ * its retry budget per-run in `buildOptions` (see the claude executor), so it
122
+ * never needs a dedicated app-server.
123
+ */
124
+ export declare function codexRetryEnv(runtime: AgentRuntimeId, budget?: ResolvedExecutionBudget): Record<string, string> | undefined;
125
+ export declare function codexBackendKey(runtime: AgentRuntimeId, budget?: ResolvedExecutionBudget): string;
126
+ export declare class LocalAgentHost implements CodexCapabilities {
127
+ private readonly config;
128
+ private readonly sessionStore;
129
+ private readonly allowedRoots;
130
+ private readonly defaultRuntime;
131
+ private readonly injectedCommandRunner?;
132
+ private readonly injectedAppServerClient?;
133
+ private readonly clock;
134
+ private readonly backendIdleTtlMs;
135
+ private readonly forwarderClientFactory;
136
+ private readonly backends;
137
+ private readonly sessionId;
138
+ private sessionSandbox?;
139
+ private sessionApprovalPolicy?;
140
+ private cachedCodexHome?;
141
+ private readonly liveSessions;
142
+ private readonly liveClaudeSessions;
143
+ private readonly liveEnsuring;
144
+ constructor({ config, commandRunner, sessionStore, allowedRoots, appServerClient, now, backendIdleTtlMs, forwarderClientFactory, sessionId, }: {
145
+ config: AppConfig;
146
+ commandRunner?: CodexCommandRunner;
147
+ sessionStore?: CodexSessionStore;
148
+ allowedRoots?: string[];
149
+ appServerClient?: CodexAppServerClient | null;
150
+ /** Monotonic-ish clock for idle reaping (tests inject a controllable one). */
151
+ now?: () => number;
152
+ /** Idle TTL after which a per-budget composite backend is reaped (ms). */
153
+ backendIdleTtlMs?: number;
154
+ /** Override the live-session forwarder connection factory (tests inject a fake). */
155
+ forwarderClientFactory?: (appServerUrl: string) => CodexAppServerClient;
156
+ /** The rynx session (localThreadId) this host serves — a per-session runner
157
+ * child sets it from `RYNX_RUNNER_SESSION` so the private CODEX_HOME is
158
+ * session-scoped. The shared `__cap__` child / tests fall back to a sentinel
159
+ * (its home only backs home-agnostic caps like listModels). */
160
+ sessionId?: string;
161
+ });
162
+ private getBackend;
163
+ /**
164
+ * Stop + drop per-budget composite backends that have been idle longer than
165
+ * the TTL and have no in-flight run. Base per-runtime backends (key === the
166
+ * bare runtime id) are never reaped — they live for the process lifetime, as
167
+ * before. There are no per-conversation close hooks, so this lazy sweep (run
168
+ * on every `getBackend`) is how dedicated app-servers get reclaimed.
169
+ */
170
+ private reapIdleBackends;
171
+ private createBackend;
172
+ /** This session's private CODEX_HOME (prepared once), shared by its app-server + TUI. */
173
+ private codexHome;
174
+ private createAppServerClient;
175
+ /**
176
+ * Deliver a user's decision for a pending interactive approval (Phase D).
177
+ * Routes to the codex app-server client for the thread's runtime. Returns
178
+ * false when no matching pending approval exists (unknown id / already
179
+ * resolved / claude runtime).
180
+ */
181
+ resolveApproval(localThreadId: string, approvalId: string, decision: "acceptForSession" | "accept" | "decline" | "cancel"): Promise<boolean>;
182
+ /**
183
+ * The command to run in a session's live terminal so it co-drives the codex
184
+ * app-server thread (Phase D). Returns `null` when live-terminal is off, the
185
+ * runtime is claude (not codex-lineage), or the app-server has no ws endpoint —
186
+ * the caller then falls back to a plain shell. Ensures the app-server is up so
187
+ * its ws url is available; `codex --remote resume <threadId>` attaches to the
188
+ * SAME thread the structured data channel drives.
189
+ */
190
+ codexTerminalSpec(localThreadId: string): Promise<{
191
+ command: string;
192
+ args: string[];
193
+ cwd: string;
194
+ env?: Record<string, string>;
195
+ } | null>;
196
+ /**
197
+ * Bring up (idempotently) a session's persistent codex forwarder and emit its
198
+ * mirrored {@link SessionEvent}s. omnigent's codex-native single-writer model:
199
+ * this forwarder is the SOLE producer of the session's canonical events — every
200
+ * turn on the thread, web-composer-injected AND co-driving-`codex --remote`-TUI
201
+ * initiated. Each turn gets a {@link SessionNormalizer} whose `responseId` is
202
+ * DETERMINISTIC (`resp_codex_<turnId>`) so items converge across snapshot/live.
203
+ *
204
+ * Returns false for claude / no app-server (the caller falls back to the
205
+ * streaming run path). This client CREATES the codex thread (`threadStart`) so
206
+ * it owns the thread's item/turn notification stream — the app-server delivers
207
+ * items to the thread-creating connection, for turns started by ANY client (web
208
+ * inject AND the co-driving `codex --remote` TUI). The TUI pane (launched by the
209
+ * runner child, which owns the terminal registry) merely `resume`s this thread
210
+ * to display it. The thread id is persisted so `codexTerminalSpec` resumes it.
211
+ */
212
+ ensureLiveCodexSession(localThreadId: string, emit: (event: SessionEvent) => void, opts?: LiveSessionOpts): Promise<boolean>;
213
+ private startLiveCodexSession;
214
+ /** Bind a session's codex thread id once known (TUI broadcast or store): persist
215
+ * it, unblock injection, and kick off the resume-subscribe loop (once). */
216
+ private onLiveThreadStarted;
217
+ /**
218
+ * Subscribe the forwarder connection to a thread (omnigent's
219
+ * `_subscribe_until_ready`). A fresh TUI thread has no rollout until its first
220
+ * turn, so `thread/resume` is retried: park until the forwarder observes the
221
+ * thread active, then retry WITHOUT `excludeTurns` so the response backfills the
222
+ * first turn's items (replayed, deduped against the live stream). Once resume
223
+ * succeeds, subsequent turns arrive as live notifications on this connection.
224
+ */
225
+ private subscribeUntilReady;
226
+ /** Await a live session's thread binding (bounded). Returns false on timeout /
227
+ * no live session. Injection and the runner's `live.ready` gate on this. */
228
+ waitLiveReady(localThreadId: string, timeoutMs?: number): Promise<boolean>;
229
+ /**
230
+ * Inject a user turn into a session's live codex thread — omnigent's
231
+ * single-writer web send. `turn/steer` when a turn is open (mid-turn
232
+ * supplement), else `turn/start`. NEVER creates a thread (the TUI owns creation;
233
+ * this targets the id the forwarder captured). Serialized per session so two
234
+ * injects can't double-open a turn.
235
+ *
236
+ * Returns an {@link InjectOutcome}: `notLive` when this session has no live
237
+ * forwarder (caller may use the run path); `notReady`/`failed` are hard errors
238
+ * the caller reports WITHOUT re-running (re-running double-writes alongside the
239
+ * forwarder). Park-until-ready (~60s), aligning omnigent's executor waiting for
240
+ * the bridge instead of a short race that falls back to a second output path.
241
+ */
242
+ injectMessage(localThreadId: string, text: string): Promise<InjectOutcome>;
243
+ /**
244
+ * Interrupt the session's active turn — the web Stop button. codex: the
245
+ * app-server `turn/interrupt` on the active `{threadId, turnId}` (exactly what
246
+ * codex's own TUI interrupt key sends — omnigent `_handle_codex_native_interrupt`).
247
+ * claude: an Escape keystroke into the pane (no app-server — omnigent
248
+ * `inject_interrupt`). Returns false as a no-op when there is no active turn.
249
+ */
250
+ interruptLive(localThreadId: string): Promise<boolean>;
251
+ /** Stop + drop a session's live forwarder and its dedicated connection (session
252
+ * close / runner shutdown). The backend inject client is shared — left running. */
253
+ stopLiveCodexSession(localThreadId: string): void;
254
+ /** The interactive `claude` TUI spec for a claude-native live session: the real
255
+ * `claude` binary with rynx's hooks (`--settings`), resuming the bound session.
256
+ * Trust is pre-seeded so the daemon-spawned TUI never blocks on a first-run
257
+ * prompt. (PermissionRequest approvals land in Phase 6; until then the TUI
258
+ * surfaces its own permission prompt in the co-driven pane.) */
259
+ private claudeTerminalSpec;
260
+ /** Bring up a claude-native live forwarder: prepare the bridge dir (the TUI's
261
+ * hooks write into it), then tail bridge + transcript and drive a per-turn
262
+ * {@link SessionNormalizer} → `emit` (the SAME sink shape the codex path uses). */
263
+ /** Resolve an agent spec's launch config — model + the session skill env —
264
+ * for a LIVE native session, reusing the same core resolvers the non-live
265
+ * run path uses ({@link resolveAgentExecution} for the model,
266
+ * {@link resolveAgent} for skills). Instructions are intentionally NOT
267
+ * returned: the co-driven TUI runs on vendor defaults (see
268
+ * startLiveClaudeSession).
269
+ *
270
+ * Skills are spec-rooted: each declared ref is replayed into a
271
+ * SESSION-scoped temp dir (cache-accelerated); the owner's catalog plays no
272
+ * role, and no spec / no `skills` means ZERO skills. The env-build report
273
+ * (installs, failures, content drift) is logged — a declared skill that
274
+ * can't be provisioned is loudly absent, never silent; the session still
275
+ * starts. `skillsCleanup` removes the session dir (call on session stop). */
276
+ private resolveLiveAgentConfig;
277
+ private startLiveClaudeSession;
278
+ /** Persist claude's discovered session id (reusing the `codexSessionId` store
279
+ * field, as the claude executor already does) and release the readiness gate. */
280
+ private onClaudeDiscovered;
281
+ /** Inject a web message into a claude-native pane via tmux (omnigent recipe),
282
+ * serialized per session. Parks until the thread is ready AND the tmux injector
283
+ * is (re)attached, then pastes; `injectViaTerminal` RAISES if the prompt never
284
+ * appears (omnigent RAISE), so a not-ready pane is a hard error — NOT a
285
+ * fall-through-to-run signal. Returns {@link InjectOutcome}. */
286
+ private injectClaude;
287
+ /** Park until the claude session's tmux injector is (re)attached by the
288
+ * runner-child, or the deadline passes. Pane relaunch re-attaches it via
289
+ * {@link attachTerminalInjector}; without this a message during that window
290
+ * would fail and (pre-omnigent) fall through to a second output path. */
291
+ private waitInjector;
292
+ /** Attach a session's tmux pane injector (from the runner-child, which owns the
293
+ * terminal registry — the host does not hold tmux). Ignored for codex sessions
294
+ * (they inject via the app-server). Idempotent. */
295
+ attachTerminalInjector(localThreadId: string, injector: TerminalInjector): void;
296
+ /** The PermissionRequest hook's server target from the runner-child env (the
297
+ * daemon control URL + token), or undefined when unset — then no permission
298
+ * hook is registered and claude surfaces its own TUI prompt in the pane. */
299
+ private claudePermissionConfig;
300
+ /** List the models a runtime exposes (App Server for codex/traex; static for claude). */
301
+ listModels(runtime?: AgentRuntimeId): Promise<ModelListResponse | null>;
302
+ /**
303
+ * Run a per-thread capability against a session's active thread on whichever
304
+ * runtime it is bound to. Normalises the "no app-server backend" and "session
305
+ * has no thread yet" cases into a `CapabilityResult`.
306
+ */
307
+ private withCodexThread;
308
+ getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
309
+ setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
310
+ clearGoal(localThreadId: string): Promise<CapabilityResult>;
311
+ forkSession(currentLocalThreadId: string, newLocalThreadId: string): Promise<CapabilityResult>;
312
+ }
313
+ export declare function parseCodexLoginStatus(exitCode: number, output: string): {
314
+ loggedIn: boolean;
315
+ authMode: string | null;
316
+ issues: string[];
317
+ };
318
+ export declare function getCodexModel(config: AppConfig, override?: string): string;
319
+ export { createCodexChildEnv };
320
+ export declare function isUnsupportedMethodError(error: unknown): boolean;
321
+ /** A `thread/resume` failure meaning the thread has no (or an empty) rollout yet
322
+ * — a fresh TUI thread before its first turn. Retryable (park until active).
323
+ * Mirrors omnigent's `_is_thread_not_ready_error`. */
324
+ export declare function isThreadNotReadyError(error: unknown): boolean;