@rynx-ai/server 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/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 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;
@@ -170,8 +162,6 @@ export interface PluginRuntimeHostServices {
170
162
  export interface PluginRuntimeLifecycle {
171
163
  bindHostServices(services: PluginRuntimeHostServices): void;
172
164
  startPluginRuntimes(): Promise<void>;
173
- reloadPluginRuntime(pluginId: string): Promise<unknown>;
174
- ensurePluginRuntime(pluginId: string): Promise<unknown>;
175
165
  /** Cached process/contribution health; must not block on plugin RPC. */
176
166
  statusSnapshot(): unknown;
177
167
  dispose(): Promise<void>;
@@ -206,8 +196,24 @@ export interface PluginManagementHost {
206
196
  prepareInstallation(input: PluginInstallPrepareInput, context: DaemonManagementInvocationContext): Promise<PluginInstallPreparation>;
207
197
  commitInstallation(token: string, input: PluginInstallCommitInput, context: DaemonManagementInvocationContext): Promise<PluginInstallCommitResult>;
208
198
  cancelPreparation(token: string): boolean | Promise<boolean>;
199
+ settingsUi(pluginId: string): {
200
+ generation: string;
201
+ assetGeneration: string;
202
+ html: string;
203
+ } | undefined;
204
+ settingsUiAsset(pluginId: string, assetGeneration: string, assetPath: string): {
205
+ contentType: string;
206
+ data: Buffer;
207
+ } | undefined;
208
+ callSettings(pluginId: string, generation: string, method: string, params: PluginJsonValue | undefined, context: DaemonManagementInvocationContext): Promise<PluginJsonValue>;
209
209
  dispose?(): void | Promise<void>;
210
210
  }
211
+ export interface PluginMarketplaceManagementHost {
212
+ list(): PluginMarketplaceItem[] | Promise<PluginMarketplaceItem[]>;
213
+ add(input: PluginMarketplaceAddInput, context: DaemonManagementInvocationContext): Promise<PluginMarketplaceItem>;
214
+ refresh(id: string, context: DaemonManagementInvocationContext): Promise<PluginMarketplaceItem>;
215
+ remove(id: string): boolean | Promise<boolean>;
216
+ }
211
217
  export interface BrowserArtifactManagementHost {
212
218
  install(input: DaemonBrowserArtifactInstallInput, context: DaemonManagementInvocationContext): Promise<DaemonBrowserArtifactInstallResult>;
213
219
  update(input: DaemonBrowserArtifactUpdateInput, context: DaemonManagementInvocationContext): Promise<DaemonBrowserArtifactInstallResult>;
@@ -233,6 +239,8 @@ export interface DaemonManagementHost {
233
239
  shutdownIfIdle?(): DaemonShutdownIfIdleResult;
234
240
  /** Stateful plugin registry operations owned by the resident daemon. */
235
241
  pluginManagement?: PluginManagementHost;
242
+ /** Registered Git/local plugin catalogs. */
243
+ pluginMarketplaces?: PluginMarketplaceManagementHost;
236
244
  /** Stateful Chrome for Testing artifact operations owned by the daemon. */
237
245
  browserArtifacts?: BrowserArtifactManagementHost;
238
246
  /** Destructive maintenance operations over daemon-owned state. */
@@ -415,4 +423,4 @@ export declare function createSessionRuntimeServices(input: {
415
423
  * no agent request / response / SSE endpoints are exposed here.
416
424
  */
417
425
  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>;
426
+ 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;
@@ -63,15 +62,19 @@ export function createSessionRuntimeServices(input) {
63
62
  });
64
63
  const sessionBus = new InMemorySessionBus();
65
64
  const sessionRuntimeIndex = new SessionRuntimeIndex();
65
+ const sessionMirrorQueue = new SessionMirrorQueue((sessionId, event) => persistSessionEvent(sessionId, event, { sessionLog: input.sessionLog, sessionBus }));
66
+ const mirrorSessionEvent = (sessionId, event) => {
67
+ sessionRuntimeIndex.observe(sessionId, event);
68
+ return sessionMirrorQueue.enqueue(sessionId, event);
69
+ };
66
70
  const conversationRuntime = new ConversationRuntime({
67
71
  config: input.config,
68
72
  executor: runnerManager,
69
73
  sessionBus,
74
+ sessionEventSink: mirrorSessionEvent,
70
75
  });
71
- const sessionMirrorQueue = new SessionMirrorQueue((sessionId, event) => persistSessionEvent(sessionId, event, { sessionLog: input.sessionLog, sessionBus }));
72
76
  runnerManager.onMirror((sessionId, event) => {
73
- sessionRuntimeIndex.observe(sessionId, event);
74
- void sessionMirrorQueue.enqueue(sessionId, event).catch((error) => {
77
+ void mirrorSessionEvent(sessionId, event).catch((error) => {
75
78
  input.onMirrorError?.(error, sessionId);
76
79
  });
77
80
  });
@@ -81,6 +84,7 @@ export function createSessionRuntimeServices(input) {
81
84
  sessionBus,
82
85
  sessionRuntimeIndex,
83
86
  conversationRuntime,
87
+ publishSessionEvent: mirrorSessionEvent,
84
88
  };
85
89
  }
86
90
  /**
@@ -131,7 +135,7 @@ export function createApp({ config = loadConfig(), control } = {}) {
131
135
  }
132
136
  return app;
133
137
  }
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, } = {}) {
138
+ 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
139
  const runtimeWebAccess = runtimeWebAccessPolicyForBindHost(config.HOST);
136
140
  const desktopBrowserManagementToken = desktopBrowserHost
137
141
  ? requireDaemonManagementToken(daemonManagement)
@@ -148,10 +152,10 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
148
152
  createRunnerSessionTerminalHost({
149
153
  runnerManager,
150
154
  resolveLiveSession: async (sessionId) => {
151
- const session = await sessionStore.get(sessionId);
155
+ const session = sessionRegistry?.get(sessionId);
152
156
  if (!session || !runnerManager.hasLiveSession(sessionId))
153
157
  return undefined;
154
- return { cwd: session.cwd };
158
+ return { cwd: session.workspace.cwd };
155
159
  },
156
160
  });
157
161
  if (pluginRuntime) {
@@ -178,136 +182,49 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
178
182
  resolveSessionInteraction: (sessionId, interactionId, resolution) => runnerManager.resolveInteraction(sessionId, interactionId, resolution),
179
183
  });
180
184
  }
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.)
185
+ // Provider TUI `/clear`·`/fork` uses the same publication boundary as an
186
+ // explicit Rynx fork. The manager exposes the transferred terminal only after
187
+ // this resolves, so no target can appear with missing history or metadata.
184
188
  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(),
189
+ if (!localMachineSessions) {
190
+ throw new Error("native Session rotation requires the machine Session service");
191
+ }
192
+ return localMachineSessions.recordNativeRotation({
193
+ sourceSessionId: r.from,
194
+ targetSessionId: r.to,
195
+ kind: r.kind,
196
196
  });
197
197
  });
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();
198
+ const controlShutdown = new AbortController();
199
+ const app = createApp({
200
+ config,
201
+ control: control
202
+ ? {
203
+ deps: control,
204
+ runtime: conversationRuntime,
205
+ sessionBus,
206
+ sessionLog,
207
+ runnerManager,
208
+ sessionStore,
209
+ pluginRuntimeStatus: pluginRuntime
210
+ ? () => pluginRuntime.statusSnapshot()
211
+ : undefined,
212
+ daemonManagement,
213
+ remoteRuntime,
214
+ remoteRuntimeAdmin,
215
+ runtimeTargetControl,
216
+ runtimeConnectionResolver,
217
+ localSessionResources,
218
+ localMachineSessions,
219
+ sessionEmulators,
220
+ shutdownSignal: controlShutdown.signal,
221
+ sessionRuntimeIndex,
222
+ sessionLifecycle,
223
+ runtimeLocalBrowser,
224
+ providerClis,
225
+ }
226
+ : undefined,
227
+ });
311
228
  return new Promise((resolve) => {
312
229
  const server = app.listen(config.PORT, config.HOST, () => {
313
230
  console.log(JSON.stringify({
@@ -318,18 +235,42 @@ export async function startServer({ config = loadConfig(), channelInstances, loa
318
235
  runtime: config.AGENT_RUNTIME,
319
236
  model: resolveRuntimeModel(config, config.AGENT_RUNTIME),
320
237
  }));
321
- const webSocketServers = [];
238
+ const startup = pluginRuntime?.startPluginRuntimes() ?? Promise.resolve();
239
+ void startup.catch((error) => {
240
+ console.error(JSON.stringify({
241
+ level: "error",
242
+ type: "plugin-runtime",
243
+ event: "startup_failed",
244
+ error: error instanceof Error ? error.message : String(error),
245
+ }));
246
+ });
247
+ const webSocketServers = [
248
+ attachTerminalWs(server, {
249
+ runtimeConnectionResolver,
250
+ legacyLocalTerminalHost,
251
+ runtimeWebAccess,
252
+ }),
253
+ attachBrowserSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
254
+ attachEmulatorSurfaceWs(server, { runtimeConnectionResolver, runtimeWebAccess }),
255
+ ];
322
256
  if (desktopBrowserHost && desktopBrowserManagementToken) {
323
257
  webSocketServers.push(attachDesktopBrowserHostServer(server, {
324
258
  ...desktopBrowserHost,
325
259
  managementToken: desktopBrowserManagementToken,
326
260
  }));
327
261
  }
262
+ if (control?.emulator) {
263
+ webSocketServers.push(attachEmulatorTouchWs(server, { emulator: control.emulator }));
264
+ }
328
265
  resolve(bindServerLifecycle(server, {
329
- ...(webSocketServers.length > 0
330
- ? { closeUpgrades: () => closeWebSocketServers(webSocketServers) }
331
- : {}),
332
- cleanup: () => runnerManager.stop(),
266
+ fence: () => controlShutdown.abort(new Error("Rynx server is shutting down")),
267
+ closeUpgrades: () => closeWebSocketServers(webSocketServers),
268
+ cleanup: async () => runCleanupSteps([
269
+ () => startup.catch(() => undefined),
270
+ () => Promise.resolve(daemonManagement?.pluginManagement?.dispose?.()),
271
+ () => pluginRuntime?.dispose() ?? Promise.resolve(),
272
+ () => runnerManager.stop(),
273
+ ]),
333
274
  }));
334
275
  });
335
276
  });
@@ -12,23 +12,27 @@ export class SessionRuntimeIndex {
12
12
  case "response.created":
13
13
  state.activeResponseIds.add(event.responseId);
14
14
  state.status = "running";
15
+ state.statusKind = undefined;
15
16
  return;
16
17
  case "response.completed":
17
18
  state.activeResponseIds.delete(event.responseId);
18
19
  this.removeResponseInteractions(state, event.responseId);
19
20
  state.status = state.activeResponseIds.size > 0 ? "running" : "idle";
21
+ state.statusKind = undefined;
20
22
  return;
21
23
  case "response.failed":
22
24
  state.activeResponseIds.delete(event.responseId);
23
25
  this.removeResponseInteractions(state, event.responseId);
24
26
  // Response failure is Turn-local. It must not poison a reusable Session.
25
27
  state.status = state.activeResponseIds.size > 0 ? "running" : "idle";
28
+ state.statusKind = undefined;
26
29
  return;
27
30
  case "session.status":
28
31
  state.status =
29
32
  state.pendingInteractions.size > 0 || state.activeResponseIds.size > 0
30
33
  ? "running"
31
34
  : event.status;
35
+ state.statusKind = event.statusKind;
32
36
  return;
33
37
  case "session.interaction.requested":
34
38
  state.pendingInteractions.set(event.interaction.interactionId, {
@@ -37,6 +41,7 @@ export class SessionRuntimeIndex {
37
41
  });
38
42
  state.activeResponseIds.add(event.responseId);
39
43
  state.status = "running";
44
+ state.statusKind = undefined;
40
45
  return;
41
46
  case "session.interaction.resolved":
42
47
  case "session.interaction.cancelled":
@@ -47,6 +52,7 @@ export class SessionRuntimeIndex {
47
52
  state.activeResponseIds.clear();
48
53
  state.pendingInteractions.clear();
49
54
  state.status = "idle";
55
+ state.statusKind = undefined;
50
56
  return;
51
57
  default:
52
58
  return;
@@ -58,6 +64,7 @@ export class SessionRuntimeIndex {
58
64
  return { status: "idle", activeResponseIds: [], pendingInteractions: [] };
59
65
  return {
60
66
  status: state.pendingInteractions.size > 0 ? "running" : state.status,
67
+ ...(state.statusKind === undefined ? {} : { statusKind: state.statusKind }),
61
68
  activeResponseIds: [...state.activeResponseIds],
62
69
  pendingInteractions: [...state.pendingInteractions.values()],
63
70
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/server",
3
- "version": "0.1.10",
3
+ "version": "0.1.11-beta.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -27,15 +27,15 @@
27
27
  "@koa/router": "^15.4.0",
28
28
  "koa": "^3.2.0",
29
29
  "ws": "^8.21.0",
30
- "@rynx-ai/plugin-sdk": "0.1.10",
31
- "@rynx-ai/protocol": "0.1.10",
32
- "@rynx-ai/core": "0.1.10",
33
- "@rynx-ai/remote-runtime-e2ee": "0.1.10",
34
- "@rynx-ai/runtime": "0.1.10"
30
+ "@rynx-ai/core": "0.1.11-beta.2",
31
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.2",
32
+ "@rynx-ai/protocol": "0.1.11-beta.2",
33
+ "@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.2",
34
+ "@rynx-ai/runtime": "0.1.11-beta.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/ws": "^8.18.1",
38
- "@rynx-ai/control-web": "0.1.10"
38
+ "@rynx-ai/control-web": "0.1.11-beta.2"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rm -rf dist && tsc -p tsconfig.json && mkdir -p dist/control-web && cp -R ../control-web/dist/. dist/control-web/",
@@ -1,60 +0,0 @@
1
- /**
2
- * Runtime manager for mounted channel instances in the single-process control
3
- * plane. Holds live channel handles keyed by instance id and can start / stop /
4
- * reload a single instance without restarting the daemon — the seam the web
5
- * control page drives.
6
- *
7
- * Shared pieces (host/conversationRuntime/codex session store) are injected once
8
- * and reused across instances; only the per-instance {@link ChannelContext}
9
- * fields (instanceId / options / defaultAgent) differ, so two Lark bots get
10
- * distinct credentials + state while sharing one agent runtime.
11
- */
12
- import type { AgentCapabilities, AgentSessionStore, AppConfig, ChannelInstanceDescriptor, ConversationRuntime, SessionBus, SessionLogStore, SessionRegistry } from "@rynx-ai/core";
13
- export interface ChannelInstanceStatus {
14
- instanceId: string;
15
- type: string;
16
- running: boolean;
17
- agent?: string;
18
- /** Connection state from the channel, or null when it exposes no status. */
19
- connected: boolean | null;
20
- lastError?: string | null;
21
- lastEventAt?: string | null;
22
- }
23
- export interface ChannelManagerOptions {
24
- config: AppConfig;
25
- conversationRuntime: ConversationRuntime;
26
- capabilities: AgentCapabilities;
27
- sessionStore: AgentSessionStore;
28
- /** Durable canonical session log, shared across instances (absent ⇒ no
29
- * persistence). */
30
- sessionLog?: SessionLogStore;
31
- /** Shared canonical session event bus (one instance ⇒ cross-channel fan-out). */
32
- sessionBus?: SessionBus;
33
- /** Unified machine-session registry, shared across instances; channels register
34
- * their sessions' identity rows here so the control plane lists them. */
35
- sessionRegistry?: SessionRegistry;
36
- /** Source of truth for current instance descriptors (daemon reads config). */
37
- loadInstances: () => Promise<ChannelInstanceDescriptor[]>;
38
- log?: (line: string) => void;
39
- }
40
- export declare class ChannelManager {
41
- private readonly opts;
42
- private readonly mounted;
43
- constructor(opts: ChannelManagerOptions);
44
- /** Mount + start every enabled instance from `loadInstances`. */
45
- startAll(): Promise<void>;
46
- /** Stop every mounted instance (on server close). */
47
- stopAll(): Promise<void>;
48
- /** Live status of currently-mounted instances. */
49
- list(): ChannelInstanceStatus[];
50
- isRunning(instanceId: string): boolean;
51
- /** Start one instance by id (re-reads descriptors). No-op if already running. */
52
- startInstance(instanceId: string): Promise<void>;
53
- /** Stop + unmount one instance. No-op if not running. */
54
- stopInstance(instanceId: string): Promise<void>;
55
- /** Stop then start, picking up edited options/agent. */
56
- reloadInstance(instanceId: string): Promise<void>;
57
- private mountAndStart;
58
- private buildContext;
59
- private log;
60
- }