@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.
Files changed (65) hide show
  1. package/dist/claude/executor.d.ts +19 -5
  2. package/dist/claude/executor.js +56 -12
  3. package/dist/claude/models.d.ts +0 -5
  4. package/dist/claude/models.js +1 -7
  5. package/dist/claude/native-bridge.d.ts +103 -1
  6. package/dist/claude/native-bridge.js +445 -30
  7. package/dist/claude/native-hook-main.js +81 -1
  8. package/dist/claude/native-hooks.js +7 -0
  9. package/dist/claude/native-integration.d.ts +178 -26
  10. package/dist/claude/native-integration.js +1528 -170
  11. package/dist/claude/session-status.d.ts +39 -0
  12. package/dist/claude/session-status.js +163 -0
  13. package/dist/claude/transcript-clone.d.ts +18 -0
  14. package/dist/claude/transcript-clone.js +497 -0
  15. package/dist/claude/transcript.d.ts +27 -4
  16. package/dist/claude/transcript.js +158 -47
  17. package/dist/codex-app-server/client.d.ts +10 -6
  18. package/dist/codex-app-server/client.js +67 -15
  19. package/dist/codex-app-server/forwarder.d.ts +92 -3
  20. package/dist/codex-app-server/forwarder.js +532 -57
  21. package/dist/codex-app-server/mapping.d.ts +3 -6
  22. package/dist/codex-app-server/mapping.js +206 -36
  23. package/dist/codex-app-server/mcp-startup.d.ts +13 -0
  24. package/dist/codex-app-server/mcp-startup.js +63 -0
  25. package/dist/codex-app-server/process-registry.d.ts +36 -0
  26. package/dist/codex-app-server/process-registry.js +320 -0
  27. package/dist/codex-app-server/protocol.d.ts +64 -7
  28. package/dist/codex-app-server/ws-channel.d.ts +7 -0
  29. package/dist/codex-app-server/ws-channel.js +104 -28
  30. package/dist/codex-home.d.ts +35 -3
  31. package/dist/codex-home.js +323 -18
  32. package/dist/codex-session-store.d.ts +23 -0
  33. package/dist/codex-session-store.js +21 -0
  34. package/dist/host.d.ts +103 -46
  35. package/dist/host.js +1988 -634
  36. package/dist/index.d.ts +3 -3
  37. package/dist/index.js +1 -1
  38. package/dist/input-resources.d.ts +4 -0
  39. package/dist/input-resources.js +21 -5
  40. package/dist/models-catalog.d.ts +2 -1
  41. package/dist/models-catalog.js +94 -6
  42. package/dist/runner/child.d.ts +97 -28
  43. package/dist/runner/child.js +1486 -100
  44. package/dist/runner/manager.d.ts +110 -29
  45. package/dist/runner/manager.js +1481 -246
  46. package/dist/runner/protocol.d.ts +212 -24
  47. package/dist/runner/protocol.js +5 -0
  48. package/dist/runner/startup-policy.d.ts +7 -0
  49. package/dist/runner/startup-policy.js +10 -0
  50. package/dist/runner/transport.d.ts +18 -2
  51. package/dist/runner/transport.js +82 -3
  52. package/dist/runner-main.js +8 -3
  53. package/dist/terminal/claude-tui.d.ts +3 -1
  54. package/dist/terminal/claude-tui.js +3 -1
  55. package/dist/terminal/codex-tui.d.ts +4 -0
  56. package/dist/terminal/codex-tui.js +5 -0
  57. package/dist/terminal/control-parser.d.ts +39 -0
  58. package/dist/terminal/control-parser.js +172 -0
  59. package/dist/terminal/registry.d.ts +18 -15
  60. package/dist/terminal/registry.js +44 -23
  61. package/dist/terminal/spool.d.ts +47 -0
  62. package/dist/terminal/spool.js +231 -0
  63. package/dist/terminal/tmux.d.ts +126 -74
  64. package/dist/terminal/tmux.js +807 -211
  65. package/package.json +4 -4
