@rynx-ai/runtime 0.1.11-beta.1 → 0.1.11-beta.3
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 +799 -351
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- 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 +92 -19
- package/dist/runner/manager.js +838 -83
- 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/dist/terminal/tmux.d.ts +8 -0
- package/dist/terminal/tmux.js +36 -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,14 +6,13 @@
|
|
|
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";
|
|
14
13
|
export type { ResolveInteractionResult, RuntimeInteractionEvent, RuntimeInteractionListener, } from "./interactions.js";
|
|
15
14
|
export { probeRuntimeStatus } from "./runtime-status.js";
|
|
16
15
|
export { listRuntimeModels } from "./models-catalog.js";
|
|
17
|
-
export { TmuxTerminal, isTmuxAvailable } from "./terminal/tmux.js";
|
|
16
|
+
export { TmuxTerminal, isTmuxAvailable, terminateTmuxServer, tmuxSocketPath, } from "./terminal/tmux.js";
|
|
18
17
|
export type { TerminalAttachment, TmuxTerminalOptions } from "./terminal/tmux.js";
|
|
19
18
|
export { TerminalRegistry } from "./terminal/registry.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`.
|
|
@@ -13,5 +12,5 @@ export { RunnerManager, TerminalOpenError } from "./runner/manager.js";
|
|
|
13
12
|
export { probeRuntimeStatus } from "./runtime-status.js";
|
|
14
13
|
export { listRuntimeModels } from "./models-catalog.js";
|
|
15
14
|
// Live-terminal subsystem (Phase C): tmux-backed terminals + per-runner registry.
|
|
16
|
-
export { TmuxTerminal, isTmuxAvailable } from "./terminal/tmux.js";
|
|
15
|
+
export { TmuxTerminal, isTmuxAvailable, terminateTmuxServer, tmuxSocketPath, } from "./terminal/tmux.js";
|
|
17
16
|
export { TerminalRegistry } from "./terminal/registry.js";
|
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
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* transport drops in here later without touching `ConversationRuntime` or any
|
|
16
16
|
* channel.
|
|
17
17
|
*/
|
|
18
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
19
|
-
import { type
|
|
18
|
+
import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
|
|
19
|
+
import { type AdmissionReservation, 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";
|
|
@@ -53,8 +53,22 @@ export interface RunnerManagerOptions {
|
|
|
53
53
|
liveStartTimeoutMs?: number;
|
|
54
54
|
/** Max wait for a Provider-native thread to become ready. */
|
|
55
55
|
liveReadyTimeoutMs?: number;
|
|
56
|
+
/** Max wait for a native interrupt acknowledgement before reporting it
|
|
57
|
+
* unproven. Callers may then use the explicit force-stop path. */
|
|
58
|
+
liveInterruptTimeoutMs?: number;
|
|
59
|
+
/** Max wait for an accepted owner TUI submission to become observable as a
|
|
60
|
+
* mirrored response or a published native rotation. */
|
|
61
|
+
terminalInputHandoffTimeoutMs?: number;
|
|
62
|
+
/** Initial exponential backoff for retrying unproven terminal cleanup. */
|
|
63
|
+
terminalInputCleanupRetryMs?: number;
|
|
56
64
|
/** Injected for tests. */
|
|
57
65
|
spawn?: typeof nodeSpawn;
|
|
66
|
+
/** Injected for tests. Defaults to signaling the whole POSIX process group
|
|
67
|
+
* created for the runner child, with direct-child fallback. */
|
|
68
|
+
signalChild?: (child: ChildProcess, signal: NodeJS.Signals, processGroup: boolean) => void;
|
|
69
|
+
/** Injected for tests. Kills and verifies the deterministic private tmux
|
|
70
|
+
* server owned by a Session runner. */
|
|
71
|
+
terminateTerminalServer?: (terminalName: string) => boolean | Promise<boolean>;
|
|
58
72
|
now?: () => number;
|
|
59
73
|
/** Extra env merged into every spawned runner child — e.g. the control-plane
|
|
60
74
|
* URL + token so a claude-native PermissionRequest hook can POST back. */
|
|
@@ -62,6 +76,13 @@ export interface RunnerManagerOptions {
|
|
|
62
76
|
/** Optional lifecycle context for Session runners. The shared `__cap__`
|
|
63
77
|
* runner never opens one. */
|
|
64
78
|
sessionContextProvider?: RunnerSessionContextProvider;
|
|
79
|
+
/** Dynamic process-wide admission fence. Existing cancellation and read-only
|
|
80
|
+
* inspection remain available; every path that may spawn or attach execution
|
|
81
|
+
* checks this immediately before admission. */
|
|
82
|
+
admissionOpen?: () => boolean;
|
|
83
|
+
/** Atomically reserves one work-producing admission while it crosses the
|
|
84
|
+
* asynchronous boundary into an observable live runner/turn. */
|
|
85
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
65
86
|
}
|
|
66
87
|
/** A session rotation reported by a runner child (claude `/clear`·`/fork`): the
|
|
67
88
|
* new session `to` is now aliased to `from`'s runner; carries meta to carry over. */
|
|
@@ -69,9 +90,8 @@ export interface RotateInfo {
|
|
|
69
90
|
from: string;
|
|
70
91
|
to: string;
|
|
71
92
|
kind: "clear" | "fork";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
cwd?: string;
|
|
93
|
+
workspace: SessionWorkspaceSnapshot;
|
|
94
|
+
execution: ResolvedExecutionSnapshot;
|
|
75
95
|
parentSessionId?: string;
|
|
76
96
|
}
|
|
77
97
|
/** Options for {@link RunnerManager.openTerminal}. */
|
|
@@ -119,6 +139,8 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
119
139
|
private readonly spawn;
|
|
120
140
|
private readonly childEnv;
|
|
121
141
|
private readonly sessionContextProvider;
|
|
142
|
+
private readonly admissionOpen;
|
|
143
|
+
private readonly admissionReserve;
|
|
122
144
|
private readonly now;
|
|
123
145
|
private readonly defaultRuntime;
|
|
124
146
|
private readonly handles;
|
|
@@ -128,8 +150,13 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
128
150
|
private readonly reapTimer;
|
|
129
151
|
private readonly shutdownGraceMs;
|
|
130
152
|
private readonly shutdownKillGraceMs;
|
|
153
|
+
private readonly signalChild;
|
|
154
|
+
private readonly terminateTerminalServer;
|
|
131
155
|
private readonly liveStartTimeoutMs;
|
|
132
156
|
private readonly liveReadyTimeoutMs;
|
|
157
|
+
private readonly liveInterruptTimeoutMs;
|
|
158
|
+
private readonly terminalInputHandoffTimeoutMs;
|
|
159
|
+
private readonly terminalInputCleanupRetryMs;
|
|
133
160
|
private stopping;
|
|
134
161
|
private stopPromise;
|
|
135
162
|
/** Sink for mirrored {@link SessionEvent}s from every session's forwarder. */
|
|
@@ -140,6 +167,24 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
140
167
|
private readonly liveSessionKeys;
|
|
141
168
|
/** Last live-start error per local session, surfaced by the control API. */
|
|
142
169
|
private readonly liveErrors;
|
|
170
|
+
/** Last immutable launch snapshots seen for a Session. Used only to restore a
|
|
171
|
+
* target request that crossed a fork reservation boundary. */
|
|
172
|
+
private readonly liveOptions;
|
|
173
|
+
/** A fork reserves its target before the first asynchronous store read. This
|
|
174
|
+
* prevents another entry point from starting the target against an
|
|
175
|
+
* uncommitted Provider binding. */
|
|
176
|
+
private readonly forkReservations;
|
|
177
|
+
/** A native fork temporarily makes the source read-only so its canonical
|
|
178
|
+
* snapshot and Provider context are captured at the same boundary. */
|
|
179
|
+
private readonly sourceForkReservations;
|
|
180
|
+
/** Manager-wide fork de-duplication. LocalAgentHost only sees one source
|
|
181
|
+
* runner, so the fence must live here to cover concurrent source runners. */
|
|
182
|
+
private readonly forkOperations;
|
|
183
|
+
private readonly forkBufferedMessages;
|
|
184
|
+
private readonly forkBufferedTerminalInputs;
|
|
185
|
+
/** Owner TUI submissions accepted by the parent but not yet represented by a
|
|
186
|
+
* mirrored response or a durably published native rotation. */
|
|
187
|
+
private readonly terminalInputHandoffs;
|
|
143
188
|
constructor(opts: RunnerManagerOptions);
|
|
144
189
|
/**
|
|
145
190
|
* Open a live terminal on the session's runner child (spawning it if needed).
|
|
@@ -166,33 +211,39 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
166
211
|
onMirror(listener: (sessionId: string, event: SessionEvent) => void): void;
|
|
167
212
|
/** Register the sink for session rotations (claude `/clear`·`/fork`): the server
|
|
168
213
|
* records the new session's meta (carry-over agent/model/title). */
|
|
169
|
-
onRotate(listener: (rotation: RotateInfo) => void): void;
|
|
214
|
+
onRotate(listener: (rotation: RotateInfo) => void | Promise<void>): void;
|
|
215
|
+
/** Accepted TUI submissions that have not crossed into an observable runtime
|
|
216
|
+
* state. The daemon folds this into runningTurns after closing admission. */
|
|
217
|
+
pendingTerminalInputCount(): number;
|
|
218
|
+
private beginTerminalInputHandoffs;
|
|
219
|
+
private settleTerminalInputHandoff;
|
|
220
|
+
private settleTerminalRotationHandoff;
|
|
221
|
+
private currentTerminalSessionId;
|
|
222
|
+
private finishTerminalInputHandoff;
|
|
223
|
+
private finishAllTerminalInputHandoffs;
|
|
224
|
+
private preserveTerminalInputHandoffsAsActivity;
|
|
225
|
+
private scheduleTerminalInputCleanupRetry;
|
|
226
|
+
private expireTerminalInputHandoffs;
|
|
170
227
|
/**
|
|
171
228
|
* Eagerly bring up a session's codex-native live view (persistent forwarder +
|
|
172
229
|
* detached `codex --remote` TUI) in its runner child, spawning the runner if
|
|
173
230
|
* needed. Idempotent. Resolves true once the codex thread is bound; false for a
|
|
174
231
|
* non-codex / non-live session (the caller then uses the normal run path).
|
|
175
232
|
*/
|
|
176
|
-
ensureLiveSession(localThreadId: string, opts
|
|
177
|
-
|
|
233
|
+
ensureLiveSession(localThreadId: string, opts: {
|
|
234
|
+
workspace: SessionWorkspaceSnapshot;
|
|
235
|
+
execution: ResolvedExecutionSnapshot;
|
|
178
236
|
cols?: number;
|
|
179
237
|
rows?: number;
|
|
180
|
-
runtime?: AgentRuntimeId;
|
|
181
|
-
reasoningEffort?: ReasoningEffort;
|
|
182
|
-
agentName?: string;
|
|
183
|
-
agentSpec?: AgentSpec;
|
|
184
238
|
}): Promise<boolean>;
|
|
185
239
|
/** Start the Provider pane without waiting for login/onboarding to create a
|
|
186
240
|
* native thread. This is the setup-terminal gate; callers may attach as soon
|
|
187
241
|
* as it resolves, while normal message delivery still uses ensureLiveSession. */
|
|
188
|
-
startLiveSession(localThreadId: string, opts
|
|
189
|
-
|
|
242
|
+
startLiveSession(localThreadId: string, opts: {
|
|
243
|
+
workspace: SessionWorkspaceSnapshot;
|
|
244
|
+
execution: ResolvedExecutionSnapshot;
|
|
190
245
|
cols?: number;
|
|
191
246
|
rows?: number;
|
|
192
|
-
runtime?: AgentRuntimeId;
|
|
193
|
-
reasoningEffort?: ReasoningEffort;
|
|
194
|
-
agentName?: string;
|
|
195
|
-
agentSpec?: AgentSpec;
|
|
196
247
|
}): Promise<boolean>;
|
|
197
248
|
private requestLiveSession;
|
|
198
249
|
lastLiveSessionError(localThreadId: string): string | undefined;
|
|
@@ -203,6 +254,7 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
203
254
|
* the session has no live forwarder (caller falls back to the run path).
|
|
204
255
|
*/
|
|
205
256
|
injectMessage(localThreadId: string, input: RuntimeUserInput | string): Promise<InjectOutcome>;
|
|
257
|
+
private injectMessageAdmitted;
|
|
206
258
|
/**
|
|
207
259
|
* Interrupt a session's active live turn — the web Stop button (codex
|
|
208
260
|
* `turn/interrupt`, claude Escape). Best-effort: resolves false with NO spawn
|
|
@@ -215,7 +267,16 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
215
267
|
getGoal(localThreadId: string): Promise<CapabilityResult<ThreadGoal | null>>;
|
|
216
268
|
setGoal(localThreadId: string, objective: string): Promise<CapabilityResult>;
|
|
217
269
|
clearGoal(localThreadId: string): Promise<CapabilityResult>;
|
|
218
|
-
forkSession(currentLocalThreadId: string, newLocalThreadId: string
|
|
270
|
+
forkSession(currentLocalThreadId: string, newLocalThreadId: string, options: {
|
|
271
|
+
workspace: SessionWorkspaceSnapshot;
|
|
272
|
+
execution: ResolvedExecutionSnapshot;
|
|
273
|
+
beforeProviderFork: () => Promise<void>;
|
|
274
|
+
}): Promise<{
|
|
275
|
+
ok: true;
|
|
276
|
+
} | {
|
|
277
|
+
ok: false;
|
|
278
|
+
message: string;
|
|
279
|
+
}>;
|
|
219
280
|
/**
|
|
220
281
|
* Backend-free runtime readiness (not part of `AgentCapabilities`; surfaced for
|
|
221
282
|
* the control console). Never spawns a runner.
|
|
@@ -226,13 +287,25 @@ export declare class RunnerManager implements AgentCapabilities {
|
|
|
226
287
|
}): Promise<import("../host.js").CodexRuntimeStatus>;
|
|
227
288
|
/** Stop the runner bound to one session (if any), rejecting its in-flight work. */
|
|
228
289
|
stopRunner(localThreadId: string): void;
|
|
290
|
+
/** Force-stop and join the runner bound to one Session. Unlike stopRunner,
|
|
291
|
+
* completion proves that the child process has exited. */
|
|
292
|
+
terminateLiveSession(localThreadId: string): Promise<boolean>;
|
|
229
293
|
/** Stop every runner and join all child exits. Idempotent across concurrent calls. */
|
|
230
294
|
stop(): Promise<void>;
|
|
295
|
+
private handleForCleanup;
|
|
231
296
|
/** Forward a capability to a runner child. Session-less caps (listModels/status)
|
|
232
297
|
* use the shared `CAP_KEY` child; per-thread caps pass the session's key so they
|
|
233
298
|
* run on that session's child (which owns its private CODEX_HOME). */
|
|
234
299
|
private forwardCap;
|
|
235
300
|
private getOrSpawn;
|
|
301
|
+
private assertAdmissionOpen;
|
|
302
|
+
private reserveAdmission;
|
|
303
|
+
private performManagedFork;
|
|
304
|
+
private performClaudeManagedFork;
|
|
305
|
+
private bufferForkTargetMessage;
|
|
306
|
+
private reservedForkTarget;
|
|
307
|
+
private deliverForkBufferedMessage;
|
|
308
|
+
private deliverRotateMessage;
|
|
236
309
|
private spawnHandle;
|
|
237
310
|
private onChildMessage;
|
|
238
311
|
/** Mark a handle dead and reject every pending run/cap with the exit reason. */
|