@rynx-ai/runtime 0.1.10 → 0.1.11-beta.10
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/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.js +3 -8
- package/dist/claude/native-integration.d.ts +12 -1
- package/dist/claude/native-integration.js +16 -2
- package/dist/claude/transcript.d.ts +0 -7
- package/dist/claude/transcript.js +6 -20
- package/dist/codex-app-server/client.d.ts +2 -1
- package/dist/codex-app-server/forwarder.d.ts +4 -1
- package/dist/codex-app-server/forwarder.js +19 -1
- package/dist/codex-app-server/protocol.d.ts +45 -1
- package/dist/codex-home.d.ts +9 -26
- package/dist/codex-home.js +37 -65
- package/dist/codex-session-store.d.ts +22 -10
- package/dist/codex-session-store.js +277 -12
- package/dist/host.d.ts +48 -47
- package/dist/host.js +810 -355
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/models-catalog.d.ts +3 -1
- package/dist/models-catalog.js +125 -4
- package/dist/provider-workspace.d.ts +56 -0
- package/dist/provider-workspace.js +83 -0
- package/dist/runner/child.d.ts +59 -6
- package/dist/runner/child.js +138 -19
- package/dist/runner/manager.d.ts +104 -19
- package/dist/runner/manager.js +922 -89
- package/dist/runner/protocol.d.ts +7 -18
- package/dist/runner-main.js +12 -4
- package/dist/runtime-state-paths.d.ts +10 -0
- package/dist/runtime-state-paths.js +53 -0
- package/dist/terminal/claude-tui.d.ts +10 -1
- package/dist/terminal/claude-tui.js +9 -1
- package/dist/terminal/codex-tui.d.ts +5 -1
- package/dist/terminal/codex-tui.js +12 -3
- package/dist/terminal/tmux.d.ts +11 -1
- package/dist/terminal/tmux.js +61 -12
- package/package.json +3 -3
- package/dist/codex/rollout-synth.d.ts +0 -42
- package/dist/codex/rollout-synth.js +0 -245
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Synthesize a codex rollout file from rynx's canonical session log, so
|
|
3
|
-
* `codex --remote resume <threadId>` works when the local rollout is missing
|
|
4
|
-
* (fork / worktree / cross-machine). Ports reference implementation's
|
|
5
|
-
* `_ensure_local_codex_resume_rollout`, adapted to rynx: the daemon (control-api)
|
|
6
|
-
* has BOTH the session items (`SessionLogStore`) and — since the private CODEX_HOME
|
|
7
|
-
* is a deterministic uid-scoped path — the app-server's rollout dir, so this runs
|
|
8
|
-
* entirely daemon-side with no runner round-trip.
|
|
9
|
-
*
|
|
10
|
-
* The record shapes match codex 0.142.5 (empirically captured). The minimal set
|
|
11
|
-
* codex needs to resume with VISIBLE turns is: a `session_meta` first line, then
|
|
12
|
-
* per item a `response_item`, and — critically — an `event_msg` mirror per message
|
|
13
|
-
* (codex ≥0.136 renders an empty thread without them). `turn_context` groups items
|
|
14
|
-
* into turns.
|
|
15
|
-
*/
|
|
16
|
-
import { createHash } from "node:crypto";
|
|
17
|
-
import { copyFileSync, mkdirSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
18
|
-
import { join, relative } from "node:path";
|
|
19
|
-
import { getRuntimeProfile, resolveRuntimeHome, rynxHome, } from "@rynx-ai/core";
|
|
20
|
-
import { legacyCodexHomePath, runtimeHomePath, } from "../codex-home.js";
|
|
21
|
-
/** codex validates the thread id straight into a filename + resume arg. */
|
|
22
|
-
const THREAD_ID_RE = /^[0-9a-fA-F-]+$/;
|
|
23
|
-
function sessionsRoot(runtimeHome, runtime) {
|
|
24
|
-
return join(runtimeHome, ...getRuntimeProfile(runtime).sessionsSubpath);
|
|
25
|
-
}
|
|
26
|
-
/** Locate an existing rollout for `threadId` under the runtime's sessions root. */
|
|
27
|
-
export function findCodexRollout(runtimeHome, threadId, runtime = "codex") {
|
|
28
|
-
const root = sessionsRoot(runtimeHome, runtime);
|
|
29
|
-
const suffix = `-${threadId}.jsonl`;
|
|
30
|
-
const stack = [root];
|
|
31
|
-
while (stack.length) {
|
|
32
|
-
const dir = stack.pop();
|
|
33
|
-
let entries;
|
|
34
|
-
try {
|
|
35
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
continue; // missing dir — fine
|
|
39
|
-
}
|
|
40
|
-
for (const e of entries) {
|
|
41
|
-
const full = join(dir, e.name);
|
|
42
|
-
if (e.isDirectory())
|
|
43
|
-
stack.push(full);
|
|
44
|
-
else if (e.name.startsWith("rollout-") && e.name.endsWith(suffix))
|
|
45
|
-
return full;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Back-compat: before per-session homes, rollouts lived under one shared
|
|
52
|
-
* {@link legacyCodexHomePath}. If a session's rollout is only there, copy it forward
|
|
53
|
-
* into this session's home (preserving the `sessions/YYYY/MM/DD/` layout codex
|
|
54
|
-
* expects) so the per-session app-server can resume it. Best-effort; gated by
|
|
55
|
-
* `RYNX_CODEX_HOME_LEGACY_FALLBACK` (default on; set `0`/`false` to disable).
|
|
56
|
-
*/
|
|
57
|
-
function adoptLegacyRollout(runtimeHome, threadId, runtime) {
|
|
58
|
-
const flag = (process.env.RYNX_RUNTIME_HOME_LEGACY_FALLBACK
|
|
59
|
-
?? process.env.RYNX_CODEX_HOME_LEGACY_FALLBACK)?.trim().toLowerCase();
|
|
60
|
-
if (flag === "0" || flag === "false")
|
|
61
|
-
return false;
|
|
62
|
-
const legacy = runtime === "codex"
|
|
63
|
-
? legacyCodexHomePath()
|
|
64
|
-
: resolveRuntimeHome(getRuntimeProfile(runtime));
|
|
65
|
-
if (legacy === runtimeHome)
|
|
66
|
-
return false;
|
|
67
|
-
const src = findCodexRollout(legacy, threadId, runtime);
|
|
68
|
-
if (!src)
|
|
69
|
-
return false;
|
|
70
|
-
const rel = relative(sessionsRoot(legacy, runtime), src);
|
|
71
|
-
const dst = join(sessionsRoot(runtimeHome, runtime), rel);
|
|
72
|
-
try {
|
|
73
|
-
mkdirSync(join(dst, ".."), { recursive: true, mode: 0o700 });
|
|
74
|
-
copyFileSync(src, dst);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function iso(ms) {
|
|
82
|
-
return new Date(ms).toISOString();
|
|
83
|
-
}
|
|
84
|
-
/** `resp_codex_<turnId>` → `<turnId>` (rynx derives the responseId from the codex
|
|
85
|
-
* turn id); anything else is used verbatim as a stable turn key. */
|
|
86
|
-
function turnIdOf(responseId) {
|
|
87
|
-
return responseId.startsWith("resp_codex_") ? responseId.slice("resp_codex_".length) : responseId;
|
|
88
|
-
}
|
|
89
|
-
function textOf(item) {
|
|
90
|
-
return item.data.content
|
|
91
|
-
.filter((part) => "text" in part)
|
|
92
|
-
.map((part) => part.text)
|
|
93
|
-
.join("");
|
|
94
|
-
}
|
|
95
|
-
/** Convert one canonical {@link SessionItem} to its codex `response_item` payload,
|
|
96
|
-
* or null for types codex doesn't carry (reasoning/terminal_command/error). */
|
|
97
|
-
function responseItemPayload(item) {
|
|
98
|
-
switch (item.type) {
|
|
99
|
-
case "message": {
|
|
100
|
-
const apiType = item.data.role === "assistant" ? "output_text" : "input_text";
|
|
101
|
-
const content = item.data.content.flatMap((part) => "text" in part && part.text
|
|
102
|
-
? [{ type: apiType, text: part.text }]
|
|
103
|
-
: []);
|
|
104
|
-
if (content.length === 0)
|
|
105
|
-
return null;
|
|
106
|
-
return { type: "message", role: item.data.role, content };
|
|
107
|
-
}
|
|
108
|
-
case "function_call":
|
|
109
|
-
return {
|
|
110
|
-
type: "function_call",
|
|
111
|
-
name: item.data.name,
|
|
112
|
-
call_id: item.data.callId,
|
|
113
|
-
arguments: item.data.arguments,
|
|
114
|
-
};
|
|
115
|
-
case "function_call_output":
|
|
116
|
-
return { type: "function_call_output", call_id: item.data.callId, output: item.data.output };
|
|
117
|
-
default:
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/** The `event_msg` mirror for a message (required for a VISIBLE turn on codex ≥0.136). */
|
|
122
|
-
function eventMsgPayload(item, sessionId) {
|
|
123
|
-
const message = textOf(item).trim();
|
|
124
|
-
const localImages = item.data.role === "user" && sessionId
|
|
125
|
-
? item.data.content.flatMap((part) => part.type === "input_image" ? [resourcePath(sessionId, part)] : [])
|
|
126
|
-
: [];
|
|
127
|
-
if (!message && localImages.length === 0)
|
|
128
|
-
return null;
|
|
129
|
-
if (item.data.role === "user") {
|
|
130
|
-
return {
|
|
131
|
-
type: "user_message",
|
|
132
|
-
message,
|
|
133
|
-
images: [],
|
|
134
|
-
local_images: localImages,
|
|
135
|
-
text_elements: [],
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (item.data.role === "assistant") {
|
|
139
|
-
return { type: "agent_message", message, phase: "final_answer", memory_citation: null };
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
function resourcePath(sessionId, part) {
|
|
144
|
-
const sessionKey = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
145
|
-
const extension = part.mediaType === "image/png"
|
|
146
|
-
? "png"
|
|
147
|
-
: part.mediaType === "image/jpeg"
|
|
148
|
-
? "jpg"
|
|
149
|
-
: "webp";
|
|
150
|
-
return join(rynxHome(), "resources", sessionKey, `${part.resourceId}.${extension}`);
|
|
151
|
-
}
|
|
152
|
-
/** Build the ordered rollout records (session_meta first, then per-turn
|
|
153
|
-
* turn_context + per-item response_item + per-message event_msg). */
|
|
154
|
-
export function buildRolloutRecords(opts) {
|
|
155
|
-
const now = opts.now ?? (() => Date.now());
|
|
156
|
-
const modelProvider = opts.modelProvider ?? (opts.runtime === "traex" ? "trae" : "openai");
|
|
157
|
-
const cliVersion = opts.cliVersion ?? "0.0.0";
|
|
158
|
-
const metaTs = iso(opts.items[0]?.createdAt ?? now());
|
|
159
|
-
const records = [
|
|
160
|
-
{
|
|
161
|
-
timestamp: metaTs,
|
|
162
|
-
type: "session_meta",
|
|
163
|
-
payload: {
|
|
164
|
-
id: opts.threadId,
|
|
165
|
-
session_id: opts.threadId,
|
|
166
|
-
timestamp: metaTs,
|
|
167
|
-
cwd: opts.cwd,
|
|
168
|
-
originator: "rynx",
|
|
169
|
-
cli_version: cliVersion,
|
|
170
|
-
source: "rynx",
|
|
171
|
-
thread_source: "user",
|
|
172
|
-
model_provider: modelProvider,
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
];
|
|
176
|
-
const seenTurns = new Set();
|
|
177
|
-
for (const item of opts.items) {
|
|
178
|
-
const payload = responseItemPayload(item);
|
|
179
|
-
const eventPayload = item.type === "message"
|
|
180
|
-
? eventMsgPayload(item, opts.sessionId)
|
|
181
|
-
: null;
|
|
182
|
-
if (!payload && !eventPayload)
|
|
183
|
-
continue;
|
|
184
|
-
const ts = iso(item.createdAt);
|
|
185
|
-
const turnId = turnIdOf(item.responseId);
|
|
186
|
-
if (!seenTurns.has(turnId)) {
|
|
187
|
-
seenTurns.add(turnId);
|
|
188
|
-
records.push({
|
|
189
|
-
timestamp: ts,
|
|
190
|
-
type: "turn_context",
|
|
191
|
-
payload: { turn_id: turnId, cwd: opts.cwd, approval_policy: "on-request" },
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
if (payload)
|
|
195
|
-
records.push({ timestamp: ts, type: "response_item", payload });
|
|
196
|
-
if (eventPayload)
|
|
197
|
-
records.push({ timestamp: ts, type: "event_msg", payload: eventPayload });
|
|
198
|
-
}
|
|
199
|
-
return records;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Ensure a resumable codex rollout exists for `threadId`. No-op if one is already
|
|
203
|
-
* present (codex owns its own runtime rollouts). Otherwise synthesize one from the
|
|
204
|
-
* session items and write it atomically. Returns `"exists"`, `"written"`, or
|
|
205
|
-
* `"skipped"` (invalid thread id / nothing to carry). Best-effort: never throws.
|
|
206
|
-
*/
|
|
207
|
-
export function ensureCodexResumeRollout(opts) {
|
|
208
|
-
if (!THREAD_ID_RE.test(opts.threadId))
|
|
209
|
-
return "skipped";
|
|
210
|
-
const runtime = opts.runtime ?? "codex";
|
|
211
|
-
const runtimeHome = opts.runtimeHome
|
|
212
|
-
?? opts.codexHome
|
|
213
|
-
?? (opts.sessionId ? runtimeHomePath(opts.sessionId, runtime) : undefined);
|
|
214
|
-
if (!runtimeHome)
|
|
215
|
-
return "skipped"; // no session context → can't locate the home
|
|
216
|
-
if (findCodexRollout(runtimeHome, opts.threadId, runtime))
|
|
217
|
-
return "exists";
|
|
218
|
-
// Back-compat: a pre-per-session rollout may live under the OLD shared home. Copy
|
|
219
|
-
// it forward into this session's home so the per-session app-server can resume it.
|
|
220
|
-
if (adoptLegacyRollout(runtimeHome, opts.threadId, runtime))
|
|
221
|
-
return "exists";
|
|
222
|
-
const records = buildRolloutRecords(opts);
|
|
223
|
-
// Nothing but the session_meta header → no history to carry; skip (a fresh
|
|
224
|
-
// thread resumes fine without a rollout once it has run a turn).
|
|
225
|
-
if (records.length <= 1)
|
|
226
|
-
return "skipped";
|
|
227
|
-
const now = opts.now ?? (() => Date.now());
|
|
228
|
-
const d = new Date(now());
|
|
229
|
-
const yyyy = String(d.getUTCFullYear());
|
|
230
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
231
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
232
|
-
const stamp = iso(d.getTime()).slice(0, 19).replace(/:/g, "-"); // YYYY-MM-DDTHH-MM-SS
|
|
233
|
-
const dir = join(sessionsRoot(runtimeHome, runtime), yyyy, mm, dd);
|
|
234
|
-
const target = join(dir, `rollout-${stamp}-${opts.threadId}.jsonl`);
|
|
235
|
-
const tmp = `${target}.tmp`;
|
|
236
|
-
try {
|
|
237
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
238
|
-
writeFileSync(tmp, records.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
239
|
-
renameSync(tmp, target);
|
|
240
|
-
return "written";
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
return "skipped"; // best-effort: a failed synth just leaves resume to fail as before
|
|
244
|
-
}
|
|
245
|
-
}
|