@rynx-ai/daemon 0.1.9 → 0.1.10-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.
Files changed (50) hide show
  1. package/dist/app-browser-host-supervisor.d.ts +97 -0
  2. package/dist/app-browser-host-supervisor.js +529 -0
  3. package/dist/browser-artifact-management.d.ts +20 -0
  4. package/dist/browser-artifact-management.js +61 -0
  5. package/dist/chrome-for-testing-store.js +1 -1
  6. package/dist/chrome-inspection-gateway.d.ts +58 -0
  7. package/dist/chrome-inspection-gateway.js +806 -0
  8. package/dist/chrome-inspection-manager.d.ts +94 -0
  9. package/dist/chrome-inspection-manager.js +573 -0
  10. package/dist/cli.js +13 -2
  11. package/dist/control-client.d.ts +9 -0
  12. package/dist/control-client.js +63 -0
  13. package/dist/daemon-build-status.d.ts +8 -0
  14. package/dist/daemon-build-status.js +18 -0
  15. package/dist/daemon-server.d.ts +19 -2
  16. package/dist/daemon-server.js +615 -59
  17. package/dist/db.js +56 -0
  18. package/dist/desktop-browser-host-client.d.ts +2 -1
  19. package/dist/desktop-browser-host-client.js +3 -1
  20. package/dist/direct-runtime-authenticator.js +11 -6
  21. package/dist/headless-browser-host.js +18 -1
  22. package/dist/index-daemon.js +28 -1
  23. package/dist/maintenance-management.d.ts +11 -0
  24. package/dist/maintenance-management.js +13 -0
  25. package/dist/plugin-installer.d.ts +35 -0
  26. package/dist/plugin-installer.js +195 -94
  27. package/dist/plugin-management-service.d.ts +58 -0
  28. package/dist/plugin-management-service.js +240 -0
  29. package/dist/plugin-package.d.ts +26 -3
  30. package/dist/plugin-package.js +235 -56
  31. package/dist/pm2.js +37 -5
  32. package/dist/remote-runtime-access-store.d.ts +1 -0
  33. package/dist/remote-runtime-access-store.js +7 -0
  34. package/dist/remote-runtime-admin.d.ts +3 -0
  35. package/dist/remote-runtime-admin.js +3 -0
  36. package/dist/remote-runtime-connection-manager.d.ts +12 -1
  37. package/dist/remote-runtime-connection-manager.js +170 -4
  38. package/dist/remote-runtime-target-control.d.ts +5 -1
  39. package/dist/remote-runtime-target-control.js +62 -7
  40. package/dist/remote-runtime-target-store.d.ts +4 -1
  41. package/dist/remote-runtime-target-store.js +21 -2
  42. package/dist/session-log-store.js +29 -0
  43. package/dist/session-meta-store.js +3 -1
  44. package/dist/session-pending-message-store.d.ts +2 -0
  45. package/dist/session-pending-message-store.js +41 -0
  46. package/dist/session-resource-store.d.ts +32 -0
  47. package/dist/session-resource-store.js +700 -0
  48. package/dist/setup.d.ts +16 -0
  49. package/dist/setup.js +136 -2
  50. package/package.json +14 -9
@@ -1,9 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { RUNTIME_BROWSER_INSPECT_SEMANTIC_CAPABILITY, } from "@rynx-ai/protocol/runtime-browser-inspect";
2
3
  import { RUNTIME_BROWSER_SURFACE_SEMANTIC_CAPABILITY, } from "@rynx-ai/protocol/runtime-browser-surface";
3
4
  import { RUNTIME_EMULATOR_SURFACE_SEMANTIC_CAPABILITY, } from "@rynx-ai/protocol/runtime-emulator-surface";
4
5
  import { assertRemoteRuntimeRpcMethodSupported, assertRemoteRuntimeSessionEventsSupported, assertRemoteRuntimeSessionTerminalSupported, parseRemoteRuntimeRpcRequest, REMOTE_RUNTIME_RPC_METHOD_METADATA, } from "@rynx-ai/protocol/remote-runtime-rpc";
5
6
  import { REMOTE_RUNTIME_CORE_PROTOCOL, } from "@rynx-ai/protocol/remote-runtime";
6
- import { connectDirectRuntime, openDirectRuntimeBrowserSurface, DirectRuntimeCallTransportError, DirectRuntimeClientCapacityError, DirectRuntimeClientError, DirectRuntimeRpcError, DirectRuntimeSessionEventsError, DirectRuntimeSessionTerminalError, } from "@rynx-ai/remote-runtime-client";
7
+ import { connectDirectRuntime, openDirectRuntimeBrowserInspect, openDirectRuntimeBrowserSurface, DirectRuntimeCallTransportError, DirectRuntimeClientCapacityError, DirectRuntimeClientError, DirectRuntimeRpcError, DirectRuntimeSessionEventsError, DirectRuntimeSessionTerminalError, } from "@rynx-ai/remote-runtime-client";
7
8
  import { SessionTerminalOpenError } from "@rynx-ai/server";
