@rynx-ai/server 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.
@@ -1,4 +1,5 @@
1
1
  import { encodeRemoteRuntimeRpcResponse, parseRemoteRuntimeRpcResponseForMethod, REMOTE_RUNTIME_RPC_METHOD_METADATA, } from "@rynx-ai/protocol/remote-runtime-rpc";
2
+ import { RuntimeProjectInputError } from "@rynx-ai/core";
2
3
  import { encodeDaemonStatus } from "./remote-runtime.js";
3
4
  import { MachineSessionServiceFailure, MachineSessionServiceInputError, } from "./machine-session-service.js";
4
5
  import { SessionBrowserServiceError, } from "./session-browser-service.js";
@@ -35,6 +36,30 @@ export class RemoteRuntimeDispatcher {
35
36
  return this.options.providerClis
36
37
  ? rpcResult(request, await this.options.providerClis.listStatuses())
37
38
  : rpcError(request.id, "method_not_found", "Provider CLI status is unavailable");
39
+ case "project.list":
40
+ return await this.withProjects(request, async (projects) => ({
41
+ projects: await projects.list(),
42
+ }));
43
+ case "project.get":
44
+ return await this.withProjects(request, async (projects) => ({
45
+ project: requireProject(await projects.get(request.params.projectId)),
46
+ }));
47
+ case "project.create":
48
+ return await this.withProjects(request, async (projects) => ({
49
+ project: await projects.create(request.params),
50
+ }));
51
+ case "project.update": {
52
+ const { projectId, ...input } = request.params;
53
+ return await this.withProjects(request, async (projects) => ({
54
+ project: requireProject(await projects.update(projectId, input)),
55
+ }));
56
+ }
57
+ case "project.delete":
58
+ return await this.withProjects(request, async (projects) => {
59
+ if (!await projects.remove(request.params.projectId))
60
+ throw new ProjectNotFoundError();
61
+ return { deleted: true };
62
+ });
38
63
  case "emulator.devices.list":
39
64
  return await this.withEmulator(request, (emulator) => emulator.devices());
40
65
  case "emulator.active.get":
@@ -102,6 +127,8 @@ export class RemoteRuntimeDispatcher {
102
127
  return await this.withSessions(request, (sessions) => sessions.launchOptions());
103
128
  case "session.create":
104
129
  return await this.withSessions(request, (sessions) => sessions.create(request.params));
130
+ case "session.fork":
131
+ return await this.withSessions(request, (sessions) => sessions.fork(request.params));
105
132
  case "session.message.send":
106
133
  return await this.withSessions(request, (sessions) => {
107
134
  const { sessionId, ...input } = request.params;
@@ -234,6 +261,13 @@ export class RemoteRuntimeDispatcher {
234
261
  }
235
262
  return rpcResult(request, await invoke(emulator));
236
263
  }
264
+ async withProjects(request, invoke) {
265
+ const projects = this.options.projects;
266
+ if (!projects) {
267
+ return rpcError(request.id, "method_not_found", "Runtime Projects are unavailable");
268
+ }
269
+ return rpcResult(request, await invoke(projects));
270
+ }
237
271
  async withSessions(request, invoke) {
238
272
  const sessions = this.options.sessions;
239
273
  if (!sessions) {
@@ -257,6 +291,17 @@ export class RemoteRuntimeDispatcher {
257
291
  }
258
292
  }
259
293
  function mapHostError(request, error) {
294
+ if (error instanceof ProjectNotFoundError) {
295
+ return { code: "not_found", message: "Runtime Project was not found" };
296
+ }
297
+ if (error instanceof RuntimeProjectInputError) {
298
+ return {
299
+ code: error.code,
300
+ message: error.code === "invalid_request"
301
+ ? "Runtime Project configuration is invalid"
302
+ : "Runtime Project directory is unavailable",
303
+ };
304
+ }
260
305
  if (error instanceof SessionBrowserServiceError) {
261
306
  switch (error.code) {
262
307
  case "invalid_request":
@@ -340,6 +385,13 @@ function mapHostError(request, error) {
340
385
  }
341
386
  return { code: "internal", message: "Remote Runtime request failed" };
342
387
  }
388
+ class ProjectNotFoundError extends Error {
389
+ }
390
+ function requireProject(project) {
391
+ if (!project)
392
+ throw new ProjectNotFoundError();
393
+ return project;
394
+ }
343
395
  function idempotentCancel(watch) {
344
396
  let pending;
345
397
  return () => {
@@ -64,6 +64,7 @@ function projectRuntimeSnapshot(value) {
64
64
  const originalActive = runtime.activeResponseIds;
65
65
  const projected = {
66
66
  status: runtime.status,
67
+ ...(runtime.statusKind === undefined ? {} : { statusKind: runtime.statusKind }),
67
68
  activeResponseIds: [],
68
69
  pendingInteractions: [],
69
70
  };
package/dist/server.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { Server as HttpServer } from "node:http";
2
2
  import Koa from "koa";
3
- import { ConversationRuntime, type AgentCapabilities, type AgentSessionStore, type AppConfig, type ChannelInstanceDescriptor, type SessionEvent, type SessionBus, type SessionLogStore, type SessionRegistry } from "@rynx-ai/core";
3
+ import { ConversationRuntime, type AdmissionReservation, type AgentCapabilities, type AgentSessionStore, type AppConfig, type SessionEvent, type SessionBus, type SessionLogStore, type SessionRegistry } from "@rynx-ai/core";
4
4
  import type { SessionInteractionResolution } from "@rynx-ai/protocol";
5
5
  import type { ControlRuntimeShareAddress, DaemonBrowserArtifactCleanResult, DaemonBrowserArtifactInstallInput, DaemonBrowserArtifactInstallResult, DaemonBrowserArtifactUpdateInput, DaemonBrowserArtifactVersionResult, DaemonCleanupSessionsInput, DaemonCleanupSessionsResult, DaemonChromeInspectionConfigureInput, DaemonChromeInspectionStatus, DaemonShutdownIfIdleResult } from "@rynx-ai/protocol/control";
6
- import type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, PluginManagementItem } from "@rynx-ai/protocol/plugin-management";
6
+ import type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, PluginManagementItem, PluginMarketplaceAddInput, PluginMarketplaceItem } from "@rynx-ai/protocol/plugin-management";
7
+ import type { PluginJsonValue } from "@rynx-ai/plugin-sdk";
7
8
  import type { PairingOffer } from "@rynx-ai/protocol/direct-runtime";
8
9
  import type { DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
9
10
  import type { RuntimeBrowserBootstrapCredential, RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -13,8 +14,8 @@ import type { RuntimeEmulatorSurfaceImageFrame, RuntimeEmulatorSurfaceReadyFrame
13
14
  import type { RemoteRuntimeRpcMethod, RemoteRuntimeRpcParams, RemoteRuntimeRpcResultFor, RemoteRuntimeSequencedSessionEvent } from "@rynx-ai/protocol/remote-runtime-rpc";
14
15
  import type { PluginAgentSummary, PluginResolvedSessionExecution, PluginSessionExecutionSnapshot } from "@rynx-ai/plugin-sdk";
15
16
  import { RunnerManager, type CodexSessionStore, type RunnerSessionContextProvider } from "@rynx-ai/runtime";
16
- import { ChannelManager } from "./channel-manager.js";
17
17
  import { type ControlPlaneDeps, type LocalSessionResourceHost, type SessionResourceLifecycle } from "./control-api.js";
18
+ import type { MachineSessionService } from "./machine-session-service.js";
18
19
  import type { DaemonRuntimeHost } from "./remote-runtime.js";
19
20
  import { type AttachDesktopBrowserHostServerOptions } from "./desktop-browser-host.js";
20
21
  import { SessionRuntimeIndex } from "./session-runtime-index.js";
@@ -22,11 +23,9 @@ import type { SessionEmulatorService } from "./session-emulator-service.js";
22
23
  import { type SessionTerminalHost } from "./session-terminal-host.js";
23
24
  import type { ProviderCliManagementHost } from "./provider-cli-host.js";
24
25
  import { type RuntimeWebAccessPolicy } from "./runtime-web-auth.js";
25
- export { ChannelManager } from "./channel-manager.js";
26
- export type { ChannelInstanceStatus } from "./channel-manager.js";
27
26
  export type { ProviderCliManagementHost, ProviderCliStatusHost, } from "./provider-cli-host.js";
28
27
  export { MachineSessionService, MachineSessionServiceFailure, MachineSessionServiceInputError, synthesizeSessionTitle, } from "./machine-session-service.js";
29
- export type { MachineSessionAgentCatalogPort, MachineSessionCreateInput, MachineSessionCreateResult, MachineSessionDeleteResult, MachineSessionInterruptPort, MachineSessionInterruptResult, MachineSessionInteractionResult, MachineSessionListInput, MachineSessionListPage, MachineSessionMessageResult, MachineSessionMessageEnqueueResult, MachineSessionMessageInput, MachineSessionMessageOperationState, MachineSessionPendingMessage, MachineSessionPendingMessagePort, MachineSessionPreparedInput, MachineSessionResourcePort, MachineSessionRunnerPort, MachineSessionRuntimeStatePort, MachineSessionServiceFailureCode, MachineSessionServiceOptions, MachineSessionServicePorts, MachineSessionSnapshotInput, MachineSessionSnapshotPage, MachineSessionWatch, } from "./machine-session-service.js";
28
+ export type { MachineSessionAgentCatalogPort, MachineSessionCreateInput, MachineSessionCreateResult, MachineSessionDeleteResult, MachineSessionForkInput, MachineSessionForkOperationRecord, MachineSessionForkOperationState, MachineSessionForkResult, MachineSessionForkStorePort, MachineSessionNativeRotationInput, MachineSessionInterruptPort, MachineSessionInterruptResult, MachineSessionInteractionResult, MachineSessionListInput, MachineSessionListPage, MachineSessionMessageResult, MachineSessionMessageEnqueueResult, MachineSessionMessageInput, MachineSessionMessageOperationState, MachineSessionPendingMessage, MachineSessionPendingMessagePort, MachineSessionPreparedInput, MachineSessionResourcePort, MachineSessionRunnerPort, MachineSessionRuntimeStatePort, MachineSessionServiceFailureCode, MachineSessionServiceOptions, MachineSessionServicePorts, MachineSessionSnapshotInput, MachineSessionSnapshotPage, MachineSessionWatch, } from "./machine-session-service.js";
30
29
  export { SessionBrowserHostError, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceUnsupportedError, } from "./session-browser-service.js";
31
30
  export type { SessionBrowserHost, SessionBrowserHostCreateInput, SessionBrowserHostErrorCode, SessionBrowserHostEvent, SessionBrowserHostHandle, SessionBrowserHostPage, SessionBrowserHostSurfaceFrame, SessionBrowserHostSurfaceOpenInput, SessionBrowserHostSurfaceSource, SessionBrowserHostSurfaceTrustedInput, SessionBrowserHostSurfaceViewport, SessionBrowserHostSnapshot, SessionBrowserInspectionEndpoint, SessionBrowserInspectionTarget, SessionBrowserRegistryPort, SessionBrowserServiceErrorCode, SessionBrowserServiceOptions, SessionBrowserServicePorts, SessionBrowserSurfaceOpenInput, SessionBrowserSurfaceSource, } from "./session-browser-service.js";
32
31
  export { SessionEmulatorBindingConflictError, SessionEmulatorService, SessionEmulatorServiceError, } from "./session-emulator-service.js";
@@ -48,14 +47,14 @@ export { RemoteRuntimeDispatcher } from "./remote-runtime-dispatcher.js";
48
47
  export type { AuthenticatedRuntimeSubject, RemoteRuntimeEmulatorHost, RemoteRuntimeSessionEmulatorHost, RemoteRuntimeDispatchContext, RemoteRuntimeDispatcherOptions, } from "./remote-runtime-dispatcher.js";
49
48
  export { createRunnerSessionTerminalHost, SessionTerminalOpenError, SessionTerminalStreamError, } from "./session-terminal-host.js";
50
49
  export type { RunnerSessionTerminalHostOptions, RunnerSessionTerminalHost, SessionTerminalAttachment, SessionTerminalCloseInfo, SessionTerminalCloseReason, SessionTerminalHost, SessionTerminalOpenErrorCode, SessionTerminalOpenOptions, SessionTerminalRole, } from "./session-terminal-host.js";
51
- export type { ControlChannel, ControlChannelType, ControlInstanceConfig, ControlInstanceStatus, ControlAgentSummary, } from "@rynx-ai/protocol/control";
50
+ export type { ControlAgentSummary } from "@rynx-ai/protocol/control";
51
+ export { listRuntimeModels } from "@rynx-ai/runtime";
52
52
  export interface CreateAppOptions {
53
53
  config?: AppConfig;
54
54
  /** When present, mount the control plane (page + JSON API) beside `/health`.
55
55
  * `runtime`/`sessionBus`/`sessionLog`/`runnerManager` (from `startServer`'s
56
56
  * scope) enable the channel-agnostic `/api/sessions` agent-run endpoints. */
57
57
  control?: {
58
- manager: ChannelManager;
59
58
  deps: ControlPlaneDeps;
60
59
  runtime?: ConversationRuntime;
61
60
  sessionBus?: SessionBus;
@@ -63,8 +62,6 @@ export interface CreateAppOptions {
63
62
  runnerManager?: RunnerManager;
64
63
  sessionStore?: CodexSessionStore;
65
64
  pluginRuntimeStatus?: () => unknown;
66
- pluginRuntimeReload?: (pluginId: string) => Promise<unknown>;
67
- pluginRuntimeEnsure?: (pluginId: string) => Promise<unknown>;
68
65
  daemonManagement?: DaemonManagementHost;
69
66
  remoteRuntime?: DaemonRuntimeHost;
70
67
  remoteRuntimeAdmin?: RemoteRuntimeAdminHost;
@@ -72,6 +69,7 @@ export interface CreateAppOptions {
72
69
  runtimeWebAccess?: RuntimeWebAccessPolicy;
73
70
  runtimeConnectionResolver?: RuntimeConnectionResolverHost;
74
71
  localSessionResources?: LocalSessionResourceHost;
72
+ localMachineSessions?: MachineSessionService;
75
73
  sessionEmulators?: SessionEmulatorService;
76
74
  /** Host lifecycle signal for ending SSE/video streams before drain. */
77
75
  shutdownSignal?: AbortSignal;
@@ -83,29 +81,20 @@ export interface CreateAppOptions {
83
81
  }
84
82
  export interface StartServerOptions {
85
83
  config?: AppConfig;
86
- /** Static set of channel instances to mount, each with its own context. */
87
- channelInstances?: ChannelInstanceDescriptor[];
88
84
  /**
89
- * Dynamic source of channel instance descriptors, re-read on reloads. When
90
- * provided, the server mounts them via a {@link ChannelManager} (the seam the
91
- * control plane drives). Takes precedence over `channelInstances`.
92
- */
93
- loadInstances?: () => Promise<ChannelInstanceDescriptor[]>;
94
- /**
95
- * Control-plane deps (config + agent-spec mutators + log tail) injected by the
96
- * daemon. When present (and instances are managed), the server serves the
97
- * control page + JSON API. Requires `loadInstances`/`channelInstances`.
85
+ * Control-plane deps injected by the daemon. Plugin/channel management is
86
+ * exposed through the plugin host rather than generic channel instances.
98
87
  */
99
88
  control?: ControlPlaneDeps;
100
89
  /**
101
90
  * Durable canonical session log (the daemon injects the SQLite-backed store).
102
- * Absent ⇒ channels render from the live stream without persisting.
91
+ * Absent means live session events are not persisted by this server.
103
92
  */
104
93
  sessionLog?: SessionLogStore;
105
94
  /**
106
- * Unified machine-session registry (the daemon injects the SQLite-backed store
107
- * over `sessions`). Handed to mounted channels so they register an identity row
108
- * per session; the control plane lists every session uniformly from it.
95
+ * Unified machine-session registry (the daemon injects the SQLite-backed
96
+ * store over `sessions`). Plugins reach it through Host RPC; the control plane
97
+ * lists every session uniformly from it.
109
98
  */
110
99
  sessionRegistry?: SessionRegistry;
111
100
  /** Trusted daemon-side bridge for isolated plugin runners. Installed plugin
@@ -124,6 +113,8 @@ export interface StartServerOptions {
124
113
  runtimeConnectionResolver?: RuntimeConnectionResolverHost;
125
114
  /** In-process resource authority used only by the Local Runtime BFF. */
126
115
  localSessionResources?: LocalSessionResourceHost;
116
+ /** Canonical same-daemon Session service used by Local compatibility routes. */
117
+ localMachineSessions?: MachineSessionService;
127
118
  /** Runtime-owned Session Emulator binding authority for Local compatibility routes. */
128
119
  sessionEmulators?: SessionEmulatorService;
129
120
  /** Pre-composed daemon-owned Session runtime. Supplying this lets the daemon
@@ -148,6 +139,7 @@ export interface SessionRuntimeServices {
148
139
  sessionBus: SessionBus;
149
140
  sessionRuntimeIndex: SessionRuntimeIndex;
150
141
  conversationRuntime: ConversationRuntime;
142
+ publishSessionEvent: (sessionId: string, event: SessionEvent) => Promise<void>;
151
143
  }
152
144
  export interface PluginRuntimeHostServices {
153
145
  conversationRuntime: ConversationRuntime;
@@ -163,15 +155,18 @@ export interface PluginRuntimeHostServices {
163
155
  }): Promise<PluginResolvedSessionExecution>;
164
156
  };
165
157
  interruptSession(sessionId: string): Promise<boolean>;
158
+ terminateSession?(sessionId: string): Promise<boolean>;
166
159
  resolveSessionInteraction(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
167
160
  disposition: "applied" | "already_resolved" | "not_found" | "invalid";
168
161
  }>;
162
+ /** Dynamic daemon-wide admission state. Plugin runtimes may start while this is
163
+ * closed, but cannot provision Sessions or begin Turns. */
164
+ admissionOpen?: () => boolean;
165
+ admissionReserve?: () => AdmissionReservation | undefined;
169
166
  }
170
167
  export interface PluginRuntimeLifecycle {
171
168
  bindHostServices(services: PluginRuntimeHostServices): void;
172
169
  startPluginRuntimes(): Promise<void>;
173
- reloadPluginRuntime(pluginId: string): Promise<unknown>;
174
- ensurePluginRuntime(pluginId: string): Promise<unknown>;
175
170
  /** Cached process/contribution health; must not block on plugin RPC. */
176
171
  statusSnapshot(): unknown;
177
172
  dispose(): Promise<void>;
@@ -206,8 +201,24 @@ export interface PluginManagementHost {
206
201
  prepareInstallation(input: PluginInstallPrepareInput, context: DaemonManagementInvocationContext): Promise<PluginInstallPreparation>;
207
202
  commitInstallation(token: string, input: PluginInstallCommitInput, context: DaemonManagementInvocationContext): Promise<PluginInstallCommitResult>;
208
203
  cancelPreparation(token: string): boolean | Promise<boolean>;
204
+ settingsUi(pluginId: string): {
205
+ generation: string;
206
+ assetGeneration: string;
207
+ html: string;
208
+ } | undefined;
209
+ settingsUiAsset(pluginId: string, assetGeneration: string, assetPath: string): {
210
+ contentType: string;
211
+ data: Buffer;
212
+ } | undefined;
213
+ callSettings(pluginId: string, generation: string, method: string, params: PluginJsonValue | undefined, context: DaemonManagementInvocationContext): Promise<PluginJsonValue>;
209
214
  dispose?(): void | Promise<void>;
210
215
  }
216
+ export interface PluginMarketplaceManagementHost {
217
+ list(): PluginMarketplaceItem[] | Promise<PluginMarketplaceItem[]>;
218
+ add(input: PluginMarketplaceAddInput, context: DaemonManagementInvocationContext): Promise<PluginMarketplaceItem>;
219
+ refresh(id: string, context: DaemonManagementInvocationContext): Promise<PluginMarketplaceItem>;
220
+ remove(id: string): boolean | Promise<boolean>;
221
+ }
211
222
  export interface BrowserArtifactManagementHost {
212
223
  install(input: DaemonBrowserArtifactInstallInput, context: DaemonManagementInvocationContext): Promise<DaemonBrowserArtifactInstallResult>;
213
224
  update(input: DaemonBrowserArtifactUpdateInput, context: DaemonManagementInvocationContext): Promise<DaemonBrowserArtifactInstallResult>;
@@ -230,9 +241,15 @@ export interface DaemonManagementHost {
230
241
  restartRequired: boolean;
231
242
  };
232
243
  /** Perform one final activity check and schedule shutdown only when idle. */
233
- shutdownIfIdle?(): DaemonShutdownIfIdleResult;
244
+ shutdownIfIdle?(maintenanceLeaseToken?: string): DaemonShutdownIfIdleResult | Promise<DaemonShutdownIfIdleResult>;
245
+ /** Release a cross-process maintenance fence after a verified cutover. */
246
+ releaseMaintenanceLease?(maintenanceLeaseToken: string): {
247
+ outcome: "released" | "not_active" | "denied";
248
+ };
234
249
  /** Stateful plugin registry operations owned by the resident daemon. */
235
250
  pluginManagement?: PluginManagementHost;
251
+ /** Registered Git/local plugin catalogs. */
252
+ pluginMarketplaces?: PluginMarketplaceManagementHost;
236
253
  /** Stateful Chrome for Testing artifact operations owned by the daemon. */
237
254
  browserArtifacts?: BrowserArtifactManagementHost;
238
255
  /** Destructive maintenance operations over daemon-owned state. */
@@ -407,6 +424,8 @@ export declare function createSessionRuntimeServices(input: {
407
424
  sessionLog?: SessionLogStore;
408
425
  onMirrorError?: (error: unknown, sessionId: string) => void;
409
426
  sessionContextProvider?: RunnerSessionContextProvider;
427
+ admissionOpen?: () => boolean;
428
+ admissionReserve?: () => AdmissionReservation | undefined;
410
429
  }): SessionRuntimeServices;
411
430
  /**
412
431
  * The HTTP surface is just a liveness probe for container orchestration — all
@@ -415,4 +434,4 @@ export declare function createSessionRuntimeServices(input: {
415
434
  * no agent request / response / SSE endpoints are exposed here.
416
435
  */
417
436
  export declare function createApp({ config, control }?: CreateAppOptions): Koa<Koa.DefaultState, Koa.DefaultContext>;
418
- export declare function startServer({ config, channelInstances, loadInstances, control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, }?: StartServerOptions): Promise<RynxServer>;
437
+ export declare function startServer({ config, control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, localMachineSessions, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, }?: StartServerOptions): Promise<RynxServer>;
package/dist/server.js CHANGED
@@ -3,7 +3,6 @@ import Koa from "koa";
3
3
  import Router from "@koa/router";
4
4
  import { loadConfig, listAgentSpecs, loadAgentSpec, resolveSessionExecution, resolveRuntimeModel, ConversationRuntime, InMemorySessionBus, persistSessionEvent, } from "@rynx-ai/core";
5
5
  import { RunnerManager, FileCodexSessionStore, resolveCodexSessionStorePath, } from "@rynx-ai/runtime";
6
- import { ChannelManager } from "./channel-manager.js";
7
6
  import { createControlRouter, } from "./control-api.js";
8
7
  import { attachEmulatorTouchWs } from "./emulator-touch-ws.js";
9
8
  import { attachTerminalWs } from "./terminal-ws.js";
@@ -14,7 +13,6 @@ import { resolveControlWebDist, sendSpaFile } from "./control-web-dist.js";
14
13
  import { SessionRuntimeIndex } from "./session-runtime-index.js";
15
14
  import { createRunnerSessionTerminalHost, } from "./session-terminal-host.js";
16
15
  import { runtimeWebAccessPolicyForBindHost, } from "./runtime-web-auth.js";
17
- export { ChannelManager } from "./channel-manager.js";
18
16
  export { MachineSessionService, MachineSessionServiceFailure, MachineSessionServiceInputError, synthesizeSessionTitle, } from "./machine-session-service.js";
19
17
  export { SessionBrowserHostError, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceUnsupportedError, } from "./session-browser-service.js";
20
18
  export { SessionEmulatorBindingConflictError, SessionEmulatorService, SessionEmulatorServiceError, } from "./session-emulator-service.js";
@@ -26,6 +24,7 @@ export { attachDirectRuntimeBrowserSurfaceServer, DirectRuntimeBrowserSurfaceHos
26
24
  export { attachDirectRuntimeBrowserInspectServer, DirectRuntimeBrowserInspectHostError, } from "./direct-runtime-browser-inspect-server.js";
27
25
  export { RemoteRuntimeDispatcher } from "./remote-runtime-dispatcher.js";
28
26
  export { createRunnerSessionTerminalHost, SessionTerminalOpenError, SessionTerminalStreamError, } from "./session-terminal-host.js";
27
+ export { listRuntimeModels } from "@rynx-ai/runtime";
29
28
  /** Preserve one session's mirror order without serializing unrelated sessions. */
30
29
  export class SessionMirrorQueue {
31
30
  persist;
@@ -60,18 +59,24 @@ export function createSessionRuntimeServices(input) {
60
59
  ...(input.sessionContextProvider
61
60
  ? { sessionContextProvider: input.sessionContextProvider }
62
61
  : {}),
62
+ ...(input.admissionOpen ? { admissionOpen: input.admissionOpen } : {}),
63
+ ...(input.admissionReserve ? { admissionReserve: input.admissionReserve } : {}),
63
64
  });
64
65
  const sessionBus = new InMemorySessionBus();
65
66
  const sessionRuntimeIndex = new SessionRuntimeIndex();
67
+ const sessionMirrorQueue = new SessionMirrorQueue((sessionId, event) => persistSessionEvent(sessionId, event, { sessionLog: input.sessionLog, sessionBus }));
68
+ const mirrorSessionEvent = (sessionId, event) => {
69
+ sessionRuntimeIndex.observe(sessionId, event);
70
+ return sessionMirrorQueue.enqueue(sessionId, event);
71
+ };
66
72
  const conversationRuntime = new ConversationRuntime({
67
73
  config: input.config,
68
74
  executor: runnerManager,
69
75
  sessionBus,
76
+ sessionEventSink: mirrorSessionEvent,
70
77
  });
71
- const sessionMirrorQueue = new SessionMirrorQueue((sessionId, event) => persistSessionEvent(sessionId, event, { sessionLog: input.sessionLog, sessionBus }));
72
78
  runnerManager.onMirror((sessionId, event) => {
73
- sessionRuntimeIndex.observe(sessionId, event);
74
- void sessionMirrorQueue.enqueue(sessionId, event).catch((error) => {
79
+ void mirrorSessionEvent(sessionId, event).catch((error) => {
75
80
  input.onMirrorError?.(error, sessionId);
76
81
  });
77
82
  });
@@ -81,6 +86,7 @@ export function createSessionRuntimeServices(input) {
81
86
  sessionBus,
82
87
  sessionRuntimeIndex,
83
88
  conversationRuntime,
89
+ publishSessionEvent: mirrorSessionEvent,
84
90
  };
85
91
  }
86
92
  /**
@@ -131,7 +137,7 @@ export function createApp({ config = loadConfig(), control } = {}) {
131
137
  }
132
138
  return app;
133
139
  }
134
- export async function startServer({ config = loadConfig(), channelInstances, loadInstances, control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, } = {}) {
140
+ export async function startServer({ config = loadConfig(), control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, localMachineSessions, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, } = {}) {
135
141
  const runtimeWebAccess = runtimeWebAccessPolicyForBindHost(config.HOST);
136
142
  const desktopBrowserManagementToken = desktopBrowserHost
137
143
  ? requireDaemonManagementToken(daemonManagement)
@@ -148,10 +154,10 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
148
154
  createRunnerSessionTerminalHost({
149
155
  runnerManager,
150
156
  resolveLiveSession: async (sessionId) => {
151
- const session = await sessionStore.get(sessionId);
157
+ const session = sessionRegistry?.get(sessionId);
152
158
  if (!session || !runnerManager.hasLiveSession(sessionId))
153
159
  return undefined;
154
- return { cwd: session.cwd };
160
+ return { cwd: session.workspace.cwd };
155
161
  },
156
162
  });
157
163
  if (pluginRuntime) {
@@ -175,139 +181,53 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
175
181
  }),
176
182
  },
177
183
  interruptSession: (sessionId) => runnerManager.interruptLiveSession(sessionId),
184
+ terminateSession: (sessionId) => runnerManager.terminateLiveSession(sessionId),
178
185
  resolveSessionInteraction: (sessionId, interactionId, resolution) => runnerManager.resolveInteraction(sessionId, interactionId, resolution),
179
186
  });
180
187
  }
181
- // claude `/clear`·`/fork` rotates to a fresh machine-session; record its meta so
182
- // the console list shows it with the carried-over launch target/model + a source tag.
183
- // (It also surfaces via the log-merge once its first turn mirrors.)
188
+ // Provider TUI `/clear`·`/fork` uses the same publication boundary as an
189
+ // explicit Rynx fork. The manager exposes the transferred terminal only after
190
+ // this resolves, so no target can appear with missing history or metadata.
184
191
  runnerManager.onRotate((r) => {
185
- const source = control?.getSessionMeta(r.from);
186
- control?.createSessionMeta({
187
- id: r.to,
188
- source: r.kind === "fork" ? "fork" : "clear",
189
- ...(r.agent
190
- ? { agent: r.agent }
191
- : source?.provider
192
- ? { provider: source.provider }
193
- : {}),
194
- ...(r.model ? { model: r.model } : {}),
195
- createdAt: new Date().toISOString(),
192
+ if (!localMachineSessions) {
193
+ throw new Error("native Session rotation requires the machine Session service");
194
+ }
195
+ return localMachineSessions.recordNativeRotation({
196
+ sourceSessionId: r.from,
197
+ targetSessionId: r.to,
198
+ kind: r.kind,
196
199
  });
197
200
  });
198
- // New path: per-instance contexts via the ChannelManager (the daemon supplies
199
- // `loadInstances`; the control plane drives live start/stop/reload).
200
- const resolveInstances = loadInstances ?? (channelInstances ? async () => channelInstances : null);
201
- if (resolveInstances) {
202
- const controlShutdown = new AbortController();
203
- const manager = new ChannelManager({
204
- config,
205
- conversationRuntime,
206
- capabilities: runnerManager,
207
- sessionStore,
208
- sessionLog,
209
- sessionBus,
210
- sessionRegistry,
211
- loadInstances: resolveInstances,
212
- });
213
- const app = createApp({
214
- config,
215
- control: control
216
- ? {
217
- manager,
218
- deps: control,
219
- runtime: conversationRuntime,
220
- sessionBus,
221
- sessionLog,
222
- runnerManager,
223
- sessionStore,
224
- pluginRuntimeStatus: pluginRuntime
225
- ? () => pluginRuntime.statusSnapshot()
226
- : undefined,
227
- pluginRuntimeReload: pluginRuntime
228
- ? (pluginId) => pluginRuntime.reloadPluginRuntime(pluginId)
229
- : undefined,
230
- pluginRuntimeEnsure: pluginRuntime
231
- ? (pluginId) => pluginRuntime.ensurePluginRuntime(pluginId)
232
- : undefined,
233
- daemonManagement,
234
- remoteRuntime,
235
- remoteRuntimeAdmin,
236
- runtimeTargetControl,
237
- runtimeConnectionResolver,
238
- localSessionResources,
239
- sessionEmulators,
240
- shutdownSignal: controlShutdown.signal,
241
- sessionRuntimeIndex,
242
- sessionLifecycle,
243
- runtimeLocalBrowser,
244
- providerClis,
245
- }
246
- : undefined,
247
- });
248
- return new Promise((resolve) => {
249
- const server = app.listen(config.PORT, config.HOST, () => {
250
- console.log(JSON.stringify({
251
- level: config.LOG_LEVEL,
252
- msg: "Harness agent server listening",
253
- host: config.HOST,
254
- port: config.PORT,
255
- runtime: config.AGENT_RUNTIME,
256
- model: resolveRuntimeModel(config, config.AGENT_RUNTIME),
257
- }));
258
- const startup = (async () => {
259
- await pluginRuntime?.startPluginRuntimes();
260
- await manager.startAll();
261
- })();
262
- void startup.catch((error) => {
263
- console.error(JSON.stringify({
264
- level: "error",
265
- type: "plugin-runtime",
266
- event: "startup_failed",
267
- error: error instanceof Error ? error.message : String(error),
268
- }));
269
- });
270
- // Live WS bridges share this http.Server (no second port).
271
- const webSocketServers = [
272
- attachTerminalWs(server, {
273
- runtimeConnectionResolver,
274
- legacyLocalTerminalHost,
275
- runtimeWebAccess,
276
- }),
277
- attachBrowserSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
278
- attachEmulatorSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
279
- ];
280
- if (desktopBrowserHost && desktopBrowserManagementToken) {
281
- webSocketServers.push(attachDesktopBrowserHostServer(server, {
282
- ...desktopBrowserHost,
283
- managementToken: desktopBrowserManagementToken,
284
- }));
285
- }
286
- if (control?.emulator) {
287
- webSocketServers.push(attachEmulatorTouchWs(server, { emulator: control.emulator }));
288
- }
289
- resolve(bindServerLifecycle(server, {
290
- fence: () => {
291
- controlShutdown.abort(new Error("Rynx server is shutting down"));
292
- },
293
- closeUpgrades: async () => {
294
- await closeWebSocketServers(webSocketServers);
295
- },
296
- cleanup: async () => {
297
- await runCleanupSteps([
298
- () => startup.catch(() => undefined),
299
- () => manager.stopAll(),
300
- () => Promise.resolve(daemonManagement?.pluginManagement?.dispose?.()),
301
- () => pluginRuntime?.dispose() ?? Promise.resolve(),
302
- () => runnerManager.stop(),
303
- ]);
304
- },
305
- }));
306
- });
307
- });
308
- }
309
- // Core-only mode: no chat instances and no plugin code are required.
310
- const app = createApp();
201
+ const controlShutdown = new AbortController();
202
+ const app = createApp({
203
+ config,
204
+ control: control
205
+ ? {
206
+ deps: control,
207
+ runtime: conversationRuntime,
208
+ sessionBus,
209
+ sessionLog,
210
+ runnerManager,
211
+ sessionStore,
212
+ pluginRuntimeStatus: pluginRuntime
213
+ ? () => pluginRuntime.statusSnapshot()
214
+ : undefined,
215
+ daemonManagement,
216
+ remoteRuntime,
217
+ remoteRuntimeAdmin,
218
+ runtimeTargetControl,
219
+ runtimeConnectionResolver,
220
+ localSessionResources,
221
+ localMachineSessions,
222
+ sessionEmulators,
223
+ shutdownSignal: controlShutdown.signal,
224
+ sessionRuntimeIndex,
225
+ sessionLifecycle,
226
+ runtimeLocalBrowser,
227
+ providerClis,
228
+ }
229
+ : undefined,
230
+ });
311
231
  return new Promise((resolve) => {
312
232
  const server = app.listen(config.PORT, config.HOST, () => {
313
233
  console.log(JSON.stringify({
@@ -315,21 +235,45 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
315
235
  msg: "Harness agent server listening",
316
236
  host: config.HOST,
317
237
  port: config.PORT,
318
- runtime: config.AGENT_RUNTIME,
319
- model: resolveRuntimeModel(config, config.AGENT_RUNTIME),
238
+ runtime: config.DEFAULT_RUNTIME,
239
+ model: resolveRuntimeModel(config, config.DEFAULT_RUNTIME),
320
240
  }));
321
- const webSocketServers = [];
241
+ const startup = pluginRuntime?.startPluginRuntimes() ?? Promise.resolve();
242
+ void startup.catch((error) => {
243
+ console.error(JSON.stringify({
244
+ level: "error",
245
+ type: "plugin-runtime",
246
+ event: "startup_failed",
247
+ error: error instanceof Error ? error.message : String(error),
248
+ }));
249
+ });
250
+ const webSocketServers = [
251
+ attachTerminalWs(server, {
252
+ runtimeConnectionResolver,
253
+ legacyLocalTerminalHost,
254
+ runtimeWebAccess,
255
+ }),
256
+ attachBrowserSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
257
+ attachEmulatorSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
258
+ ];
322
259
  if (desktopBrowserHost && desktopBrowserManagementToken) {
323
260
  webSocketServers.push(attachDesktopBrowserHostServer(server, {
324
261
  ...desktopBrowserHost,
325
262
  managementToken: desktopBrowserManagementToken,
326
263
  }));
327
264
  }
265
+ if (control?.emulator) {
266
+ webSocketServers.push(attachEmulatorTouchWs(server, { emulator: control.emulator }));
267
+ }
328
268
  resolve(bindServerLifecycle(server, {
329
- ...(webSocketServers.length > 0
330
- ? { closeUpgrades: () => closeWebSocketServers(webSocketServers) }
331
- : {}),
332
- cleanup: () => runnerManager.stop(),
269
+ fence: () => controlShutdown.abort(new Error("Rynx server is shutting down")),
270
+ closeUpgrades: () => closeWebSocketServers(webSocketServers),
271
+ cleanup: async () => runCleanupSteps([
272
+ () => startup.catch(() => undefined),
273
+ () => Promise.resolve(daemonManagement?.pluginManagement?.dispose?.()),
274
+ () => pluginRuntime?.dispose() ?? Promise.resolve(),
275
+ () => runnerManager.stop(),
276
+ ]),
333
277
  }));
334
278
  });
335
279
  });
@@ -1,3 +1,4 @@
1
+ import type { AdmissionReservation } from "@rynx-ai/core";
1
2
  import { type RuntimeBrowserCloseParams, type RuntimeBrowserCloseResult, type RuntimeBrowserExecutionBackend, type RuntimeBrowserOpenParams, type RuntimeBrowserPageCreateParams, type RuntimeBrowserPageMutationParams, type RuntimeBrowserPageNavigateParams, type RuntimeBrowserState, type RuntimeBrowserStateGetParams } from "@rynx-ai/protocol/runtime-browser";
2
3
  import type { RuntimeBrowserSurfaceFormat, RuntimeBrowserSurfaceInputEvent } from "@rynx-ai/protocol/runtime-browser-surface";
3
4
  import { type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -155,6 +156,11 @@ export interface SessionBrowserServiceOptions {
155
156
  pageIdFactory?: () => string;
156
157
  /** Injectable boot nonce; production randomization fences stale pre-restart mutations. */
157
158
  bootGenerationSeed?: number;
159
+ /**
160
+ * Synchronous daemon admission fence. A successful reservation is held until
161
+ * Browser open has either failed or registered the physical Host handle.
162
+ */
163
+ admissionReserve?: () => AdmissionReservation | undefined;
158
164
  }
159
165
  export type SessionBrowserHostErrorCode = "unavailable" | "page_not_found" | "capacity" | "outcome_unknown";
160
166
  /** Expected failure reported by a Browser Host implementation. */
@@ -181,6 +187,7 @@ export declare class SessionBrowserService {
181
187
  private readonly observers;
182
188
  private readonly pageIdFactory;
183
189
  private readonly bootGenerationSeed;
190
+ private readonly admissionReserve?;
184
191
  private shuttingDown;
185
192
  private closeAllPromise?;
186
193
  constructor(ports: SessionBrowserServicePorts, options?: SessionBrowserServiceOptions);
@@ -225,6 +232,7 @@ export declare class SessionBrowserService {
225
232
  closeAll(): Promise<void>;
226
233
  private closeEveryRecord;
227
234
  private createBrowser;
235
+ private reserveOpenAdmission;
228
236
  private closeUnavailableForRecreate;
229
237
  private pageMutation;
230
238
  private pageMutationParsed;