@rynx-ai/runtime 0.1.0 → 0.1.10-beta.2
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 +3 -5
- package/dist/claude/executor.js +3 -5
- package/dist/claude/native-bridge.d.ts +74 -17
- package/dist/claude/native-bridge.js +225 -30
- package/dist/claude/native-hook-main.js +327 -38
- package/dist/claude/native-hooks.d.ts +3 -2
- package/dist/claude/native-hooks.js +15 -6
- package/dist/claude/native-integration.d.ts +123 -16
- package/dist/claude/native-integration.js +624 -81
- package/dist/claude/settings.d.ts +8 -0
- package/dist/claude/settings.js +50 -0
- package/dist/claude/transcript.d.ts +2 -2
- package/dist/claude/transcript.js +14 -3
- package/dist/codex/rollout-synth.d.ts +8 -3
- package/dist/codex/rollout-synth.js +65 -32
- package/dist/codex-app-server/client.d.ts +27 -40
- package/dist/codex-app-server/client.js +1134 -99
- package/dist/codex-app-server/forwarder.d.ts +36 -10
- package/dist/codex-app-server/forwarder.js +146 -28
- package/dist/codex-app-server/mapping.d.ts +1 -1
- package/dist/codex-app-server/mapping.js +64 -5
- package/dist/codex-app-server/protocol.d.ts +269 -4
- package/dist/codex-app-server/transport.d.ts +20 -5
- package/dist/codex-app-server/transport.js +93 -40
- package/dist/codex-app-server/ws-channel.d.ts +3 -3
- package/dist/codex-app-server/ws-channel.js +23 -7
- package/dist/codex-child-env.js +33 -0
- package/dist/codex-home.d.ts +16 -6
- package/dist/codex-home.js +46 -15
- package/dist/codex-session-store.d.ts +2 -1
- package/dist/host.d.ts +38 -38
- package/dist/host.js +626 -121
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/input-resources.d.ts +13 -0
- package/dist/input-resources.js +67 -0
- package/dist/interactions.d.ts +61 -0
- package/dist/interactions.js +236 -0
- package/dist/models-catalog.d.ts +5 -13
- package/dist/models-catalog.js +60 -9
- package/dist/runner/child.d.ts +9 -1
- package/dist/runner/child.js +100 -19
- package/dist/runner/manager.d.ts +79 -11
- package/dist/runner/manager.js +423 -43
- package/dist/runner/protocol.d.ts +30 -11
- package/dist/runner-main.js +9 -6
- package/dist/runtime-status.js +1 -1
- package/dist/terminal/claude-tui.d.ts +8 -3
- package/dist/terminal/claude-tui.js +6 -2
- package/dist/terminal/codex-tui.d.ts +3 -3
- package/dist/terminal/codex-tui.js +1 -1
- package/dist/terminal/registry.d.ts +1 -1
- package/dist/terminal/registry.js +1 -1
- package/dist/terminal/tmux.d.ts +6 -6
- package/dist/terminal/tmux.js +10 -10
- package/package.json +8 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the single explicit settings document for a rynx-managed Claude
|
|
3
|
+
* Session. Host/project settings still provide auth, env and permission policy,
|
|
4
|
+
* but their plugins and hooks are excluded: rynx must be the only owner of
|
|
5
|
+
* PermissionRequest and AskUserQuestion, and Agent Skills arrive only through
|
|
6
|
+
* the selected `--plugin-dir`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildManagedClaudeSettings(cwd: string, rynxSettings: object, configDir?: string): Record<string, unknown>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function readSettings(path) {
|
|
8
|
+
try {
|
|
9
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
10
|
+
return isRecord(value) ? value : {};
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function mergeSettings(base, override) {
|
|
17
|
+
const merged = { ...base };
|
|
18
|
+
for (const [key, value] of Object.entries(override)) {
|
|
19
|
+
const current = merged[key];
|
|
20
|
+
merged[key] = isRecord(current) && isRecord(value)
|
|
21
|
+
? mergeSettings(current, value)
|
|
22
|
+
: value;
|
|
23
|
+
}
|
|
24
|
+
return merged;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the single explicit settings document for a rynx-managed Claude
|
|
28
|
+
* Session. Host/project settings still provide auth, env and permission policy,
|
|
29
|
+
* but their plugins and hooks are excluded: rynx must be the only owner of
|
|
30
|
+
* PermissionRequest and AskUserQuestion, and Agent Skills arrive only through
|
|
31
|
+
* the selected `--plugin-dir`.
|
|
32
|
+
*/
|
|
33
|
+
export function buildManagedClaudeSettings(cwd, rynxSettings, configDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude")) {
|
|
34
|
+
const sources = [
|
|
35
|
+
join(configDir, "settings.json"),
|
|
36
|
+
join(cwd, ".claude", "settings.json"),
|
|
37
|
+
join(cwd, ".claude", "settings.local.json"),
|
|
38
|
+
];
|
|
39
|
+
let inherited = {};
|
|
40
|
+
for (const source of sources)
|
|
41
|
+
inherited = mergeSettings(inherited, readSettings(source));
|
|
42
|
+
// These sources can register competing interaction owners or host Skills.
|
|
43
|
+
// Rynx injects its own hooks/status line below and selected Skills separately.
|
|
44
|
+
delete inherited.hooks;
|
|
45
|
+
delete inherited.statusLine;
|
|
46
|
+
delete inherited.disableAllHooks;
|
|
47
|
+
delete inherited.enabledPlugins;
|
|
48
|
+
delete inherited.extraKnownMarketplaces;
|
|
49
|
+
return mergeSettings(inherited, rynxSettings);
|
|
50
|
+
}
|
|
@@ -45,10 +45,10 @@ export declare function parseTranscriptRecord(record: unknown, opts?: ParseTrans
|
|
|
45
45
|
/**
|
|
46
46
|
* Whether a transcript is a `/fork` (branch) of another session: claude stamps a
|
|
47
47
|
* `forkedFrom: { sessionId }` marker in an early record pointing at the source
|
|
48
|
-
* session (
|
|
48
|
+
* session (reference implementation's `transcript_has_forked_from_marker`). Used to distinguish a
|
|
49
49
|
* fork from an ordinary `resume` (both arrive as `SessionStart source="resume"`).
|
|
50
50
|
* Scans only the head of the file (the marker lands up front). NOTE: unverified on
|
|
51
|
-
* this host — no local transcript carries the marker — so it follows
|
|
51
|
+
* this host — no local transcript carries the marker — so it follows reference implementation's shape.
|
|
52
52
|
*/
|
|
53
53
|
export declare function transcriptHasForkedFrom(path: string, currentSessionId?: string): boolean;
|
|
54
54
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Claude Code transcript reader (Phase E, claude-native). Claude Code appends a
|
|
3
3
|
* JSONL transcript per session at
|
|
4
4
|
* `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`; the interactive TUI's
|
|
5
|
-
* stdout is a full-screen UI, so — like
|
|
5
|
+
* stdout is a full-screen UI, so — like reference implementation's claude-native — we take the
|
|
6
6
|
* structured story from this file, not the PTY. Each `user`/`assistant` record
|
|
7
7
|
* wraps an Anthropic message (`{ role, content: [blocks] }`), so we map its
|
|
8
8
|
* blocks to the same typed {@link AgentEvent}s the headless executor emits.
|
|
@@ -121,6 +121,17 @@ export function parseTranscriptRecord(record, opts) {
|
|
|
121
121
|
return [];
|
|
122
122
|
const out = [];
|
|
123
123
|
if (rec.type === "assistant" && message.role === "assistant") {
|
|
124
|
+
// Claude writes this zero-token synthetic filler after an accidental empty
|
|
125
|
+
// submit. It is transcript bookkeeping, not an assistant response. Filter
|
|
126
|
+
// by provider provenance and exact shape here, before normalization loses
|
|
127
|
+
// `model: "<synthetic>"`; a UI string filter could hide legitimate output.
|
|
128
|
+
if (!rec.isApiErrorMessage &&
|
|
129
|
+
message.model === "<synthetic>" &&
|
|
130
|
+
message.content.length === 1 &&
|
|
131
|
+
message.content[0]?.type === "text" &&
|
|
132
|
+
message.content[0].text === "No response requested.") {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
124
135
|
const itemId = message.id;
|
|
125
136
|
const texts = [];
|
|
126
137
|
for (const block of message.content) {
|
|
@@ -173,10 +184,10 @@ function isObject(value) {
|
|
|
173
184
|
/**
|
|
174
185
|
* Whether a transcript is a `/fork` (branch) of another session: claude stamps a
|
|
175
186
|
* `forkedFrom: { sessionId }` marker in an early record pointing at the source
|
|
176
|
-
* session (
|
|
187
|
+
* session (reference implementation's `transcript_has_forked_from_marker`). Used to distinguish a
|
|
177
188
|
* fork from an ordinary `resume` (both arrive as `SessionStart source="resume"`).
|
|
178
189
|
* Scans only the head of the file (the marker lands up front). NOTE: unverified on
|
|
179
|
-
* this host — no local transcript carries the marker — so it follows
|
|
190
|
+
* this host — no local transcript carries the marker — so it follows reference implementation's shape.
|
|
180
191
|
*/
|
|
181
192
|
export function transcriptHasForkedFrom(path, currentSessionId) {
|
|
182
193
|
let text;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SessionItem } from "@rynx-ai/core";
|
|
2
|
+
import { type CodexLineageRuntime } from "../codex-home.js";
|
|
2
3
|
export interface SynthesizeRolloutOptions {
|
|
3
4
|
threadId: string;
|
|
4
5
|
cwd: string;
|
|
@@ -8,6 +9,10 @@ export interface SynthesizeRolloutOptions {
|
|
|
8
9
|
sessionId?: string;
|
|
9
10
|
/** Override the private CODEX_HOME (tests, or a pre-resolved home). */
|
|
10
11
|
codexHome?: string;
|
|
12
|
+
/** Codex-lineage runtime whose private home/session layout is being written. */
|
|
13
|
+
runtime?: CodexLineageRuntime;
|
|
14
|
+
/** Runtime-neutral alias for `codexHome`; preferred for Traex callers. */
|
|
15
|
+
runtimeHome?: string;
|
|
11
16
|
/** codex CLI version for `session_meta` (informational for ≥0.133; presence
|
|
12
17
|
* matters). */
|
|
13
18
|
cliVersion?: string;
|
|
@@ -22,8 +27,8 @@ interface RolloutRecord {
|
|
|
22
27
|
type: "session_meta" | "turn_context" | "response_item" | "event_msg";
|
|
23
28
|
payload: Record<string, unknown>;
|
|
24
29
|
}
|
|
25
|
-
/** Locate an existing rollout for `threadId` under
|
|
26
|
-
export declare function findCodexRollout(
|
|
30
|
+
/** Locate an existing rollout for `threadId` under the runtime's sessions root. */
|
|
31
|
+
export declare function findCodexRollout(runtimeHome: string, threadId: string, runtime?: CodexLineageRuntime): string | null;
|
|
27
32
|
/** Build the ordered rollout records (session_meta first, then per-turn
|
|
28
33
|
* turn_context + per-item response_item + per-message event_msg). */
|
|
29
34
|
export declare function buildRolloutRecords(opts: SynthesizeRolloutOptions): RolloutRecord[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Synthesize a codex rollout file from rynx's canonical session log, so
|
|
3
3
|
* `codex --remote resume <threadId>` works when the local rollout is missing
|
|
4
|
-
* (fork / worktree / cross-machine). Ports
|
|
4
|
+
* (fork / worktree / cross-machine). Ports reference implementation's
|
|
5
5
|
* `_ensure_local_codex_resume_rollout`, adapted to rynx: the daemon (control-api)
|
|
6
6
|
* has BOTH the session items (`SessionLogStore`) and — since the private CODEX_HOME
|
|
7
7
|
* is a deterministic uid-scoped path — the app-server's rollout dir, so this runs
|
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
* (codex ≥0.136 renders an empty thread without them). `turn_context` groups items
|
|
14
14
|
* into turns.
|
|
15
15
|
*/
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
16
17
|
import { copyFileSync, mkdirSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
17
18
|
import { join, relative } from "node:path";
|
|
18
|
-
import {
|
|
19
|
+
import { getRuntimeProfile, resolveRuntimeHome, rynxHome, } from "@rynx-ai/core";
|
|
20
|
+
import { legacyCodexHomePath, runtimeHomePath, } from "../codex-home.js";
|
|
19
21
|
/** codex validates the thread id straight into a filename + resume arg. */
|
|
20
22
|
const THREAD_ID_RE = /^[0-9a-fA-F-]+$/;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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);
|
|
24
29
|
const suffix = `-${threadId}.jsonl`;
|
|
25
30
|
const stack = [root];
|
|
26
31
|
while (stack.length) {
|
|
@@ -49,18 +54,21 @@ export function findCodexRollout(codexHome, threadId) {
|
|
|
49
54
|
* expects) so the per-session app-server can resume it. Best-effort; gated by
|
|
50
55
|
* `RYNX_CODEX_HOME_LEGACY_FALLBACK` (default on; set `0`/`false` to disable).
|
|
51
56
|
*/
|
|
52
|
-
function adoptLegacyRollout(
|
|
53
|
-
const flag = process.env.
|
|
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();
|
|
54
60
|
if (flag === "0" || flag === "false")
|
|
55
61
|
return false;
|
|
56
|
-
const legacy =
|
|
57
|
-
|
|
62
|
+
const legacy = runtime === "codex"
|
|
63
|
+
? legacyCodexHomePath()
|
|
64
|
+
: resolveRuntimeHome(getRuntimeProfile(runtime));
|
|
65
|
+
if (legacy === runtimeHome)
|
|
58
66
|
return false;
|
|
59
|
-
const src = findCodexRollout(legacy, threadId);
|
|
67
|
+
const src = findCodexRollout(legacy, threadId, runtime);
|
|
60
68
|
if (!src)
|
|
61
69
|
return false;
|
|
62
|
-
const rel = relative(
|
|
63
|
-
const dst = join(
|
|
70
|
+
const rel = relative(sessionsRoot(legacy, runtime), src);
|
|
71
|
+
const dst = join(sessionsRoot(runtimeHome, runtime), rel);
|
|
64
72
|
try {
|
|
65
73
|
mkdirSync(join(dst, ".."), { recursive: true, mode: 0o700 });
|
|
66
74
|
copyFileSync(src, dst);
|
|
@@ -79,7 +87,10 @@ function turnIdOf(responseId) {
|
|
|
79
87
|
return responseId.startsWith("resp_codex_") ? responseId.slice("resp_codex_".length) : responseId;
|
|
80
88
|
}
|
|
81
89
|
function textOf(item) {
|
|
82
|
-
return item.data.content
|
|
90
|
+
return item.data.content
|
|
91
|
+
.filter((part) => "text" in part)
|
|
92
|
+
.map((part) => part.text)
|
|
93
|
+
.join("");
|
|
83
94
|
}
|
|
84
95
|
/** Convert one canonical {@link SessionItem} to its codex `response_item` payload,
|
|
85
96
|
* or null for types codex doesn't carry (reasoning/terminal_command/error). */
|
|
@@ -87,9 +98,9 @@ function responseItemPayload(item) {
|
|
|
87
98
|
switch (item.type) {
|
|
88
99
|
case "message": {
|
|
89
100
|
const apiType = item.data.role === "assistant" ? "output_text" : "input_text";
|
|
90
|
-
const content = item.data.content
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
const content = item.data.content.flatMap((part) => "text" in part && part.text
|
|
102
|
+
? [{ type: apiType, text: part.text }]
|
|
103
|
+
: []);
|
|
93
104
|
if (content.length === 0)
|
|
94
105
|
return null;
|
|
95
106
|
return { type: "message", role: item.data.role, content };
|
|
@@ -108,23 +119,41 @@ function responseItemPayload(item) {
|
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
/** The `event_msg` mirror for a message (required for a VISIBLE turn on codex ≥0.136). */
|
|
111
|
-
function eventMsgPayload(item) {
|
|
122
|
+
function eventMsgPayload(item, sessionId) {
|
|
112
123
|
const message = textOf(item).trim();
|
|
113
|
-
|
|
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)
|
|
114
128
|
return null;
|
|
115
129
|
if (item.data.role === "user") {
|
|
116
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
type: "user_message",
|
|
132
|
+
message,
|
|
133
|
+
images: [],
|
|
134
|
+
local_images: localImages,
|
|
135
|
+
text_elements: [],
|
|
136
|
+
};
|
|
117
137
|
}
|
|
118
138
|
if (item.data.role === "assistant") {
|
|
119
139
|
return { type: "agent_message", message, phase: "final_answer", memory_citation: null };
|
|
120
140
|
}
|
|
121
141
|
return null;
|
|
122
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
|
+
}
|
|
123
152
|
/** Build the ordered rollout records (session_meta first, then per-turn
|
|
124
153
|
* turn_context + per-item response_item + per-message event_msg). */
|
|
125
154
|
export function buildRolloutRecords(opts) {
|
|
126
155
|
const now = opts.now ?? (() => Date.now());
|
|
127
|
-
const modelProvider = opts.modelProvider ?? "openai";
|
|
156
|
+
const modelProvider = opts.modelProvider ?? (opts.runtime === "traex" ? "trae" : "openai");
|
|
128
157
|
const cliVersion = opts.cliVersion ?? "0.0.0";
|
|
129
158
|
const metaTs = iso(opts.items[0]?.createdAt ?? now());
|
|
130
159
|
const records = [
|
|
@@ -147,7 +176,10 @@ export function buildRolloutRecords(opts) {
|
|
|
147
176
|
const seenTurns = new Set();
|
|
148
177
|
for (const item of opts.items) {
|
|
149
178
|
const payload = responseItemPayload(item);
|
|
150
|
-
|
|
179
|
+
const eventPayload = item.type === "message"
|
|
180
|
+
? eventMsgPayload(item, opts.sessionId)
|
|
181
|
+
: null;
|
|
182
|
+
if (!payload && !eventPayload)
|
|
151
183
|
continue;
|
|
152
184
|
const ts = iso(item.createdAt);
|
|
153
185
|
const turnId = turnIdOf(item.responseId);
|
|
@@ -159,12 +191,10 @@ export function buildRolloutRecords(opts) {
|
|
|
159
191
|
payload: { turn_id: turnId, cwd: opts.cwd, approval_policy: "on-request" },
|
|
160
192
|
});
|
|
161
193
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
records.push({ timestamp: ts, type: "event_msg", payload: evt });
|
|
167
|
-
}
|
|
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 });
|
|
168
198
|
}
|
|
169
199
|
return records;
|
|
170
200
|
}
|
|
@@ -177,14 +207,17 @@ export function buildRolloutRecords(opts) {
|
|
|
177
207
|
export function ensureCodexResumeRollout(opts) {
|
|
178
208
|
if (!THREAD_ID_RE.test(opts.threadId))
|
|
179
209
|
return "skipped";
|
|
180
|
-
const
|
|
181
|
-
|
|
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)
|
|
182
215
|
return "skipped"; // no session context → can't locate the home
|
|
183
|
-
if (findCodexRollout(
|
|
216
|
+
if (findCodexRollout(runtimeHome, opts.threadId, runtime))
|
|
184
217
|
return "exists";
|
|
185
218
|
// Back-compat: a pre-per-session rollout may live under the OLD shared home. Copy
|
|
186
219
|
// it forward into this session's home so the per-session app-server can resume it.
|
|
187
|
-
if (adoptLegacyRollout(
|
|
220
|
+
if (adoptLegacyRollout(runtimeHome, opts.threadId, runtime))
|
|
188
221
|
return "exists";
|
|
189
222
|
const records = buildRolloutRecords(opts);
|
|
190
223
|
// Nothing but the session_meta header → no history to carry; skip (a fresh
|
|
@@ -197,7 +230,7 @@ export function ensureCodexResumeRollout(opts) {
|
|
|
197
230
|
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
198
231
|
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
199
232
|
const stamp = iso(d.getTime()).slice(0, 19).replace(/:/g, "-"); // YYYY-MM-DDTHH-MM-SS
|
|
200
|
-
const dir = join(
|
|
233
|
+
const dir = join(sessionsRoot(runtimeHome, runtime), yyyy, mm, dd);
|
|
201
234
|
const target = join(dir, `rollout-${stamp}-${opts.threadId}.jsonl`);
|
|
202
235
|
const tmp = `${target}.tmp`;
|
|
203
236
|
try {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { RuntimeUserInput, SessionInteractionResolution } from "@rynx-ai/core";
|
|
1
2
|
import type { AskForApproval, ClientInfo, CollaborationModeListResponse, GetAuthStatusParams, GetAuthStatusResponse, InitializeResponse, ModelListParams, ModelListResponse, ReviewStartParams, ReviewStartResponse, SandboxMode, ThreadForkParams, ThreadGoalClearParams, ThreadGoalGetParams, ThreadGoalGetResponse, ThreadGoalSetParams, ThreadListParams, ThreadListResponse, ResumedThread, ThreadResumeParams, ThreadSettingsUpdateParams, ThreadStartParams, TurnInterruptParams, TurnStartParams, TurnSteerParams, UserInput } from "./protocol.js";
|
|
2
3
|
import { CodexAppServerTransport, type CodexAppServerProcessSpawner, type RpcChannel, type TransportLogger } from "./transport.js";
|
|
4
|
+
import type { ResolveInteractionResult, RuntimeInteractionListener } from "../interactions.js";
|
|
3
5
|
export type ApprovalDecisionPolicy = "auto-approve-session" | "auto-decline" | "auto-cancel";
|
|
4
6
|
export type CodexNotificationListener = (method: string, params: unknown) => void;
|
|
5
7
|
export interface CodexAppServerClientOptions {
|
|
@@ -16,36 +18,21 @@ export interface CodexAppServerClientOptions {
|
|
|
16
18
|
* prompting a human.
|
|
17
19
|
*/
|
|
18
20
|
approvalDecisionPolicy?: ApprovalDecisionPolicy;
|
|
19
|
-
/**
|
|
20
|
-
* Surface approvals to a user instead of auto-deciding. When true and an
|
|
21
|
-
* approval listener is set, each request blocks until {@link
|
|
22
|
-
* CodexAppServerClient.resolveApproval} is called (or a timeout falls back to
|
|
23
|
-
* {@link approvalDecisionPolicy}).
|
|
24
|
-
*/
|
|
25
|
-
interactiveApprovals?: boolean;
|
|
26
|
-
}
|
|
27
|
-
/** A codex approval decision (superset of exec + patch decision enums). */
|
|
28
|
-
export type ApprovalDecision = "acceptForSession" | "accept" | "decline" | "cancel";
|
|
29
|
-
/** A pending approval surfaced to the user, awaiting {@link CodexAppServerClient.resolveApproval}. */
|
|
30
|
-
export interface ApprovalRequest {
|
|
31
|
-
approvalId: string;
|
|
32
|
-
kind: "exec" | "patch";
|
|
33
|
-
command?: string;
|
|
34
|
-
cwd?: string;
|
|
35
|
-
diff?: string;
|
|
36
21
|
}
|
|
37
22
|
export declare class CodexAppServerClient {
|
|
38
23
|
readonly transport: CodexAppServerTransport;
|
|
39
24
|
private readonly logger;
|
|
40
25
|
private readonly clientInfo;
|
|
41
26
|
private readonly approvalDecisionPolicy;
|
|
42
|
-
private readonly interactiveApprovals;
|
|
43
27
|
private readonly channel;
|
|
44
28
|
private readonly notificationSubscribers;
|
|
45
|
-
private
|
|
46
|
-
private
|
|
29
|
+
private interactionListener;
|
|
30
|
+
private connectionListener;
|
|
31
|
+
private connectionState;
|
|
32
|
+
private readonly pendingInteractions;
|
|
33
|
+
private readonly settledInteractions;
|
|
47
34
|
private initializeResponse;
|
|
48
|
-
constructor({ spawner, channel, logger, clientInfo, approvalDecisionPolicy,
|
|
35
|
+
constructor({ spawner, channel, logger, clientInfo, approvalDecisionPolicy, }: CodexAppServerClientOptions);
|
|
49
36
|
/**
|
|
50
37
|
* The multi-client endpoint a `codex --remote` TUI can attach to, when this
|
|
51
38
|
* client runs over a {@link WsRpcChannel}. `undefined` for the default stdio
|
|
@@ -101,27 +88,26 @@ export declare class CodexAppServerClient {
|
|
|
101
88
|
private dispatchNotification;
|
|
102
89
|
private handleServerRequest;
|
|
103
90
|
/**
|
|
104
|
-
* Register the
|
|
105
|
-
* the
|
|
106
|
-
*
|
|
107
|
-
*/
|
|
108
|
-
setApprovalRequestListener(listener: ((request: ApprovalRequest) => void) | null): void;
|
|
109
|
-
/**
|
|
110
|
-
* Deliver a user's decision for a pending interactive approval. Returns false
|
|
111
|
-
* if the approval id is unknown (already resolved, timed out, or auto-decided).
|
|
112
|
-
*/
|
|
113
|
-
resolveApproval(approvalId: string, decision: ApprovalDecision): boolean;
|
|
114
|
-
/**
|
|
115
|
-
* Interactive approval path: surface the request and block the codex
|
|
116
|
-
* server-request until the user decides (or the timeout falls back to the
|
|
117
|
-
* auto policy). When interactive approvals are off, decide immediately.
|
|
91
|
+
* Register the provider-neutral interaction listener. A request is inserted
|
|
92
|
+
* into the pending map before the listener is invoked, so a synchronous
|
|
93
|
+
* resolver still wins correctly.
|
|
118
94
|
*/
|
|
119
|
-
|
|
95
|
+
setInteractionListener(listener: RuntimeInteractionListener | null): void;
|
|
96
|
+
/** Observe the underlying app-server connection independently from any one
|
|
97
|
+
* interaction. A disconnected client that never received the duplicate
|
|
98
|
+
* native request still has to count as unavailable during host failover. */
|
|
99
|
+
setConnectionListener(listener: ((state: "connected" | "disconnected") => void) | null): void;
|
|
100
|
+
private setConnectionState;
|
|
101
|
+
resolveInteraction(interactionId: string, resolution: SessionInteractionResolution): ResolveInteractionResult;
|
|
102
|
+
/** Cancel every pending request owned by this connection without replying. */
|
|
103
|
+
cancelInteractions(reason?: string): void;
|
|
104
|
+
private handleInteraction;
|
|
120
105
|
private autoApprovalDecision;
|
|
121
|
-
private
|
|
122
|
-
private
|
|
123
|
-
private
|
|
124
|
-
private
|
|
106
|
+
private resolveByProvider;
|
|
107
|
+
private handleServerRequestResponseDelivery;
|
|
108
|
+
private cancelPendingInteractions;
|
|
109
|
+
private emitInteraction;
|
|
110
|
+
private rememberSettled;
|
|
125
111
|
}
|
|
126
112
|
export interface BuildSandboxPolicyOptions {
|
|
127
113
|
mode: SandboxMode;
|
|
@@ -135,4 +121,5 @@ export interface BuildSandboxPolicyOptions {
|
|
|
135
121
|
*/
|
|
136
122
|
export declare function buildSandboxPolicy(options: BuildSandboxPolicyOptions): Record<string, unknown>;
|
|
137
123
|
export declare function buildTextUserInput(message: string): UserInput[];
|
|
124
|
+
export declare function buildRuntimeUserInput(input: RuntimeUserInput): UserInput[];
|
|
138
125
|
export type { AskForApproval };
|