8
9
  const LOCAL_SELECTOR = "local";
9
10
  const TARGET_SELECTOR_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,255}$/;
@@ -41,6 +42,8 @@ export class DaemonRuntimeConnectionManager {
41
42
  maxIdleConnections;
42
43
  slots = new Map();
43
44
  generations = new Map();
45
+ /** Independent inspect sockets must outlive shared control-channel eviction. */
46
+ browserInspectClosers = new Map();
44
47
  localStatusSnapshot;
45
48
  closed = false;
46
49
  managerClosePromise;
@@ -48,6 +51,7 @@ export class DaemonRuntimeConnectionManager {
48
51
  this.options = options;
49
52
  this.client = options.client ?? {
50
53
  connect: (credential) => connectDirectRuntime(credential),
54
+ openBrowserInspect: (credential, target, inspectOptions) => openDirectRuntimeBrowserInspect(credential, target, inspectOptions),
51
55
  openBrowserSurface: (credential, frame, surfaceOptions) => openDirectRuntimeBrowserSurface(credential, frame, surfaceOptions),
52
56
  };
53
57
  this.idleTtlMs = nonNegativeInteger(options.idleTtlMs, DEFAULT_IDLE_TTL_MS, "idleTtlMs");
@@ -89,11 +93,12 @@ export class DaemonRuntimeConnectionManager {
89
93
  }
90
94
  this.bumpGeneration(daemonId);
91
95
  const slot = this.slots.get(daemonId);
96
+ const closingInspects = this.closeBrowserInspects(daemonId);
92
97
  if (!slot)
93
- return Promise.resolve();
98
+ return closingInspects;
94
99
  this.slots.delete(daemonId);
95
100
  this.cancelIdleTimer(slot);
96
- return this.closeSlot(slot);
101
+ return closeAll([this.closeSlot(slot), closingInspects]);
97
102
  }
98
103
  close() {
99
104
  if (this.managerClosePromise)
@@ -105,7 +110,12 @@ export class DaemonRuntimeConnectionManager {
105
110
  this.bumpGeneration(slot.daemonId);
106
111
  this.cancelIdleTimer(slot);
107
112
  }
108
- this.managerClosePromise = closeAll(slots.map((slot) => this.closeSlot(slot)));
113
+ const inspectClosures = [...this.browserInspectClosers.keys()]
114
+ .map((daemonId) => this.closeBrowserInspects(daemonId));
115
+ this.managerClosePromise = closeAll([
116
+ ...slots.map((slot) => this.closeSlot(slot)),
117
+ ...inspectClosures,
118
+ ]);
109
119
  return this.managerClosePromise;
110
120
  }
111
121
  localLease() {
@@ -226,6 +236,9 @@ export class DaemonRuntimeConnectionManager {
226
236
  throw error;
227
237
  }
228
238
  },
239
+ openBrowserInspect: async () => {
240
+ throw managerError("incompatible", "Local Runtime Browser Inspect is not available through the Runtime connection lease", undefined, "not_started");
241
+ },
229
242
  openEmulatorSurface: async (sessionId, bindingId, surfaceOptions = {}) => {
230
243
  if (released)
231
244
  throw managerError("closed", "Runtime connection lease was released");
@@ -487,6 +500,20 @@ export class DaemonRuntimeConnectionManager {
487
500
  }
488
501
  return await this.openDedicatedDirectBrowserSurface(slot, frame, surfaceOptions);
489
502
  },
503
+ openBrowserInspect: async (target, inspectOptions = {}) => {
504
+ if (released)
505
+ throw managerError("closed", "Runtime connection lease was released");
506
+ this.assertOpen();
507
+ this.assertSlotCurrent(slot);
508
+ throwIfAborted(inspectOptions.signal);
509
+ const status = slot.status;
510
+ if (!status)
511
+ throw managerError("closed", "Runtime status is not available");
512
+ if (!status.semanticCapabilities.includes(RUNTIME_BROWSER_INSPECT_SEMANTIC_CAPABILITY)) {
513
+ throw managerError("incompatible", "Remote Runtime Browser Inspect is unavailable", undefined, "not_started");
514
+ }
515
+ return await this.openDedicatedDirectBrowserInspect(slot, target, inspectOptions);
516
+ },
490
517
  openEmulatorSurface: async (sessionId, bindingId, surfaceOptions = {}) => {
491
518
  if (released)
492
519
  throw managerError("closed", "Runtime connection lease was released");
@@ -701,6 +728,71 @@ export class DaemonRuntimeConnectionManager {
701
728
  resolveOpeningOperation();
702
729
  }
703
730
  }
731
+ async openDedicatedDirectBrowserInspect(slot, target, options) {
732
+ const timeoutMs = parseCallTimeout(options.timeoutMs);
733
+ let persisted;
734
+ try {
735
+ persisted = await this.options.credentials.load(slot.target.credentialRef);
736
+ }
737
+ catch (error) {
738
+ throw managerError("authentication_failed", "Runtime target credential is unavailable", error);
739
+ }
740
+ if (persisted.daemonId !== slot.target.daemonId ||
741
+ persisted.identityPublicKey !== slot.target.identityPublicKey) {
742
+ throw managerError("identity_mismatch", "Runtime target metadata does not match its protected credential");
743
+ }
744
+ this.assertSlotCurrent(slot);
745
+ throwIfAborted(options.signal);
746
+ const credential = {
747
+ ...persisted,
748
+ directEndpoint: slot.target.directRoute.endpoint,
749
+ };
750
+ const openBrowserInspect = this.client.openBrowserInspect
751
+ ?? ((inspectCredential, inspectTarget, inspectOptions) => openDirectRuntimeBrowserInspect(inspectCredential, inspectTarget, inspectOptions));
752
+ const signal = combinedSignal(options.signal, slot.dedicatedAbortController.signal);
753
+ let resolveOpeningOperation;
754
+ const openingOperation = new Promise((resolve) => {
755
+ resolveOpeningOperation = resolve;
756
+ });
757
+ slot.pendingDedicatedOpenings.add(openingOperation);
758
+ let resolvedConnection;
759
+ let opening;
760
+ try {
761
+ throwIfAborted(signal);
762
+ opening = openBrowserInspect(credential, target, {
763
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
764
+ ...(signal ? { signal } : {}),
765
+ }).then((opened) => {
766
+ resolvedConnection = opened;
767
+ return opened;
768
+ });
769
+ const connection = await waitFor(opening, signal);
770
+ this.assertSlotCurrent(slot);
771
+ throwIfAborted(signal);
772
+ slot.lastUsedAt = Date.now();
773
+ return this.trackDedicatedBrowserInspect(slot, connection, options.signal);
774
+ }
775
+ catch (error) {
776
+ if (resolvedConnection) {
777
+ await resolvedConnection.close().catch(() => undefined);
778
+ }
779
+ else if (opening) {
780
+ const closeLate = opening.then((opened) => opened.close()).catch(() => undefined);
781
+ if (slot.dedicatedAbortController.signal.aborted)
782
+ await closeLate;
783
+ else
784
+ void closeLate;
785
+ }
786
+ if (slot.dedicatedAbortController.signal.aborted && isAbort(error)) {
787
+ throw managerError("closed", "Runtime connection was invalidated while opening Browser Inspect");
788
+ }
789
+ throw error;
790
+ }
791
+ finally {
792
+ slot.pendingDedicatedOpenings.delete(openingOperation);
793
+ resolveOpeningOperation();
794
+ }
795
+ }
704
796
  async openDedicatedDirectEmulatorSurface(slot, sessionId, bindingId, options) {
705
797
  const timeoutMs = parseCallTimeout(options.timeoutMs);
706
798
  let persisted;
@@ -934,6 +1026,80 @@ export class DaemonRuntimeConnectionManager {
934
1026
  },
935
1027
  };
936
1028
  }
