@rynx-ai/server 0.1.11-beta.22 → 0.1.11-beta.24
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 +2 -2
- package/dist/control-api.js +2 -1
- package/dist/control-web/assets/{highlighted-body-OFNGDK62-WjfT1ceR.js → highlighted-body-OFNGDK62-DV5jYwcf.js} +1 -1
- package/dist/control-web/assets/{index-BQFR9W8k.js → index-D-5t2eCT.js} +91 -91
- package/dist/control-web/assets/{mermaid-GHXKKRXX-CJPtDfFU.js → mermaid-GHXKKRXX-DPZO2Q9W.js} +3 -3
- package/dist/control-web/index.html +1 -1
- package/dist/machine-session-service.d.ts +7 -7
- package/dist/machine-session-service.js +23 -72
- package/dist/remote-runtime-session-projection.js +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +9 -1
- package/dist/session-runtime-index.d.ts +2 -6
- package/dist/session-runtime-index.js +57 -38
- package/package.json +7 -7
|
@@ -7,7 +7,7 @@
|
|
|
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-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-D-5t2eCT.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-0noHpRXD.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -3,10 +3,9 @@ import type { SequencedSessionEvent, SessionEvent, SessionInteractionResolution,
|
|
|
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";
|
|
5
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
|
-
import type { CodexRuntimeStatus,
|
|
6
|
+
import type { CodexRuntimeStatus, InjectResult } from "@rynx-ai/runtime";
|
|
7
7
|
export interface MachineSessionRuntimeStatePort {
|
|
8
8
|
snapshot(sessionId: string): ControlSessionRuntimeSnapshot;
|
|
9
|
-
beginResponseHandoff?(sessionId: string, responseId: string): () => void;
|
|
10
9
|
remove?(sessionId: string): void;
|
|
11
10
|
}
|
|
12
11
|
export interface MachineSessionInterruptPort {
|
|
@@ -69,7 +68,7 @@ export interface MachineSessionRunnerPort {
|
|
|
69
68
|
}): Promise<boolean>;
|
|
70
69
|
lastLiveSessionError?(sessionId: string): string | undefined;
|
|
71
70
|
lastLiveSessionFailure?(sessionId: string): LiveSessionFailure | undefined;
|
|
72
|
-
injectMessage(sessionId: string, input: RuntimeUserInput | string): Promise<
|
|
71
|
+
injectMessage(sessionId: string, input: RuntimeUserInput | string): Promise<InjectResult>;
|
|
73
72
|
stopRunner(sessionId: string): void;
|
|
74
73
|
resolveInteraction(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
|
|
75
74
|
disposition: ControlSessionInteractionResolveDisposition;
|
|
@@ -313,10 +312,11 @@ export declare class MachineSessionService {
|
|
|
313
312
|
launchOptions(): Promise<RemoteRuntimeSessionLaunchOptionsListResult>;
|
|
314
313
|
/** Read the effective model defaults used for the next turn. */
|
|
315
314
|
executionSettings(sessionIdInput: string): Promise<RemoteRuntimeSessionExecutionGetResult>;
|
|
316
|
-
/** Replace the
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
315
|
+
/** Replace the defaults for the next new Turn. Codex-lineage Providers keep
|
|
316
|
+
* their live runner: the host applies changed native thread settings under
|
|
317
|
+
* its injection lock immediately before the next `turn/start`. Claude still
|
|
318
|
+
* needs an idle cold resume because its live CLI has no confirmed settings
|
|
319
|
+
* update boundary. Provider CLIs own model availability validation. */
|
|
320
320
|
updateExecutionSettings(input: RemoteRuntimeSessionExecutionUpdateParams): Promise<RemoteRuntimeSessionExecutionUpdateResult>;
|
|
321
321
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
322
322
|
create(input: MachineSessionCreateInput): Promise<MachineSessionCreateResult>;
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Direct adapters can therefore expose the same behavior without copying state
|
|
8
8
|
* into a Control Plane database.
|
|
9
9
|
*/
|
|
10
|
-
import { createHash
|
|
11
|
-
import { SESSION_PROVIDER_IDS, getRuntimeProfile, isSessionProviderId,
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { SESSION_PROVIDER_IDS, getRuntimeProfile, isSessionProviderId, newSessionId, normalizeSessionTitle, } from "@rynx-ai/core";
|
|
12
12
|
import { REMOTE_RUNTIME_SESSION_DEFAULT_PAGE_SIZE, REMOTE_RUNTIME_SESSION_MAX_CURSOR_CHARS, REMOTE_RUNTIME_SESSION_MAX_ID_CHARS, REMOTE_RUNTIME_SESSION_MAX_PAGE_SIZE, } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
13
13
|
const DEFAULT_LIST_LIMIT = REMOTE_RUNTIME_SESSION_DEFAULT_PAGE_SIZE;
|
|
14
14
|
const MAX_LIST_LIMIT = REMOTE_RUNTIME_SESSION_MAX_PAGE_SIZE;
|
|
@@ -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
|
|
@@ -240,7 +243,9 @@ export class MachineSessionService {
|
|
|
240
243
|
reasoningEffort,
|
|
241
244
|
};
|
|
242
245
|
this.ports.registry.setExecution(sessionId, nextExecution);
|
|
243
|
-
|
|
246
|
+
if (nextExecution.provider === "claude") {
|
|
247
|
+
executionPort.runner.stopRunner(sessionId);
|
|
248
|
+
}
|
|
244
249
|
return executionSettingsResult(nextExecution);
|
|
245
250
|
}
|
|
246
251
|
/** Create target-owned Session identity from one Agent preset or direct Provider. */
|
|
@@ -508,69 +513,21 @@ export class MachineSessionService {
|
|
|
508
513
|
if (request.clientMessageId) {
|
|
509
514
|
await resources?.markMessageInjecting(sessionId, request.clientMessageId);
|
|
510
515
|
}
|
|
511
|
-
const responseId = this.ports.publishEvent ? `resp_${randomUUID()}` : undefined;
|
|
512
|
-
if (responseId) {
|
|
513
|
-
await this.ports.publishEvent(sessionId, {
|
|
514
|
-
type: "session.input.consumed",
|
|
515
|
-
item: {
|
|
516
|
-
id: newSessionItemId("message"),
|
|
517
|
-
sessionId,
|
|
518
|
-
position: 0,
|
|
519
|
-
responseId,
|
|
520
|
-
status: "completed",
|
|
521
|
-
createdAt: Date.now(),
|
|
522
|
-
type: "message",
|
|
523
|
-
data: {
|
|
524
|
-
role: "user",
|
|
525
|
-
content: prepared.input.content.map((part) => part.type === "text"
|
|
526
|
-
? { type: "input_text", text: part.text }
|
|
527
|
-
: { ...part.resource }),
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
});
|
|
531
|
-
await this.ports.publishEvent(sessionId, {
|
|
532
|
-
type: "session.status",
|
|
533
|
-
sessionId,
|
|
534
|
-
responseId,
|
|
535
|
-
status: "running",
|
|
536
|
-
statusKind: "startup",
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
const abandonResponseHandoff = responseId
|
|
540
|
-
? this.ports.runtimeState.beginResponseHandoff?.(sessionId, responseId)
|
|
541
|
-
: undefined;
|
|
542
|
-
const clearStartup = async (status) => {
|
|
543
|
-
if (!responseId)
|
|
544
|
-
return;
|
|
545
|
-
await this.ports.publishEvent(sessionId, {
|
|
546
|
-
type: "session.status",
|
|
547
|
-
sessionId,
|
|
548
|
-
responseId,
|
|
549
|
-
status,
|
|
550
|
-
});
|
|
551
|
-
};
|
|
552
516
|
let execution;
|
|
553
517
|
try {
|
|
554
518
|
execution = await this.ensureLiveSession(sessionId, meta);
|
|
555
519
|
}
|
|
556
520
|
catch (error) {
|
|
557
|
-
abandonResponseHandoff?.();
|
|
558
|
-
await clearStartup("idle");
|
|
559
521
|
if (request.clientMessageId) {
|
|
560
522
|
await resources?.markMessageFailedNotStarted(sessionId, request.clientMessageId, error instanceof Error ? error.message : String(error));
|
|
561
523
|
}
|
|
562
524
|
throw error;
|
|
563
525
|
}
|
|
564
|
-
let
|
|
526
|
+
let result;
|
|
565
527
|
try {
|
|
566
|
-
|
|
567
|
-
? { ...prepared.input, responseId }
|
|
568
|
-
: prepared.input;
|
|
569
|
-
outcome = await execution.runner.injectMessage(sessionId, responseId || needsPreparedOperation ? runtimeInput : request.message);
|
|
528
|
+
result = await execution.runner.injectMessage(sessionId, needsPreparedOperation ? prepared.input : request.message);
|
|
570
529
|
}
|
|
571
530
|
catch (error) {
|
|
572
|
-
abandonResponseHandoff?.();
|
|
573
|
-
await clearStartup("idle");
|
|
574
531
|
const failure = execution.runner.lastLiveSessionFailure?.(sessionId)
|
|
575
532
|
?? liveSessionFailureFrom(error);
|
|
576
533
|
const reason = failure?.message
|
|
@@ -580,9 +537,8 @@ export class MachineSessionService {
|
|
|
580
537
|
}
|
|
581
538
|
throw new MachineSessionServiceFailure("outcome_unknown", "live injection outcome is unknown", reason, failure);
|
|
582
539
|
}
|
|
540
|
+
const outcome = result.outcome;
|
|
583
541
|
if (outcome === "failed") {
|
|
584
|
-
abandonResponseHandoff?.();
|
|
585
|
-
await clearStartup("idle");
|
|
586
542
|
const failure = execution.runner.lastLiveSessionFailure?.(sessionId);
|
|
587
543
|
const reason = failure?.message
|
|
588
544
|
?? execution.runner.lastLiveSessionError?.(sessionId)
|
|
@@ -593,8 +549,6 @@ export class MachineSessionService {
|
|
|
593
549
|
throw new MachineSessionServiceFailure("outcome_unknown", `live injection ${outcome}`, reason, failure);
|
|
594
550
|
}
|
|
595
551
|
if (outcome !== "injected" && outcome !== "steered") {
|
|
596
|
-
abandonResponseHandoff?.();
|
|
597
|
-
await clearStartup("idle");
|
|
598
552
|
const failure = execution.runner.lastLiveSessionFailure?.(sessionId);
|
|
599
553
|
const reason = failure?.message
|
|
600
554
|
?? execution.runner.lastLiveSessionError?.(sessionId)
|
|
@@ -604,9 +558,6 @@ export class MachineSessionService {
|
|
|
604
558
|
}
|
|
605
559
|
throw new MachineSessionServiceFailure("failed_precondition", `live injection ${outcome}`, reason, failure);
|
|
606
560
|
}
|
|
607
|
-
if (outcome === "steered")
|
|
608
|
-
abandonResponseHandoff?.();
|
|
609
|
-
await clearStartup("running");
|
|
610
561
|
if (request.clientMessageId) {
|
|
611
562
|
try {
|
|
612
563
|
await resources?.markMessageInjected(sessionId, request.clientMessageId);
|
|
@@ -838,7 +789,7 @@ export class MachineSessionService {
|
|
|
838
789
|
current.message !== pending.message)
|
|
839
790
|
return;
|
|
840
791
|
injectionAttempted = true;
|
|
841
|
-
const outcome = await execution.runner.injectMessage(sessionId, pending.message);
|
|
792
|
+
const { outcome } = await execution.runner.injectMessage(sessionId, pending.message);
|
|
842
793
|
if (outcome === "injected" || outcome === "steered") {
|
|
843
794
|
await Promise.resolve(pendingStore.delete(sessionId)).catch(async () => {
|
|
844
795
|
await Promise.resolve(pendingStore.markOutcomeUnknown(sessionId)).catch(() => undefined);
|
|
@@ -534,6 +534,7 @@ function assertEventScope(event, expectedSessionId) {
|
|
|
534
534
|
case "session.interaction.requested":
|
|
535
535
|
case "session.interaction.resolved":
|
|
536
536
|
case "session.interaction.cancelled":
|
|
537
|
+
case "session.interrupted":
|
|
537
538
|
case "session.todos":
|
|
538
539
|
case "session.rotated":
|
|
539
540
|
actual = event.sessionId;
|
package/dist/server.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ConversationRuntime, type AdmissionReservation, type AgentCapabilities,
|
|
|
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";
|
|
7
|
-
import type { PluginJsonValue } from "@rynx-ai/plugin-sdk";
|
|
7
|
+
import type { PluginAgentSummary, PluginJsonValue, PluginResolvedSessionExecution, PluginSessionActivitySnapshot, PluginSessionExecutionSnapshot } from "@rynx-ai/plugin-sdk";
|
|
8
8
|
import type { PairingOffer } from "@rynx-ai/protocol/direct-runtime";
|
|
9
9
|
import type { DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
|
|
10
10
|
import type { RuntimeBrowserBootstrapCredential, RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
|
|
@@ -12,7 +12,6 @@ import type { RuntimeBrowserInspectMessage, RuntimeBrowserInspectTarget } from "
|
|
|
12
12
|
import type { RuntimeBrowserSurfaceClientFrame, RuntimeBrowserSurfaceImageFrame, RuntimeBrowserSurfaceOpenFrame, RuntimeBrowserSurfaceReadyFrame, RuntimeBrowserSurfaceServerFrame } from "@rynx-ai/protocol/runtime-browser-surface";
|
|
13
13
|
import type { RuntimeEmulatorSurfaceImageFrame, RuntimeEmulatorSurfaceReadyFrame } from "@rynx-ai/protocol/runtime-emulator-surface";
|
|
14
14
|
import type { RemoteRuntimeRpcMethod, RemoteRuntimeRpcParams, RemoteRuntimeRpcResultFor, RemoteRuntimeSequencedSessionEvent } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
15
|
-
import type { PluginAgentSummary, PluginResolvedSessionExecution, PluginSessionExecutionSnapshot } from "@rynx-ai/plugin-sdk";
|
|
16
15
|
import { RunnerManager, type CodexSessionStore, type RunnerSessionContextProvider } from "@rynx-ai/runtime";
|
|
17
16
|
import { type ControlPlaneDeps, type LocalSessionResourceHost, type SessionResourceLifecycle } from "./control-api.js";
|
|
18
17
|
import type { MachineSessionService } from "./machine-session-service.js";
|
|
@@ -160,6 +159,7 @@ export interface PluginRuntimeHostServices {
|
|
|
160
159
|
session: PluginSessionExecutionSnapshot;
|
|
161
160
|
}): Promise<PluginResolvedSessionExecution>;
|
|
162
161
|
};
|
|
162
|
+
sessionActivity(sessionId: string): PluginSessionActivitySnapshot;
|
|
163
163
|
interruptSession(sessionId: string): Promise<boolean>;
|
|
164
164
|
terminateSession?(sessionId: string): Promise<boolean>;
|
|
165
165
|
resolveSessionInteraction(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
|
package/dist/server.js
CHANGED
|
@@ -75,7 +75,6 @@ export function createSessionRuntimeServices(input) {
|
|
|
75
75
|
config: input.config,
|
|
76
76
|
executor: runnerManager,
|
|
77
77
|
sessionBus,
|
|
78
|
-
sessionEventSink: mirrorSessionEvent,
|
|
79
78
|
});
|
|
80
79
|
runnerManager.onMirror((sessionId, event) => {
|
|
81
80
|
void mirrorSessionEvent(sessionId, event).catch((error) => {
|
|
@@ -222,6 +221,15 @@ export async function startServer({ config = loadConfig(), control, sessionLog,
|
|
|
222
221
|
session: input.session,
|
|
223
222
|
}),
|
|
224
223
|
},
|
|
224
|
+
sessionActivity: (sessionId) => {
|
|
225
|
+
const snapshot = sessionRuntimeIndex.snapshot(sessionId);
|
|
226
|
+
return {
|
|
227
|
+
...snapshot,
|
|
228
|
+
status: snapshot.pendingInteractions.length > 0
|
|
229
|
+
? "waiting"
|
|
230
|
+
: snapshot.status,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
225
233
|
interruptSession: (sessionId) => runnerManager.interruptLiveSession(sessionId),
|
|
226
234
|
terminateSession: (sessionId) => runnerManager.terminateLiveSession(sessionId),
|
|
227
235
|
resolveSessionInteraction: (sessionId, interactionId, resolution) => runnerManager.resolveInteraction(sessionId, interactionId, resolution),
|
|
@@ -13,12 +13,6 @@ export interface SessionRuntimeActivitySnapshot {
|
|
|
13
13
|
*/
|
|
14
14
|
export declare class SessionRuntimeIndex {
|
|
15
15
|
private readonly sessions;
|
|
16
|
-
/**
|
|
17
|
-
* Make an accepted Turn visible before the request admission reservation is
|
|
18
|
-
* released. The returned callback only abandons the pre-created handoff; once
|
|
19
|
-
* response.created arrives, the native response remains authoritative.
|
|
20
|
-
*/
|
|
21
|
-
beginResponseHandoff(sessionId: string, responseId: string): () => void;
|
|
22
16
|
observe(sessionId: string, event: SessionEvent): void;
|
|
23
17
|
snapshot(sessionId: string): SessionRuntimeSnapshot;
|
|
24
18
|
/** Aggregate only process-live work; durable Session history is not activity. */
|
|
@@ -27,4 +21,6 @@ export declare class SessionRuntimeIndex {
|
|
|
27
21
|
private state;
|
|
28
22
|
private removeResponseInteractions;
|
|
29
23
|
private hasRunningTurn;
|
|
24
|
+
private statusAfterResponseTerminal;
|
|
25
|
+
private retireStatusResponses;
|
|
30
26
|
}
|
|
@@ -6,57 +6,63 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export class SessionRuntimeIndex {
|
|
8
8
|
sessions = new Map();
|
|
9
|
-
/**
|
|
10
|
-
* Make an accepted Turn visible before the request admission reservation is
|
|
11
|
-
* released. The returned callback only abandons the pre-created handoff; once
|
|
12
|
-
* response.created arrives, the native response remains authoritative.
|
|
13
|
-
*/
|
|
14
|
-
beginResponseHandoff(sessionId, responseId) {
|
|
15
|
-
const state = this.state(sessionId);
|
|
16
|
-
state.pendingResponseIds.add(responseId);
|
|
17
|
-
state.status = "running";
|
|
18
|
-
state.statusKind = "startup";
|
|
19
|
-
let pending = true;
|
|
20
|
-
return () => {
|
|
21
|
-
if (!pending)
|
|
22
|
-
return;
|
|
23
|
-
pending = false;
|
|
24
|
-
state.pendingResponseIds.delete(responseId);
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
9
|
observe(sessionId, event) {
|
|
28
10
|
const state = this.state(sessionId);
|
|
29
11
|
switch (event.type) {
|
|
30
12
|
case "response.created":
|
|
31
|
-
state.pendingResponseIds.delete(event.responseId);
|
|
32
13
|
state.activeResponseIds.add(event.responseId);
|
|
33
14
|
state.status = "running";
|
|
34
15
|
state.statusKind = undefined;
|
|
35
16
|
return;
|
|
36
17
|
case "response.completed":
|
|
37
|
-
state.pendingResponseIds.delete(event.responseId);
|
|
38
18
|
state.activeResponseIds.delete(event.responseId);
|
|
39
19
|
this.removeResponseInteractions(state, event.responseId);
|
|
40
|
-
state.status = this.
|
|
20
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
41
21
|
state.statusKind = undefined;
|
|
42
22
|
return;
|
|
43
23
|
case "response.failed":
|
|
44
|
-
state.pendingResponseIds.delete(event.responseId);
|
|
45
24
|
state.activeResponseIds.delete(event.responseId);
|
|
46
25
|
this.removeResponseInteractions(state, event.responseId);
|
|
47
26
|
// Response failure is Turn-local. It must not poison a reusable Session.
|
|
48
|
-
state.status = this.
|
|
27
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
49
28
|
state.statusKind = undefined;
|
|
50
29
|
return;
|
|
51
|
-
case "session.
|
|
52
|
-
state.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
30
|
+
case "session.interrupted":
|
|
31
|
+
state.activeResponseIds.delete(event.responseId);
|
|
32
|
+
this.removeResponseInteractions(state, event.responseId);
|
|
33
|
+
state.status = this.statusAfterResponseTerminal(state);
|
|
34
|
+
state.statusKind = undefined;
|
|
35
|
+
return;
|
|
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;
|
|
57
63
|
return;
|
|
64
|
+
}
|
|
58
65
|
case "session.interaction.requested":
|
|
59
|
-
state.pendingResponseIds.delete(event.responseId);
|
|
60
66
|
state.pendingInteractions.set(event.interaction.interactionId, {
|
|
61
67
|
responseId: event.responseId,
|
|
62
68
|
interaction: event.interaction,
|
|
@@ -68,10 +74,10 @@ export class SessionRuntimeIndex {
|
|
|
68
74
|
case "session.interaction.resolved":
|
|
69
75
|
case "session.interaction.cancelled":
|
|
70
76
|
state.pendingInteractions.delete(event.interactionId);
|
|
71
|
-
|
|
77
|
+
if (this.hasRunningTurn(state))
|
|
78
|
+
state.status = "running";
|
|
72
79
|
return;
|
|
73
80
|
case "session.rotated":
|
|
74
|
-
state.pendingResponseIds.clear();
|
|
75
81
|
state.activeResponseIds.clear();
|
|
76
82
|
state.pendingInteractions.clear();
|
|
77
83
|
state.status = "idle";
|
|
@@ -86,9 +92,7 @@ export class SessionRuntimeIndex {
|
|
|
86
92
|
if (!state)
|
|
87
93
|
return { status: "idle", activeResponseIds: [], pendingInteractions: [] };
|
|
88
94
|
return {
|
|
89
|
-
status: state.pendingInteractions.size > 0
|
|
90
|
-
? "running"
|
|
91
|
-
: state.status,
|
|
95
|
+
status: state.pendingInteractions.size > 0 ? "running" : state.status,
|
|
92
96
|
...(state.statusKind === undefined ? {} : { statusKind: state.statusKind }),
|
|
93
97
|
activeResponseIds: [...state.activeResponseIds],
|
|
94
98
|
pendingInteractions: [...state.pendingInteractions.values()],
|
|
@@ -99,7 +103,9 @@ export class SessionRuntimeIndex {
|
|
|
99
103
|
let runningTurns = 0;
|
|
100
104
|
let pendingInteractions = 0;
|
|
101
105
|
for (const state of this.sessions.values()) {
|
|
102
|
-
|
|
106
|
+
if (state.status === "running" || state.pendingInteractions.size > 0) {
|
|
107
|
+
runningTurns += Math.max(1, state.activeResponseIds.size);
|
|
108
|
+
}
|
|
103
109
|
pendingInteractions += state.pendingInteractions.size;
|
|
104
110
|
}
|
|
105
111
|
return { runningTurns, pendingInteractions };
|
|
@@ -112,7 +118,6 @@ export class SessionRuntimeIndex {
|
|
|
112
118
|
if (!state) {
|
|
113
119
|
state = {
|
|
114
120
|
status: "idle",
|
|
115
|
-
pendingResponseIds: new Set(),
|
|
116
121
|
activeResponseIds: new Set(),
|
|
117
122
|
pendingInteractions: new Map(),
|
|
118
123
|
};
|
|
@@ -127,6 +132,20 @@ export class SessionRuntimeIndex {
|
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
hasRunningTurn(state) {
|
|
130
|
-
return state.
|
|
135
|
+
return state.activeResponseIds.size > 0;
|
|
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 Omnigent'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();
|
|
131
150
|
}
|
|
132
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.24",
|
|
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/
|
|
34
|
-
"@rynx-ai/
|
|
30
|
+
"@rynx-ai/plugin-sdk": "0.1.11-beta.24",
|
|
31
|
+
"@rynx-ai/protocol": "0.1.11-beta.24",
|
|
32
|
+
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.24",
|
|
33
|
+
"@rynx-ai/core": "0.1.11-beta.24",
|
|
34
|
+
"@rynx-ai/runtime": "0.1.11-beta.24"
|
|
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.24"
|
|
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/",
|