@rynx-ai/runtime 0.1.11-beta.5 → 0.1.11-beta.51

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 +460 -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 +1571 -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 +183 -46
  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 +96 -3
  20. package/dist/codex-app-server/forwarder.js +542 -57
  21. package/dist/codex-app-server/mapping.d.ts +3 -6
  22. package/dist/codex-app-server/mapping.js +228 -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 +68 -7
  28. package/dist/codex-app-server/transport.d.ts +2 -2
  29. package/dist/codex-app-server/transport.js +4 -0
  30. package/dist/codex-app-server/ws-channel.d.ts +8 -1
  31. package/dist/codex-app-server/ws-channel.js +113 -28
  32. package/dist/codex-child-env.js +3 -32
  33. package/dist/codex-home.d.ts +35 -3
  34. package/dist/codex-home.js +323 -18
  35. package/dist/codex-session-store.d.ts +23 -0
  36. package/dist/codex-session-store.js +21 -0
  37. package/dist/host.d.ts +106 -48
  38. package/dist/host.js +2024 -640
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +1 -1
  41. package/dist/input-resources.d.ts +4 -0
  42. package/dist/input-resources.js +21 -5
  43. package/dist/models-catalog.js +12 -3
  44. package/dist/runner/child.d.ts +105 -28
  45. package/dist/runner/child.js +1625 -100
  46. package/dist/runner/manager.d.ts +114 -46
  47. package/dist/runner/manager.js +1523 -446
  48. package/dist/runner/protocol.d.ts +212 -24
  49. package/dist/runner/protocol.js +5 -0
  50. package/dist/runner/startup-policy.d.ts +7 -0
  51. package/dist/runner/startup-policy.js +10 -0
  52. package/dist/runner/transport.d.ts +18 -2
  53. package/dist/runner/transport.js +82 -3
  54. package/dist/runner-main.js +8 -3
  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";
8
83
  }
9
- return new Error(fallback);
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");
101
+ }
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,52 @@ export function mapCodexItem(method, item) {
113
249
  });
114
250
  return { events };
115
251
  }
252
+ case "imageView": {
253
+ const image = item;
254
+ if (typeof image.id !== "string" || !image.id ||
255
+ typeof image.path !== "string" || !image.path)
256
+ return { events };
257
+ events.push({
258
+ type: "tool",
259
+ event: isStart ? "on_tool_start" : "on_tool_end",
260
+ name: "view_image",
261
+ // Image views may arrive only as completed items, including backfill.
262
+ input: isStart || isEnd ? { id: image.id, path: image.path } : undefined,
263
+ output: isEnd
264
+ ? {
265
+ id: image.id,
266
+ aggregatedOutput: `Viewed image at ${image.path}`,
267
+ generatedImage: { savedPath: image.path },
268
+ }
269
+ : undefined,
270
+ data: { method, item },
271
+ });
272
+ return { events };
273
+ }
274
+ case "imageGeneration": {
275
+ const image = item;
276
+ events.push({
277
+ type: "tool",
278
+ event: isStart ? "on_tool_start" : "on_tool_end",
279
+ name: "image_generation",
280
+ input: isStart ? { id: image.id } : undefined,
281
+ output: isEnd
282
+ ? {
283
+ id: image.id,
284
+ status: image.status,
285
+ generatedImage: {
286
+ ...(!image.savedPath || !isSupportedAbsoluteImagePath(image.savedPath)
287
+ ? { result: image.result }
288
+ : {}),
289
+ revisedPrompt: image.revisedPrompt,
290
+ ...(image.savedPath ? { savedPath: image.savedPath } : {}),
291
+ },
292
+ }
293
+ : undefined,
294
+ data: { method, item: { ...image, result: image.result ? "[image data omitted]" : "" } },
295
+ });
296
+ return { events };
297
+ }
116
298
  case "agentMessage": {
117
299
  const text = item.text?.trim() ?? "";
118
300
  if (isEnd && text) {
@@ -122,7 +304,11 @@ export function mapCodexItem(method, item) {
122
304
  return { events };
123
305
  }
124
306
  case "plan": {
125
- events.push({ type: "runtime_debug", channel: "codexEvent", data: { method, item } });
307
+ const text = item.text?.trim() ?? "";
308
+ if (isEnd && text) {
309
+ events.push({ type: "message_completed", itemId: item.id, text });
310
+ return { events, finalText: text };
311
+ }
126
312
  return { events };
127
313
  }
128
314
  case "reasoning": {
@@ -151,23 +337,18 @@ export function mapCodexNotification(method, params) {
151
337
  return { events };
152
338
  case "turn/started":
153
339
  return { events };
154
- case "turn/completed": {
340
+ case "turn/completed":
341
+ case "turn/failed": {
155
342
  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 };
343
+ const fatalError = terminalTurnError(turnPayload, typed.method);
344
+ const turnInterrupted = typed.method === "turn/completed" &&
345
+ ["interrupted", "cancelled", "canceled"].includes(codexTurnStatus(turnPayload) ?? "");
346
+ return {
347
+ events,
348
+ ...(fatalError ? { fatalError } : {}),
349
+ turnCompleted: true,
350
+ ...(turnInterrupted ? { turnInterrupted: true } : {}),
351
+ };
171
352
  }
172
353
  case "turn/plan/updated": {
173
354
  const planParams = typed.params;
@@ -195,6 +376,17 @@ export function mapCodexNotification(method, params) {
195
376
  }
196
377
  return { events };
197
378
  }
379
+ case "item/plan/delta": {
380
+ const delta = typed.params.delta ?? "";
381
+ if (delta) {
382
+ events.push({
383
+ type: "token",
384
+ text: delta,
385
+ metadata: { source: "app_server", itemId: typed.params.itemId },
386
+ });
387
+ }
388
+ return { events };
389
+ }
198
390
  case "item/reasoning/summaryTextDelta":
199
391
  case "item/reasoning/textDelta": {
200
392
  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;