@rynx-ai/runtime 0.1.11-beta.3 → 0.1.11-beta.30
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.
- package/dist/claude/executor.d.ts +19 -5
- package/dist/claude/executor.js +56 -12
- package/dist/claude/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.d.ts +2 -0
- package/dist/claude/native-bridge.js +23 -0
- package/dist/claude/native-hook-main.js +62 -0
- package/dist/claude/native-integration.d.ts +50 -10
- package/dist/claude/native-integration.js +262 -37
- package/dist/claude/session-status.d.ts +39 -0
- package/dist/claude/session-status.js +163 -0
- package/dist/claude/transcript.js +27 -17
- package/dist/codex-app-server/client.d.ts +10 -6
- package/dist/codex-app-server/client.js +67 -15
- package/dist/codex-app-server/forwarder.d.ts +92 -3
- package/dist/codex-app-server/forwarder.js +509 -56
- package/dist/codex-app-server/mapping.d.ts +3 -6
- package/dist/codex-app-server/mapping.js +174 -28
- package/dist/codex-app-server/mcp-startup.d.ts +13 -0
- package/dist/codex-app-server/mcp-startup.js +63 -0
- package/dist/codex-app-server/protocol.d.ts +64 -7
- package/dist/codex-app-server/ws-channel.js +19 -19
- package/dist/codex-home.js +2 -4
- package/dist/host.d.ts +64 -21
- package/dist/host.js +1330 -441
- package/dist/index.d.ts +1 -1
- package/dist/input-resources.d.ts +4 -0
- package/dist/input-resources.js +21 -5
- package/dist/models-catalog.d.ts +2 -1
- package/dist/models-catalog.js +94 -6
- package/dist/runner/child.d.ts +48 -21
- package/dist/runner/child.js +550 -48
- package/dist/runner/manager.d.ts +54 -13
- package/dist/runner/manager.js +479 -114
- package/dist/runner/protocol.d.ts +62 -19
- package/dist/runner/protocol.js +5 -0
- package/dist/runner/startup-policy.d.ts +7 -0
- package/dist/runner/startup-policy.js +10 -0
- package/dist/terminal/claude-tui.d.ts +3 -1
- package/dist/terminal/claude-tui.js +3 -1
- package/dist/terminal/registry.js +3 -2
- package/dist/terminal/tmux.d.ts +50 -7
- package/dist/terminal/tmux.js +168 -47
- package/package.json +4 -3
|
@@ -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 (
|
|
3
|
-
return new Error(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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");
|
|
8
95
|
}
|
|
9
|
-
|
|
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,23 @@ 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
|
+
const kind = isRecord(change.kind) && typeof change.kind.type === "string" && change.kind.type
|
|
131
|
+
? change.kind.type
|
|
132
|
+
: "change";
|
|
133
|
+
lines.push(`${kind} ${String(change.path)}`);
|
|
134
|
+
}
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
function isSupportedAbsoluteImagePath(path) {
|
|
138
|
+
if (!isAbsolute(path))
|
|
139
|
+
return false;
|
|
140
|
+
return [".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg"].includes(extname(path).toLowerCase());
|
|
141
|
+
}
|
|
32
142
|
/** Map one thread item (`item/started` | `item/completed`) to events. */
|
|
33
143
|
export function mapCodexItem(method, item) {
|
|
34
144
|
const events = [];
|
|
@@ -58,16 +168,18 @@ export function mapCodexItem(method, item) {
|
|
|
58
168
|
return { events };
|
|
59
169
|
}
|
|
60
170
|
case "fileChange": {
|
|
171
|
+
const fileChange = item;
|
|
172
|
+
const changes = Array.isArray(fileChange.changes) ? fileChange.changes : [];
|
|
61
173
|
events.push({
|
|
62
174
|
type: "tool",
|
|
63
175
|
event: isStart ? "on_tool_start" : "on_tool_end",
|
|
64
|
-
name: "
|
|
65
|
-
input: isStart ? { id: item.id } : undefined,
|
|
176
|
+
name: "apply_patch",
|
|
177
|
+
input: isStart || isEnd ? { id: item.id, ...(changes.length ? { changes } : {}) } : undefined,
|
|
66
178
|
output: isEnd
|
|
67
179
|
? {
|
|
68
180
|
id: item.id,
|
|
69
|
-
|
|
70
|
-
|
|
181
|
+
status: fileChange.status,
|
|
182
|
+
aggregatedOutput: fileChangeSummary(changes),
|
|
71
183
|
}
|
|
72
184
|
: undefined,
|
|
73
185
|
data: { method, item },
|
|
@@ -113,6 +225,30 @@ export function mapCodexItem(method, item) {
|
|
|
113
225
|
});
|
|
114
226
|
return { events };
|
|
115
227
|
}
|
|
228
|
+
case "imageGeneration": {
|
|
229
|
+
const image = item;
|
|
230
|
+
events.push({
|
|
231
|
+
type: "tool",
|
|
232
|
+
event: isStart ? "on_tool_start" : "on_tool_end",
|
|
233
|
+
name: "image_generation",
|
|
234
|
+
input: isStart ? { id: image.id } : undefined,
|
|
235
|
+
output: isEnd
|
|
236
|
+
? {
|
|
237
|
+
id: image.id,
|
|
238
|
+
status: image.status,
|
|
239
|
+
generatedImage: {
|
|
240
|
+
...(!image.savedPath || !isSupportedAbsoluteImagePath(image.savedPath)
|
|
241
|
+
? { result: image.result }
|
|
242
|
+
: {}),
|
|
243
|
+
revisedPrompt: image.revisedPrompt,
|
|
244
|
+
...(image.savedPath ? { savedPath: image.savedPath } : {}),
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
: undefined,
|
|
248
|
+
data: { method, item: { ...image, result: image.result ? "[image data omitted]" : "" } },
|
|
249
|
+
});
|
|
250
|
+
return { events };
|
|
251
|
+
}
|
|
116
252
|
case "agentMessage": {
|
|
117
253
|
const text = item.text?.trim() ?? "";
|
|
118
254
|
if (isEnd && text) {
|
|
@@ -122,7 +258,11 @@ export function mapCodexItem(method, item) {
|
|
|
122
258
|
return { events };
|
|
123
259
|
}
|
|
124
260
|
case "plan": {
|
|
125
|
-
|
|
261
|
+
const text = item.text?.trim() ?? "";
|
|
262
|
+
if (isEnd && text) {
|
|
263
|
+
events.push({ type: "message_completed", itemId: item.id, text });
|
|
264
|
+
return { events, finalText: text };
|
|
265
|
+
}
|
|
126
266
|
return { events };
|
|
127
267
|
}
|
|
128
268
|
case "reasoning": {
|
|
@@ -151,23 +291,18 @@ export function mapCodexNotification(method, params) {
|
|
|
151
291
|
return { events };
|
|
152
292
|
case "turn/started":
|
|
153
293
|
return { events };
|
|
154
|
-
case "turn/completed":
|
|
294
|
+
case "turn/completed":
|
|
295
|
+
case "turn/failed": {
|
|
155
296
|
const turnPayload = typed.params?.turn;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
events,
|
|
166
|
-
fatalError: turnError(turnPayload.error, "Codex turn was interrupted"),
|
|
167
|
-
turnCompleted: true,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
return { events, turnCompleted: true };
|
|
297
|
+
const fatalError = terminalTurnError(turnPayload, typed.method);
|
|
298
|
+
const turnInterrupted = typed.method === "turn/completed" &&
|
|
299
|
+
["interrupted", "cancelled", "canceled"].includes(codexTurnStatus(turnPayload) ?? "");
|
|
300
|
+
return {
|
|
301
|
+
events,
|
|
302
|
+
...(fatalError ? { fatalError } : {}),
|
|
303
|
+
turnCompleted: true,
|
|
304
|
+
...(turnInterrupted ? { turnInterrupted: true } : {}),
|
|
305
|
+
};
|
|
171
306
|
}
|
|
172
307
|
case "turn/plan/updated": {
|
|
173
308
|
const planParams = typed.params;
|
|
@@ -195,6 +330,17 @@ export function mapCodexNotification(method, params) {
|
|
|
195
330
|
}
|
|
196
331
|
return { events };
|
|
197
332
|
}
|
|
333
|
+
case "item/plan/delta": {
|
|
334
|
+
const delta = typed.params.delta ?? "";
|
|
335
|
+
if (delta) {
|
|
336
|
+
events.push({
|
|
337
|
+
type: "token",
|
|
338
|
+
text: delta,
|
|
339
|
+
metadata: { source: "app_server", itemId: typed.params.itemId },
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return { events };
|
|
343
|
+
}
|
|
198
344
|
case "item/reasoning/summaryTextDelta":
|
|
199
345
|
case "item/reasoning/textDelta": {
|
|
200
346
|
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
|
+
}
|
|
@@ -56,6 +56,21 @@ export interface GetAuthStatusResponse {
|
|
|
56
56
|
authToken: string | null;
|
|
57
57
|
requiresOpenaiAuth: boolean;
|
|
58
58
|
}
|
|
59
|
+
/** Read the Provider's effective configuration through the app-server rather
|
|
60
|
+
* than parsing a runtime-specific config file (Traex has used both YAML and
|
|
61
|
+
* TOML across releases). `cwd` includes project-scoped configuration layers. */
|
|
62
|
+
export interface ConfigReadParams {
|
|
63
|
+
includeLayers: boolean;
|
|
64
|
+
cwd?: string | null;
|
|
65
|
+
}
|
|
66
|
+
export interface ConfigReadResponse {
|
|
67
|
+
config: {
|
|
68
|
+
model: string | null;
|
|
69
|
+
model_reasoning_effort: ReasoningEffort | null;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
};
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
59
74
|
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never";
|
|
60
75
|
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
|
61
76
|
export type SandboxPolicy = {
|
|
@@ -87,6 +102,16 @@ export interface PermissionProfileModificationParams {
|
|
|
87
102
|
export type PermissionSelection = string | PermissionProfileSelectionParams;
|
|
88
103
|
/** Open since Codex App Server 0.144; values are advertised by `model/list`. */
|
|
89
104
|
export type ReasoningEffort = string;
|
|
105
|
+
export type CollaborationModeKind = "plan" | "default";
|
|
106
|
+
/** Full Codex-lineage mode snapshot required by the App Server wire protocol. */
|
|
107
|
+
export interface CollaborationMode {
|
|
108
|
+
mode: CollaborationModeKind;
|
|
109
|
+
settings: {
|
|
110
|
+
model: string;
|
|
111
|
+
reasoning_effort: ReasoningEffort | null;
|
|
112
|
+
developer_instructions: string | null;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
90
115
|
export interface ThreadStartParams {
|
|
91
116
|
model?: string | null;
|
|
92
117
|
modelProvider?: string | null;
|
|
@@ -102,14 +127,18 @@ export interface ThreadStartParams {
|
|
|
102
127
|
baseInstructions?: string | null;
|
|
103
128
|
developerInstructions?: string | null;
|
|
104
129
|
ephemeral?: boolean | null;
|
|
130
|
+
/** Native UI intent for a context-clearing fresh thread. */
|
|
131
|
+
sessionStartSource?: "clear" | string;
|
|
105
132
|
}
|
|
106
133
|
export interface ThreadResumeParams extends ThreadStartParams {
|
|
107
134
|
threadId: string;
|
|
108
|
-
/**
|
|
109
|
-
* SUBSCRIBE without re-replaying history). When false/absent, the response
|
|
110
|
-
* carries `thread.turns[].items[]` — the backfill the forwarder replays for a
|
|
111
|
-
* fresh thread's first turn (reference implementation's `_replay_resume_response`). */
|
|
135
|
+
/** Suppress rollout history when the caller only needs to load/subscribe. */
|
|
112
136
|
excludeTurns?: boolean;
|
|
137
|
+
initialTurnsPage?: {
|
|
138
|
+
limit?: number | null;
|
|
139
|
+
sortDirection?: "asc" | "desc" | null;
|
|
140
|
+
itemsView?: "notLoaded" | "summary" | "full" | null;
|
|
141
|
+
} | null;
|
|
113
142
|
}
|
|
114
143
|
/** One turn in a resumed thread's backlog (`thread/resume` response). */
|
|
115
144
|
export interface ResumedTurn {
|
|
@@ -125,6 +154,13 @@ export interface ResumedThread {
|
|
|
125
154
|
turns?: ResumedTurn[];
|
|
126
155
|
[key: string]: unknown;
|
|
127
156
|
}
|
|
157
|
+
/** Settings selected by the Provider while starting or resuming a thread.
|
|
158
|
+
* Older app-server builds may omit them, so the bridge treats them as optional
|
|
159
|
+
* and falls back to `config/read` only for a fresh, not-yet-created thread. */
|
|
160
|
+
export interface ThreadRuntimeSettings {
|
|
161
|
+
model?: string;
|
|
162
|
+
reasoningEffort?: ReasoningEffort | null;
|
|
163
|
+
}
|
|
128
164
|
export interface ThreadDescriptor {
|
|
129
165
|
id: string;
|
|
130
166
|
cwd: string;
|
|
@@ -155,6 +191,7 @@ export interface TurnStartParams {
|
|
|
155
191
|
permissions?: PermissionSelection | null;
|
|
156
192
|
model?: string | null;
|
|
157
193
|
effort?: ReasoningEffort | null;
|
|
194
|
+
collaborationMode?: CollaborationMode | null;
|
|
158
195
|
}
|
|
159
196
|
export interface TurnInterruptParams {
|
|
160
197
|
threadId: string;
|
|
@@ -169,7 +206,7 @@ export interface TurnSteerParams {
|
|
|
169
206
|
expectedTurnId: string;
|
|
170
207
|
input: UserInput[];
|
|
171
208
|
}
|
|
172
|
-
export type TurnStatus = "completed" | "interrupted" | "failed" | "inProgress";
|
|
209
|
+
export type TurnStatus = "completed" | "interrupted" | "cancelled" | "canceled" | "failed" | "errored" | "inProgress";
|
|
173
210
|
export interface TurnPlanStep {
|
|
174
211
|
step: string;
|
|
175
212
|
status: "pending" | "inProgress" | "completed";
|
|
@@ -252,7 +289,16 @@ export interface WebSearchItem extends ThreadItemBase {
|
|
|
252
289
|
type: "other";
|
|
253
290
|
} | null;
|
|
254
291
|
}
|
|
255
|
-
export
|
|
292
|
+
export interface ImageGenerationItem extends ThreadItemBase {
|
|
293
|
+
type: "imageGeneration";
|
|
294
|
+
status: string;
|
|
295
|
+
revisedPrompt: string | null;
|
|
296
|
+
/** Base64 image bytes supplied when no durable saved path is available. */
|
|
297
|
+
result: string;
|
|
298
|
+
/** Codex App Server guarantees this is absolute when present. */
|
|
299
|
+
savedPath?: string;
|
|
300
|
+
}
|
|
301
|
+
export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | ImageGenerationItem | (ThreadItemBase & Record<string, unknown>);
|
|
256
302
|
export interface ThreadSummary {
|
|
257
303
|
id?: string;
|
|
258
304
|
threadId?: string;
|
|
@@ -302,7 +348,7 @@ export interface ThreadSettingsUpdateParams {
|
|
|
302
348
|
serviceTier?: string | null;
|
|
303
349
|
effort?: ReasoningEffort | null;
|
|
304
350
|
summary?: string | null;
|
|
305
|
-
collaborationMode?:
|
|
351
|
+
collaborationMode?: CollaborationMode | null;
|
|
306
352
|
personality?: string | null;
|
|
307
353
|
}
|
|
308
354
|
export interface ModelListParams {
|
|
@@ -476,6 +522,14 @@ export interface QueueStatusNotificationParams {
|
|
|
476
522
|
export type ServerNotification = {
|
|
477
523
|
method: "thread/started";
|
|
478
524
|
params: ThreadStartedNotificationParams;
|
|
525
|
+
} | {
|
|
526
|
+
method: "thread/settings/updated";
|
|
527
|
+
params: {
|
|
528
|
+
threadId: string;
|
|
529
|
+
threadSettings: {
|
|
530
|
+
collaborationMode?: CollaborationMode | null;
|
|
531
|
+
} & Record<string, unknown>;
|
|
532
|
+
};
|
|
479
533
|
} | {
|
|
480
534
|
method: "turn/started";
|
|
481
535
|
params: TurnStartedNotificationParams;
|
|
@@ -497,6 +551,9 @@ export type ServerNotification = {
|
|
|
497
551
|
} | {
|
|
498
552
|
method: "item/agentMessage/delta";
|
|
499
553
|
params: AgentMessageDeltaNotificationParams;
|
|
554
|
+
} | {
|
|
555
|
+
method: "item/plan/delta";
|
|
556
|
+
params: AgentMessageDeltaNotificationParams;
|
|
500
557
|
} | {
|
|
501
558
|
method: "item/reasoning/summaryTextDelta";
|
|
502
559
|
params: ReasoningSummaryTextDeltaNotificationParams;
|
|
@@ -28,6 +28,7 @@ async function freeLoopbackPort() {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
31
32
|
export class WsRpcChannel {
|
|
32
33
|
opts;
|
|
33
34
|
child = null;
|
|
@@ -40,7 +41,7 @@ export class WsRpcChannel {
|
|
|
40
41
|
url = "";
|
|
41
42
|
constructor(opts) {
|
|
42
43
|
this.opts = opts;
|
|
43
|
-
this.readyTimeoutMs = opts.readyTimeoutMs ??
|
|
44
|
+
this.readyTimeoutMs = opts.readyTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
44
45
|
}
|
|
45
46
|
onLine(cb) {
|
|
46
47
|
this.lineCb = cb;
|
|
@@ -114,7 +115,7 @@ export class WsRpcChannel {
|
|
|
114
115
|
// eslint-disable-next-line no-constant-condition
|
|
115
116
|
while (Date.now() < deadline) {
|
|
116
117
|
try {
|
|
117
|
-
return await this.tryConnect(url);
|
|
118
|
+
return await this.tryConnect(url, Math.max(1, deadline - Date.now()));
|
|
118
119
|
}
|
|
119
120
|
catch (error) {
|
|
120
121
|
lastError = error;
|
|
@@ -123,9 +124,9 @@ export class WsRpcChannel {
|
|
|
123
124
|
}
|
|
124
125
|
throw new Error(`codex app-server ws did not become ready at ${url}: ${lastError?.message ?? "timeout"}`);
|
|
125
126
|
}
|
|
126
|
-
tryConnect(url) {
|
|
127
|
+
tryConnect(url, handshakeTimeout) {
|
|
127
128
|
return new Promise((resolve, reject) => {
|
|
128
|
-
const ws = new WebSocket(url);
|
|
129
|
+
const ws = new WebSocket(url, { handshakeTimeout });
|
|
129
130
|
const onOpen = () => {
|
|
130
131
|
ws.off("error", onError);
|
|
131
132
|
resolve(ws);
|
|
@@ -166,7 +167,7 @@ export class ExternalWsChannel {
|
|
|
166
167
|
/** The `ws://IP:PORT` of the already-running app-server to attach to. */
|
|
167
168
|
url, opts = {}) {
|
|
168
169
|
this.url = url;
|
|
169
|
-
this.readyTimeoutMs = opts.readyTimeoutMs ??
|
|
170
|
+
this.readyTimeoutMs = opts.readyTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
170
171
|
}
|
|
171
172
|
onLine(cb) {
|
|
172
173
|
this.lineCb = cb;
|
|
@@ -178,20 +179,16 @@ export class ExternalWsChannel {
|
|
|
178
179
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
179
180
|
}
|
|
180
181
|
async start() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
catch (error) {
|
|
189
|
-
lastError = error;
|
|
190
|
-
await new Promise((r) => setTimeout(r, 150));
|
|
191
|
-
}
|
|
182
|
+
// A channel instance is reusable after an observer disconnect. Do not let
|
|
183
|
+
// the prior closed socket make a failed reconnect look successful, and arm
|
|
184
|
+
// close delivery for the newly connected socket.
|
|
185
|
+
this.ws = null;
|
|
186
|
+
this.closedEmitted = false;
|
|
187
|
+
try {
|
|
188
|
+
this.ws = await this.connect(this.url);
|
|
192
189
|
}
|
|
193
|
-
|
|
194
|
-
throw new Error(`could not attach to app-server ws ${this.url}: ${
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw new Error(`could not attach to app-server ws ${this.url}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
195
192
|
}
|
|
196
193
|
this.ws.on("message", (data) => this.lineCb?.(data.toString()));
|
|
197
194
|
this.ws.on("close", (code) => this.emitClose(code ?? null, null, null));
|
|
@@ -227,7 +224,10 @@ export class ExternalWsChannel {
|
|
|
227
224
|
}
|
|
228
225
|
connect(url) {
|
|
229
226
|
return new Promise((resolve, reject) => {
|
|
230
|
-
|
|
227
|
+
// The app-server owner has already completed its readiness probe. Use one
|
|
228
|
+
// bounded attach, then reuse this exact connection as the forwarder
|
|
229
|
+
// instead of running a second startup retry stage.
|
|
230
|
+
const ws = new WebSocket(url, { handshakeTimeout: this.readyTimeoutMs });
|
|
231
231
|
const onOpen = () => {
|
|
232
232
|
ws.off("error", onError);
|
|
233
233
|
resolve(ws);
|
package/dist/codex-home.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { copyFileSync, cpSync, existsSync, mkdirSync, renameSync, rmSync, symlinkSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { getRuntimeProfile, resolveRuntimeHome,
|
|
5
|
+
import { assertSkillPathComponent, getRuntimeProfile, resolveRuntimeHome, } from "@rynx-ai/core";
|
|
6
6
|
import { adoptLegacyRuntimeDirectory, legacyRuntimeStateRoot, runtimeSessionDigest, runtimeSessionStateDir, } from "./runtime-state-paths.js";
|
|
7
7
|
/** Inherit the user's LIVE login by symlink (stays in sync). */
|
|
8
8
|
const SYMLINK_FILES = ["auth.json"];
|
|
@@ -113,9 +113,7 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
|
|
|
113
113
|
export function populateCodexSkills(codexHome, skills) {
|
|
114
114
|
const skillsDir = join(codexHome, "skills");
|
|
115
115
|
for (const skill of skills) {
|
|
116
|
-
|
|
117
|
-
throw new Error(`unsafe skill name: ${skill.name}`);
|
|
118
|
-
}
|
|
116
|
+
assertSkillPathComponent(skill.name);
|
|
119
117
|
}
|
|
120
118
|
if (skills.length === 0)
|
|
121
119
|
return;
|