@@ -1,9 +1,3 @@
1
- /**
2
- * Pure codex app-server notification → {@link AgentEvent} mapping. Extracted from
3
- * the executor so BOTH the per-turn executor and the persistent session
4
- * forwarder (which mirrors every thread turn — web- AND TUI-initiated — into the
5
- * canonical log, reference implementation's codex-native model) share one mapping.
6
- */
7
1
  import type { AgentEvent } from "@rynx-ai/core";
8
2
  import type { ThreadItem } from "./protocol.js";
9
3
  export interface CodexMapResult {
@@ -11,8 +5,11 @@ export interface CodexMapResult {
11
5
  finalText?: string;
12
6
  usage?: Record<string, unknown>;
13
7
  turnCompleted?: boolean;
8
+ turnInterrupted?: boolean;
14
9
  fatalError?: Error;
15
10
  }
11
+ export declare function codexTurnStatus(turn: unknown): string | undefined;
12
+ export declare function codexResumeTerminalStatus(turn: unknown): "idle" | "failed" | undefined;
16
13
  /** Map one thread item (`item/started` | `item/completed`) to events. */
17
14
  export declare function mapCodexItem(method: string, item: ThreadItem): CodexMapResult;
18
15
  /** Map one codex app-server notification to events (+ turn/usage/error signals). */
@@ -1,12 +1,105 @@
1
+ /**
2
+ * Pure codex app-server notification → {@link AgentEvent} mapping. Extracted from
3
+ * the executor so BOTH the per-turn executor and the persistent session
4
+ * forwarder (which mirrors every thread turn — web- AND TUI-initiated — into the
5
+ * canonical log, reference implementation's codex-native model) share one mapping.
6
+ */
7
+ import { extname, isAbsolute } from "node:path";
8
+ function isRecord(value) {
9
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10
+ }
11
+ const CODEX_AUTH_ERROR_FRAGMENTS = [
12
+ "401",
13
+ "403",
14
+ "unauthorized",
15
+ "authentication",
16
+ "not logged in",
17
+ "not authenticated",
18
+ "log in",
19
+ "login",
20
+ "sign in",
21
+ "re-authenticate",
22
+ "reauthenticate",
23
+ "credentials",
24
+ "access token",
25
+ "token expired",
26
+ "expired token",
27
+ "session expired",
28
+ "api key",
29
+ ];
30
+ const CODEX_REAUTH_HINT = "Codex needs you to re-authenticate. Run `codex login` and retry.";
31
+ function codexErrorMessage(error) {
32
+ for (const key of ["message", "error", "text", "detail"]) {
33
+ const value = error[key];
34
+ if (typeof value === "string" && value.trim())
35
+ return value.trim();
36
+ }
37
+ return "Codex turn ended with an unspecified error.";
38
+ }
39
+ function codexErrorIsAuth(error, message) {
40
+ const info = error.codexErrorInfo;
41
+ let variant;
42
+ let httpStatusCode;
43
+ if (typeof info === "string") {
44
+ variant = info;
45
+ }
46
+ else if (isRecord(info)) {
47
+ variant = info.type ?? info.kind ?? info.variant;
48
+ httpStatusCode = info.httpStatusCode;
49
+ }
50
+ if (typeof variant === "string" && variant.toLowerCase() === "unauthorized")
51
+ return true;
52
+ if (httpStatusCode === 401 || httpStatusCode === 403)
53
+ return true;
54
+ const lowered = message.toLowerCase();
55
+ return CODEX_AUTH_ERROR_FRAGMENTS.some((fragment) => lowered.includes(fragment));
56
+ }
1
57
  function turnError(error, fallback) {
2
- if (typeof error === "string" && error.trim())
3
- return new Error(error);
4
- if (error && typeof error === "object" && "message" in error) {
5
- const message = error.message;
6
- if (typeof message === "string" && message.trim())
7
- return new Error(message);
58
+ if (!error)
59
+ return new Error(fallback);
60
+ const message = codexErrorMessage(error);
61
+ return new Error(codexErrorIsAuth(error, message)
62
+ ? `${message}\n\n${CODEX_REAUTH_HINT}`
63
+ : message);
64
+ }
65
+ export function codexTurnStatus(turn) {
66
+ if (!isRecord(turn))
67
+ return undefined;
68
+ const status = turn.status;
69
+ if (typeof status === "string")
70
+ return status;
71
+ if (!isRecord(status))
72
+ return undefined;
73
+ const value = status.type ?? status.status;
74
+ return typeof value === "string" ? value : undefined;
75
+ }
76
+ export function codexResumeTerminalStatus(turn) {
77
+ if (terminalTurnError(turn, "turn/completed"))
78
+ return "failed";
79
+ const status = codexTurnStatus(turn);
80
+ if (status === "completed" || status === "interrupted" ||
81
+ status === "cancelled" || status === "canceled") {
82
+ return "idle";
83
+ }
84
+ if (status === "failed" || status === "errored")
85
+ return "failed";
86
+ return undefined;
87
+ }
88
+ function terminalTurnError(turn, method) {
89
+ if (!isRecord(turn)) {
90
+ return method === "turn/failed" ? new Error("Codex turn failed") : undefined;
91
+ }
92
+ let error = turn.error;
93
+ if (!isRecord(error) && Array.isArray(turn.items)) {
94
+ error = turn.items.find((item) => isRecord(item) && item.type === "error");
95
+ }
96
+ if (isRecord(error))
97
+ return turnError(error, "Codex turn failed");
98
+ const status = codexTurnStatus(turn);
99
+ if (method === "turn/failed" || status === "failed" || status === "errored") {
100
+ return turnError(undefined, "Codex turn failed");
8
101
  }
9
- return new Error(fallback);
102
+ return undefined;
10
103
  }
11
104
  function webSearchInput(item) {
12
105
  const action = item.action;
@@ -29,6 +122,25 @@ function webSearchInput(item) {
29
122
  }
30
123
  return { id: item.id, query: item.query };
31
124
  }
125
+ function fileChangeSummary(changes) {
126
+ const lines = [];
127
+ for (const change of changes) {
128
+ if (!isRecord(change))
129
+ continue;
130
+ if (typeof change.path !== "string")
131
+ continue;
132
+ const kind = isRecord(change.kind) && typeof change.kind.type === "string" && change.kind.type
133
+ ? change.kind.type
134
+ : "change";
135
+ lines.push(`${kind} ${change.path}`);
136
+ }
137
+ return lines.join("\n");
138
+ }
139
+ function isSupportedAbsoluteImagePath(path) {
140
+ if (!isAbsolute(path))
141
+ return false;
142
+ return [".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg"].includes(extname(path).toLowerCase());
143
+ }
32
144
  /** Map one thread item (`item/started` | `item/completed`) to events. */
33
145
  export function mapCodexItem(method, item) {
34
146
  const events = [];
@@ -37,20 +149,37 @@ export function mapCodexItem(method, item) {
37
149
  switch (item.type) {
38
150
  case "commandExecution": {
39
151
  const commandItem = item;
152
+ const id = typeof commandItem.id === "string" ? commandItem.id : "";
153
+ const command = typeof commandItem.command === "string" ? commandItem.command : "";
154
+ if (!id || !command)
155
+ return { events };
156
+ const exitCode = typeof commandItem.exitCode === "number"
157
+ ? commandItem.exitCode
158
+ : null;
159
+ const rawOutput = commandItem.aggregatedOutput ?? "";
160
+ const aggregatedOutput = exitCode !== null && exitCode !== 0
161
+ ? `${rawOutput}${rawOutput ? "\n" : ""}[exit code: ${exitCode}]`
162
+ : rawOutput;
40
163
  events.push({
41
164
  type: "tool",
42
165
  event: isStart ? "on_tool_start" : "on_tool_end",
43
- name: "command_execution",
44
- input: isStart
45
- ? { id: commandItem.id, command: commandItem.command, cwd: commandItem.cwd }
166
+ name: "shell",
167
+ input: isStart || isEnd
168
+ ? {
169
+ id,
170
+ command,
171
+ ...(typeof commandItem.cwd === "string" && commandItem.cwd
172
+ ? { cwd: commandItem.cwd }
173
+ : {}),
174
+ }
46
175
  : undefined,
47
176
  output: isEnd
48
177
  ? {
49
- id: commandItem.id,
50
- command: commandItem.command,
178
+ id,
179
+ command,
51
180
  status: commandItem.status,
52
- aggregatedOutput: commandItem.aggregatedOutput ?? null,
53
- exitCode: commandItem.exitCode ?? null,
181
+ aggregatedOutput,
182
+ exitCode,
54
183
  }
55
184
  : undefined,
56
185
  data: { method, item },
@@ -58,16 +187,23 @@ export function mapCodexItem(method, item) {
58
187
  return { events };
59
188
  }
60
189
  case "fileChange": {
190
+ const fileChange = item;
191
+ const changes = Array.isArray(fileChange.changes) ? fileChange.changes : [];
192
+ const id = typeof item.id === "string" ? item.id : "";
193
+ if (!id ||
194
+ changes.length === 0 ||
195
+ !changes.some((change) => isRecord(change) && typeof change.path === "string"))
196
+ return { events };
61
197
  events.push({
62
198
  type: "tool",
63
199
  event: isStart ? "on_tool_start" : "on_tool_end",
64
- name: "file_change",
65
- input: isStart ? { id: item.id } : undefined,
200
+ name: "apply_patch",
201
+ input: isStart || isEnd ? { id, changes } : undefined,
66
202
  output: isEnd
67
203
  ? {
68
- id: item.id,
69
- changes: item.changes,
70
- status: item.status,
204
+ id,
205
+ status: fileChange.status,
206
+ aggregatedOutput: fileChangeSummary(changes),
71
207
  }
72
208
  : undefined,
73
209
  data: { method, item },
@@ -113,6 +249,30 @@ export function mapCodexItem(method, item) {
113
249
  });
114
250
  return { events };
115
251
  }
252
+ case "imageGeneration": {
253
+ const image = item;
254
+ events.push({
255
+ type: "tool",
256
+ event: isStart ? "on_tool_start" : "on_tool_end",
257
+ name: "image_generation",
258
+ input: isStart ? { id: image.id } : undefined,
259
+ output: isEnd
260
+ ? {
261
+ id: image.id,
262
+ status: image.status,
263
+ generatedImage: {
264
+ ...(!image.savedPath || !isSupportedAbsoluteImagePath(image.savedPath)
265
+ ? { result: image.result }
266
+ : {}),
267
+ revisedPrompt: image.revisedPrompt,
268
+ ...(image.savedPath ? { savedPath: image.savedPath } : {}),
269
+ },
270
+ }
271
+ : undefined,
272
+ data: { method, item: { ...image, result: image.result ? "[image data omitted]" : "" } },
273
+ });
274
+ return { events };
275
+ }
116
276
  case "agentMessage": {
117
277
  const text = item.text?.trim() ?? "";
118
278
  if (isEnd && text) {
@@ -122,7 +282,11 @@ export function mapCodexItem(method, item) {
122
282
  return { events };
123
283
  }
124
284
  case "plan": {
125
- events.push({ type: "runtime_debug", channel: "codexEvent", data: { method, item } });
285
+ const text = item.text?.trim() ?? "";
286
+ if (isEnd && text) {
287
+ events.push({ type: "message_completed", itemId: item.id, text });
288
+ return { events, finalText: text };
289
+ }
126
290
  return { events };
127
291
  }
128
292
  case "reasoning": {
@@ -151,23 +315,18 @@ export function mapCodexNotification(method, params) {
151
315
  return { events };
152
316
  case "turn/started":
153
317
  return { events };
154
- case "turn/completed": {
318
+ case "turn/completed":
319
+ case "turn/failed": {
155
320
  const turnPayload = typed.params?.turn;
156
- if (turnPayload?.status === "failed") {
157
- return {
158
- events,
159
- fatalError: turnError(turnPayload.error, "Codex turn failed"),
160
- turnCompleted: true,
161
- };
162
- }
163
- if (turnPayload?.status === "interrupted") {
164
- return {
165
- events,
166
- fatalError: turnError(turnPayload.error, "Codex turn was interrupted"),
167
- turnCompleted: true,
168
- };
169
- }
170
- return { events, turnCompleted: true };
321
+ const fatalError = terminalTurnError(turnPayload, typed.method);
322
+ const turnInterrupted = typed.method === "turn/completed" &&
323
+ ["interrupted", "cancelled", "canceled"].includes(codexTurnStatus(turnPayload) ?? "");
324
+ return {
325
+ events,
326
+ ...(fatalError ? { fatalError } : {}),
327
+ turnCompleted: true,
328
+ ...(turnInterrupted ? { turnInterrupted: true } : {}),
329
+ };
171
330
  }
172
331
  case "turn/plan/updated": {
173
332
  const planParams = typed.params;
@@ -195,6 +354,17 @@ export function mapCodexNotification(method, params) {
195
354
  }
196
355
  return { events };
197
356
  }
357
+ case "item/plan/delta": {
358
+ const delta = typed.params.delta ?? "";
359
+ if (delta) {
360
+ events.push({
361
+ type: "token",
362
+ text: delta,
363
+ metadata: { source: "app_server", itemId: typed.params.itemId },
364
+ });
365
+ }
366
+ return { events };
367
+ }
198
368
  case "item/reasoning/summaryTextDelta":
199
369
  case "item/reasoning/textDelta": {
200
370
  const p = typed.params;
@@ -0,0 +1,13 @@
1
+ import type { CodexLineageRuntime } from "../codex-home.js";
2
+ export interface McpStartupPlan {
3
+ servers: string[];
4
+ settleTimeoutMs: number;
5
+ }
6
+ /**
7
+ * Read the MCP table fields needed for startup tracking. Provider config stays
8
+ * authoritative: malformed TOML disables the synthesized status rather than
9
+ * preventing the native CLI from reporting its own startup error.
10
+ */
11
+ export declare function parseMcpStartupToml(input: string): McpStartupPlan | null;
12
+ /** Enabled Provider-configured MCP servers and their synthesized settle window. */
13
+ export declare function readMcpStartupPlan(runtimeHome: string, runtime: CodexLineageRuntime): McpStartupPlan | null;
@@ -0,0 +1,63 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse } from "smol-toml";
4
+ const DEFAULT_SERVER_TIMEOUT_MS = 10_000;
5
+ const SETTLE_GRACE_MS = 15_000;
6
+ const MAX_SETTLE_TIMEOUT_MS = 240_000;
7
+ function configNames(runtime) {
8
+ return runtime === "codex"
9
+ ? ["config.toml"]
10
+ : ["traecli.toml"];
11
+ }
12
+ function isTomlTable(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ /**
16
+ * Read the MCP table fields needed for startup tracking. Provider config stays
17
+ * authoritative: malformed TOML disables the synthesized status rather than
18
+ * preventing the native CLI from reporting its own startup error.
19
+ */
20
+ export function parseMcpStartupToml(input) {
21
+ let config;
22
+ try {
23
+ config = parse(input);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ const servers = config.mcp_servers;
29
+ if (!isTomlTable(servers))
30
+ return null;
31
+ const enabled = Object.entries(servers)
32
+ .filter(([name, server]) => Boolean(name) && isTomlTable(server) && server.enabled !== false)
33
+ .sort(([left], [right]) => left.localeCompare(right));
34
+ if (enabled.length === 0)
35
+ return null;
36
+ const slowest = Math.max(DEFAULT_SERVER_TIMEOUT_MS, ...enabled.map(([, server]) => {
37
+ if (!isTomlTable(server))
38
+ return DEFAULT_SERVER_TIMEOUT_MS;
39
+ const seconds = server.startup_timeout_sec;
40
+ return typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0
41
+ ? seconds * 1_000
42
+ : DEFAULT_SERVER_TIMEOUT_MS;
43
+ }));
44
+ return {
45
+ servers: enabled.map(([name]) => name),
46
+ settleTimeoutMs: Math.min(slowest + SETTLE_GRACE_MS, MAX_SETTLE_TIMEOUT_MS),
47
+ };
48
+ }
49
+ /** Enabled Provider-configured MCP servers and their synthesized settle window. */
50
+ export function readMcpStartupPlan(runtimeHome, runtime) {
51
+ for (const name of configNames(runtime)) {
52
+ try {
53
+ const plan = parseMcpStartupToml(readFileSync(join(runtimeHome, name), "utf8"));
54
+ if (plan)
55
+ return plan;
56
+ }
57
+ catch {
58
+ // Missing/unreadable config: the Provider still owns startup; Rynx simply
59
+ // cannot synthesize per-server progress for this launch.
60
+ }
61
+ }
62
+ return null;
63
+ }
@@ -0,0 +1,36 @@
1
+ export interface RuntimeProcessEntry {
2
+ pid: number;
3
+ pgid: number;
4
+ ownerPid: number;
5
+ /** OS process-start identity. Unlike a PID, this cannot silently refer to a
6
+ * later process after the launcher crashes and the PID is recycled. */
7
+ ownerIdentity?: string;
8
+ sessionTag: string;
9
+ stateDir: string;
10
+ }
11
+ export declare function runtimeProcessRegistryPath(): string;
12
+ export declare function runtimeProcessTagArg(sessionTag: string): string;
13
+ export declare function runtimeProcessArgv0(cliPath: string, sessionTag: string): string;
14
+ export declare function withRuntimeProcessStateArg(baseArgs: string[], stateDir: string): string[];
15
+ export declare function registerRuntimeProcess(entry: RuntimeProcessEntry, registryPath?: string): void;
16
+ export declare function unregisterRuntimeProcess(sessionTag: string, registryPath?: string): void;
17
+ export interface RuntimeProcessReconcileOptions {
18
+ registryPath?: string;
19
+ ownerAlive?: (pid: number) => boolean;
20
+ ownerIdentity?: (pid: number) => string;
21
+ childAlive?: (pid: number) => boolean;
22
+ commandLine?: (pid: number) => string;
23
+ terminateGroup?: (pgid: number) => boolean;
24
+ }
25
+ export declare function reconcileRuntimeProcesses(opts?: RuntimeProcessReconcileOptions): number;
26
+ export declare function reapRuntimeProcessesForStateDir(stateDir: string, opts?: RuntimeProcessStateReapOptions): number;
27
+ export interface RuntimeProcessStateReapOptions {
28
+ processListing?: () => string;
29
+ currentPgid?: () => number;
30
+ childAlive?: (pid: number) => boolean;
31
+ signalGroup?: (pgid: number, signal: NodeJS.Signals) => boolean;
32
+ graceMs?: number;
33
+ }
34
+ /** Return an OS process-start identity used to distinguish PID reuse. */
35
+ export declare function runtimeProcessOwnerIdentity(pid: number): string;
36
+ export declare function newRuntimeProcessTag(): string;