@rynx-ai/server 0.1.11-beta.2 → 0.1.11-beta.21
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/control-api.d.ts +10 -3
- package/dist/control-api.js +168 -7
- package/dist/control-web/assets/{highlighted-body-OFNGDK62-DWWsUtHF.js → highlighted-body-OFNGDK62-Ctle6Bpj.js} +1 -1
- package/dist/control-web/assets/index-0noHpRXD.css +32 -0
- package/dist/control-web/assets/index-dOafa0IY.js +709 -0
- package/dist/control-web/assets/{mermaid-GHXKKRXX-C1fxAgiw.js → mermaid-GHXKKRXX-DvISAPzD.js} +3 -3
- package/dist/control-web/index.html +2 -2
- package/dist/control-web-dist.d.ts +1 -1
- package/dist/control-web-dist.js +4 -2
- package/dist/machine-session-service.d.ts +31 -6
- package/dist/machine-session-service.js +124 -7
- package/dist/remote-runtime-dispatcher.d.ts +1 -1
- package/dist/remote-runtime-dispatcher.js +4 -0
- package/dist/server.d.ts +25 -3
- package/dist/server.js +117 -43
- package/dist/session-browser-service.d.ts +8 -0
- package/dist/session-browser-service.js +37 -20
- package/dist/session-portal.d.ts +42 -0
- package/dist/session-portal.js +746 -0
- package/dist/session-runtime-index.d.ts +7 -0
- package/dist/session-runtime-index.js +35 -6
- package/dist/session-terminal-host.d.ts +6 -0
- package/dist/session-terminal-host.js +56 -41
- package/dist/terminal-ws.js +8 -6
- package/package.json +7 -7
- package/dist/control-web/assets/index-BnX7NWJX.js +0 -689
- package/dist/control-web/assets/index-CUoWRX2i.css +0 -32
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
8
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
9
9
|
<title>rynx · control</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-dOafa0IY.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-0noHpRXD.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
|
@@ -3,4 +3,4 @@ import type { Context } from "koa";
|
|
|
3
3
|
export declare function resolveControlWebDist(): string | null;
|
|
4
4
|
/** Serve a static file from the SPA dir; missing non-asset paths fall back to
|
|
5
5
|
* index.html (client-side routing), missing assets return 404. */
|
|
6
|
-
export declare function sendSpaFile(ctx: Context, distDir: string, basePath?: string): Promise<void>;
|
|
6
|
+
export declare function sendSpaFile(ctx: Context, distDir: string, basePath?: string, transformIndex?: (html: string) => string): Promise<void>;
|
package/dist/control-web-dist.js
CHANGED
|
@@ -37,7 +37,7 @@ export function resolveControlWebDist() {
|
|
|
37
37
|
}
|
|
38
38
|
/** Serve a static file from the SPA dir; missing non-asset paths fall back to
|
|
39
39
|
* index.html (client-side routing), missing assets return 404. */
|
|
40
|
-
export async function sendSpaFile(ctx, distDir, basePath = "/") {
|
|
40
|
+
export async function sendSpaFile(ctx, distDir, basePath = "/", transformIndex) {
|
|
41
41
|
const path = basePath !== "/" && ctx.path.startsWith(basePath)
|
|
42
42
|
? ctx.path.slice(basePath.length) || "/"
|
|
43
43
|
: ctx.path;
|
|
@@ -64,5 +64,7 @@ export async function sendSpaFile(ctx, distDir, basePath = "/") {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
ctx.type = CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
67
|
-
ctx.body =
|
|
67
|
+
ctx.body = ext === ".html" && transformIndex
|
|
68
|
+
? transformIndex(data.toString("utf8"))
|
|
69
|
+
: data;
|
|
68
70
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { type AgentRuntimeId, type ConversationRuntime, type ResolvedExecutionSnapshot, type SessionWorkspaceSnapshot, type SessionProviderId, type MachineSessionRecord, type ReasoningEffort, type RuntimeUserInput, type SessionBus, type SessionLogStore, type SessionRegistry } from "@rynx-ai/core";
|
|
1
|
+
import { type AdmissionReservation, type AgentRuntimeId, type ConversationRuntime, type ResolvedExecutionSnapshot, type SessionWorkspaceSnapshot, type SessionProviderId, type MachineSessionRecord, type ReasoningEffort, type RuntimeUserInput, type SessionBus, type SessionLogStore, type SessionRegistry } from "@rynx-ai/core";
|
|
2
2
|
import type { SequencedSessionEvent, SessionEvent, SessionInteractionResolution, SessionItem, UserContentPart } from "@rynx-ai/protocol";
|
|
3
3
|
import type { RemoteRuntimeSessionResourceDeleteParams, RemoteRuntimeSessionResourceDeleteResult, RemoteRuntimeSessionResourceGetParams, RemoteRuntimeSessionResourceGetResult, RemoteRuntimeSessionResourcePolicyResult, RemoteRuntimeSessionResourceReadParams, RemoteRuntimeSessionResourceReadResult, RemoteRuntimeSessionResourceUploadBeginParams, RemoteRuntimeSessionResourceUploadBeginResult, RemoteRuntimeSessionResourceUploadChunkParams, RemoteRuntimeSessionResourceUploadChunkResult, RemoteRuntimeSessionResourceUploadCommitParams, RemoteRuntimeSessionResourceUploadCommitResult } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
4
|
-
import type { ControlAgentSummary, ControlSessionInteractionResolveDisposition, ControlSessionRuntimeSnapshot } from "@rynx-ai/protocol/control";
|
|
5
|
-
import { type RemoteRuntimeSessionInterruptResult, type RemoteRuntimeSessionLaunchOptionsListResult, type RemoteRuntimeSessionListParams, type RemoteRuntimeSessionListResult, type RemoteRuntimeSessionSnapshotParams } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
4
|
+
import type { ControlAgentModelOption, ControlAgentSummary, ControlSessionInteractionResolveDisposition, ControlSessionRuntimeSnapshot, SessionPermissionPreset } from "@rynx-ai/protocol/control";
|
|
5
|
+
import { type RemoteRuntimeSessionInterruptResult, type RemoteRuntimeSessionExecutionGetResult, type RemoteRuntimeSessionExecutionUpdateParams, type RemoteRuntimeSessionExecutionUpdateResult, type RemoteRuntimeSessionLaunchOptionsListResult, type RemoteRuntimeSessionListParams, type RemoteRuntimeSessionListResult, type RemoteRuntimeSessionSnapshotParams } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
6
6
|
import type { CodexRuntimeStatus, InjectOutcome } from "@rynx-ai/runtime";
|
|
7
7
|
export interface MachineSessionRuntimeStatePort {
|
|
8
8
|
snapshot(sessionId: string): ControlSessionRuntimeSnapshot;
|
|
9
|
+
beginResponseHandoff?(sessionId: string, responseId: string): () => void;
|
|
9
10
|
remove?(sessionId: string): void;
|
|
10
11
|
}
|
|
11
12
|
export interface MachineSessionInterruptPort {
|
|
@@ -129,7 +130,7 @@ export interface MachineSessionForkStorePort {
|
|
|
129
130
|
markTargetDeleted(sessionId: string): void | Promise<void>;
|
|
130
131
|
}
|
|
131
132
|
export interface MachineSessionServicePorts {
|
|
132
|
-
registry: Pick<SessionRegistry, "create" | "get" | "list" | "setTitle" | "remove">;
|
|
133
|
+
registry: Pick<SessionRegistry, "create" | "get" | "list" | "setTitle" | "setExecution" | "remove">;
|
|
133
134
|
log: Pick<SessionLogStore, "list" | "listSessions" | "snapshot" | "deleteSession">;
|
|
134
135
|
events: Pick<SessionBus, "subscribe"> & Partial<Pick<SessionBus, "close">>;
|
|
135
136
|
/** Canonical persistence + fan-out for user turns accepted before a native
|
|
@@ -153,10 +154,12 @@ export interface MachineSessionServicePorts {
|
|
|
153
154
|
resolve(input: {
|
|
154
155
|
agent?: string;
|
|
155
156
|
provider?: SessionProviderId;
|
|
156
|
-
model?: string;
|
|
157
|
+
model?: string | null;
|
|
157
158
|
reasoningEffort?: ReasoningEffort;
|
|
159
|
+
permissionPreset?: SessionPermissionPreset;
|
|
158
160
|
cwd: string;
|
|
159
161
|
}): Promise<ResolvedExecutionSnapshot>;
|
|
162
|
+
listModels?(provider: SessionProviderId): Promise<ControlAgentModelOption[]>;
|
|
160
163
|
runner: MachineSessionRunnerPort;
|
|
161
164
|
};
|
|
162
165
|
/** Resolve a temporary Project selection, or create a Session-only fallback directory. */
|
|
@@ -181,6 +184,11 @@ export interface MachineSessionServiceOptions {
|
|
|
181
184
|
directoryScanLimit?: number;
|
|
182
185
|
/** Delay between setup-readiness retries for durable pending messages. */
|
|
183
186
|
pendingMessageRetryMs?: number;
|
|
187
|
+
/** Dynamic daemon-wide admission fence used during process replacement. */
|
|
188
|
+
admissionOpen?: () => boolean;
|
|
189
|
+
/** Atomically reserves one work-producing admission until it is rejected or
|
|
190
|
+
* has become visible in the daemon activity projection. */
|
|
191
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
184
192
|
}
|
|
185
193
|
export type MachineSessionListInput = RemoteRuntimeSessionListParams;
|
|
186
194
|
export interface MachineSessionListPage extends RemoteRuntimeSessionListResult {
|
|
@@ -198,8 +206,9 @@ export interface MachineSessionSnapshotPage {
|
|
|
198
206
|
}
|
|
199
207
|
export type MachineSessionInterruptResult = RemoteRuntimeSessionInterruptResult;
|
|
200
208
|
interface MachineSessionCreateOptions {
|
|
201
|
-
model?: string;
|
|
209
|
+
model?: string | null;
|
|
202
210
|
reasoningEffort?: ReasoningEffort;
|
|
211
|
+
permissionPreset?: SessionPermissionPreset;
|
|
203
212
|
title?: string;
|
|
204
213
|
projectId?: string;
|
|
205
214
|
}
|
|
@@ -277,6 +286,8 @@ export declare class MachineSessionService {
|
|
|
277
286
|
private readonly snapshotPageMaxBytes;
|
|
278
287
|
private readonly directoryScanLimit;
|
|
279
288
|
private readonly pendingMessageRetryMs;
|
|
289
|
+
private readonly admissionOpen;
|
|
290
|
+
private readonly admissionReserve;
|
|
280
291
|
private readonly pendingDeliveries;
|
|
281
292
|
private readonly cancelledPendingDeliveries;
|
|
282
293
|
private readonly forkTasks;
|
|
@@ -298,12 +309,21 @@ export declare class MachineSessionService {
|
|
|
298
309
|
}>;
|
|
299
310
|
/** Target-owned Provider readiness plus the existing Agent preset catalog. */
|
|
300
311
|
launchOptions(): Promise<RemoteRuntimeSessionLaunchOptionsListResult>;
|
|
312
|
+
/** Read the effective model defaults used for the next turn. */
|
|
313
|
+
executionSettings(sessionIdInput: string): Promise<RemoteRuntimeSessionExecutionGetResult>;
|
|
314
|
+
/** Replace the Turn defaults of an idle Session. Execution settings are
|
|
315
|
+
* passed through to the Provider CLI, which owns availability validation.
|
|
316
|
+
* The runner is closed so every Provider resumes its native context with
|
|
317
|
+
* the new settings on the next turn. */
|
|
318
|
+
updateExecutionSettings(input: RemoteRuntimeSessionExecutionUpdateParams): Promise<RemoteRuntimeSessionExecutionUpdateResult>;
|
|
301
319
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
302
320
|
create(input: MachineSessionCreateInput): Promise<MachineSessionCreateResult>;
|
|
321
|
+
private createAdmitted;
|
|
303
322
|
/** Create one independent Session at the source's stable Provider/canonical
|
|
304
323
|
* boundary. Project and Agent selectors are intentionally absent: the target
|
|
305
324
|
* receives exact copies of the source's already-frozen snapshots. */
|
|
306
325
|
fork(input: MachineSessionForkInput): Promise<MachineSessionForkResult>;
|
|
326
|
+
private forkAdmitted;
|
|
307
327
|
/** Publish a Provider TUI `/clear` or `/fork` after its native binding has
|
|
308
328
|
* already been persisted. The source snapshots remain the only workspace and
|
|
309
329
|
* execution authority; Provider events cannot alter them during rotation. */
|
|
@@ -312,6 +332,7 @@ export declare class MachineSessionService {
|
|
|
312
332
|
private assertForkableSource;
|
|
313
333
|
/** Inject one turn through the target daemon's native single-writer runner. */
|
|
314
334
|
sendMessage(sessionIdInput: string, messageInput: string | MachineSessionMessageInput): Promise<MachineSessionMessageResult>;
|
|
335
|
+
private sendMessageAdmitted;
|
|
315
336
|
resourcePolicy(sessionIdInput: string): RemoteRuntimeSessionResourcePolicyResult;
|
|
316
337
|
beginResourceUpload(input: RemoteRuntimeSessionResourceUploadBeginParams): Promise<RemoteRuntimeSessionResourceUploadBeginResult>;
|
|
317
338
|
writeResourceUploadChunk(input: RemoteRuntimeSessionResourceUploadChunkParams): Promise<RemoteRuntimeSessionResourceUploadChunkResult>;
|
|
@@ -323,13 +344,17 @@ export declare class MachineSessionService {
|
|
|
323
344
|
* delivery worker starts the pane now, waits for a real native thread, then
|
|
324
345
|
* injects exactly once. `failed` is fenced as outcome_unknown and never retried. */
|
|
325
346
|
enqueueMessage(sessionIdInput: string, messageInput: string): Promise<MachineSessionMessageEnqueueResult>;
|
|
347
|
+
private enqueueMessageAdmitted;
|
|
326
348
|
/** Explicitly restore the target daemon's live runner without starting a turn. */
|
|
327
349
|
startTerminal(sessionIdInput: string): Promise<MachineSessionTerminalStartResult>;
|
|
350
|
+
private startTerminalAdmitted;
|
|
328
351
|
private ensureLiveSession;
|
|
329
352
|
private liveSessionRequest;
|
|
330
353
|
resolveInteraction(sessionIdInput: string, interactionIdInput: string, resolution: SessionInteractionResolution): Promise<MachineSessionInteractionResult>;
|
|
331
354
|
delete(sessionIdInput: string): Promise<MachineSessionDeleteResult>;
|
|
332
355
|
private requireAgents;
|
|
356
|
+
private reserveAdmission;
|
|
357
|
+
private withAdmission;
|
|
333
358
|
private requireResources;
|
|
334
359
|
private requireResourceSession;
|
|
335
360
|
private resumePendingDeliveries;
|
|
@@ -47,6 +47,8 @@ export class MachineSessionService {
|
|
|
47
47
|
snapshotPageMaxBytes;
|
|
48
48
|
directoryScanLimit;
|
|
49
49
|
pendingMessageRetryMs;
|
|
50
|
+
admissionOpen;
|
|
51
|
+
admissionReserve;
|
|
50
52
|
pendingDeliveries = new Map();
|
|
51
53
|
cancelledPendingDeliveries = new Set();
|
|
52
54
|
forkTasks = new Map();
|
|
@@ -58,6 +60,8 @@ export class MachineSessionService {
|
|
|
58
60
|
this.snapshotPageMaxBytes = boundedInteger(options.snapshotPageMaxBytes ?? DEFAULT_SNAPSHOT_PAGE_MAX_BYTES, MIN_PAGE_MAX_BYTES, MAX_PAGE_MAX_BYTES, "snapshotPageMaxBytes");
|
|
59
61
|
this.directoryScanLimit = boundedInteger(options.directoryScanLimit ?? DEFAULT_DIRECTORY_SCAN_LIMIT, 1, MAX_DIRECTORY_SCAN_LIMIT, "directoryScanLimit");
|
|
60
62
|
this.pendingMessageRetryMs = boundedInteger(options.pendingMessageRetryMs ?? 1_000, 10, 60_000, "pendingMessageRetryMs");
|
|
63
|
+
this.admissionOpen = options.admissionOpen ?? (() => true);
|
|
64
|
+
this.admissionReserve = options.admissionReserve;
|
|
61
65
|
void this.resumePendingDeliveries();
|
|
62
66
|
void this.resumeReservedForks();
|
|
63
67
|
}
|
|
@@ -155,11 +159,18 @@ export class MachineSessionService {
|
|
|
155
159
|
const statusCache = new Map();
|
|
156
160
|
const { agents } = await this.agentOptions();
|
|
157
161
|
const [providers, launchAgents] = await Promise.all([
|
|
158
|
-
Promise.all(SESSION_PROVIDER_IDS.map(async (provider) =>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
Promise.all(SESSION_PROVIDER_IDS.map(async (provider) => {
|
|
163
|
+
const execution = this.ports.execution;
|
|
164
|
+
const models = execution?.listModels
|
|
165
|
+
? await execution.listModels(provider).catch(() => [])
|
|
166
|
+
: undefined;
|
|
167
|
+
return {
|
|
168
|
+
id: provider,
|
|
169
|
+
name: getRuntimeProfile(provider).displayName,
|
|
170
|
+
...await this.runtimeReadiness(provider, statusCache),
|
|
171
|
+
...(models === undefined ? {} : { models }),
|
|
172
|
+
};
|
|
173
|
+
})),
|
|
163
174
|
Promise.all(agents.map(async (agent) => {
|
|
164
175
|
const execution = this.ports.execution;
|
|
165
176
|
if (!execution) {
|
|
@@ -188,8 +199,53 @@ export class MachineSessionService {
|
|
|
188
199
|
]);
|
|
189
200
|
return { providers, agents: launchAgents };
|
|
190
201
|
}
|
|
202
|
+
/** Read the effective model defaults used for the next turn. */
|
|
203
|
+
async executionSettings(sessionIdInput) {
|
|
204
|
+
const sessionId = validOpaqueToken(sessionIdInput, "sessionId", MAX_SESSION_ID_CHARS);
|
|
205
|
+
const meta = this.ports.registry.get(sessionId);
|
|
206
|
+
if (!meta) {
|
|
207
|
+
throw new MachineSessionServiceFailure("not_found", "session not found");
|
|
208
|
+
}
|
|
209
|
+
return executionSettingsResult(meta.execution);
|
|
210
|
+
}
|
|
211
|
+
/** Replace the Turn defaults of an idle Session. Execution settings are
|
|
212
|
+
* passed through to the Provider CLI, which owns availability validation.
|
|
213
|
+
* The runner is closed so every Provider resumes its native context with
|
|
214
|
+
* the new settings on the next turn. */
|
|
215
|
+
async updateExecutionSettings(input) {
|
|
216
|
+
const sessionId = validOpaqueToken(input.sessionId, "sessionId", MAX_SESSION_ID_CHARS);
|
|
217
|
+
const modelId = input.model === null ? null : validRequiredText(input.model, "model");
|
|
218
|
+
const meta = this.ports.registry.get(sessionId);
|
|
219
|
+
if (!meta) {
|
|
220
|
+
throw new MachineSessionServiceFailure("not_found", "session not found");
|
|
221
|
+
}
|
|
222
|
+
await this.assertNotForkReserved(sessionId);
|
|
223
|
+
const runtime = this.ports.runtimeState.snapshot(sessionId);
|
|
224
|
+
const pendingMessage = await this.ports.pendingMessages?.get(sessionId);
|
|
225
|
+
if (runtime.status !== "idle" ||
|
|
226
|
+
runtime.activeResponseIds.length > 0 ||
|
|
227
|
+
runtime.pendingInteractions.length > 0 ||
|
|
228
|
+
pendingMessage) {
|
|
229
|
+
throw new MachineSessionServiceFailure("failed_precondition", "Session must be idle before changing model settings");
|
|
230
|
+
}
|
|
231
|
+
const executionPort = this.requireExecution();
|
|
232
|
+
const reasoningEffort = input.reasoningEffort === null
|
|
233
|
+
? null
|
|
234
|
+
: validRequiredText(input.reasoningEffort, "reasoningEffort");
|
|
235
|
+
const nextExecution = {
|
|
236
|
+
...structuredClone(meta.execution),
|
|
237
|
+
model: modelId,
|
|
238
|
+
reasoningEffort,
|
|
239
|
+
};
|
|
240
|
+
this.ports.registry.setExecution(sessionId, nextExecution);
|
|
241
|
+
executionPort.runner.stopRunner(sessionId);
|
|
242
|
+
return executionSettingsResult(nextExecution);
|
|
243
|
+
}
|
|
191
244
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
192
245
|
async create(input) {
|
|
246
|
+
return this.withAdmission(() => this.createAdmitted(input));
|
|
247
|
+
}
|
|
248
|
+
async createAdmitted(input) {
|
|
193
249
|
const hasAgent = input.agent !== undefined;
|
|
194
250
|
const hasProvider = input.provider !== undefined;
|
|
195
251
|
if (hasAgent === hasProvider) {
|
|
@@ -216,6 +272,7 @@ export class MachineSessionService {
|
|
|
216
272
|
...target,
|
|
217
273
|
...(input.model === undefined ? {} : { model: input.model }),
|
|
218
274
|
...(input.reasoningEffort === undefined ? {} : { reasoningEffort: input.reasoningEffort }),
|
|
275
|
+
...(input.permissionPreset === undefined ? {} : { permissionPreset: input.permissionPreset }),
|
|
219
276
|
cwd: workspace.cwd,
|
|
220
277
|
});
|
|
221
278
|
const sessionId = newSessionId();
|
|
@@ -240,6 +297,9 @@ export class MachineSessionService {
|
|
|
240
297
|
* boundary. Project and Agent selectors are intentionally absent: the target
|
|
241
298
|
* receives exact copies of the source's already-frozen snapshots. */
|
|
242
299
|
async fork(input) {
|
|
300
|
+
return this.withAdmission(() => this.forkAdmitted(input));
|
|
301
|
+
}
|
|
302
|
+
async forkAdmitted(input) {
|
|
243
303
|
const sourceSessionId = validOpaqueToken(input.sourceSessionId, "sourceSessionId", MAX_SESSION_ID_CHARS);
|
|
244
304
|
const operationId = validOpaqueToken(input.operationId, "operationId", MAX_SESSION_ID_CHARS);
|
|
245
305
|
const title = input.title === undefined
|
|
@@ -383,6 +443,9 @@ export class MachineSessionService {
|
|
|
383
443
|
}
|
|
384
444
|
/** Inject one turn through the target daemon's native single-writer runner. */
|
|
385
445
|
async sendMessage(sessionIdInput, messageInput) {
|
|
446
|
+
return this.withAdmission(() => this.sendMessageAdmitted(sessionIdInput, messageInput));
|
|
447
|
+
}
|
|
448
|
+
async sendMessageAdmitted(sessionIdInput, messageInput) {
|
|
386
449
|
const sessionId = validOpaqueToken(sessionIdInput, "sessionId", MAX_SESSION_ID_CHARS);
|
|
387
450
|
await this.assertNotForkReserved(sessionId);
|
|
388
451
|
const request = typeof messageInput === "string"
|
|
@@ -471,6 +534,9 @@ export class MachineSessionService {
|
|
|
471
534
|
statusKind: "startup",
|
|
472
535
|
});
|
|
473
536
|
}
|
|
537
|
+
const abandonResponseHandoff = responseId
|
|
538
|
+
? this.ports.runtimeState.beginResponseHandoff?.(sessionId, responseId)
|
|
539
|
+
: undefined;
|
|
474
540
|
const clearStartup = async (status) => {
|
|
475
541
|
if (!responseId)
|
|
476
542
|
return;
|
|
@@ -486,6 +552,7 @@ export class MachineSessionService {
|
|
|
486
552
|
execution = await this.ensureLiveSession(sessionId, meta);
|
|
487
553
|
}
|
|
488
554
|
catch (error) {
|
|
555
|
+
abandonResponseHandoff?.();
|
|
489
556
|
await clearStartup("idle");
|
|
490
557
|
if (request.clientMessageId) {
|
|
491
558
|
await resources?.markMessageFailedNotStarted(sessionId, request.clientMessageId, error instanceof Error ? error.message : String(error));
|
|
@@ -500,6 +567,7 @@ export class MachineSessionService {
|
|
|
500
567
|
outcome = await execution.runner.injectMessage(sessionId, responseId || needsPreparedOperation ? runtimeInput : request.message);
|
|
501
568
|
}
|
|
502
569
|
catch (error) {
|
|
570
|
+
abandonResponseHandoff?.();
|
|
503
571
|
await clearStartup("idle");
|
|
504
572
|
if (request.clientMessageId) {
|
|
505
573
|
await Promise.resolve(resources?.markMessageOutcomeUnknown(sessionId, request.clientMessageId, error instanceof Error ? error.message : String(error))).catch(() => undefined);
|
|
@@ -507,19 +575,23 @@ export class MachineSessionService {
|
|
|
507
575
|
throw new MachineSessionServiceFailure("outcome_unknown", "live injection outcome is unknown");
|
|
508
576
|
}
|
|
509
577
|
if (outcome === "failed") {
|
|
578
|
+
abandonResponseHandoff?.();
|
|
510
579
|
await clearStartup("idle");
|
|
511
580
|
if (request.clientMessageId) {
|
|
512
581
|
await resources?.markMessageOutcomeUnknown(sessionId, request.clientMessageId, `live injection ${outcome}`);
|
|
513
582
|
}
|
|
514
583
|
throw new MachineSessionServiceFailure("outcome_unknown", `live injection ${outcome}`);
|
|
515
584
|
}
|
|
516
|
-
if (outcome !== "injected") {
|
|
585
|
+
if (outcome !== "injected" && outcome !== "steered") {
|
|
586
|
+
abandonResponseHandoff?.();
|
|
517
587
|
await clearStartup("idle");
|
|
518
588
|
if (request.clientMessageId) {
|
|
519
589
|
await resources?.markMessageFailedNotStarted(sessionId, request.clientMessageId, `live injection ${outcome}`);
|
|
520
590
|
}
|
|
521
591
|
throw new MachineSessionServiceFailure("failed_precondition", `live injection ${outcome}`);
|
|
522
592
|
}
|
|
593
|
+
if (outcome === "steered")
|
|
594
|
+
abandonResponseHandoff?.();
|
|
523
595
|
await clearStartup("running");
|
|
524
596
|
if (request.clientMessageId) {
|
|
525
597
|
try {
|
|
@@ -564,6 +636,9 @@ export class MachineSessionService {
|
|
|
564
636
|
* delivery worker starts the pane now, waits for a real native thread, then
|
|
565
637
|
* injects exactly once. `failed` is fenced as outcome_unknown and never retried. */
|
|
566
638
|
async enqueueMessage(sessionIdInput, messageInput) {
|
|
639
|
+
return this.withAdmission(() => this.enqueueMessageAdmitted(sessionIdInput, messageInput));
|
|
640
|
+
}
|
|
641
|
+
async enqueueMessageAdmitted(sessionIdInput, messageInput) {
|
|
567
642
|
const sessionId = validOpaqueToken(sessionIdInput, "sessionId", MAX_SESSION_ID_CHARS);
|
|
568
643
|
await this.assertNotForkReserved(sessionId);
|
|
569
644
|
if (typeof messageInput !== "string" || messageInput.length === 0) {
|
|
@@ -595,6 +670,9 @@ export class MachineSessionService {
|
|
|
595
670
|
}
|
|
596
671
|
/** Explicitly restore the target daemon's live runner without starting a turn. */
|
|
597
672
|
async startTerminal(sessionIdInput) {
|
|
673
|
+
return this.withAdmission(() => this.startTerminalAdmitted(sessionIdInput));
|
|
674
|
+
}
|
|
675
|
+
async startTerminalAdmitted(sessionIdInput) {
|
|
598
676
|
const sessionId = validOpaqueToken(sessionIdInput, "sessionId", MAX_SESSION_ID_CHARS);
|
|
599
677
|
const request = await this.liveSessionRequest(sessionId);
|
|
600
678
|
const start = request.execution.runner.startLiveSession?.bind(request.execution.runner) ??
|
|
@@ -662,6 +740,24 @@ export class MachineSessionService {
|
|
|
662
740
|
}
|
|
663
741
|
return this.ports.agents;
|
|
664
742
|
}
|
|
743
|
+
reserveAdmission() {
|
|
744
|
+
const reservation = this.admissionReserve?.();
|
|
745
|
+
if (reservation)
|
|
746
|
+
return reservation;
|
|
747
|
+
if (!this.admissionReserve && this.admissionOpen()) {
|
|
748
|
+
return { release() { } };
|
|
749
|
+
}
|
|
750
|
+
throw new MachineSessionServiceFailure("failed_precondition", "daemon maintenance is in progress");
|
|
751
|
+
}
|
|
752
|
+
async withAdmission(operation) {
|
|
753
|
+
const reservation = this.reserveAdmission();
|
|
754
|
+
try {
|
|
755
|
+
return await operation();
|
|
756
|
+
}
|
|
757
|
+
finally {
|
|
758
|
+
reservation.release();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
665
761
|
requireResources() {
|
|
666
762
|
if (!this.ports.resources) {
|
|
667
763
|
throw new MachineSessionServiceFailure("failed_precondition", "Session resources are unavailable");
|
|
@@ -713,7 +809,9 @@ export class MachineSessionService {
|
|
|
713
809
|
if (!pending || pending.state !== "queued")
|
|
714
810
|
return;
|
|
715
811
|
let injectionAttempted = false;
|
|
812
|
+
let admission;
|
|
716
813
|
try {
|
|
814
|
+
admission = this.reserveAdmission();
|
|
717
815
|
const execution = await this.ensureLiveSession(sessionId);
|
|
718
816
|
if (this.cancelledPendingDeliveries.has(sessionId))
|
|
719
817
|
return;
|
|
@@ -725,7 +823,7 @@ export class MachineSessionService {
|
|
|
725
823
|
return;
|
|
726
824
|
injectionAttempted = true;
|
|
727
825
|
const outcome = await execution.runner.injectMessage(sessionId, pending.message);
|
|
728
|
-
if (outcome === "injected") {
|
|
826
|
+
if (outcome === "injected" || outcome === "steered") {
|
|
729
827
|
await Promise.resolve(pendingStore.delete(sessionId)).catch(async () => {
|
|
730
828
|
await Promise.resolve(pendingStore.markOutcomeUnknown(sessionId)).catch(() => undefined);
|
|
731
829
|
});
|
|
@@ -746,6 +844,9 @@ export class MachineSessionService {
|
|
|
746
844
|
return;
|
|
747
845
|
}
|
|
748
846
|
}
|
|
847
|
+
finally {
|
|
848
|
+
admission?.release();
|
|
849
|
+
}
|
|
749
850
|
await retryDelay(this.pendingMessageRetryMs);
|
|
750
851
|
}
|
|
751
852
|
}
|
|
@@ -948,6 +1049,13 @@ function sessionSummary(id, meta, log, runtimeState, pendingState) {
|
|
|
948
1049
|
updatedAt: validTimestamp(updatedAt, "updatedAt"),
|
|
949
1050
|
};
|
|
950
1051
|
}
|
|
1052
|
+
function executionSettingsResult(execution) {
|
|
1053
|
+
return {
|
|
1054
|
+
provider: execution.provider,
|
|
1055
|
+
model: execution.model,
|
|
1056
|
+
reasoningEffort: execution.reasoningEffort,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
951
1059
|
function compareMetaRecency(left, right) {
|
|
952
1060
|
const leftTime = validTimestamp(left.updatedAt ?? left.createdAt, "updatedAt");
|
|
953
1061
|
const rightTime = validTimestamp(right.updatedAt ?? right.createdAt, "updatedAt");
|
|
@@ -1042,6 +1150,15 @@ function validOpaqueToken(value, field, maximum) {
|
|
|
1042
1150
|
}
|
|
1043
1151
|
return value;
|
|
1044
1152
|
}
|
|
1153
|
+
function validRequiredText(value, field) {
|
|
1154
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.length > 512) {
|
|
1155
|
+
throw new MachineSessionServiceInputError(`${field} must be a non-empty string of at most 512 characters`);
|
|
1156
|
+
}
|
|
1157
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
1158
|
+
throw new MachineSessionServiceInputError(`${field} contains invalid control characters`);
|
|
1159
|
+
}
|
|
1160
|
+
return value.trim();
|
|
1161
|
+
}
|
|
1045
1162
|
function validTimestamp(value, field) {
|
|
1046
1163
|
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
1047
1164
|
throw new MachineSessionServiceInputError(`${field} must be a valid timestamp`);
|
|
@@ -34,7 +34,7 @@ export interface RemoteRuntimeProjectHost {
|
|
|
34
34
|
update(projectId: string, input: RemoteRuntimeProjectCreateParams): RemoteRuntimeProject | undefined | Promise<RemoteRuntimeProject | undefined>;
|
|
35
35
|
remove(projectId: string): boolean | Promise<boolean>;
|
|
36
36
|
}
|
|
37
|
-
export type RemoteRuntimeSessionHost = Pick<MachineSessionService, "list" | "snapshot" | "watch" | "interrupt" | "agentOptions" | "launchOptions" | "create" | "fork" | "sendMessage" | "resourcePolicy" | "beginResourceUpload" | "writeResourceUploadChunk" | "commitResourceUpload" | "getResource" | "readResource" | "deleteResource" | "enqueueMessage" | "startTerminal" | "delete" | "resolveInteraction">;
|
|
37
|
+
export type RemoteRuntimeSessionHost = Pick<MachineSessionService, "list" | "snapshot" | "watch" | "interrupt" | "agentOptions" | "launchOptions" | "executionSettings" | "updateExecutionSettings" | "create" | "fork" | "sendMessage" | "resourcePolicy" | "beginResourceUpload" | "writeResourceUploadChunk" | "commitResourceUpload" | "getResource" | "readResource" | "deleteResource" | "enqueueMessage" | "startTerminal" | "delete" | "resolveInteraction">;
|
|
38
38
|
export type RemoteRuntimeBrowserHost = Pick<SessionBrowserService, "getState" | "open" | "close" | "createPage" | "closePage" | "activatePage" | "navigatePage" | "goBack" | "goForward" | "reload">;
|
|
39
39
|
export type RemoteRuntimeSessionEmulatorHost = Pick<SessionEmulatorService, "getState" | "listDevices" | "attach" | "detach" | "tap" | "type" | "button" | "rotate" | "launch" | "gesture" | "releaseDevice" | "stopDevice">;
|
|
40
40
|
export interface OpenRemoteRuntimeSessionEvents {
|
|
@@ -125,6 +125,10 @@ export class RemoteRuntimeDispatcher {
|
|
|
125
125
|
return await this.withSessions(request, (sessions) => sessions.agentOptions());
|
|
126
126
|
case "session.launchOptions.list":
|
|
127
127
|
return await this.withSessions(request, (sessions) => sessions.launchOptions());
|
|
128
|
+
case "session.execution.get":
|
|
129
|
+
return await this.withSessions(request, (sessions) => sessions.executionSettings(request.params.sessionId));
|
|
130
|
+
case "session.execution.update":
|
|
131
|
+
return await this.withSessions(request, (sessions) => sessions.updateExecutionSettings(request.params));
|
|
128
132
|
case "session.create":
|
|
129
133
|
return await this.withSessions(request, (sessions) => sessions.create(request.params));
|
|
130
134
|
case "session.fork":
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 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
6
|
import type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, PluginManagementItem, PluginMarketplaceAddInput, PluginMarketplaceItem } from "@rynx-ai/protocol/plugin-management";
|
|
@@ -18,6 +18,9 @@ import { type ControlPlaneDeps, type LocalSessionResourceHost, type SessionResou
|
|
|
18
18
|
import type { MachineSessionService } from "./machine-session-service.js";
|
|
19
19
|
import type { DaemonRuntimeHost } from "./remote-runtime.js";
|
|
20
20
|
import { type AttachDesktopBrowserHostServerOptions } from "./desktop-browser-host.js";
|
|
21
|
+
import { type SessionPortalAuthorizationPorts } from "./session-portal.js";
|
|
22
|
+
export { createSessionPortalApp, startSessionPortal } from "./session-portal.js";
|
|
23
|
+
export type { SessionPortalAuthorization, SessionPortalAuthorizationPorts, SessionPortalOptions, SessionPortalServer, } from "./session-portal.js";
|
|
21
24
|
import { SessionRuntimeIndex } from "./session-runtime-index.js";
|
|
22
25
|
import type { SessionEmulatorService } from "./session-emulator-service.js";
|
|
23
26
|
import { type SessionTerminalHost } from "./session-terminal-host.js";
|
|
@@ -132,6 +135,9 @@ export interface StartServerOptions {
|
|
|
132
135
|
providerClis?: ProviderCliManagementHost;
|
|
133
136
|
/** Private resident-app Browser Host socket, authenticated by daemonManagement. */
|
|
134
137
|
desktopBrowserHost?: Omit<AttachDesktopBrowserHostServerOptions, "managementToken">;
|
|
138
|
+
/** Hashed ticket/grant authority for the native Session Portal. Required
|
|
139
|
+
* when RYNX_SESSION_PORTAL_PORT enables that listener. */
|
|
140
|
+
sessionPortalAuthorization?: SessionPortalAuthorizationPorts;
|
|
135
141
|
}
|
|
136
142
|
export interface SessionRuntimeServices {
|
|
137
143
|
sessionStore: CodexSessionStore;
|
|
@@ -155,9 +161,19 @@ export interface PluginRuntimeHostServices {
|
|
|
155
161
|
}): Promise<PluginResolvedSessionExecution>;
|
|
156
162
|
};
|
|
157
163
|
interruptSession(sessionId: string): Promise<boolean>;
|
|
164
|
+
terminateSession?(sessionId: string): Promise<boolean>;
|
|
158
165
|
resolveSessionInteraction(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
|
|
159
166
|
disposition: "applied" | "already_resolved" | "not_found" | "invalid";
|
|
160
167
|
}>;
|
|
168
|
+
/** Dynamic daemon-wide admission state. Plugin runtimes may start while this is
|
|
169
|
+
* closed, but cannot provision Sessions or begin Turns. */
|
|
170
|
+
admissionOpen?: () => boolean;
|
|
171
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
172
|
+
sessionPortal?: {
|
|
173
|
+
scheme: "http" | "https";
|
|
174
|
+
bindHost: string;
|
|
175
|
+
port: number;
|
|
176
|
+
};
|
|
161
177
|
}
|
|
162
178
|
export interface PluginRuntimeLifecycle {
|
|
163
179
|
bindHostServices(services: PluginRuntimeHostServices): void;
|
|
@@ -236,7 +252,11 @@ export interface DaemonManagementHost {
|
|
|
236
252
|
restartRequired: boolean;
|
|
237
253
|
};
|
|
238
254
|
/** Perform one final activity check and schedule shutdown only when idle. */
|
|
239
|
-
shutdownIfIdle?(): DaemonShutdownIfIdleResult
|
|
255
|
+
shutdownIfIdle?(maintenanceLeaseToken?: string): DaemonShutdownIfIdleResult | Promise<DaemonShutdownIfIdleResult>;
|
|
256
|
+
/** Release a cross-process maintenance fence after a verified cutover. */
|
|
257
|
+
releaseMaintenanceLease?(maintenanceLeaseToken: string): {
|
|
258
|
+
outcome: "released" | "not_active" | "denied";
|
|
259
|
+
};
|
|
240
260
|
/** Stateful plugin registry operations owned by the resident daemon. */
|
|
241
261
|
pluginManagement?: PluginManagementHost;
|
|
242
262
|
/** Registered Git/local plugin catalogs. */
|
|
@@ -415,6 +435,8 @@ export declare function createSessionRuntimeServices(input: {
|
|
|
415
435
|
sessionLog?: SessionLogStore;
|
|
416
436
|
onMirrorError?: (error: unknown, sessionId: string) => void;
|
|
417
437
|
sessionContextProvider?: RunnerSessionContextProvider;
|
|
438
|
+
admissionOpen?: () => boolean;
|
|
439
|
+
admissionReserve?: () => AdmissionReservation | undefined;
|
|
418
440
|
}): SessionRuntimeServices;
|
|
419
441
|
/**
|
|
420
442
|
* The HTTP surface is just a liveness probe for container orchestration — all
|
|
@@ -423,4 +445,4 @@ export declare function createSessionRuntimeServices(input: {
|
|
|
423
445
|
* no agent request / response / SSE endpoints are exposed here.
|
|
424
446
|
*/
|
|
425
447
|
export declare function createApp({ config, control }?: CreateAppOptions): Koa<Koa.DefaultState, Koa.DefaultContext>;
|
|
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>;
|
|
448
|
+
export declare function startServer({ config, control, sessionLog, sessionRegistry, pluginRuntime, daemonManagement, remoteRuntime, remoteRuntimeAdmin, runtimeTargetControl, runtimeConnectionResolver, localSessionResources, localMachineSessions, sessionEmulators, sessionRuntimeServices, sessionLifecycle, sessionTerminals, runtimeLocalBrowser, providerClis, desktopBrowserHost, sessionPortalAuthorization, }?: StartServerOptions): Promise<RynxServer>;
|