1029
+ trackDedicatedBrowserInspect(slot, connection, signal) {
1030
+ let close;
1031
+ let finished = false;
1032
+ const onAbort = () => {
1033
+ void close().catch(() => undefined);
1034
+ };
1035
+ close = this.registerBrowserInspectCloser(slot.daemonId, () => {
1036
+ finished = true;
1037
+ signal?.removeEventListener("abort", onAbort);
1038
+ return connection.close();
1039
+ }, connection.closed.then(() => {
1040
+ finished = true;
1041
+ signal?.removeEventListener("abort", onAbort);
1042
+ }));
1043
+ if (signal?.aborted)
1044
+ onAbort();
1045
+ else
1046
+ signal?.addEventListener("abort", onAbort, { once: true });
1047
+ return {
1048
+ daemonInstanceId: connection.daemonInstanceId,
1049
+ send: async (message) => {
1050
+ if (finished) {
1051
+ throw managerError("closed", "Remote Browser Inspect connection is closed");
1052
+ }
1053
+ throwIfAborted(signal);
1054
+ await connection.send(message);
1055
+ },
1056
+ close,
1057
+ [Symbol.asyncIterator]: () => {
1058
+ const iterator = connection[Symbol.asyncIterator]();
1059
+ return {
1060
+ next: async () => {
1061
+ if (finished)
1062
+ return { done: true, value: undefined };
1063
+ throwIfAborted(signal);
1064
+ return await iterator.next();
1065
+ },
1066
+ return: async () => {
1067
+ await close();
1068
+ return { done: true, value: undefined };
1069
+ },
1070
+ };
1071
+ },
1072
+ };
1073
+ }
1074
+ registerBrowserInspectCloser(daemonId, closeResource, naturallyClosed) {
1075
+ let closers = this.browserInspectClosers.get(daemonId);
1076
+ if (!closers) {
1077
+ closers = new Set();
1078
+ this.browserInspectClosers.set(daemonId, closers);
1079
+ }
1080
+ let closePromise;
1081
+ const forget = () => {
1082
+ closers.delete(close);
1083
+ if (closers.size === 0)
1084
+ this.browserInspectClosers.delete(daemonId);
1085
+ };
1086
+ const close = () => {
1087
+ closePromise ??= Promise.resolve()
1088
+ .then(closeResource)
1089
+ .finally(forget);
1090
+ return closePromise;
1091
+ };
1092
+ closers.add(close);
1093
+ void naturallyClosed.then(forget, forget);
1094
+ return close;
1095
+ }
1096
+ closeBrowserInspects(daemonId) {
1097
+ const closers = this.browserInspectClosers.get(daemonId);
1098
+ if (!closers)
1099
+ return Promise.resolve();
1100
+ this.browserInspectClosers.delete(daemonId);
1101
+ return closeAll([...closers].map((close) => close()));
1102
+ }
937
1103
  trackDedicatedEmulatorSurface(slot, connection, surface) {
938
1104
  const close = this.registerDedicatedCloser(slot, async () => {
939
1105
  try {
@@ -5,7 +5,7 @@ import { type DaemonRuntimeHost, type RuntimeTargetControlHost, type RuntimeTarg
5
5
  import { DaemonRuntimeConnectionManager } from "./remote-runtime-connection-manager.js";
6
6
  import { FileRemoteRuntimeCredentialStore } from "./remote-runtime-credential-store.js";
7
7
  import { RemoteRuntimeTargetStore } from "./remote-runtime-target-store.js";
8
- export type RuntimeTargetControlErrorCode = "invalid_offer" | "invalid_target" | "not_found" | "conflict" | "tls_error" | "identity_mismatch" | "incompatible" | "authentication_failed" | "protocol_error" | "deadline_exceeded" | "outcome_unknown" | "unreachable";
8
+ export type RuntimeTargetControlErrorCode = "invalid_offer" | "offer_expired" | "offer_rejected" | "invalid_target" | "not_found" | "conflict" | "tls_error" | "identity_mismatch" | "incompatible" | "authentication_failed" | "protocol_error" | "deadline_exceeded" | "outcome_unknown" | "unreachable";
9
9
  export declare class RuntimeTargetControlError extends Error {
10
10
  readonly code: RuntimeTargetControlErrorCode;
11
11
  constructor(code: RuntimeTargetControlErrorCode, message: string, options?: ErrorOptions);
@@ -13,6 +13,8 @@ export declare class RuntimeTargetControlError extends Error {
13
13
  interface PairInput {
14
14
  offer: unknown;
15
15
  displayName?: string;
16
+ replaceExisting?: boolean;
17
+ expectedDaemonId?: string;
16
18
  }
17
19
  interface RuntimeTargetClientAdapter {
18
20
  pair(offer: PairingOffer, options: PairDirectRuntimeOptions): ReturnType<typeof pairDirectRuntime>;
@@ -33,7 +35,9 @@ export declare class DaemonRuntimeTargetControl implements RuntimeTargetControlH
33
35
  status: DaemonStatus;
34
36
  }>;
35
37
  private discardExpiredPending;
38
+ private removePendingEnrollment;
36
39
  private resumePendingEnrollment;
40
+ private cleanupReplacedTarget;
37
41
  private probe;
38
42
  test(selector: string): Promise<{
39
43
  target: RuntimeTargetView;
@@ -51,15 +51,25 @@ export class DaemonRuntimeTargetControl {
51
51
  if (offer.daemonId === this.localRuntime.status().daemonId) {
52
52
  throw controlError("conflict", "cannot pair the local daemon as a remote target");
53
53
  }
54
+ if (input.replaceExisting
55
+ ? input.expectedDaemonId !== offer.daemonId
56
+ : input.expectedDaemonId !== undefined) {
57
+ throw controlError("invalid_target", "the fresh pairing link does not match the Runtime selected for reauthorization");
58
+ }
54
59
  // Validate all client-local metadata before any network enrollment.
55
- const targetName = displayName(input.displayName, offer.daemonId);
56
60
  const existing = this.targets.get(offer.daemonId);
57
61
  if (existing) {
58
62
  if (existing.identityPublicKey !== offer.identityPublicKey) {
59
63
  throw controlError("identity_mismatch", "a target with this daemonId has a different pinned identity key");
60
64
  }
61
- throw controlError("conflict", "this Runtime target is already paired");
65
+ if (!input.replaceExisting) {
66
+ throw controlError("conflict", "this Runtime target is already paired");
67
+ }
68
+ }
69
+ else if (input.replaceExisting) {
70
+ throw controlError("not_found", "the Runtime target to reauthorize was not found");
62
71
  }
72
+ const targetName = displayName(input.displayName ?? (input.replaceExisting ? existing?.displayName : undefined), offer.daemonId);
63
73
  const fingerprint = pairingOfferFingerprint(offer);
64
74
  let pending = this.targets.getPendingEnrollment(offer.claimId);
65
75
  const otherPending = this.targets.getPendingEnrollmentForDaemon(offer.daemonId);
@@ -70,7 +80,7 @@ export class DaemonRuntimeTargetControl {
70
80
  if (pending)
71
81
  assertPendingOffer(pending, offer, fingerprint);
72
82
  if (!pending && parsed.expired) {
73
- throw controlError("invalid_offer", "pairing offer has expired");
83
+ throw controlError("offer_expired", "pairing offer has expired");
74
84
  }
75
85
  if (!pending) {
76
86
  const identity = generateDirectRuntimeClientIdentity();
@@ -93,7 +103,7 @@ export class DaemonRuntimeTargetControl {
93
103
  throw persistenceError("failed to prepare the Runtime enrollment", error);
94
104
  }
95
105
  }
96
- return await this.resumePendingEnrollment(pending, offer, targetName, parsed.expired);
106
+ return await this.resumePendingEnrollment(pending, offer, targetName, parsed.expired, input.replaceExisting ? existing : undefined);
97
107
  }
98
108
  finally {
99
109
  this.pairing = false;
@@ -103,15 +113,18 @@ export class DaemonRuntimeTargetControl {
103
113
  if (Date.parse(pending.expiresAt) > Date.now()) {
104
114
  throw controlError("conflict", "a different unexpired enrollment is already pending for this Runtime");
105
115
  }
116
+ await this.removePendingEnrollment(pending, "failed to discard an expired Runtime enrollment");
117
+ }
118
+ async removePendingEnrollment(pending, failureMessage) {
106
119
  try {
107
120
  await this.credentials.remove(pending.credentialRef);
108
121
  this.targets.removePendingEnrollment(pending.claimId);
109
122
  }
110
123
  catch (error) {
111
- throw persistenceError("failed to discard an expired Runtime enrollment", error);
124
+ throw persistenceError(failureMessage, error);
112
125
  }
113
126
  }
114
- async resumePendingEnrollment(pending, offer, targetName, offerExpired) {
127
+ async resumePendingEnrollment(pending, offer, targetName, offerExpired, replacedTarget) {
115
128
  let enrollment;
116
129
  try {
117
130
  enrollment = await this.credentials.loadEnrollment(pending.credentialRef);
@@ -131,7 +144,11 @@ export class DaemonRuntimeTargetControl {
131
144
  });
132
145
  }
133
146
  catch (error) {
134
- throw normalizeClientError(error);
147
+ const normalized = normalizePairingClientError(error);
148
+ if (normalized.code === "offer_rejected") {
149
+ await this.removePendingEnrollment(pending, "failed to discard the rejected Runtime enrollment");
150
+ }
151
+ throw normalized;
135
152
  }
136
153
  assertPairResult(paired, offer, enrollment.credential);
137
154
  accepted = secretCredentialFrom(paired.credential);
@@ -148,6 +165,9 @@ export class DaemonRuntimeTargetControl {
148
165
  assertCredentialTarget(accepted, pending.daemonId, pending.identityPublicKey);
149
166
  status = await this.probe(directCredentialForOffer(accepted, offer));
150
167
  }
168
+ const invalidation = replacedTarget
169
+ ? this.connections.invalidate(replacedTarget.daemonId)
170
+ : undefined;
151
171
  let target;
152
172
  try {
153
173
  target = this.targets.commitPendingEnrollment(pending.claimId, {
@@ -156,13 +176,42 @@ export class DaemonRuntimeTargetControl {
156
176
  identityPublicKey: offer.identityPublicKey,
157
177
  credentialRef: pending.credentialRef,
158
178
  directEndpoint: offer.directEndpoint,
179
+ }, {
180
+ replaceExisting: replacedTarget !== undefined,
159
181
  });
160
182
  }
161
183
  catch (error) {
184
+ await invalidation?.catch(() => undefined);
162
185
  throw persistenceError("failed to persist the paired Runtime target", error);
163
186
  }
187
+ if (replacedTarget) {
188
+ await this.cleanupReplacedTarget(replacedTarget, invalidation);
189
+ }
164
190
  return { target: targetView(target), status };
165
191
  }
192
+ async cleanupReplacedTarget(replacedTarget, invalidation) {
193
+ const failures = [];
194
+ try {
195
+ await invalidation;
196
+ }
197
+ catch (error) {
198
+ failures.push(error);
199
+ }
200
+ try {
201
+ await this.credentials.remove(replacedTarget.credentialRef);
202
+ }
203
+ catch (error) {
204
+ failures.push(error);
205
+ }
206
+ if (failures.length > 0) {
207
+ console.warn(JSON.stringify({
208
+ level: "warn",
209
+ type: "remote-runtime-reauthorization-cleanup",
210
+ daemonId: replacedTarget.daemonId,
211
+ errors: failures.map((error) => error instanceof Error ? error.message : String(error)),
212
+ }));
213
+ }
214
+ }
166
215
  async probe(credential) {
167
216
  let connection;
168
217
  try {
@@ -408,6 +457,12 @@ function normalizeClientError(error) {
408
457
  }
409
458
  return controlError("unreachable", "remote Runtime connection failed", error);
410
459
  }
460
+ function normalizePairingClientError(error) {
461
+ if (error instanceof DirectRuntimeClientError && error.code === "authentication_failed") {
462
+ return controlError("offer_rejected", "the remote Host rejected this pairing offer; it may already have been used, expired, or revoked", error);
463
+ }
464
+ return normalizeClientError(error);
465
+ }
411
466
  function normalizeConnectionManagerError(error) {
412
467
  const code = error.code === "closed" || error.code === "cancelled" ? "unreachable" : error.code;
413
468
  return controlError(code, error.message, error);
@@ -34,6 +34,9 @@ export interface AddRemoteRuntimePendingEnrollmentInput {
34
34
  offerFingerprint: string;
35
35
  expiresAt: string;
36
36
  }
37
+ export interface CommitRemoteRuntimePendingEnrollmentOptions {
38
+ replaceExisting?: boolean;
39
+ }
37
40
  export declare class RemoteRuntimeTargetStore {
38
41
  private readonly connection;
39
42
  private readonly clock;
@@ -43,7 +46,7 @@ export declare class RemoteRuntimeTargetStore {
43
46
  getPendingEnrollment(claimIdInput: string): RemoteRuntimePendingEnrollment | undefined;
44
47
  getPendingEnrollmentForDaemon(daemonIdInput: string): RemoteRuntimePendingEnrollment | undefined;
45
48
  removePendingEnrollment(claimIdInput: string): RemoteRuntimePendingEnrollment | undefined;
46
- commitPendingEnrollment(claimIdInput: string, input: AddRemoteRuntimeTargetInput): RemoteRuntimeTarget;
49
+ commitPendingEnrollment(claimIdInput: string, input: AddRemoteRuntimeTargetInput, options?: CommitRemoteRuntimePendingEnrollmentOptions): RemoteRuntimeTarget;
47
50
  get(daemonIdInput: string): RemoteRuntimeTarget | undefined;
48
51
  list(): RemoteRuntimeTarget[];
49
52
  remove(daemonIdInput: string): RemoteRuntimeTarget | undefined;
@@ -78,7 +78,7 @@ export class RemoteRuntimeTargetStore {
78
78
  });
79
79
  return remove.immediate();
80
80
  }
81
- commitPendingEnrollment(claimIdInput, input) {
81
+ commitPendingEnrollment(claimIdInput, input, options = {}) {
82
82
  const claimId = validId(claimIdInput, "claimId");
83
83
  const normalized = normalizeTargetInput(input);
84
84
  const now = validNow(this.clock()).toISOString();
@@ -91,7 +91,12 @@ export class RemoteRuntimeTargetStore {
91
91
  pending.credentialRef !== normalized.credentialRef) {
92
92
  throw new Error("pending Runtime enrollment does not match the accepted credential");
93
93
  }
94
- insertTarget(this.connection, normalized, now);
94
+ if (options.replaceExisting) {
95
+ replaceTarget(this.connection, normalized, now);
96
+ }
97
+ else {
98
+ insertTarget(this.connection, normalized, now);
99
+ }
95
100
  const deleted = this.connection.prepare("DELETE FROM runtime_pending_enrollments WHERE claim_id = ?").run(claimId);
96
101
  if (deleted.changes !== 1) {
97
102
  throw new Error("pending Runtime enrollment changed concurrently");
@@ -193,6 +198,20 @@ function insertTarget(connection, input, now) {
193
198
  (daemon_id, endpoint, updated_at)
194
199
  VALUES (?, ?, ?)`).run(input.daemonId, input.directEndpoint, now);
195
200
  }
201
+ function replaceTarget(connection, input, now) {
202
+ const updatedTarget = connection.prepare(`UPDATE runtime_targets
203
+ SET display_name = ?, credential_ref = ?, updated_at = ?
204
+ WHERE daemon_id = ? AND identity_public_key = ?`).run(input.displayName, input.credentialRef, now, input.daemonId, input.identityPublicKey);
205
+ if (updatedTarget.changes !== 1) {
206
+ throw new RemoteRuntimeTargetConflictError("the Runtime target to reauthorize is missing or has a different pinned identity");
207
+ }
208
+ const updatedRoute = connection.prepare(`UPDATE runtime_direct_routes
209
+ SET endpoint = ?, updated_at = ?
210
+ WHERE daemon_id = ?`).run(input.directEndpoint, now, input.daemonId);
211
+ if (updatedRoute.changes !== 1) {
212
+ throw new Error("the Runtime target route to reauthorize is missing");
213
+ }
214
+ }
196
215
  function targetFromRow(row) {
197
216
  const input = normalizeTargetInput({
198
217
  daemonId: row.daemon_id,
@@ -37,6 +37,35 @@ export class SqliteSessionLogStore {
37
37
  created_by: stored.createdBy ?? null,
38
38
  created_at: stored.createdAt,
39
39
  });
40
+ if (stored.type === "message" && stored.data.role === "user") {
41
+ const resourceIds = stored.data.content.flatMap((part) => part.type === "input_image" ? [part.resourceId] : []);
42
+ const operationIds = new Set();
43
+ for (const resourceId of resourceIds) {
44
+ const resource = conn.prepare(`SELECT state, message_item_id
45
+ FROM session_resources
46
+ WHERE id = ? AND session_id = ?`).get(resourceId, sessionId);
47
+ if (!resource ||
48
+ resource.state === "uploading" ||
49
+ (resource.message_item_id && resource.message_item_id !== stored.id)) {
50
+ throw new Error(`Session image resource ${resourceId} is not committable`);
51
+ }
52
+ conn.prepare(`UPDATE session_resources
53
+ SET state = 'committed', message_item_id = ?,
54
+ committed_at = COALESCE(committed_at, ?)
55
+ WHERE id = ? AND session_id = ?`).run(stored.id, stored.createdAt, resourceId, sessionId);
56
+ const operation = conn.prepare(`SELECT client_message_id
57
+ FROM session_message_operation_resources
58
+ WHERE session_id = ? AND resource_id = ?`).get(sessionId, resourceId);
59
+ if (operation)
60
+ operationIds.add(operation.client_message_id);
61
+ }
62
+ for (const clientMessageId of operationIds) {
63
+ conn.prepare(`UPDATE session_message_operations
64
+ SET state = 'mirrored', error = NULL, updated_at = ?
65
+ WHERE session_id = ? AND client_message_id = ?
66
+ AND state IN ('injecting', 'injected', 'outcome_unknown')`).run(stored.createdAt, sessionId, clientMessageId);
67
+ }
68
+ }
40
69
  return stored;
41
70
  });
42
71
  });
@@ -94,7 +94,9 @@ export const sessionRegistry = {
94
94
  function rowToMeta(row) {
95
95
  return {
96
96
  id: row.id,
97
- provider: row.provider === "codex" || row.provider === "claude" ? row.provider : undefined,
97
+ provider: row.provider === "codex" || row.provider === "traex" || row.provider === "claude"
98
+ ? row.provider
99
+ : undefined,
98
100
  agent: row.agent ?? undefined,
99
101
  config: row.config ? JSON.parse(row.config) : undefined,
100
102
  model: row.model ?? undefined,
@@ -0,0 +1,2 @@
1
+ import type { MachineSessionPendingMessagePort } from "@rynx-ai/server";
2
+ export declare const sessionPendingMessageStore: MachineSessionPendingMessagePort;
@@ -0,0 +1,41 @@
1
+ import { db } from "./db.js";
2
+ export const sessionPendingMessageStore = {
3
+ put(message) {
4
+ const result = db()
5
+ .prepare(`INSERT OR IGNORE INTO session_pending_messages
6
+ (session_id, message, state, created_at)
7
+ VALUES (?, ?, 'queued', ?)`)
8
+ .run(message.sessionId, message.message, message.createdAt);
9
+ return result.changes === 1;
10
+ },
11
+ get(sessionId) {
12
+ const row = db()
13
+ .prepare(`SELECT session_id, message, state, created_at
14
+ FROM session_pending_messages WHERE session_id = ?`)
15
+ .get(sessionId);
16
+ return row ? fromRow(row) : undefined;
17
+ },
18
+ list() {
19
+ return db()
20
+ .prepare(`SELECT session_id, message, state, created_at
21
+ FROM session_pending_messages ORDER BY created_at`)
22
+ .all().map(fromRow);
23
+ },
24
+ markOutcomeUnknown(sessionId) {
25
+ db()
26
+ .prepare(`UPDATE session_pending_messages SET state = 'outcome_unknown'
27
+ WHERE session_id = ? AND state = 'queued'`)
28
+ .run(sessionId);
29
+ },
30
+ delete(sessionId) {
31
+ db().prepare("DELETE FROM session_pending_messages WHERE session_id = ?").run(sessionId);
32
+ },
33
+ };
34
+ function fromRow(row) {
35
+ return {
36
+ sessionId: row.session_id,
37
+ message: row.message,
38
+ state: row.state,
39
+ createdAt: row.created_at,
40
+ };
41
+ }
@@ -0,0 +1,32 @@
1
+ import { type UserContentPart } from "@rynx-ai/core";
2
+ import { type RemoteRuntimeSessionResourceDeleteParams, type RemoteRuntimeSessionResourceDeleteResult, type RemoteRuntimeSessionResourceGetParams, type RemoteRuntimeSessionResourceGetResult, type RemoteRuntimeSessionResourcePolicyResult, type RemoteRuntimeSessionResourceReadParams, type RemoteRuntimeSessionResourceReadResult, type RemoteRuntimeSessionResourceUploadBeginParams, type RemoteRuntimeSessionResourceUploadBeginResult, type RemoteRuntimeSessionResourceUploadChunkParams, type RemoteRuntimeSessionResourceUploadChunkResult, type RemoteRuntimeSessionResourceUploadCommitParams, type RemoteRuntimeSessionResourceUploadCommitResult } from "@rynx-ai/protocol/remote-runtime-rpc";
3
+ import { type MachineSessionPreparedInput, type MachineSessionResourcePort } from "@rynx-ai/server";
4
+ export declare class SqliteSessionResourceStore implements MachineSessionResourcePort {
5
+ private readonly root;
6
+ constructor(root?: string);
7
+ policy(_sessionId: string): RemoteRuntimeSessionResourcePolicyResult;
8
+ beginUpload(input: RemoteRuntimeSessionResourceUploadBeginParams): Promise<RemoteRuntimeSessionResourceUploadBeginResult>;
9
+ writeUploadChunk(input: RemoteRuntimeSessionResourceUploadChunkParams): Promise<RemoteRuntimeSessionResourceUploadChunkResult>;
10
+ commitUpload(input: RemoteRuntimeSessionResourceUploadCommitParams): Promise<RemoteRuntimeSessionResourceUploadCommitResult>;
11
+ getResource(input: RemoteRuntimeSessionResourceGetParams): Promise<RemoteRuntimeSessionResourceGetResult>;
12
+ readResource(input: RemoteRuntimeSessionResourceReadParams): Promise<RemoteRuntimeSessionResourceReadResult>;
13
+ deleteResource(input: RemoteRuntimeSessionResourceDeleteParams): Promise<RemoteRuntimeSessionResourceDeleteResult>;
14
+ prepareInput(sessionId: string, content: UserContentPart[], clientMessageId?: string): Promise<MachineSessionPreparedInput>;
15
+ markMessageInjecting(sessionId: string, clientMessageId: string): void;
16
+ markMessageInjected(sessionId: string, clientMessageId: string): void;
17
+ markMessageOutcomeUnknown(sessionId: string, clientMessageId: string, reason: string): void;
18
+ markMessageFailedNotStarted(sessionId: string, clientMessageId: string, reason: string): void;
19
+ deleteSession(sessionId: string): void;
20
+ private prepareMessageOperation;
21
+ private bindOperationResources;
22
+ private transitionOperation;
23
+ private finalizeUpload;
24
+ private reconcileUploadOffset;
25
+ private rowByUpload;
26
+ private requireUpload;
27
+ private requireResource;
28
+ private sessionDirectory;
29
+ private partialPath;
30
+ private finalPath;
31
+ private sweepExpiredStagedResources;
32
+ }