@rynx-ai/daemon 0.1.11-beta.4 → 0.1.11-beta.41
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/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +24 -18
- package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
- package/bundled-plugins/plugins/lark/dist/runtime.js +6043 -6016
- package/bundled-plugins/plugins/lark/dist/settings.js +6003 -5985
- package/bundled-plugins/plugins/lark/package.json +1 -1
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/index.html +2 -2
- package/dist/agent-file.js +0 -1
- package/dist/browser-artifact-management.js +4 -1
- package/dist/chrome-for-testing-http.d.ts +9 -0
- package/dist/chrome-for-testing-http.js +58 -0
- package/dist/chrome-for-testing-release-resolver.js +13 -6
- package/dist/chrome-for-testing-runtime.js +4 -3
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +99 -17
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +87 -4
- package/dist/control-link-store.js +4 -3
- package/dist/daemon-owner.js +4 -7
- package/dist/daemon-server.js +42 -17
- package/dist/db.d.ts +2 -2
- package/dist/db.js +157 -21
- package/dist/headless-browser-host.js +397 -41
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.js +4 -1
- package/dist/migrations/cleanup-legacy-sessions.js +4 -3
- package/dist/plugin-cli.d.ts +10 -0
- package/dist/plugin-cli.js +19 -2
- package/dist/plugin-host-rpc.d.ts +48 -9
- package/dist/plugin-host-rpc.js +593 -81
- package/dist/plugin-installer.js +3 -2
- package/dist/plugin-management-service.js +0 -2
- package/dist/plugin-marketplace.js +2 -1
- package/dist/plugin-store.js +2 -4
- package/dist/plugin-supervisor.js +0 -4
- package/dist/pm2.d.ts +2 -0
- package/dist/pm2.js +6 -0
- package/dist/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- package/dist/remote-runtime-connection-manager.js +25 -42
- package/dist/remote-runtime-target-store.d.ts +2 -2
- package/dist/remote-runtime-target-store.js +6 -6
- package/dist/session-emulator-binding-store.js +4 -6
- package/dist/session-fork-store.d.ts +1 -0
- package/dist/session-fork-store.js +85 -30
- package/dist/session-launch-operations.js +6 -5
- package/dist/session-log-store.d.ts +1 -0
- package/dist/session-log-store.js +46 -22
- package/dist/session-meta-store.d.ts +5 -0
- package/dist/session-meta-store.js +25 -2
- package/dist/session-portal-grant-store.d.ts +55 -0
- package/dist/session-portal-grant-store.js +160 -0
- package/dist/session-resource-store.d.ts +24 -6
- package/dist/session-resource-store.js +387 -69
- package/dist/session-timeline-projection-store.d.ts +24 -0
- package/dist/session-timeline-projection-store.js +53 -0
- package/dist/setup-service.d.ts +1 -0
- package/dist/setup-service.js +1 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +317 -10
- package/dist/sqlite.d.ts +22 -0
- package/dist/sqlite.js +57 -0
- package/dist/stdio-epipe-guard.d.ts +1 -1
- package/dist/stdio-epipe-guard.js +1 -1
- package/dist/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +17 -12
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
- /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
package/dist/plugin-installer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { MARKETPLACE_ID_PATTERN, pluginCliInvocationLeasePid, readPluginRuntimeLeases, removePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
5
5
|
import { rynxHome } from "@rynx-ai/core";
|
|
6
6
|
import { db } from "./db.js";
|
|
7
|
+
import { transaction } from "./sqlite.js";
|
|
7
8
|
import { isBuiltInPluginMarketplace } from "./plugin-marketplace.js";
|
|
8
9
|
import { detectPluginSource, inspectPluginPackage, materializePluginPackage, } from "./plugin-package.js";
|
|
9
10
|
import { canonicalPluginId, getPlugin, removePlugin, upsertPlugin, } from "./plugin-store.js";
|
|
@@ -117,7 +118,7 @@ export async function preparePluginInstallation(spec, onProgress = () => { }, op
|
|
|
117
118
|
promotedByThisTransaction = true;
|
|
118
119
|
}
|
|
119
120
|
promotedPath = realpathSync(target);
|
|
120
|
-
db()
|
|
121
|
+
transaction(db(), () => {
|
|
121
122
|
const current = getPlugin(canonicalId);
|
|
122
123
|
if (!samePluginGeneration(existing, current)) {
|
|
123
124
|
throw new Error(`plugin "${canonicalId}" changed concurrently; retry`);
|
|
@@ -172,7 +173,7 @@ export async function preparePluginInstallation(spec, onProgress = () => { }, op
|
|
|
172
173
|
}
|
|
173
174
|
export async function uninstallPlugin(identity, onProgress = () => { }, options = {}) {
|
|
174
175
|
let record;
|
|
175
|
-
const removed = db()
|
|
176
|
+
const removed = transaction(db(), () => {
|
|
176
177
|
record = getPlugin(identity);
|
|
177
178
|
if (!record)
|
|
178
179
|
return false;
|
|
@@ -295,7 +295,6 @@ function toManagementItem(record, settingsAvailable = false, updateState = "unkn
|
|
|
295
295
|
enabled: record.enabled,
|
|
296
296
|
runtimeState: record.manifest.runtime ? record.runtimeState : "absent",
|
|
297
297
|
...(record.runtimeError ? { runtimeError: record.runtimeError } : {}),
|
|
298
|
-
capabilities: [...record.manifest.capabilities],
|
|
299
298
|
hasSettingsUi: settingsAvailable,
|
|
300
299
|
updatedAt: record.updatedAt,
|
|
301
300
|
};
|
|
@@ -322,7 +321,6 @@ function toInstallPreparation(token, expiresAtMs, prepared) {
|
|
|
322
321
|
subdirectory: prepared.requestedSource.subdir ?? ".",
|
|
323
322
|
},
|
|
324
323
|
digest: prepared.integrity,
|
|
325
|
-
capabilities: [...prepared.manifest.capabilities],
|
|
326
324
|
hasRuntime: Boolean(prepared.manifest.runtime),
|
|
327
325
|
hasSettingsUi: Boolean(prepared.manifest.settings),
|
|
328
326
|
...(prepared.manifest.runtime ? {
|
|
@@ -3,6 +3,7 @@ import { isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
|
3
3
|
import { MARKETPLACE_ID_PATTERN, parsePluginMarketplace, } from "@rynx-ai/plugin-sdk";
|
|
4
4
|
import { rynxHome } from "@rynx-ai/core";
|
|
5
5
|
import { db } from "./db.js";
|
|
6
|
+
import { transaction } from "./sqlite.js";
|
|
6
7
|
import { detectPluginSource, materializePluginPackage, parsePluginSourceSpec, pluginSourceFingerprint, } from "./plugin-package.js";
|
|
7
8
|
export function listPluginMarketplaces() {
|
|
8
9
|
return db().prepare("SELECT * FROM plugin_marketplaces ORDER BY marketplace_id").all().map(toRecord);
|
|
@@ -27,7 +28,7 @@ export async function addPluginMarketplace(spec, options = {}) {
|
|
|
27
28
|
throw new Error(`built-in marketplace "${marketplaceId}" is managed by Rynx`);
|
|
28
29
|
}
|
|
29
30
|
const now = new Date().toISOString();
|
|
30
|
-
db()
|
|
31
|
+
transaction(db(), () => {
|
|
31
32
|
const binding = db().prepare(`SELECT source_fingerprint AS sourceFingerprint
|
|
32
33
|
FROM plugin_marketplace_bindings
|
|
33
34
|
WHERE marketplace_id = ?`).get(marketplaceId);
|
package/dist/plugin-store.js
CHANGED
|
@@ -57,11 +57,11 @@ export function upsertPlugin(rec) {
|
|
|
57
57
|
db().prepare(`INSERT INTO plugin_installations (
|
|
58
58
|
canonical_id, marketplace_id, plugin_id, source_type, requested_source,
|
|
59
59
|
resolved_revision, source_fingerprint, version, enabled, installed_at,
|
|
60
|
-
updated_at, install_path, manifest, integrity,
|
|
60
|
+
updated_at, install_path, manifest, integrity, runtime_state, runtime_error
|
|
61
61
|
) VALUES (
|
|
62
62
|
@canonicalId, @marketplaceId, @pluginId, @source, @requestedSource,
|
|
63
63
|
@resolvedRevision, @sourceFingerprint, @version, @enabled, @now,
|
|
64
|
-
@now, @installPath, @manifest, @integrity,
|
|
64
|
+
@now, @installPath, @manifest, @integrity, 'stopped', NULL
|
|
65
65
|
)
|
|
66
66
|
ON CONFLICT(canonical_id) DO UPDATE SET
|
|
67
67
|
source_type = excluded.source_type,
|
|
@@ -74,7 +74,6 @@ export function upsertPlugin(rec) {
|
|
|
74
74
|
install_path = excluded.install_path,
|
|
75
75
|
manifest = excluded.manifest,
|
|
76
76
|
integrity = excluded.integrity,
|
|
77
|
-
capabilities = excluded.capabilities,
|
|
78
77
|
runtime_state = 'stopped',
|
|
79
78
|
runtime_error = NULL`).run({
|
|
80
79
|
canonicalId: rec.canonicalId,
|
|
@@ -90,7 +89,6 @@ export function upsertPlugin(rec) {
|
|
|
90
89
|
installPath: rec.installPath,
|
|
91
90
|
manifest: JSON.stringify(rec.manifest),
|
|
92
91
|
integrity: rec.integrity,
|
|
93
|
-
capabilities: JSON.stringify(rec.manifest.capabilities),
|
|
94
92
|
});
|
|
95
93
|
}
|
|
96
94
|
export function setPluginEnabled(identity, enabled) {
|
|
@@ -294,7 +294,6 @@ export class PluginSupervisor {
|
|
|
294
294
|
daemonInstanceId: this.daemonInstanceId,
|
|
295
295
|
productVersion: this.productVersion,
|
|
296
296
|
cliVersion: this.cliVersion,
|
|
297
|
-
capabilities: state.record.manifest.capabilities,
|
|
298
297
|
services: this.services,
|
|
299
298
|
...(state.record.pluginId === "workspace"
|
|
300
299
|
&& isBuiltInPluginMarketplace(state.record.marketplaceId)
|
|
@@ -324,7 +323,6 @@ export class PluginSupervisor {
|
|
|
324
323
|
role: "runtime",
|
|
325
324
|
dataDir,
|
|
326
325
|
cacheDir,
|
|
327
|
-
capabilities: state.record.manifest.capabilities,
|
|
328
326
|
handleHostCall: (method, params, signal) => hostRpc.call(method, params, signal),
|
|
329
327
|
onStderr: (line) => this.log({
|
|
330
328
|
level: "info",
|
|
@@ -411,7 +409,6 @@ export class PluginSupervisor {
|
|
|
411
409
|
daemonInstanceId: this.daemonInstanceId,
|
|
412
410
|
productVersion: this.productVersion,
|
|
413
411
|
cliVersion: this.cliVersion,
|
|
414
|
-
capabilities: state.record.manifest.capabilities,
|
|
415
412
|
services: this.services,
|
|
416
413
|
...(state.record.pluginId === "workspace"
|
|
417
414
|
&& isBuiltInPluginMarketplace(state.record.marketplaceId)
|
|
@@ -440,7 +437,6 @@ export class PluginSupervisor {
|
|
|
440
437
|
role: "settings",
|
|
441
438
|
dataDir,
|
|
442
439
|
cacheDir,
|
|
443
|
-
capabilities: state.record.manifest.capabilities,
|
|
444
440
|
handleHostCall: (method, params, signal) => hostRpc.call(method, params, signal),
|
|
445
441
|
onStderr: (line) => this.log({
|
|
446
442
|
level: "info",
|
package/dist/pm2.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare function startDaemon({ force, quiet, launchEnvironment, }: {
|
|
|
14
14
|
launchEnvironment?: NodeJS.ProcessEnv;
|
|
15
15
|
}): number;
|
|
16
16
|
export declare function isPm2DaemonOnline(): boolean;
|
|
17
|
+
/** Whether `start` must replace or recreate the resident daemon generation. */
|
|
18
|
+
export declare function isDaemonStartRequired(): boolean;
|
|
17
19
|
/**
|
|
18
20
|
* Stop the daemon and terminate its dedicated pm2 supervisor.
|
|
19
21
|
*
|
package/dist/pm2.js
CHANGED
|
@@ -234,6 +234,12 @@ export function startDaemon({ force, quiet = false, launchEnvironment, }) {
|
|
|
234
234
|
export function isPm2DaemonOnline() {
|
|
235
235
|
return describe()?.pm2_env?.status === "online";
|
|
236
236
|
}
|
|
237
|
+
/** Whether `start` must replace or recreate the resident daemon generation. */
|
|
238
|
+
export function isDaemonStartRequired() {
|
|
239
|
+
const existing = describe();
|
|
240
|
+
return existing?.pm2_env?.status !== "online" ||
|
|
241
|
+
residentBuildRestartRequired(existing);
|
|
242
|
+
}
|
|
237
243
|
/**
|
|
238
244
|
* Stop the daemon and terminate its dedicated pm2 supervisor.
|
|
239
245
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type DatabaseSync } from "node:sqlite";
|
|
2
2
|
declare const DEFAULT_AUTHORITIES: readonly ["daemon.full"];
|
|
3
3
|
export type RuntimeAuthority = (typeof DEFAULT_AUTHORITIES)[number];
|
|
4
4
|
export type RemoteRuntimeAccessErrorCode = "invalid_claim" | "claim_expired" | "claim_used" | "grant_not_found" | "grant_revoked" | "grant_identity_mismatch" | "conflict" | "corrupt_state";
|
|
@@ -46,7 +46,7 @@ type Clock = () => Date;
|
|
|
46
46
|
export declare class RemoteRuntimeAccessStore {
|
|
47
47
|
private readonly connection;
|
|
48
48
|
private readonly clock;
|
|
49
|
-
constructor(connection?:
|
|
49
|
+
constructor(connection?: DatabaseSync, clock?: Clock);
|
|
50
50
|
createPairingClaim(input: CreatePairingClaimInput): RuntimePairingClaim;
|
|
51
51
|
redeemPairingClaim(input: RedeemPairingClaimInput): RuntimeClientGrant;
|
|
52
52
|
resolveActiveGrant(input: ResolveActiveGrantInput): RuntimeClientGrant;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, createPublicKey, randomBytes, timingSafeEqual, } from "node:crypto";
|
|
2
2
|
import { db } from "./db.js";
|
|
3
|
+
import { hasSqlitePrimaryCode, SQLITE_CONSTRAINT, transaction } from "./sqlite.js";
|
|
3
4
|
const CLAIM_SECRET_HASH_DOMAIN = "rynx-runtime-pairing-claim-secret-v1\0";
|
|
4
5
|
const DEFAULT_AUTHORITIES = Object.freeze(["daemon.full"]);
|
|
5
6
|
const MIN_TTL_MS = 1_000;
|
|
@@ -35,7 +36,7 @@ export class RemoteRuntimeAccessStore {
|
|
|
35
36
|
const created = this.now();
|
|
36
37
|
const createdAt = created.toISOString();
|
|
37
38
|
const expiresAt = new Date(created.getTime() + ttlMs).toISOString();
|
|
38
|
-
const create = this.connection
|
|
39
|
+
const create = transaction(this.connection, () => {
|
|
39
40
|
this.deleteRetainedExpiredClaims(created);
|
|
40
41
|
this.connection
|
|
41
42
|
.prepare(`INSERT INTO runtime_pairing_claims
|
|
@@ -60,7 +61,7 @@ export class RemoteRuntimeAccessStore {
|
|
|
60
61
|
const clientId = validId(input.clientId, "clientId", "invalid_claim");
|
|
61
62
|
const clientPublicKey = canonicalEd25519PublicKey(input.clientPublicKey, "invalid_claim");
|
|
62
63
|
const requestedLabel = optionalLabel(input.label, "invalid_claim");
|
|
63
|
-
const redeem = this.connection
|
|
64
|
+
const redeem = transaction(this.connection, () => {
|
|
64
65
|
const now = this.now().toISOString();
|
|
65
66
|
const claim = this.connection
|
|
66
67
|
.prepare(`SELECT claim_id, secret_hash, authorities, client_label, created_at, expires_at,
|
|
@@ -124,7 +125,7 @@ export class RemoteRuntimeAccessStore {
|
|
|
124
125
|
const grantId = validId(input.grantId, "grantId", "grant_not_found");
|
|
125
126
|
const clientId = validId(input.clientId, "clientId", "grant_identity_mismatch");
|
|
126
127
|
const clientPublicKey = canonicalEd25519PublicKey(input.clientPublicKey, "grant_identity_mismatch");
|
|
127
|
-
const resolve = this.connection
|
|
128
|
+
const resolve = transaction(this.connection, () => {
|
|
128
129
|
const row = this.connection
|
|
129
130
|
.prepare(`SELECT grant_id, client_id, client_public_key, client_label, authorities,
|
|
130
131
|
created_at, revoked_at, last_used_at
|
|
@@ -180,7 +181,7 @@ export class RemoteRuntimeAccessStore {
|
|
|
180
181
|
revokeClientGrant(grantIdInput) {
|
|
181
182
|
const grantId = validId(grantIdInput, "grantId", "grant_not_found");
|
|
182
183
|
const revokedAt = this.now().toISOString();
|
|
183
|
-
const revoke = this.connection
|
|
184
|
+
const revoke = transaction(this.connection, () => {
|
|
184
185
|
const revoked = this.connection
|
|
185
186
|
.prepare(`UPDATE runtime_client_grants
|
|
186
187
|
SET revoked_at = ?
|
|
@@ -382,11 +383,7 @@ function grantFromRow(row) {
|
|
|
382
383
|
};
|
|
383
384
|
}
|
|
384
385
|
function isSqliteConstraint(error) {
|
|
385
|
-
return (
|
|
386
|
-
error !== null &&
|
|
387
|
-
"code" in error &&
|
|
388
|
-
typeof error.code === "string" &&
|
|
389
|
-
error.code.startsWith("SQLITE_CONSTRAINT"));
|
|
386
|
+
return hasSqlitePrimaryCode(error, SQLITE_CONSTRAINT);
|
|
390
387
|
}
|
|
391
388
|
function accessError(code, message, cause) {
|
|
392
389
|
return new RemoteRuntimeAccessError(code, message, cause === undefined ? undefined : { cause });
|
|
@@ -977,7 +977,22 @@ export class DaemonRuntimeConnectionManager {
|
|
|
977
977
|
return {
|
|
978
978
|
sessionId: terminal.sessionId,
|
|
979
979
|
role: terminal.role,
|
|
980
|
+
seedBytes: terminal.seedBytes,
|
|
981
|
+
...(terminal.dimensions ? { dimensions: terminal.dimensions } : {}),
|
|
980
982
|
closed: terminal.closed,
|
|
983
|
+
onResize: (listener) => terminal.onResize?.(listener),
|
|
984
|
+
readSeed: (offset, maxBytes) => {
|
|
985
|
+
this.assertSlotCurrent(slot);
|
|
986
|
+
return terminal.readSeed(offset, maxBytes);
|
|
987
|
+
},
|
|
988
|
+
start: () => {
|
|
989
|
+
this.assertSlotCurrent(slot);
|
|
990
|
+
return terminal.start();
|
|
991
|
+
},
|
|
992
|
+
read: (offset, maxBytes) => {
|
|
993
|
+
this.assertSlotCurrent(slot);
|
|
994
|
+
return terminal.read(offset, maxBytes);
|
|
995
|
+
},
|
|
981
996
|
write: (data) => {
|
|
982
997
|
this.assertSlotCurrent(slot);
|
|
983
998
|
return terminal.write(data);
|
|
@@ -987,19 +1002,6 @@ export class DaemonRuntimeConnectionManager {
|
|
|
987
1002
|
return terminal.resize(cols, rows);
|
|
988
1003
|
},
|
|
989
1004
|
close,
|
|
990
|
-
[Symbol.asyncIterator]: () => {
|
|
991
|
-
const iterator = terminal[Symbol.asyncIterator]();
|
|
992
|
-
return {
|
|
993
|
-
next: () => {
|
|
994
|
-
this.assertSlotCurrent(slot);
|
|
995
|
-
return iterator.next();
|
|
996
|
-
},
|
|
997
|
-
return: async () => {
|
|
998
|
-
await close();
|
|
999
|
-
return { done: true, value: undefined };
|
|
1000
|
-
},
|
|
1001
|
-
};
|
|
1002
|
-
},
|
|
1003
1005
|
};
|
|
1004
1006
|
}
|
|
1005
1007
|
trackDedicatedBrowserSurface(slot, surface) {
|
|
@@ -1214,44 +1216,25 @@ function dedicatedDirectTerminal(connection, terminal, signal) {
|
|
|
1214
1216
|
onAbort();
|
|
1215
1217
|
else
|
|
1216
1218
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1217
|
-
const closed = terminal.closed
|
|
1218
|
-
.then((frame) => ({
|
|
1219
|
+
const closed = terminal.closed.then((frame) => ({
|
|
1219
1220
|
reason: frame.reason,
|
|
1220
1221
|
...(frame.exitCode === undefined ? {} : { exitCode: frame.exitCode }),
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1224
|
-
void closeConnection().catch(() => undefined);
|
|
1225
|
-
});
|
|
1222
|
+
...(frame.finalOffset === undefined ? {} : { finalOffset: frame.finalOffset }),
|
|
1223
|
+
}));
|
|
1226
1224
|
void closed.catch(() => undefined);
|
|
1227
1225
|
return {
|
|
1228
1226
|
sessionId: terminal.sessionId,
|
|
1229
1227
|
role: terminal.role,
|
|
1228
|
+
seedBytes: terminal.seedBytes,
|
|
1229
|
+
...(terminal.dimensions ? { dimensions: terminal.dimensions } : {}),
|
|
1230
1230
|
closed,
|
|
1231
|
+
onResize: (listener) => terminal.onResize(listener),
|
|
1232
|
+
readSeed: (offset, maxBytes) => terminal.readSeed(offset, maxBytes),
|
|
1233
|
+
start: () => terminal.start(),
|
|
1234
|
+
read: (offset, maxBytes) => terminal.read(offset, maxBytes),
|
|
1231
1235
|
write: (data) => terminal.write(data),
|
|
1232
1236
|
resize: (cols, rows) => terminal.resize(cols, rows),
|
|
1233
1237
|
close,
|
|
1234
|
-
[Symbol.asyncIterator]: () => {
|
|
1235
|
-
const iterator = terminal[Symbol.asyncIterator]();
|
|
1236
|
-
return {
|
|
1237
|
-
next: async () => {
|
|
1238
|
-
try {
|
|
1239
|
-
const result = await iterator.next();
|
|
1240
|
-
if (result.done)
|
|
1241
|
-
await closeConnection().catch(() => undefined);
|
|
1242
|
-
return result;
|
|
1243
|
-
}
|
|
1244
|
-
catch (error) {
|
|
1245
|
-
await closeConnection().catch(() => undefined);
|
|
1246
|
-
throw error;
|
|
1247
|
-
}
|
|
1248
|
-
},
|
|
1249
|
-
return: async () => {
|
|
1250
|
-
await close();
|
|
1251
|
-
return { done: true, value: undefined };
|
|
1252
|
-
},
|
|
1253
|
-
};
|
|
1254
|
-
},
|
|
1255
1238
|
};
|
|
1256
1239
|
}
|
|
1257
1240
|
function remoteTerminalOpenErrorCode(reason) {
|
|
@@ -1465,7 +1448,7 @@ function nonNegativeInteger(value, fallback, field) {
|
|
|
1465
1448
|
return value;
|
|
1466
1449
|
}
|
|
1467
1450
|
function parseCallTimeout(value) {
|
|
1468
|
-
if (value === undefined)
|
|
1451
|
+
if (value === undefined || value === null)
|
|
1469
1452
|
return undefined;
|
|
1470
1453
|
if (!Number.isSafeInteger(value) ||
|
|
1471
1454
|
value < MIN_CALL_TIMEOUT_MS ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type DatabaseSync } from "node:sqlite";
|
|
2
2
|
export interface RemoteRuntimeTarget {
|
|
3
3
|
daemonId: string;
|
|
4
4
|
displayName: string;
|
|
@@ -40,7 +40,7 @@ export interface CommitRemoteRuntimePendingEnrollmentOptions {
|
|
|
40
40
|
export declare class RemoteRuntimeTargetStore {
|
|
41
41
|
private readonly connection;
|
|
42
42
|
private readonly clock;
|
|
43
|
-
constructor(connection?:
|
|
43
|
+
constructor(connection?: DatabaseSync, clock?: () => Date);
|
|
44
44
|
add(input: AddRemoteRuntimeTargetInput): RemoteRuntimeTarget;
|
|
45
45
|
addPendingEnrollment(input: AddRemoteRuntimePendingEnrollmentInput): RemoteRuntimePendingEnrollment;
|
|
46
46
|
getPendingEnrollment(claimIdInput: string): RemoteRuntimePendingEnrollment | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPublicKey } from "node:crypto";
|
|
2
2
|
import { db } from "./db.js";
|
|
3
|
+
import { hasSqlitePrimaryCode, SQLITE_CONSTRAINT, transaction } from "./sqlite.js";
|
|
3
4
|
const OPAQUE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,255}$/;
|
|
4
5
|
const CREDENTIAL_REF_PATTERN = /^credential_[A-Za-z0-9_-]{24}$/;
|
|
5
6
|
const MAX_DISPLAY_NAME_CHARS = 128;
|
|
@@ -13,7 +14,7 @@ export class RemoteRuntimeTargetStore {
|
|
|
13
14
|
add(input) {
|
|
14
15
|
const normalized = normalizeTargetInput(input);
|
|
15
16
|
const now = validNow(this.clock()).toISOString();
|
|
16
|
-
const insert = this.connection
|
|
17
|
+
const insert = transaction(this.connection, () => {
|
|
17
18
|
this.connection.prepare(`INSERT INTO runtime_targets
|
|
18
19
|
(daemon_id, display_name, identity_public_key, credential_ref, created_at, updated_at)
|
|
19
20
|
VALUES (?, ?, ?, ?, ?, ?)`).run(normalized.daemonId, normalized.displayName, normalized.identityPublicKey, normalized.credentialRef, now, now);
|
|
@@ -67,7 +68,7 @@ export class RemoteRuntimeTargetStore {
|
|
|
67
68
|
}
|
|
68
69
|
removePendingEnrollment(claimIdInput) {
|
|
69
70
|
const claimId = validId(claimIdInput, "claimId");
|
|
70
|
-
const remove = this.connection
|
|
71
|
+
const remove = transaction(this.connection, () => {
|
|
71
72
|
const pending = this.getPendingEnrollment(claimId);
|
|
72
73
|
if (!pending)
|
|
73
74
|
return undefined;
|
|
@@ -82,7 +83,7 @@ export class RemoteRuntimeTargetStore {
|
|
|
82
83
|
const claimId = validId(claimIdInput, "claimId");
|
|
83
84
|
const normalized = normalizeTargetInput(input);
|
|
84
85
|
const now = validNow(this.clock()).toISOString();
|
|
85
|
-
const commit = this.connection
|
|
86
|
+
const commit = transaction(this.connection, () => {
|
|
86
87
|
const pending = this.getPendingEnrollment(claimId);
|
|
87
88
|
if (!pending)
|
|
88
89
|
throw new Error("pending Runtime enrollment was not found");
|
|
@@ -134,7 +135,7 @@ export class RemoteRuntimeTargetStore {
|
|
|
134
135
|
}
|
|
135
136
|
remove(daemonIdInput) {
|
|
136
137
|
const daemonId = validId(daemonIdInput, "daemonId");
|
|
137
|
-
const remove = this.connection
|
|
138
|
+
const remove = transaction(this.connection, () => {
|
|
138
139
|
const target = this.get(daemonId);
|
|
139
140
|
if (!target)
|
|
140
141
|
return undefined;
|
|
@@ -321,6 +322,5 @@ function validNow(value) {
|
|
|
321
322
|
return value;
|
|
322
323
|
}
|
|
323
324
|
function isSqliteConstraint(error) {
|
|
324
|
-
return
|
|
325
|
-
typeof error.code === "string" && error.code.startsWith("SQLITE_CONSTRAINT");
|
|
325
|
+
return hasSqlitePrimaryCode(error, SQLITE_CONSTRAINT);
|
|
326
326
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SessionEmulatorBindingConflictError, } from "@rynx-ai/server";
|
|
2
2
|
import { db } from "./db.js";
|
|
3
|
+
import { hasSqlitePrimaryCode, SQLITE_CONSTRAINT, transaction } from "./sqlite.js";
|
|
3
4
|
export class SqliteSessionEmulatorBindingStore {
|
|
4
5
|
get(sessionId) {
|
|
5
6
|
const row = db().prepare(`SELECT session_id, binding_id, backend, device_id, device_name, attached_at
|
|
@@ -13,7 +14,7 @@ export class SqliteSessionEmulatorBindingStore {
|
|
|
13
14
|
}
|
|
14
15
|
bind(record) {
|
|
15
16
|
const connection = db();
|
|
16
|
-
const replace =
|
|
17
|
+
const replace = transaction(connection, () => {
|
|
17
18
|
connection.prepare("DELETE FROM session_emulator_bindings WHERE session_id = ?")
|
|
18
19
|
.run(record.sessionId);
|
|
19
20
|
connection.prepare(`INSERT INTO session_emulator_bindings
|
|
@@ -40,7 +41,7 @@ export class SqliteSessionEmulatorBindingStore {
|
|
|
40
41
|
return db().prepare("DELETE FROM session_emulator_bindings WHERE session_id = ?").run(sessionId).changes > 0;
|
|
41
42
|
}
|
|
42
43
|
releaseDevice(backend, deviceId) {
|
|
43
|
-
return db().prepare("DELETE FROM session_emulator_bindings WHERE backend = ? AND device_id = ?").run(backend, deviceId).changes;
|
|
44
|
+
return Number(db().prepare("DELETE FROM session_emulator_bindings WHERE backend = ? AND device_id = ?").run(backend, deviceId).changes);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
function bindingRecord(row) {
|
|
@@ -54,8 +55,5 @@ function bindingRecord(row) {
|
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
57
|
function isSqliteConstraint(error) {
|
|
57
|
-
|
|
58
|
-
return false;
|
|
59
|
-
const code = error.code;
|
|
60
|
-
return typeof code === "string" && code.startsWith("SQLITE_CONSTRAINT");
|
|
58
|
+
return hasSqlitePrimaryCode(error, SQLITE_CONSTRAINT);
|
|
61
59
|
}
|
|
@@ -30,6 +30,7 @@ export declare class SqliteSessionForkStore implements MachineSessionForkStorePo
|
|
|
30
30
|
items: SessionItem[];
|
|
31
31
|
}): void;
|
|
32
32
|
private publishTarget;
|
|
33
|
+
private publishTargetUnchecked;
|
|
33
34
|
markError(operationId: string, error: string): void;
|
|
34
35
|
listReserved(): MachineSessionForkOperationRecord[];
|
|
35
36
|
hasReservedSource(sessionId: string): boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync, } from "node:fs";
|
|
3
|
+
import { extname, join } from "node:path";
|
|
4
4
|
import { newSessionId, newSessionItemId, normalizeSessionTitle, resolvedExecutionSnapshotSchema, rynxHome, sessionWorkspaceSnapshotSchema, } from "@rynx-ai/core";
|
|
5
5
|
import { MachineSessionServiceFailure, } from "@rynx-ai/server";
|
|
6
6
|
import { db } from "./db.js";
|
|
7
|
+
import { scalar, transaction } from "./sqlite.js";
|
|
7
8
|
const FORK_COLS = [
|
|
8
9
|
"operation_id",
|
|
9
10
|
"request_hash",
|
|
@@ -39,9 +40,8 @@ export class SqliteSessionForkStore {
|
|
|
39
40
|
}
|
|
40
41
|
const now = new Date().toISOString();
|
|
41
42
|
let targetSessionId = newSessionId();
|
|
42
|
-
while (db().prepare("SELECT 1 FROM sessions WHERE id = ?")
|
|
43
|
-
db().prepare("SELECT 1 FROM session_fork_operations WHERE target_session_id = ?")
|
|
44
|
-
.pluck().get(targetSessionId)) {
|
|
43
|
+
while (scalar(db().prepare("SELECT 1 FROM sessions WHERE id = ?")).get(targetSessionId) ||
|
|
44
|
+
scalar(db().prepare("SELECT 1 FROM session_fork_operations WHERE target_session_id = ?")).get(targetSessionId)) {
|
|
45
45
|
targetSessionId = newSessionId();
|
|
46
46
|
}
|
|
47
47
|
try {
|
|
@@ -94,6 +94,19 @@ export class SqliteSessionForkStore {
|
|
|
94
94
|
this.publishTarget(input);
|
|
95
95
|
}
|
|
96
96
|
publishTarget(input) {
|
|
97
|
+
try {
|
|
98
|
+
this.publishTargetUnchecked(input);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const published = scalar(db().prepare("SELECT 1 FROM sessions WHERE id = ?"))
|
|
102
|
+
.get(input.target.id);
|
|
103
|
+
if (!published) {
|
|
104
|
+
rmSync(this.sessionDirectory(input.target.id), { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
publishTargetUnchecked(input) {
|
|
97
110
|
const itemIds = new Map();
|
|
98
111
|
for (const item of input.items)
|
|
99
112
|
itemIds.set(item.id, newSessionItemId(item.type));
|
|
@@ -130,7 +143,7 @@ export class SqliteSessionForkStore {
|
|
|
130
143
|
};
|
|
131
144
|
});
|
|
132
145
|
const conn = db();
|
|
133
|
-
|
|
146
|
+
transaction(conn, () => {
|
|
134
147
|
if (input.operationId) {
|
|
135
148
|
const current = this.read(input.operationId);
|
|
136
149
|
if (current?.state === "completed")
|
|
@@ -152,22 +165,39 @@ export class SqliteSessionForkStore {
|
|
|
152
165
|
source, created_at, updated_at
|
|
153
166
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.target.id, JSON.stringify(sessionWorkspaceSnapshotSchema.parse(input.target.workspace)), JSON.stringify(resolvedExecutionSnapshotSchema.parse(input.target.execution)), normalizeSessionTitle(input.target.title) ?? null, input.target.forkedFromSessionId ?? null, input.target.source, input.target.createdAt, input.target.updatedAt ?? input.target.createdAt);
|
|
154
167
|
const insertResource = conn.prepare(`INSERT INTO session_resources (
|
|
155
|
-
id, session_id, client_upload_id, upload_id, filename, media_type,
|
|
168
|
+
id, session_id, client_upload_id, upload_id, kind, filename, media_type,
|
|
156
169
|
byte_length, uploaded_bytes, sha256, width, height, state,
|
|
157
170
|
message_item_id, created_at, committed_at
|
|
158
171
|
) VALUES (
|
|
159
|
-
@id, @session_id, @client_upload_id, @upload_id, @filename, @media_type,
|
|
172
|
+
@id, @session_id, @client_upload_id, @upload_id, @kind, @filename, @media_type,
|
|
160
173
|
@byte_length, @uploaded_bytes, @sha256, @width, @height, 'committed',
|
|
161
174
|
@message_item_id, @created_at, @committed_at
|
|
162
175
|
)`);
|
|
163
|
-
for (const resource of resources)
|
|
164
|
-
insertResource.run(
|
|
176
|
+
for (const resource of resources) {
|
|
177
|
+
insertResource.run({
|
|
178
|
+
id: resource.id,
|
|
179
|
+
session_id: resource.session_id,
|
|
180
|
+
client_upload_id: resource.client_upload_id,
|
|
181
|
+
upload_id: resource.upload_id,
|
|
182
|
+
kind: resource.kind,
|
|
183
|
+
filename: resource.filename,
|
|
184
|
+
media_type: resource.media_type,
|
|
185
|
+
byte_length: resource.byte_length,
|
|
186
|
+
uploaded_bytes: resource.uploaded_bytes,
|
|
187
|
+
sha256: resource.sha256,
|
|
188
|
+
width: resource.width,
|
|
189
|
+
height: resource.height,
|
|
190
|
+
message_item_id: resource.message_item_id,
|
|
191
|
+
created_at: resource.created_at,
|
|
192
|
+
committed_at: resource.committed_at,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
165
195
|
const insertItem = conn.prepare(`INSERT INTO session_items (
|
|
166
196
|
id, session_id, position, response_id, type, status,
|
|
167
|
-
data, created_by, created_at
|
|
197
|
+
data, created_by, parent_tool_call_id, created_at
|
|
168
198
|
) VALUES (
|
|
169
199
|
@id, @session_id, @position, @response_id, @type, @status,
|
|
170
|
-
@data, @created_by, @created_at
|
|
200
|
+
@data, @created_by, @parent_tool_call_id, @created_at
|
|
171
201
|
)`);
|
|
172
202
|
for (const item of clonedItems) {
|
|
173
203
|
insertItem.run({
|
|
@@ -179,6 +209,7 @@ export class SqliteSessionForkStore {
|
|
|
179
209
|
status: item.status,
|
|
180
210
|
data: JSON.stringify(item.data),
|
|
181
211
|
created_by: item.createdBy ?? null,
|
|
212
|
+
parent_tool_call_id: item.parentToolCallId ?? null,
|
|
182
213
|
created_at: item.createdAt,
|
|
183
214
|
});
|
|
184
215
|
}
|
|
@@ -203,9 +234,9 @@ export class SqliteSessionForkStore {
|
|
|
203
234
|
ORDER BY created_at`).all().map(toOperation);
|
|
204
235
|
}
|
|
205
236
|
hasReservedSource(sessionId) {
|
|
206
|
-
return Boolean(db().prepare(`SELECT 1
|
|
237
|
+
return Boolean(scalar(db().prepare(`SELECT 1
|
|
207
238
|
FROM session_fork_operations
|
|
208
|
-
WHERE source_session_id = ? AND state = 'reserved'`)
|
|
239
|
+
WHERE source_session_id = ? AND state = 'reserved'`)).get(sessionId));
|
|
209
240
|
}
|
|
210
241
|
markTargetDeleted(sessionId) {
|
|
211
242
|
const now = new Date().toISOString();
|
|
@@ -234,7 +265,7 @@ export class SqliteSessionForkStore {
|
|
|
234
265
|
return join(this.resourceRoot, key);
|
|
235
266
|
}
|
|
236
267
|
resourcePath(row) {
|
|
237
|
-
return join(this.sessionDirectory(row.session_id), `${row.id}.${extension(row
|
|
268
|
+
return join(this.sessionDirectory(row.session_id), `${row.id}.${extension(row)}`);
|
|
238
269
|
}
|
|
239
270
|
}
|
|
240
271
|
function toOperation(row) {
|
|
@@ -252,22 +283,40 @@ function toOperation(row) {
|
|
|
252
283
|
function referencedResourceIds(items) {
|
|
253
284
|
const ids = new Set();
|
|
254
285
|
for (const item of items) {
|
|
255
|
-
if (item.type
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
286
|
+
if (item.type === "message" && item.data.role === "user") {
|
|
287
|
+
for (const part of item.data.content) {
|
|
288
|
+
if ((part.type === "input_image" || part.type === "input_file") &&
|
|
289
|
+
typeof part.resourceId === "string")
|
|
290
|
+
ids.add(part.resourceId);
|
|
291
|
+
}
|
|
260
292
|
}
|
|
293
|
+
if (item.type === "function_call_output" &&
|
|
294
|
+
item.data.generatedImage &&
|
|
295
|
+
typeof item.data.generatedImage.resourceId === "string")
|
|
296
|
+
ids.add(item.data.generatedImage.resourceId);
|
|
261
297
|
}
|
|
262
298
|
return [...ids];
|
|
263
299
|
}
|
|
264
300
|
function remapItemResources(item, resources) {
|
|
265
301
|
const cloned = structuredClone(item);
|
|
266
|
-
if (cloned.type
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
302
|
+
if (cloned.type === "message" && cloned.data.role === "user") {
|
|
303
|
+
cloned.data.content = cloned.data.content.map((part) => {
|
|
304
|
+
if ((part.type === "input_image" || part.type === "input_file") &&
|
|
305
|
+
typeof part.resourceId === "string") {
|
|
306
|
+
return { ...part, resourceId: resources.get(part.resourceId) ?? part.resourceId };
|
|
307
|
+
}
|
|
308
|
+
return part;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (cloned.type === "function_call_output" &&
|
|
312
|
+
cloned.data.generatedImage &&
|
|
313
|
+
typeof cloned.data.generatedImage.resourceId === "string") {
|
|
314
|
+
const resourceId = cloned.data.generatedImage.resourceId;
|
|
315
|
+
cloned.data.generatedImage = {
|
|
316
|
+
...cloned.data.generatedImage,
|
|
317
|
+
resourceId: resources.get(resourceId) ?? resourceId,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
271
320
|
return cloned;
|
|
272
321
|
}
|
|
273
322
|
function deterministicId(prefix, targetSessionId, sourceId) {
|
|
@@ -276,13 +325,19 @@ function deterministicId(prefix, targetSessionId, sourceId) {
|
|
|
276
325
|
.digest("hex");
|
|
277
326
|
return `${prefix}_${digest.slice(0, 40)}`;
|
|
278
327
|
}
|
|
279
|
-
function extension(
|
|
280
|
-
|
|
281
|
-
|
|
328
|
+
function extension(row) {
|
|
329
|
+
if (row.kind === "image") {
|
|
330
|
+
if (row.media_type === "image/png")
|
|
282
331
|
return "png";
|
|
283
|
-
|
|
332
|
+
if (row.media_type === "image/jpeg")
|
|
284
333
|
return "jpg";
|
|
285
|
-
|
|
334
|
+
if (row.media_type === "image/webp")
|
|
286
335
|
return "webp";
|
|
336
|
+
if (row.media_type === "image/gif")
|
|
337
|
+
return "gif";
|
|
338
|
+
if (row.media_type === "image/svg+xml")
|
|
339
|
+
return "svg";
|
|
287
340
|
}
|
|
341
|
+
const value = extname(row.filename).slice(1).toLowerCase();
|
|
342
|
+
return /^[a-z0-9][a-z0-9._-]{0,15}$/.test(value) ? value : "bin";
|
|
288
343
|
}
|