@rynx-ai/runtime 0.1.10 → 0.1.11-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/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 +47 -47
- package/dist/host.js +790 -350
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/models-catalog.d.ts +1 -0
- package/dist/models-catalog.js +43 -1
- package/dist/provider-workspace.d.ts +56 -0
- package/dist/provider-workspace.js +83 -0
- package/dist/runner/child.d.ts +54 -6
- package/dist/runner/child.js +42 -17
- package/dist/runner/manager.d.ts +41 -18
- package/dist/runner/manager.js +432 -55
- 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 +8 -1
- package/dist/terminal/claude-tui.js +7 -1
- package/dist/terminal/codex-tui.d.ts +5 -1
- package/dist/terminal/codex-tui.js +12 -3
- package/package.json +2 -2
- package/dist/codex/rollout-synth.d.ts +0 -42
- package/dist/codex/rollout-synth.js +0 -245
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { LocalAgentHost, CodexRuntimeError, SpawnCodexCommandRunner, FileCodexSessionStore, resolveCodexSessionStorePath, } from "./host.js";
|
|
8
8
|
export type { CodexCapabilities, CapabilityResult, CodexRuntimeStatus, } from "./host.js";
|
|
9
|
-
export type { CodexSessionStore, CodexSessionRecord } from "./codex-session-store.js";
|
|
10
|
-
export { ensureCodexResumeRollout } from "./codex/rollout-synth.js";
|
|
9
|
+
export type { ClaudeForkIntent, CodexSessionStore, CodexSessionRecord, } from "./codex-session-store.js";
|
|
11
10
|
export { RunnerManager, TerminalOpenError } from "./runner/manager.js";
|
|
12
11
|
export type { RunnerManagerOptions, RunnerSessionContext, RunnerSessionContextProvider, OpenTerminalOptions, ParentTerminal, } from "./runner/manager.js";
|
|
13
12
|
export type { InjectOutcome, TerminalOpenErrorCode, TerminalRole } from "./runner/protocol.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* nothing about any channel.
|
|
6
6
|
*/
|
|
7
7
|
export { LocalAgentHost, CodexRuntimeError, SpawnCodexCommandRunner, FileCodexSessionStore, resolveCodexSessionStorePath, } from "./host.js";
|
|
8
|
-
export { ensureCodexResumeRollout } from "./codex/rollout-synth.js";
|
|
9
8
|
// Runner subprocess layer: the parent-side manager (an `AgentExecutor` +
|
|
10
9
|
// `AgentCapabilities` that spawns per-session runner children) plus the wire
|
|
11
10
|
// types. The composition root uses `RunnerManager` in place of `LocalAgentHost`.
|
package/dist/models-catalog.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type AgentRuntimeId, type AppConfig } from "@rynx-ai/core";
|
|
|
2
2
|
import type { ModelListResponse } from "./codex-app-server/protocol.js";
|
|
3
3
|
export interface RuntimeModelCatalogDeps {
|
|
4
4
|
readTraexModels?: () => Promise<unknown>;
|
|
5
|
+
readTraexDebugModels?: () => Promise<unknown>;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* The model list for a runtime, without an execution backend.
|
package/dist/models-catalog.js
CHANGED
|
@@ -24,7 +24,17 @@ export async function listRuntimeModels(config, runtime, deps = {}) {
|
|
|
24
24
|
if (runtime === "traex") {
|
|
25
25
|
try {
|
|
26
26
|
const value = await (deps.readTraexModels ?? readTraexModels)();
|
|
27
|
-
const
|
|
27
|
+
const configuredDefault = resolveRuntimeModel(config, runtime).trim();
|
|
28
|
+
const defaultModel = configuredDefault || resolveTraexNativeDefault(await (deps.readTraexDebugModels ?? readTraexDebugModels)());
|
|
29
|
+
if (!configuredDefault &&
|
|
30
|
+
(!Array.isArray(value) ||
|
|
31
|
+
!value.some((item) => item &&
|
|
32
|
+
typeof item === "object" &&
|
|
33
|
+
!Array.isArray(item) &&
|
|
34
|
+
item.name === defaultModel))) {
|
|
35
|
+
throw new Error(`Traex native default is missing from the public catalog: ${defaultModel}`);
|
|
36
|
+
}
|
|
37
|
+
const models = normalizeTraexModels(value, defaultModel);
|
|
28
38
|
if (models.length > 0)
|
|
29
39
|
return { data: models };
|
|
30
40
|
}
|
|
@@ -46,6 +56,38 @@ async function readTraexModels() {
|
|
|
46
56
|
});
|
|
47
57
|
return JSON.parse(stdout);
|
|
48
58
|
}
|
|
59
|
+
async function readTraexDebugModels() {
|
|
60
|
+
const { stdout } = await execFileAsync(resolveRuntimeBinary("traex"), ["debug", "models"], {
|
|
61
|
+
encoding: "utf8",
|
|
62
|
+
timeout: TRAEX_MODELS_TIMEOUT_MS,
|
|
63
|
+
maxBuffer: TRAEX_MODELS_MAX_BYTES,
|
|
64
|
+
});
|
|
65
|
+
return JSON.parse(stdout);
|
|
66
|
+
}
|
|
67
|
+
function resolveTraexNativeDefault(value) {
|
|
68
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
69
|
+
throw new Error("Traex debug model catalog is invalid");
|
|
70
|
+
}
|
|
71
|
+
const models = value.models;
|
|
72
|
+
if (!Array.isArray(models)) {
|
|
73
|
+
throw new Error("Traex debug model catalog is invalid");
|
|
74
|
+
}
|
|
75
|
+
const defaults = models.filter((item) => item &&
|
|
76
|
+
typeof item === "object" &&
|
|
77
|
+
!Array.isArray(item) &&
|
|
78
|
+
item.business_metadata &&
|
|
79
|
+
typeof item.business_metadata === "object" &&
|
|
80
|
+
item.business_metadata
|
|
81
|
+
.backend_is_default === true);
|
|
82
|
+
if (defaults.length !== 1) {
|
|
83
|
+
throw new Error(`Traex debug model catalog has ${defaults.length} native defaults`);
|
|
84
|
+
}
|
|
85
|
+
const name = defaults[0].config_name;
|
|
86
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
87
|
+
throw new Error("Traex native default has no config_name");
|
|
88
|
+
}
|
|
89
|
+
return name.trim();
|
|
90
|
+
}
|
|
49
91
|
function normalizeTraexModels(value, configuredDefault) {
|
|
50
92
|
if (!Array.isArray(value))
|
|
51
93
|
return [];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AgentRuntimeId, SandboxMode } from "@rynx-ai/core";
|
|
2
|
+
import type { PermissionProfileSelectionParams, SandboxPolicy } from "./codex-app-server/protocol.js";
|
|
3
|
+
/** Structural view of the immutable Session workspace snapshot. The canonical
|
|
4
|
+
* type lives in core; keeping the adapter input structural avoids inventing a
|
|
5
|
+
* second domain model in the Provider layer. */
|
|
6
|
+
export interface ProviderWorkspace {
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly allowedDirs: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/** Canonical roots projected without mutating the Session snapshot, with the
|
|
11
|
+
* primary working directory first for every Provider. */
|
|
12
|
+
export declare function providerWorkspaceRoots(workspace: ProviderWorkspace): string[];
|
|
13
|
+
export declare function providerAdditionalDirs(workspace: ProviderWorkspace): string[];
|
|
14
|
+
export declare function sandboxPolicyForWorkspace(mode: SandboxMode, workspace: ProviderWorkspace): SandboxPolicy;
|
|
15
|
+
export declare function codexThreadWorkspaceParams(workspace: ProviderWorkspace): {
|
|
16
|
+
cwd: string;
|
|
17
|
+
runtimeWorkspaceRoots: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function codexTurnWorkspaceParams(workspace: ProviderWorkspace, mode: SandboxMode): {
|
|
20
|
+
cwd: string;
|
|
21
|
+
runtimeWorkspaceRoots: string[];
|
|
22
|
+
sandboxPolicy: SandboxPolicy;
|
|
23
|
+
};
|
|
24
|
+
/** Traex has no runtimeWorkspaceRoots field. For a multi-root writable
|
|
25
|
+
* workspace, use the bounded named-profile modification supported by Traex
|
|
26
|
+
* 0.200; never combine it with the legacy sandbox field. */
|
|
27
|
+
export declare function traexThreadWorkspaceParams(workspace: ProviderWorkspace, mode: SandboxMode): {
|
|
28
|
+
cwd: string;
|
|
29
|
+
permissions: PermissionProfileSelectionParams;
|
|
30
|
+
} | {
|
|
31
|
+
cwd: string;
|
|
32
|
+
sandbox: SandboxMode;
|
|
33
|
+
};
|
|
34
|
+
/** Turn equivalent of {@link traexThreadWorkspaceParams}; `permissions` and
|
|
35
|
+
* `sandboxPolicy` are intentionally mutually exclusive. */
|
|
36
|
+
export declare function traexTurnWorkspaceParams(workspace: ProviderWorkspace, mode: SandboxMode): {
|
|
37
|
+
cwd: string;
|
|
38
|
+
permissions: PermissionProfileSelectionParams;
|
|
39
|
+
} | {
|
|
40
|
+
cwd: string;
|
|
41
|
+
sandboxPolicy: SandboxPolicy;
|
|
42
|
+
};
|
|
43
|
+
export declare function threadWorkspaceParams(runtime: Exclude<AgentRuntimeId, "claude">, workspace: ProviderWorkspace, mode: SandboxMode): {
|
|
44
|
+
cwd: string;
|
|
45
|
+
permissions: PermissionProfileSelectionParams;
|
|
46
|
+
} | {
|
|
47
|
+
cwd: string;
|
|
48
|
+
sandbox: SandboxMode;
|
|
49
|
+
};
|
|
50
|
+
export declare function turnWorkspaceParams(runtime: Exclude<AgentRuntimeId, "claude">, workspace: ProviderWorkspace, mode: SandboxMode): {
|
|
51
|
+
cwd: string;
|
|
52
|
+
permissions: PermissionProfileSelectionParams;
|
|
53
|
+
} | {
|
|
54
|
+
cwd: string;
|
|
55
|
+
sandboxPolicy: SandboxPolicy;
|
|
56
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Canonical roots projected without mutating the Session snapshot, with the
|
|
2
|
+
* primary working directory first for every Provider. */
|
|
3
|
+
export function providerWorkspaceRoots(workspace) {
|
|
4
|
+
return [...new Set([workspace.cwd, ...workspace.allowedDirs])];
|
|
5
|
+
}
|
|
6
|
+
export function providerAdditionalDirs(workspace) {
|
|
7
|
+
return providerWorkspaceRoots(workspace).filter((dir) => dir !== workspace.cwd);
|
|
8
|
+
}
|
|
9
|
+
export function sandboxPolicyForWorkspace(mode, workspace) {
|
|
10
|
+
switch (mode) {
|
|
11
|
+
case "read-only":
|
|
12
|
+
return { type: "readOnly", networkAccess: false };
|
|
13
|
+
case "workspace-write":
|
|
14
|
+
return {
|
|
15
|
+
type: "workspaceWrite",
|
|
16
|
+
writableRoots: providerWorkspaceRoots(workspace),
|
|
17
|
+
networkAccess: true,
|
|
18
|
+
excludeTmpdirEnvVar: false,
|
|
19
|
+
excludeSlashTmp: false,
|
|
20
|
+
};
|
|
21
|
+
case "danger-full-access":
|
|
22
|
+
return { type: "dangerFullAccess" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function codexThreadWorkspaceParams(workspace) {
|
|
26
|
+
return {
|
|
27
|
+
cwd: workspace.cwd,
|
|
28
|
+
runtimeWorkspaceRoots: providerWorkspaceRoots(workspace),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function codexTurnWorkspaceParams(workspace, mode) {
|
|
32
|
+
return {
|
|
33
|
+
...codexThreadWorkspaceParams(workspace),
|
|
34
|
+
sandboxPolicy: sandboxPolicyForWorkspace(mode, workspace),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function traexWorkspacePermissions(workspace) {
|
|
38
|
+
const additionalDirs = providerAdditionalDirs(workspace);
|
|
39
|
+
if (additionalDirs.length === 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
type: "profile",
|
|
43
|
+
id: ":workspace",
|
|
44
|
+
modifications: additionalDirs.map((path) => ({
|
|
45
|
+
type: "additionalWritableRoot",
|
|
46
|
+
path,
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Traex has no runtimeWorkspaceRoots field. For a multi-root writable
|
|
51
|
+
* workspace, use the bounded named-profile modification supported by Traex
|
|
52
|
+
* 0.200; never combine it with the legacy sandbox field. */
|
|
53
|
+
export function traexThreadWorkspaceParams(workspace, mode) {
|
|
54
|
+
const permissions = mode === "workspace-write"
|
|
55
|
+
? traexWorkspacePermissions(workspace)
|
|
56
|
+
: undefined;
|
|
57
|
+
return permissions
|
|
58
|
+
? { cwd: workspace.cwd, permissions }
|
|
59
|
+
: { cwd: workspace.cwd, sandbox: mode };
|
|
60
|
+
}
|
|
61
|
+
/** Turn equivalent of {@link traexThreadWorkspaceParams}; `permissions` and
|
|
62
|
+
* `sandboxPolicy` are intentionally mutually exclusive. */
|
|
63
|
+
export function traexTurnWorkspaceParams(workspace, mode) {
|
|
64
|
+
const permissions = mode === "workspace-write"
|
|
65
|
+
? traexWorkspacePermissions(workspace)
|
|
66
|
+
: undefined;
|
|
67
|
+
return permissions
|
|
68
|
+
? { cwd: workspace.cwd, permissions }
|
|
69
|
+
: {
|
|
70
|
+
cwd: workspace.cwd,
|
|
71
|
+
sandboxPolicy: sandboxPolicyForWorkspace(mode, workspace),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function threadWorkspaceParams(runtime, workspace, mode) {
|
|
75
|
+
return runtime === "traex"
|
|
76
|
+
? traexThreadWorkspaceParams(workspace, mode)
|
|
77
|
+
: { ...codexThreadWorkspaceParams(workspace), sandbox: mode };
|
|
78
|
+
}
|
|
79
|
+
export function turnWorkspaceParams(runtime, workspace, mode) {
|
|
80
|
+
return runtime === "traex"
|
|
81
|
+
? traexTurnWorkspaceParams(workspace, mode)
|
|
82
|
+
: codexTurnWorkspaceParams(workspace, mode);
|
|
83
|
+
}
|
package/dist/runner/child.d.ts
CHANGED
|
@@ -7,12 +7,60 @@
|
|
|
7
7
|
* per-thread capabilities, answered against the same backend so the parent never
|
|
8
8
|
* needs an app-server of its own.
|
|
9
9
|
*/
|
|
10
|
-
import { type AgentCapabilities } from "@rynx-ai/core";
|
|
10
|
+
import { type AgentCapabilities, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
11
|
+
import type { SessionEvent } from "@rynx-ai/core";
|
|
12
|
+
import type { TerminalInjector } from "../claude/native-integration.js";
|
|
13
|
+
import type { ResolveInteractionResult } from "../interactions.js";
|
|
14
|
+
import { type InjectOutcome } from "./protocol.js";
|
|
11
15
|
import type { ChildTransport } from "./transport.js";
|
|
16
|
+
/** Re-target the mirror to a freshly minted rynx session (claude `/clear`·`/fork`)
|
|
17
|
+
* + record the terminal transfer with the daemon. The child owns the transport,
|
|
18
|
+
* so it supplies this to the host. */
|
|
19
|
+
type RetargetMirror = (newSessionId: string, meta: {
|
|
20
|
+
kind: "clear" | "fork";
|
|
21
|
+
workspace: SessionWorkspaceSnapshot;
|
|
22
|
+
execution: ResolvedExecutionSnapshot;
|
|
23
|
+
parentSessionId?: string;
|
|
24
|
+
}) => void;
|
|
25
|
+
/** Codex-native live-session methods the executor (a `LocalAgentHost`) exposes
|
|
26
|
+
* beyond the `AgentExecutor`/`AgentCapabilities` contract. Duck-typed so the
|
|
27
|
+
* runner works with any executor (a claude-only host simply lacks them). */
|
|
28
|
+
interface LiveCodexProvider {
|
|
29
|
+
codexTerminalSpec?(localThreadId: string): Promise<{
|
|
30
|
+
command: string;
|
|
31
|
+
args: string[];
|
|
32
|
+
cwd: string;
|
|
33
|
+
env?: Record<string, string>;
|
|
34
|
+
} | null>;
|
|
35
|
+
ensureLiveCodexSession?(localThreadId: string, emit: (event: SessionEvent) => void, opts: {
|
|
36
|
+
workspace: SessionWorkspaceSnapshot;
|
|
37
|
+
execution: ResolvedExecutionSnapshot;
|
|
38
|
+
retargetMirror?: RetargetMirror;
|
|
39
|
+
}): Promise<boolean>;
|
|
40
|
+
waitLiveReady?(localThreadId: string, timeoutMs?: number): Promise<boolean>;
|
|
41
|
+
waitTerminalReady?(localThreadId: string, timeoutMs?: number): Promise<boolean>;
|
|
42
|
+
liveSessionError?(localThreadId: string): string | undefined;
|
|
43
|
+
injectMessage?(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectOutcome>;
|
|
44
|
+
interruptLive?(localThreadId: string): Promise<boolean>;
|
|
45
|
+
stopLiveCodexSession?(localThreadId: string, opts?: {
|
|
46
|
+
deferClaudeInteractionCleanup?: boolean;
|
|
47
|
+
}): void;
|
|
48
|
+
finalizeStoppedLiveSessions?(): void;
|
|
49
|
+
resolveInteraction?(localThreadId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<ResolveInteractionResult>;
|
|
50
|
+
/** claude-native: hand the session's tmux pane injector to the host (which
|
|
51
|
+
* doesn't own tmux). No-op for codex sessions (they inject via app-server). */
|
|
52
|
+
attachTerminalInjector?(localThreadId: string, injector: TerminalInjector): void;
|
|
53
|
+
/** Internal Provider operation used only by MachineSessionService through
|
|
54
|
+
* RunnerManager. It is deliberately absent from public AgentCapabilities. */
|
|
55
|
+
forkSession?(currentLocalThreadId: string, newLocalThreadId: string, options: {
|
|
56
|
+
workspace: SessionWorkspaceSnapshot;
|
|
57
|
+
execution: ResolvedExecutionSnapshot;
|
|
58
|
+
}): Promise<import("@rynx-ai/core").CapabilityResult>;
|
|
59
|
+
}
|
|
12
60
|
export interface RunnerSessionDeps {
|
|
13
61
|
transport: ChildTransport;
|
|
14
62
|
/** The single-backend capability + live co-drive surface (a `LocalAgentHost`). */
|
|
15
|
-
executor: AgentCapabilities;
|
|
63
|
+
executor: AgentCapabilities & LiveCodexProvider;
|
|
16
64
|
/** Invoked on a `shutdown` message (default: close transport). */
|
|
17
65
|
onShutdown?: () => void;
|
|
18
66
|
}
|
|
@@ -42,10 +90,9 @@ export declare class RunnerSession {
|
|
|
42
90
|
/**
|
|
43
91
|
* Eagerly bring up a session's codex-native live view: start the persistent
|
|
44
92
|
* forwarder connection (which resume-subscribes to mirror every turn) and
|
|
45
|
-
* launch the detached `codex --remote` TUI
|
|
46
|
-
* forwarder
|
|
47
|
-
*
|
|
48
|
-
* rollout) and its turns mirror to chat (reference implementation's model).
|
|
93
|
+
* launch the detached `codex --remote resume` TUI against the thread the
|
|
94
|
+
* structured runtime created. The forwarder subscribes to that same thread,
|
|
95
|
+
* so the TUI is usable immediately and its turns mirror to chat.
|
|
49
96
|
*/
|
|
50
97
|
private ensureLive;
|
|
51
98
|
private inject;
|
|
@@ -64,3 +111,4 @@ export declare class RunnerSession {
|
|
|
64
111
|
private runCap;
|
|
65
112
|
private dispatchCap;
|
|
66
113
|
}
|
|
114
|
+
export {};
|
package/dist/runner/child.js
CHANGED
|
@@ -114,9 +114,8 @@ export class RunnerSession {
|
|
|
114
114
|
from: localThreadId,
|
|
115
115
|
to: newId,
|
|
116
116
|
kind: meta.kind,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...(meta.cwd ? { cwd: meta.cwd } : {}),
|
|
117
|
+
workspace: meta.workspace,
|
|
118
|
+
execution: meta.execution,
|
|
120
119
|
...(meta.parentSessionId ? { parentSessionId: meta.parentSessionId } : {}),
|
|
121
120
|
});
|
|
122
121
|
};
|
|
@@ -125,21 +124,17 @@ export class RunnerSession {
|
|
|
125
124
|
/**
|
|
126
125
|
* Eagerly bring up a session's codex-native live view: start the persistent
|
|
127
126
|
* forwarder connection (which resume-subscribes to mirror every turn) and
|
|
128
|
-
* launch the detached `codex --remote` TUI
|
|
129
|
-
* forwarder
|
|
130
|
-
*
|
|
131
|
-
* rollout) and its turns mirror to chat (reference implementation's model).
|
|
127
|
+
* launch the detached `codex --remote resume` TUI against the thread the
|
|
128
|
+
* structured runtime created. The forwarder subscribes to that same thread,
|
|
129
|
+
* so the TUI is usable immediately and its turns mirror to chat.
|
|
132
130
|
*/
|
|
133
131
|
async ensureLive(msg) {
|
|
134
132
|
const provider = this.liveProvider;
|
|
135
133
|
try {
|
|
136
134
|
const { emit, retarget } = this.mirrorChannel(msg.localThreadId);
|
|
137
135
|
const started = await provider.ensureLiveCodexSession?.(msg.localThreadId, emit, {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
...(msg.reasoningEffort ? { reasoningEffort: msg.reasoningEffort } : {}),
|
|
141
|
-
...(msg.agentName ? { agentName: msg.agentName } : {}),
|
|
142
|
-
...(msg.agentSpec ? { agentSpec: msg.agentSpec } : {}),
|
|
136
|
+
workspace: msg.workspace,
|
|
137
|
+
execution: msg.execution,
|
|
143
138
|
retargetMirror: retarget,
|
|
144
139
|
});
|
|
145
140
|
if (!started) {
|
|
@@ -153,12 +148,24 @@ export class RunnerSession {
|
|
|
153
148
|
return;
|
|
154
149
|
}
|
|
155
150
|
this.liveIds.add(msg.localThreadId);
|
|
156
|
-
// Launch the TUI
|
|
157
|
-
//
|
|
158
|
-
// is absent OR its process has died (`isAlive` probes `#{pane_dead}`) — so a
|
|
151
|
+
// Launch the TUI attached to the already-bound thread. Re-launch when the
|
|
152
|
+
// pane is absent OR its process has died (`isAlive` probes `#{pane_dead}`) — so a
|
|
159
153
|
// reconnect after the TUI exited restarts it instead of skipping (a
|
|
160
154
|
// launched-once guard would leave a dead "Pane is dead" husk forever).
|
|
161
155
|
if (!this.terminals.get(`${msg.localThreadId}-main`)?.isAlive()) {
|
|
156
|
+
const terminalReady = provider.waitTerminalReady
|
|
157
|
+
? await provider.waitTerminalReady(msg.localThreadId)
|
|
158
|
+
: true;
|
|
159
|
+
if (!terminalReady) {
|
|
160
|
+
this.transport.send({
|
|
161
|
+
t: "live.ready",
|
|
162
|
+
reqId: msg.reqId,
|
|
163
|
+
localThreadId: msg.localThreadId,
|
|
164
|
+
ok: false,
|
|
165
|
+
error: "Provider thread was not ready for Terminal resume",
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
162
169
|
await this.launchCodexPane(msg.localThreadId, msg.cols, msg.rows);
|
|
163
170
|
}
|
|
164
171
|
const ready = msg.waitForReady === false
|
|
@@ -166,12 +173,27 @@ export class RunnerSession {
|
|
|
166
173
|
: provider.waitLiveReady
|
|
167
174
|
? await provider.waitLiveReady(msg.localThreadId)
|
|
168
175
|
: true;
|
|
176
|
+
const terminal = this.terminals.get(`${msg.localThreadId}-main`);
|
|
177
|
+
const paneFailure = !ready && terminal && !terminal.isAlive()
|
|
178
|
+
? terminal
|
|
179
|
+
.capturePane()
|
|
180
|
+
.split("\n")
|
|
181
|
+
.map((line) => line.trim())
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
.slice(-6)
|
|
184
|
+
.join(" ")
|
|
185
|
+
.slice(-1_000)
|
|
186
|
+
: "";
|
|
187
|
+
const readinessError = provider.liveSessionError?.(msg.localThreadId)
|
|
188
|
+
?? (paneFailure
|
|
189
|
+
? `Provider terminal exited before session discovery: ${paneFailure}`
|
|
190
|
+
: "live session was not ready before timeout");
|
|
169
191
|
this.transport.send({
|
|
170
192
|
t: "live.ready",
|
|
171
193
|
reqId: msg.reqId,
|
|
172
194
|
localThreadId: msg.localThreadId,
|
|
173
195
|
ok: ready,
|
|
174
|
-
...(ready ? {} : { error:
|
|
196
|
+
...(ready ? {} : { error: readinessError }),
|
|
175
197
|
});
|
|
176
198
|
}
|
|
177
199
|
catch (error) {
|
|
@@ -343,7 +365,10 @@ export class RunnerSession {
|
|
|
343
365
|
case "clearGoal":
|
|
344
366
|
return this.executor.clearGoal(args[0]);
|
|
345
367
|
case "forkSession":
|
|
346
|
-
|
|
368
|
+
if (!this.executor.forkSession) {
|
|
369
|
+
throw new Error("Provider fork is unavailable");
|
|
370
|
+
}
|
|
371
|
+
return this.executor.forkSession(args[0], args[1], args[2]);
|
|
347
372
|
}
|
|
348
373
|
}
|
|
349
374
|
}
|
package/dist/runner/manager.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* channel.
|
|
17
17
|
*/
|
|
18
18
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
19
|
-
import { type AgentCapabilities, type AgentRuntimeId, type
|
|
19
|
+
import { type AgentCapabilities, type AgentRuntimeId, type AppConfig, type CapabilityResult, type ModelListResponse, type ResolvedExecutionSnapshot, type RuntimeUserInput, type SessionInteractionResolution, type SessionEvent, type SessionWorkspaceSnapshot, type ThreadGoal } from "@rynx-ai/core";
|
|
20
20
|
import { type CodexSessionStore } from "../host.js";
|
|
21
21
|
import type { ResolveInteractionResult } from "../interactions.js";
|
|
22
22
|
import { type InjectOutcome, type TerminalOpenErrorCode, type TerminalRole } from "./protocol.js";
|
|
@@ -69,9 +69,8 @@ export interface RotateInfo {
|
|
|
69
69
|
from: string;
|
|
70
70
|
to: string;
|
|
71
71
|
kind: "clear" | "fork";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
cwd?: string;
|
|
72
|
+
workspace: SessionWorkspaceSnapshot;
|
|
73
|
+
execution: ResolvedExecutionSnapshot;
|
|
75
74
|
parentSessionId?: string;
|
|
76
75
|
}
|
|
77
76
|
/** Options for {@link RunnerManager.openTerminal}. */
|
|
@@ -140,6 +139,21 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
140
139
|
private readonly liveSessionKeys;
|
|
141
140
|
/** Last live-start error per local session, surfaced by the control API. */
|
|
142
141
|
private readonly liveErrors;
|
|
142
|
+
/** Last immutable launch snapshots seen for a Session. Used only to restore a
|
|
143
|
+
* target request that crossed a fork reservation boundary. */
|
|
144
|
+
private readonly liveOptions;
|
|
145
|
+
/** A fork reserves its target before the first asynchronous store read. This
|
|
146
|
+
* prevents another entry point from starting the target against an
|
|
147
|
+
* uncommitted Provider binding. */
|
|
148
|
+
private readonly forkReservations;
|
|
149
|
+
/** A native fork temporarily makes the source read-only so its canonical
|
|
150
|
+
* snapshot and Provider context are captured at the same boundary. */
|
|
151
|
+
private readonly sourceForkReservations;
|
|
152
|
+
/** Manager-wide fork de-duplication. LocalAgentHost only sees one source
|
|
153
|
+
* runner, so the fence must live here to cover concurrent source runners. */
|
|
154
|
+
private readonly forkOperations;
|
|
155
|
+
private readonly forkBufferedMessages;
|
|
156
|
+
private readonly forkBufferedTerminalInputs;
|
|
143
157
|
constructor(opts: RunnerManagerOptions);
|
|
144
158
|
/**
|
|
145
159
|
* Open a live terminal on the session's runner child (spawning it if needed).
|
|
@@ -166,33 +180,27 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
166
180
|
onMirror(listener: (sessionId: string, event: SessionEvent) => void): void;
|
|
167
181
|
/** Register the sink for session rotations (claude `/clear`·`/fork`): the server
|
|
168
182
|
* records the new session's meta (carry-over agent/model/title). */
|
|
169
|
-
onRotate(listener: (rotation: RotateInfo) => void): void;
|
|
183
|
+
onRotate(listener: (rotation: RotateInfo) => void | Promise<void>): void;
|
|
170
184
|
/**
|
|
171
185
|
* Eagerly bring up a session's codex-native live view (persistent forwarder +
|
|
172
186
|
* detached `codex --remote` TUI) in its runner child, spawning the runner if
|
|
173
187
|
* needed. Idempotent. Resolves true once the codex thread is bound; false for a
|
|
174
188
|
* non-codex / non-live session (the caller then uses the normal run path).
|
|
175
189
|
*/
|
|
176
|
-
ensureLiveSession(localThreadId: string, opts
|
|
177
|
-
|
|
190
|
+
ensureLiveSession(localThreadId: string, opts: {
|
|
191
|
+
workspace: SessionWorkspaceSnapshot;
|
|
192
|
+
execution: ResolvedExecutionSnapshot;
|
|
178
193
|
cols?: number;
|
|
179
194
|
rows?: number;
|
|
180
|
-
runtime?: AgentRuntimeId;
|
|
181
|
-
reasoningEffort?: ReasoningEffort;
|
|
182
|
-
agentName?: string;
|
|
183
|
-
agentSpec?: AgentSpec;
|
|
184
195
|
}): Promise<boolean>;
|
|
185
196
|
/** Start the Provider pane without waiting for login/onboarding to create a
|
|
186
197
|
* native thread. This is the setup-terminal gate; callers may attach as soon
|
|
187
198
|
* as it resolves, while normal message delivery still uses ensureLiveSession. */
|
|
188
|
-
startLiveSession(localThreadId: string, opts
|
|
189
|
-
|
|
199
|
+
startLiveSession(localThreadId: string, opts: {
|
|
200
|
+
workspace: SessionWorkspaceSnapshot;
|
|
201
|
+
execution: ResolvedExecutionSnapshot;
|
|
190
202
|
cols?: number;
|
|
191
203
|
rows?: number;
|
|
192
|
-
runtime?: AgentRuntimeId;
|
|
193
|
-
reasoningEffort?: ReasoningEffort;
|
|
194
|
-
agentName?: string;
|
|
195
|
-
agentSpec?: AgentSpec;
|
|
196
204
|
}): Promise<boolean>;
|
|
197
205
|
private requestLiveSession;
|
|
198
206
|
lastLiveSessionError(localThreadId: string): string | undefined;
|
|
@@ -215,7 +223,16 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
215
223
|
getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
|
|
216
224
|
setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
|
|
217
225
|
clearGoal(localThreadId: string): Promise<CapabilityResult>;
|
|
218
|
-
forkSession(currentLocalThreadId: string, newLocalThreadId: string
|
|
226
|
+
forkSession(currentLocalThreadId: string, newLocalThreadId: string, options: {
|
|
227
|
+
workspace: SessionWorkspaceSnapshot;
|
|
228
|
+
execution: ResolvedExecutionSnapshot;
|
|
229
|
+
beforeProviderFork: () => Promise<void>;
|
|
230
|
+
}): Promise<{
|
|
231
|
+
ok: true;
|
|
232
|
+
} | {
|
|
233
|
+
ok: false;
|
|
234
|
+
message: string;
|
|
235
|
+
}>;
|
|
219
236
|
/**
|
|
220
237
|
* Backend-free runtime readiness (not part of `AgentCapabilities`; surfaced for
|
|
221
238
|
* the control console). Never spawns a runner.
|
|
@@ -233,6 +250,12 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
233
250
|
* run on that session's child (which owns its private CODEX_HOME). */
|
|
234
251
|
private forwardCap;
|
|
235
252
|
private getOrSpawn;
|
|
253
|
+
private performManagedFork;
|
|
254
|
+
private performClaudeManagedFork;
|
|
255
|
+
private bufferForkTargetMessage;
|
|
256
|
+
private reservedForkTarget;
|
|
257
|
+
private deliverForkBufferedMessage;
|
|
258
|
+
private deliverRotateMessage;
|
|
236
259
|
private spawnHandle;
|
|
237
260
|
private onChildMessage;
|
|
238
261
|
/** Mark a handle dead and reject every pending run/cap with the exit reason. */
|