@rynx-ai/server 0.1.11-beta.23 → 0.1.11-beta.25
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.js +4 -1
- package/dist/control-web/assets/{highlighted-body-OFNGDK62-DV5jYwcf.js → highlighted-body-OFNGDK62-BBc3aRDM.js} +1 -1
- package/dist/control-web/assets/{index-D-5t2eCT.js → index-C1ODT189.js} +84 -79
- package/dist/control-web/assets/index-DOUyXr2J.css +32 -0
- package/dist/control-web/assets/{mermaid-GHXKKRXX-DPZO2Q9W.js → mermaid-GHXKKRXX-BkqCCgCk.js} +3 -3
- package/dist/control-web/index.html +2 -2
- package/dist/machine-session-service.d.ts +10 -5
- package/dist/machine-session-service.js +70 -12
- package/dist/remote-runtime-session-projection.js +1 -0
- package/dist/server.js +5 -0
- package/dist/session-runtime-index.d.ts +2 -0
- package/dist/session-runtime-index.js +51 -14
- package/package.json +7 -7
- package/dist/control-web/assets/index-0noHpRXD.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-C1ODT189.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DOUyXr2J.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AdmissionReservation, type AgentRuntimeId, type ConversationRuntime, type ResolvedExecutionSnapshot, type SessionWorkspaceSnapshot, type SessionProviderId, type MachineSessionRecord, type LiveSessionFailure, 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 LiveSessionFailure, type ReasoningEffort, type RuntimeUserInput, type SessionCollaborationMode, 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
4
|
import type { ControlAgentModelOption, ControlAgentSummary, ControlSessionInteractionResolveDisposition, ControlSessionRuntimeSnapshot, SessionPermissionPreset } from "@rynx-ai/protocol/control";
|
|
@@ -69,6 +69,7 @@ export interface MachineSessionRunnerPort {
|
|
|
69
69
|
lastLiveSessionError?(sessionId: string): string | undefined;
|
|
70
70
|
lastLiveSessionFailure?(sessionId: string): LiveSessionFailure | undefined;
|
|
71
71
|
injectMessage(sessionId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
72
|
+
updateCollaborationMode?(sessionId: string, mode: SessionCollaborationMode): Promise<void>;
|
|
72
73
|
stopRunner(sessionId: string): void;
|
|
73
74
|
resolveInteraction(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
|
|
74
75
|
disposition: ControlSessionInteractionResolveDisposition;
|
|
@@ -312,11 +313,15 @@ export declare class MachineSessionService {
|
|
|
312
313
|
launchOptions(): Promise<RemoteRuntimeSessionLaunchOptionsListResult>;
|
|
313
314
|
/** Read the effective model defaults used for the next turn. */
|
|
314
315
|
executionSettings(sessionIdInput: string): Promise<RemoteRuntimeSessionExecutionGetResult>;
|
|
315
|
-
/** Replace the
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
316
|
+
/** Replace the defaults for the next new Turn. Codex-lineage Providers keep
|
|
317
|
+
* their live runner: the host applies changed native thread settings under
|
|
318
|
+
* its injection lock immediately before the next `turn/start`. Claude still
|
|
319
|
+
* needs an idle cold resume because its live CLI has no confirmed settings
|
|
320
|
+
* update boundary. Provider CLIs own model availability validation. */
|
|
319
321
|
updateExecutionSettings(input: RemoteRuntimeSessionExecutionUpdateParams): Promise<RemoteRuntimeSessionExecutionUpdateResult>;
|
|
322
|
+
/** Persist a mode changed from the native TUI before its reflected event is
|
|
323
|
+
* fanned out to Web clients. Unknown/deleted and Claude Sessions are ignored. */
|
|
324
|
+
recordNativeCollaborationMode(sessionIdInput: string, mode: SessionCollaborationMode): void;
|
|
320
325
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
321
326
|
create(input: MachineSessionCreateInput): Promise<MachineSessionCreateResult>;
|
|
322
327
|
private createAdmitted;
|
|
@@ -210,10 +210,11 @@ export class MachineSessionService {
|
|
|
210
210
|
}
|
|
211
211
|
return executionSettingsResult(meta.execution);
|
|
212
212
|
}
|
|
213
|
-
/** Replace the
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
213
|
+
/** Replace the defaults for the next new Turn. Codex-lineage Providers keep
|
|
214
|
+
* their live runner: the host applies changed native thread settings under
|
|
215
|
+
* its injection lock immediately before the next `turn/start`. Claude still
|
|
216
|
+
* needs an idle cold resume because its live CLI has no confirmed settings
|
|
217
|
+
* update boundary. Provider CLIs own model availability validation. */
|
|
217
218
|
async updateExecutionSettings(input) {
|
|
218
219
|
const sessionId = validOpaqueToken(input.sessionId, "sessionId", MAX_SESSION_ID_CHARS);
|
|
219
220
|
const modelId = input.model === null ? null : validRequiredText(input.model, "model");
|
|
@@ -222,13 +223,15 @@ export class MachineSessionService {
|
|
|
222
223
|
throw new MachineSessionServiceFailure("not_found", "session not found");
|
|
223
224
|
}
|
|
224
225
|
await this.assertNotForkReserved(sessionId);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
runtime.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
if (meta.execution.provider === "claude") {
|
|
227
|
+
const runtime = this.ports.runtimeState.snapshot(sessionId);
|
|
228
|
+
const pendingMessage = await this.ports.pendingMessages?.get(sessionId);
|
|
229
|
+
if (runtime.status !== "idle" ||
|
|
230
|
+
runtime.activeResponseIds.length > 0 ||
|
|
231
|
+
runtime.pendingInteractions.length > 0 ||
|
|
232
|
+
pendingMessage) {
|
|
233
|
+
throw new MachineSessionServiceFailure("failed_precondition", "Claude Session must be idle before changing model settings");
|
|
234
|
+
}
|
|
232
235
|
}
|
|
233
236
|
const executionPort = this.requireExecution();
|
|
234
237
|
const reasoningEffort = input.reasoningEffort === null
|
|
@@ -238,11 +241,51 @@ export class MachineSessionService {
|
|
|
238
241
|
...structuredClone(meta.execution),
|
|
239
242
|
model: modelId,
|
|
240
243
|
reasoningEffort,
|
|
244
|
+
...(meta.execution.provider !== "claude" && input.collaborationMode !== undefined
|
|
245
|
+
? { collaborationMode: input.collaborationMode }
|
|
246
|
+
: {}),
|
|
241
247
|
};
|
|
248
|
+
if (meta.execution.provider !== "claude" && input.collaborationMode !== undefined) {
|
|
249
|
+
const updateMode = executionPort.runner.updateCollaborationMode;
|
|
250
|
+
if (!updateMode) {
|
|
251
|
+
const message = "This Runtime does not support live collaboration-mode updates";
|
|
252
|
+
throw new MachineSessionServiceFailure("failed_precondition", message, message, {
|
|
253
|
+
message,
|
|
254
|
+
code: "collaboration_mode_unavailable",
|
|
255
|
+
statusCode: 409,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// Omnigent parity: native state is authoritative. Do not make a failed
|
|
259
|
+
// toggle survive reload when the loaded thread rejected it.
|
|
260
|
+
try {
|
|
261
|
+
await updateMode.call(executionPort.runner, sessionId, input.collaborationMode);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const failure = liveSessionFailureFrom(error);
|
|
265
|
+
const message = collaborationModeFailureMessage(meta.execution.provider, failure, error instanceof Error ? error.message : "Native collaboration mode update failed");
|
|
266
|
+
throw new MachineSessionServiceFailure("failed_precondition", message, message, failure ? { ...failure, message } : undefined);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
242
269
|
this.ports.registry.setExecution(sessionId, nextExecution);
|
|
243
|
-
|
|
270
|
+
if (nextExecution.provider === "claude") {
|
|
271
|
+
executionPort.runner.stopRunner(sessionId);
|
|
272
|
+
}
|
|
244
273
|
return executionSettingsResult(nextExecution);
|
|
245
274
|
}
|
|
275
|
+
/** Persist a mode changed from the native TUI before its reflected event is
|
|
276
|
+
* fanned out to Web clients. Unknown/deleted and Claude Sessions are ignored. */
|
|
277
|
+
recordNativeCollaborationMode(sessionIdInput, mode) {
|
|
278
|
+
const sessionId = validOpaqueToken(sessionIdInput, "sessionId", MAX_SESSION_ID_CHARS);
|
|
279
|
+
const meta = this.ports.registry.get(sessionId);
|
|
280
|
+
if (!meta || meta.execution.provider === "claude")
|
|
281
|
+
return;
|
|
282
|
+
if (meta.execution.collaborationMode === mode)
|
|
283
|
+
return;
|
|
284
|
+
this.ports.registry.setExecution(sessionId, {
|
|
285
|
+
...structuredClone(meta.execution),
|
|
286
|
+
collaborationMode: mode,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
246
289
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
247
290
|
async create(input) {
|
|
248
291
|
return this.withAdmission(() => this.createAdmitted(input));
|
|
@@ -1016,6 +1059,7 @@ function executionSettingsResult(execution) {
|
|
|
1016
1059
|
provider: execution.provider,
|
|
1017
1060
|
model: execution.model,
|
|
1018
1061
|
reasoningEffort: execution.reasoningEffort,
|
|
1062
|
+
collaborationMode: execution.collaborationMode ?? null,
|
|
1019
1063
|
};
|
|
1020
1064
|
}
|
|
1021
1065
|
function compareMetaRecency(left, right) {
|
|
@@ -1108,6 +1152,20 @@ function liveSessionFailureFrom(error) {
|
|
|
1108
1152
|
statusCode: candidate.statusCode,
|
|
1109
1153
|
};
|
|
1110
1154
|
}
|
|
1155
|
+
/** Turn structured native mode failures into actionable, Provider-specific
|
|
1156
|
+
* messages before the Direct Runtime adapter crosses the process boundary.
|
|
1157
|
+
* Unstructured failures keep their original text and remain subject to the
|
|
1158
|
+
* adapter's normal redaction policy. */
|
|
1159
|
+
function collaborationModeFailureMessage(provider, failure, fallback) {
|
|
1160
|
+
const providerLabel = provider === "traex" ? "TraeX" : "Codex";
|
|
1161
|
+
if (failure?.code === "native_session_not_live") {
|
|
1162
|
+
return `No live ${providerLabel} runner is available. Reconnect this Session and try again.`;
|
|
1163
|
+
}
|
|
1164
|
+
if (failure?.code === "native_thread_not_ready") {
|
|
1165
|
+
return `${providerLabel} is still loading its native thread. Reconnect this Session and try again.`;
|
|
1166
|
+
}
|
|
1167
|
+
return failure?.message ?? fallback;
|
|
1168
|
+
}
|
|
1111
1169
|
function requestLimit(value, fallback, maximum, field) {
|
|
1112
1170
|
return value === undefined ? Math.min(fallback, maximum) : boundedInteger(value, 1, maximum, field);
|
|
1113
1171
|
}
|
|
@@ -530,6 +530,7 @@ function assertEventScope(event, expectedSessionId) {
|
|
|
530
530
|
let actual;
|
|
531
531
|
switch (event.type) {
|
|
532
532
|
case "session.status":
|
|
533
|
+
case "session.collaboration_mode":
|
|
533
534
|
case "response.created":
|
|
534
535
|
case "session.interaction.requested":
|
|
535
536
|
case "session.interaction.resolved":
|
package/dist/server.js
CHANGED
|
@@ -257,6 +257,11 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
257
257
|
kind: r.kind,
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
|
+
if (localMachineSessions) {
|
|
261
|
+
runnerManager.onCollaborationMode((sessionId, mode) => {
|
|
262
|
+
localMachineSessions.recordNativeCollaborationMode(sessionId, mode);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
260
265
|
const app = createApp({
|
|
261
266
|
config,
|
|
262
267
|
control: controlSurface,
|
|
@@ -17,29 +17,51 @@ export class SessionRuntimeIndex {
|
|
|
17
17
|
case "response.completed":
|
|
18
18
|
state.activeResponseIds.delete(event.responseId);
|
|
19
19
|
this.removeResponseInteractions(state, event.responseId);
|
|
20
|
-
state.status = this.
|
|
20
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
21
21
|
state.statusKind = undefined;
|
|
22
22
|
return;
|
|
23
23
|
case "response.failed":
|
|
24
24
|
state.activeResponseIds.delete(event.responseId);
|
|
25
25
|
this.removeResponseInteractions(state, event.responseId);
|
|
26
26
|
// Response failure is Turn-local. It must not poison a reusable Session.
|
|
27
|
-
state.status = this.
|
|
27
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
28
28
|
state.statusKind = undefined;
|
|
29
29
|
return;
|
|
30
30
|
case "session.interrupted":
|
|
31
31
|
state.activeResponseIds.delete(event.responseId);
|
|
32
32
|
this.removeResponseInteractions(state, event.responseId);
|
|
33
|
-
state.status = this.
|
|
33
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
34
34
|
state.statusKind = undefined;
|
|
35
35
|
return;
|
|
36
|
-
case "session.status":
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
case "session.status": {
|
|
37
|
+
if (event.status === "running") {
|
|
38
|
+
if (event.responseId)
|
|
39
|
+
state.activeResponseIds.add(event.responseId);
|
|
40
|
+
state.status = "running";
|
|
41
|
+
state.statusKind = event.statusKind;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// A provider interaction is a stronger in-flight fact than a quiet-pane
|
|
45
|
+
// idle observation. Claude's status-file path reports waiting as running;
|
|
46
|
+
// this guard preserves the same truth on its legacy PTY fallback.
|
|
47
|
+
if (event.status === "idle" && state.pendingInteractions.size > 0) {
|
|
48
|
+
state.status = "running";
|
|
49
|
+
state.statusKind = undefined;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.retireStatusResponses(state);
|
|
53
|
+
if (event.status === "failed") {
|
|
54
|
+
state.status = "failed";
|
|
55
|
+
}
|
|
56
|
+
else if (state.status !== "failed") {
|
|
57
|
+
// Provider idle is authoritative even when final transcript records
|
|
58
|
+
// arrive later. Response identity remains on those events themselves;
|
|
59
|
+
// it must not keep Session activity/loading alive.
|
|
60
|
+
state.status = this.hasRunningTurn(state) ? "running" : "idle";
|
|
61
|
+
}
|
|
62
|
+
state.statusKind = undefined;
|
|
42
63
|
return;
|
|
64
|
+
}
|
|
43
65
|
case "session.interaction.requested":
|
|
44
66
|
state.pendingInteractions.set(event.interaction.interactionId, {
|
|
45
67
|
responseId: event.responseId,
|
|
@@ -52,7 +74,8 @@ export class SessionRuntimeIndex {
|
|
|
52
74
|
case "session.interaction.resolved":
|
|
53
75
|
case "session.interaction.cancelled":
|
|
54
76
|
state.pendingInteractions.delete(event.interactionId);
|
|
55
|
-
|
|
77
|
+
if (this.hasRunningTurn(state))
|
|
78
|
+
state.status = "running";
|
|
56
79
|
return;
|
|
57
80
|
case "session.rotated":
|
|
58
81
|
state.activeResponseIds.clear();
|
|
@@ -69,9 +92,7 @@ export class SessionRuntimeIndex {
|
|
|
69
92
|
if (!state)
|
|
70
93
|
return { status: "idle", activeResponseIds: [], pendingInteractions: [] };
|
|
71
94
|
return {
|
|
72
|
-
status: state.pendingInteractions.size > 0
|
|
73
|
-
? "running"
|
|
74
|
-
: state.status,
|
|
95
|
+
status: state.pendingInteractions.size > 0 ? "running" : state.status,
|
|
75
96
|
...(state.statusKind === undefined ? {} : { statusKind: state.statusKind }),
|
|
76
97
|
activeResponseIds: [...state.activeResponseIds],
|
|
77
98
|
pendingInteractions: [...state.pendingInteractions.values()],
|
|
@@ -82,7 +103,9 @@ export class SessionRuntimeIndex {
|
|
|
82
103
|
let runningTurns = 0;
|
|
83
104
|
let pendingInteractions = 0;
|
|
84
105
|
for (const state of this.sessions.values()) {
|
|
85
|
-
|
|
106
|
+
if (state.status === "running" || state.pendingInteractions.size > 0) {
|
|
107
|
+
runningTurns += Math.max(1, state.activeResponseIds.size);
|
|
108
|
+
}
|
|
86
109
|
pendingInteractions += state.pendingInteractions.size;
|
|
87
110
|
}
|
|
88
111
|
return { runningTurns, pendingInteractions };
|
|
@@ -111,4 +134,18 @@ export class SessionRuntimeIndex {
|
|
|
111
134
|
hasRunningTurn(state) {
|
|
112
135
|
return state.activeResponseIds.size > 0;
|
|
113
136
|
}
|
|
137
|
+
statusAfterResponseTerminal(state) {
|
|
138
|
+
if (state.pendingInteractions.size > 0 || this.hasRunningTurn(state))
|
|
139
|
+
return "running";
|
|
140
|
+
return state.status === "failed" ? "failed" : "idle";
|
|
141
|
+
}
|
|
142
|
+
retireStatusResponses(state) {
|
|
143
|
+
// session.status is Session-wide. Match reference implementation's
|
|
144
|
+
// _session_active_response_cache: every idle/failed edge closes the one
|
|
145
|
+
// in-flight native response projection, even when the edge happens to be
|
|
146
|
+
// stamped with that response id. A late transcript event may still carry
|
|
147
|
+
// the old id for grouping, but it cannot keep activity alive.
|
|
148
|
+
state.activeResponseIds.clear();
|
|
149
|
+
state.pendingInteractions.clear();
|
|
150
|
+
}
|
|
114
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/server",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.25",
|
|
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/
|
|
31
|
-
"@rynx-ai/
|
|
32
|
-
"@rynx-ai/
|
|
33
|
-
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.
|
|
34
|
-
"@rynx-ai/runtime": "0.1.11-beta.
|
|
30
|
+
"@rynx-ai/plugin-sdk": "0.1.11-beta.25",
|
|
31
|
+
"@rynx-ai/protocol": "0.1.11-beta.25",
|
|
32
|
+
"@rynx-ai/core": "0.1.11-beta.25",
|
|
33
|
+
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.25",
|
|
34
|
+
"@rynx-ai/runtime": "0.1.11-beta.25"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/ws": "^8.18.1",
|
|
38
|
-
"@rynx-ai/control-web": "0.1.11-beta.
|
|
38
|
+
"@rynx-ai/control-web": "0.1.11-beta.25"
|
|
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/",
|