@rynx-ai/daemon 0.1.11-beta.30 → 0.1.11-beta.32
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/package.json +1 -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-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +78 -18
- package/dist/daemon-server.js +19 -5
- package/dist/db.js +22 -0
- package/dist/headless-browser-host.js +283 -34
- package/dist/plugin-host-rpc.d.ts +18 -1
- package/dist/plugin-host-rpc.js +129 -5
- package/dist/session-portal-grant-store.d.ts +27 -1
- package/dist/session-portal-grant-store.js +92 -17
- package/dist/session-resource-store.d.ts +4 -0
- package/dist/session-resource-store.js +22 -0
- package/dist/session-timeline-projection-store.d.ts +24 -0
- package/dist/session-timeline-projection-store.js +53 -0
- package/package.json +10 -9
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ChromeForTestingFetchInit {
|
|
2
|
+
headers?: HeadersInit;
|
|
3
|
+
redirect?: RequestRedirect;
|
|
4
|
+
signal?: AbortSignal | null;
|
|
5
|
+
}
|
|
6
|
+
/** HTTP behavior shared by Chrome for Testing metadata and artifact downloads. */
|
|
7
|
+
export declare function fetchChromeForTesting(url: string, init?: ChromeForTestingFetchInit): Promise<Response>;
|
|
8
|
+
/** Preserve nested network diagnostics instead of reporting only `fetch failed`. */
|
|
9
|
+
export declare function formatChromeForTestingNetworkError(error: unknown): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EnvHttpProxyAgent, fetch as undiciFetch } from "undici";
|
|
2
|
+
import { installedDaemonVersion } from "./entry-path.js";
|
|
3
|
+
const CONNECT_TIMEOUT_MS = 30_000;
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
5
|
+
let dispatcher;
|
|
6
|
+
/** HTTP behavior shared by Chrome for Testing metadata and artifact downloads. */
|
|
7
|
+
export async function fetchChromeForTesting(url, init = {}) {
|
|
8
|
+
const headers = new Headers(init.headers);
|
|
9
|
+
if (!headers.has("user-agent")) {
|
|
10
|
+
headers.set("user-agent", `rynx/${installedDaemonVersion()}`);
|
|
11
|
+
}
|
|
12
|
+
const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
13
|
+
const signal = init.signal
|
|
14
|
+
? AbortSignal.any([init.signal, timeoutSignal])
|
|
15
|
+
: timeoutSignal;
|
|
16
|
+
const response = await undiciFetch(url, {
|
|
17
|
+
dispatcher: dispatcher ??= createDispatcher(),
|
|
18
|
+
headers: Object.fromEntries(headers.entries()),
|
|
19
|
+
redirect: init.redirect,
|
|
20
|
+
signal,
|
|
21
|
+
});
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
/** Preserve nested network diagnostics instead of reporting only `fetch failed`. */
|
|
25
|
+
export function formatChromeForTestingNetworkError(error) {
|
|
26
|
+
const messages = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
let current = error;
|
|
29
|
+
while (current instanceof Error && !seen.has(current) && messages.length < 8) {
|
|
30
|
+
seen.add(current);
|
|
31
|
+
if (current.message && messages.at(-1) !== current.message)
|
|
32
|
+
messages.push(current.message);
|
|
33
|
+
current = current.cause;
|
|
34
|
+
}
|
|
35
|
+
return messages.join(": ") || String(error);
|
|
36
|
+
}
|
|
37
|
+
function createDispatcher() {
|
|
38
|
+
const allProxy = firstEnvironmentValue("all_proxy", "ALL_PROXY");
|
|
39
|
+
const httpProxy = firstEnvironmentValue("http_proxy", "HTTP_PROXY") ?? allProxy;
|
|
40
|
+
const httpsProxy = firstEnvironmentValue("https_proxy", "HTTPS_PROXY") ?? httpProxy;
|
|
41
|
+
const noProxy = firstEnvironmentValue("no_proxy", "NO_PROXY");
|
|
42
|
+
return new EnvHttpProxyAgent({
|
|
43
|
+
connectTimeout: CONNECT_TIMEOUT_MS,
|
|
44
|
+
headersTimeout: REQUEST_TIMEOUT_MS,
|
|
45
|
+
bodyTimeout: REQUEST_TIMEOUT_MS,
|
|
46
|
+
...(httpProxy ? { httpProxy } : {}),
|
|
47
|
+
...(httpsProxy ? { httpsProxy } : {}),
|
|
48
|
+
...(noProxy ? { noProxy } : {}),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function firstEnvironmentValue(...names) {
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const value = process.env[name];
|
|
54
|
+
if (value)
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* store computes and records the archive digest when it installs these bytes.
|
|
8
8
|
*/
|
|
9
9
|
import { resolveChromeForTestingPlatform, } from "./chrome-for-testing-store.js";
|
|
10
|
+
import { fetchChromeForTesting, formatChromeForTestingNetworkError, } from "./chrome-for-testing-http.js";
|
|
10
11
|
const MANIFEST_BASE_URL = "https://googlechromelabs.github.io/chrome-for-testing/";
|
|
11
12
|
const CHANNEL_MANIFEST_URL = `${MANIFEST_BASE_URL}last-known-good-versions-with-downloads.json`;
|
|
12
13
|
const ARTIFACT_BASE_URL = "https://storage.googleapis.com/chrome-for-testing-public/";
|
|
@@ -53,11 +54,17 @@ function validateSelector(selector) {
|
|
|
53
54
|
}
|
|
54
55
|
async function fetchManifest(manifestUrl, fetchMetadata, signal) {
|
|
55
56
|
assertManifestUrl(manifestUrl);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = await fetchMetadata(manifestUrl, {
|
|
60
|
+
signal,
|
|
61
|
+
redirect: "error",
|
|
62
|
+
headers: { accept: "application/json" },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new Error(`Chrome for Testing metadata request failed: ${formatChromeForTestingNetworkError(error)}`, { cause: error });
|
|
67
|
+
}
|
|
61
68
|
if (!response.ok) {
|
|
62
69
|
throw new Error(`Chrome for Testing metadata request failed: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`);
|
|
63
70
|
}
|
|
@@ -210,5 +217,5 @@ function isRecord(value) {
|
|
|
210
217
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
211
218
|
}
|
|
212
219
|
async function defaultFetchMetadata(url, init) {
|
|
213
|
-
return
|
|
220
|
+
return fetchChromeForTesting(url, init);
|
|
214
221
|
}
|
|
@@ -48,6 +48,8 @@ export interface ChromeForTestingArtifactStoreOptions {
|
|
|
48
48
|
arch?: string;
|
|
49
49
|
fetchArtifact?: ChromeForTestingArtifactFetcher;
|
|
50
50
|
extractZip?: ChromeForTestingZipExtractor;
|
|
51
|
+
/** Injectable only so download retries can be tested without real delays. */
|
|
52
|
+
retryDelay?: (attempt: number, signal?: AbortSignal) => Promise<void>;
|
|
51
53
|
maxArchiveBytes?: number;
|
|
52
54
|
now?: () => Date;
|
|
53
55
|
/** Defaults to ~/.rynx/browser/profiles/headless-chromium. */
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
* explicitly pinned build or an already-resolved official release.
|
|
7
7
|
*/
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { accessSync, chmodSync, closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync, } from "node:fs";
|
|
9
|
+
import { accessSync, chmodSync, closeSync, existsSync, ftruncateSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync, } from "node:fs";
|
|
10
10
|
import { constants as fsConstants } from "node:fs";
|
|
11
11
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
12
|
import { DatabaseSync } from "node:sqlite";
|
|
13
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
13
14
|
import extractZipArchive from "@electron-internal/extract-zip";
|
|
14
15
|
import { rynxHome } from "@rynx-ai/core";
|
|
16
|
+
import { fetchChromeForTesting, formatChromeForTestingNetworkError, } from "./chrome-for-testing-http.js";
|
|
15
17
|
import { hasSqlitePrimaryCode, SQLITE_BUSY, SQLITE_LOCKED } from "./sqlite.js";
|
|
16
18
|
const BUILD_OWNER_FILE = ".rynx-cft-owner.json";
|
|
17
19
|
const STAGING_OWNER_FILE = ".rynx-cft-staging-owner.json";
|
|
@@ -20,8 +22,9 @@ const STORE_LOCK_FILE = ".rynx-cft-store.sqlite";
|
|
|
20
22
|
const MAX_METADATA_BYTES = 8 * 1024;
|
|
21
23
|
const DEFAULT_MAX_ARCHIVE_BYTES = 1_024 * 1_024 * 1_024;
|
|
22
24
|
const DEFAULT_STAGING_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
23
|
-
const DOWNLOAD_PROGRESS_PERCENT_STEP =
|
|
25
|
+
const DOWNLOAD_PROGRESS_PERCENT_STEP = 5;
|
|
24
26
|
const DOWNLOAD_PROGRESS_BYTES_STEP = 16 * 1_024 * 1_024;
|
|
27
|
+
const DOWNLOAD_MAX_ATTEMPTS = 3;
|
|
25
28
|
const STAGING_NAME_PATTERN = /^install-([A-Za-z0-9]{6})$/;
|
|
26
29
|
const HEADLESS_PROFILE_NAME_PATTERN = /^session-([a-f0-9]{64})$/;
|
|
27
30
|
const VERSION_PATTERN = /^\d{1,6}\.\d{1,6}\.\d{1,6}\.\d{1,6}$/;
|
|
@@ -68,6 +71,7 @@ export function createChromeForTestingArtifactStore(options = {}) {
|
|
|
68
71
|
const rootDir = resolve(options.rootDir ?? join(rynxHome(), "cache", "browser", "chrome-for-testing"));
|
|
69
72
|
const fetchArtifact = options.fetchArtifact ?? defaultFetchArtifact;
|
|
70
73
|
const extractZip = options.extractZip ?? defaultExtractZip;
|
|
74
|
+
const retryDelay = options.retryDelay ?? defaultDownloadRetryDelay;
|
|
71
75
|
const maxArchiveBytes = options.maxArchiveBytes ?? DEFAULT_MAX_ARCHIVE_BYTES;
|
|
72
76
|
const now = options.now ?? (() => new Date());
|
|
73
77
|
const headlessProfileRoot = resolve(options.headlessProfileRoot ?? join(rynxHome(), "browser", "profiles", "headless-chromium"));
|
|
@@ -139,6 +143,7 @@ export function createChromeForTestingArtifactStore(options = {}) {
|
|
|
139
143
|
maxArchiveBytes,
|
|
140
144
|
signal: installOptions.signal,
|
|
141
145
|
onProgress: installOptions.onProgress,
|
|
146
|
+
retryDelay,
|
|
142
147
|
});
|
|
143
148
|
throwIfAborted(installOptions.signal);
|
|
144
149
|
const build = { ...release, sha256 };
|
|
@@ -450,9 +455,54 @@ function validateDownloadMetadata(input, platform) {
|
|
|
450
455
|
return { version: input.version, platform: input.platform, url: url.href };
|
|
451
456
|
}
|
|
452
457
|
async function downloadArchive(options) {
|
|
453
|
-
const
|
|
458
|
+
const descriptor = openSync(options.destination, "wx", 0o600);
|
|
459
|
+
try {
|
|
460
|
+
for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt += 1) {
|
|
461
|
+
try {
|
|
462
|
+
if (attempt > 1)
|
|
463
|
+
ftruncateSync(descriptor, 0);
|
|
464
|
+
return await downloadArchiveAttempt(options, descriptor);
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
throwIfAborted(options.signal);
|
|
468
|
+
if (!(error instanceof RetryableDownloadError) || attempt === DOWNLOAD_MAX_ATTEMPTS) {
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
options.onProgress?.(`Chrome for Testing 下载失败,正在重试 (${attempt + 1}/${DOWNLOAD_MAX_ATTEMPTS})`);
|
|
472
|
+
try {
|
|
473
|
+
await options.retryDelay(attempt, options.signal);
|
|
474
|
+
}
|
|
475
|
+
catch (delayError) {
|
|
476
|
+
throwIfAborted(options.signal);
|
|
477
|
+
throw delayError;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
closeSync(descriptor);
|
|
484
|
+
}
|
|
485
|
+
throw new Error("Chrome for Testing download failed");
|
|
486
|
+
}
|
|
487
|
+
async function downloadArchiveAttempt(options, descriptor) {
|
|
488
|
+
let response;
|
|
489
|
+
try {
|
|
490
|
+
response = await options.fetchArtifact(options.url, { signal: options.signal });
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
throw new RetryableDownloadError(`Chrome for Testing download failed: ${formatChromeForTestingNetworkError(error)}`, { cause: error });
|
|
494
|
+
}
|
|
454
495
|
if (!response.ok) {
|
|
455
|
-
|
|
496
|
+
try {
|
|
497
|
+
await response.body?.cancel();
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// The status code remains the useful failure when response cleanup fails.
|
|
501
|
+
}
|
|
502
|
+
const error = new Error(`Chrome for Testing download failed: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`);
|
|
503
|
+
if (response.status >= 500)
|
|
504
|
+
throw new RetryableDownloadError(error.message, { cause: error });
|
|
505
|
+
throw error;
|
|
456
506
|
}
|
|
457
507
|
if (!response.body)
|
|
458
508
|
throw new Error("Chrome for Testing download returned no response body");
|
|
@@ -461,15 +511,20 @@ async function downloadArchive(options) {
|
|
|
461
511
|
throw new Error(`Chrome for Testing archive exceeds ${options.maxArchiveBytes} bytes`);
|
|
462
512
|
}
|
|
463
513
|
const hash = createHash("sha256");
|
|
464
|
-
const descriptor = openSync(options.destination, "wx", 0o600);
|
|
465
514
|
let bytes = 0;
|
|
466
|
-
let
|
|
515
|
+
let lastReportedPercent = 0;
|
|
467
516
|
let nextBytes = DOWNLOAD_PROGRESS_BYTES_STEP;
|
|
468
517
|
const reader = response.body.getReader();
|
|
469
518
|
try {
|
|
470
519
|
while (true) {
|
|
471
520
|
throwIfAborted(options.signal);
|
|
472
|
-
|
|
521
|
+
let next;
|
|
522
|
+
try {
|
|
523
|
+
next = await reader.read();
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
throw new RetryableDownloadError(`Chrome for Testing download failed: ${formatChromeForTestingNetworkError(error)}`, { cause: error });
|
|
527
|
+
}
|
|
473
528
|
if (next.done)
|
|
474
529
|
break;
|
|
475
530
|
const chunk = Buffer.from(next.value);
|
|
@@ -478,24 +533,24 @@ async function downloadArchive(options) {
|
|
|
478
533
|
throw new Error(`Chrome for Testing archive exceeds ${options.maxArchiveBytes} bytes`);
|
|
479
534
|
}
|
|
480
535
|
hash.update(chunk);
|
|
481
|
-
writeAll(descriptor, chunk);
|
|
536
|
+
writeAll(descriptor, chunk, bytes - chunk.byteLength);
|
|
482
537
|
if (length !== undefined && length > 0) {
|
|
483
538
|
const completed = Math.min(100, Math.floor(bytes * 100 / length));
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
539
|
+
if (completed >= lastReportedPercent + DOWNLOAD_PROGRESS_PERCENT_STEP) {
|
|
540
|
+
lastReportedPercent = completed;
|
|
541
|
+
options.onProgress?.(`Chrome for Testing 下载进度:${formatDownloadMegabytes(bytes)} / ` +
|
|
542
|
+
`${formatDownloadMegabytes(length)} (${completed}%)`);
|
|
487
543
|
}
|
|
488
544
|
}
|
|
489
545
|
else {
|
|
490
546
|
while (nextBytes <= bytes) {
|
|
491
|
-
options.onProgress?.(`Chrome for Testing 已下载:${
|
|
547
|
+
options.onProgress?.(`Chrome for Testing 已下载:${formatDownloadMegabytes(nextBytes)}`);
|
|
492
548
|
nextBytes += DOWNLOAD_PROGRESS_BYTES_STEP;
|
|
493
549
|
}
|
|
494
550
|
}
|
|
495
551
|
}
|
|
496
552
|
}
|
|
497
553
|
finally {
|
|
498
|
-
closeSync(descriptor);
|
|
499
554
|
reader.releaseLock();
|
|
500
555
|
}
|
|
501
556
|
const actual = hash.digest("hex");
|
|
@@ -504,13 +559,18 @@ async function downloadArchive(options) {
|
|
|
504
559
|
}
|
|
505
560
|
return actual;
|
|
506
561
|
}
|
|
507
|
-
|
|
508
|
-
|
|
562
|
+
class RetryableDownloadError extends Error {
|
|
563
|
+
}
|
|
564
|
+
async function defaultDownloadRetryDelay(attempt, signal) {
|
|
565
|
+
await delay(2 ** attempt * 1_000, undefined, { signal });
|
|
566
|
+
}
|
|
567
|
+
function formatDownloadMegabytes(bytes) {
|
|
568
|
+
return `${(bytes / (1_024 * 1_024)).toFixed(1)} MB`;
|
|
509
569
|
}
|
|
510
|
-
function writeAll(descriptor, value) {
|
|
570
|
+
function writeAll(descriptor, value, position) {
|
|
511
571
|
let offset = 0;
|
|
512
572
|
while (offset < value.byteLength) {
|
|
513
|
-
const written = writeSync(descriptor, value, offset, value.byteLength - offset);
|
|
573
|
+
const written = writeSync(descriptor, value, offset, value.byteLength - offset, position + offset);
|
|
514
574
|
if (written <= 0)
|
|
515
575
|
throw new Error("Chrome for Testing archive write made no progress");
|
|
516
576
|
offset += written;
|
|
@@ -841,7 +901,7 @@ function throwIfAborted(signal) {
|
|
|
841
901
|
throw new Error("Chrome for Testing installation aborted");
|
|
842
902
|
}
|
|
843
903
|
async function defaultFetchArtifact(url, init) {
|
|
844
|
-
return
|
|
904
|
+
return fetchChromeForTesting(url, { signal: init.signal, redirect: "follow" });
|
|
845
905
|
}
|
|
846
906
|
async function defaultExtractZip(archivePath, destination) {
|
|
847
907
|
await extractZipArchive(archivePath, { dir: destination });
|
package/dist/daemon-server.js
CHANGED
|
@@ -17,8 +17,8 @@ import { REMOTE_RUNTIME_CORE_PROTOCOL } from "@rynx-ai/protocol/remote-runtime";
|
|
|
17
17
|
import { RUNTIME_BROWSER_INSPECT_MAX_MESSAGE_BYTES, RUNTIME_BROWSER_INSPECT_SEMANTIC_CAPABILITY, } from "@rynx-ai/protocol/runtime-browser-inspect";
|
|
18
18
|
import { RUNTIME_BROWSER_SURFACE_SEMANTIC_CAPABILITY } from "@rynx-ai/protocol/runtime-browser-surface";
|
|
19
19
|
import { RUNTIME_EMULATOR_SURFACE_SEMANTIC_CAPABILITY } from "@rynx-ai/protocol/runtime-emulator-surface";
|
|
20
|
-
import { createDaemonRuntimeHost, createDesktopFirstSessionBrowserHost, createRunnerSessionTerminalHost, createSessionRuntimeServices, DesktopBrowserHostAbsentError, DesktopBrowserHostRegistry, DirectRuntimeConnectionHub, DirectRuntimeBrowserInspectHostError, MachineSessionService, MachineSessionServiceFailure, RemoteRuntimeDispatcher, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
|
|
21
|
-
import { authorizeSessionPortalGrant, redeemSessionPortalTicket, } from "./session-portal-grant-store.js";
|
|
20
|
+
import { createDaemonRuntimeHost, createDesktopFirstSessionBrowserHost, createRunnerSessionTerminalHost, createSessionRuntimeServices, DesktopBrowserHostAbsentError, DesktopBrowserHostRegistry, DirectRuntimeConnectionHub, DirectRuntimeBrowserInspectHostError, MachineSessionService, MachineSessionServiceFailure, RemoteRuntimeDispatcher, SessionPendingInputIndex, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
|
|
21
|
+
import { authorizeSessionPortalGrant, inspectSessionPortalGrant, onSessionPortalGrantRevoked, redeemSessionPortalTicket, revokeEmbeddedWritableSessionPortalGrants, } from "./session-portal-grant-store.js";
|
|
22
22
|
import { APP_BROWSER_HOST_BOOTSTRAP_TYPE, AppBrowserHostSupervisor, parseAppBrowserHostLaunchConfig, } from "./app-browser-host-supervisor.js";
|
|
23
23
|
import { buildControlDeps, listControlAgentModels } from "./control-deps.js";
|
|
24
24
|
import { createBrowserArtifactManagementService } from "./browser-artifact-management.js";
|
|
@@ -311,7 +311,9 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
311
311
|
"semantic.session.interaction.v1",
|
|
312
312
|
"semantic.project.manage.v1",
|
|
313
313
|
"semantic.provider-cli.status.v1",
|
|
314
|
-
...(runtimeBrowserSupported
|
|
314
|
+
...(runtimeBrowserSupported
|
|
315
|
+
? ["semantic.browser.v1", "semantic.browser.request-headers.v1"]
|
|
316
|
+
: []),
|
|
315
317
|
...(runtimeBrowserSupported && directHostingSupported
|
|
316
318
|
? [
|
|
317
319
|
RUNTIME_BROWSER_SURFACE_SEMANTIC_CAPABILITY,
|
|
@@ -387,6 +389,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
387
389
|
};
|
|
388
390
|
};
|
|
389
391
|
let machineSessions;
|
|
392
|
+
let sessionBrowsers;
|
|
390
393
|
const pluginRuntime = {
|
|
391
394
|
bindHostServices: (services) => pluginSupervisor.bindHostServices({
|
|
392
395
|
...services,
|
|
@@ -396,6 +399,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
396
399
|
publicKey: installationIdentity.publicKey,
|
|
397
400
|
sign: installationIdentity.sign,
|
|
398
401
|
},
|
|
402
|
+
sessionBrowserRequestHeaders: sessionBrowsers,
|
|
399
403
|
sessionLaunch: {
|
|
400
404
|
resolve: async (selection) => {
|
|
401
405
|
const workspace = await resolveSessionWorkspace(runtimeProjects, selection.projectId, selection.managedCwd, selection.workspace);
|
|
@@ -434,6 +438,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
434
438
|
const directConnections = new DirectRuntimeConnectionHub();
|
|
435
439
|
const sessionLog = new SqliteSessionLogStore();
|
|
436
440
|
const sessionResources = new SqliteSessionResourceStore();
|
|
441
|
+
const sessionPendingInputs = new SessionPendingInputIndex();
|
|
437
442
|
const sessionForks = new SqliteSessionForkStore();
|
|
438
443
|
const runtimeProjects = new SqliteRuntimeProjectStore();
|
|
439
444
|
const providerClis = new ProviderCliService();
|
|
@@ -446,6 +451,11 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
446
451
|
const sessionRuntimeServices = createSessionRuntimeServices({
|
|
447
452
|
config: appConfig,
|
|
448
453
|
sessionLog,
|
|
454
|
+
pendingInputs: sessionPendingInputs,
|
|
455
|
+
onPendingInputPersisted: (input) => sessionResources.markMessageMirrored(input.sessionId, input.clientMessageId, {
|
|
456
|
+
responseId: input.responseId,
|
|
457
|
+
messageItemId: input.messageItemId,
|
|
458
|
+
}),
|
|
449
459
|
sessionContextProvider: browserRunnerSessionContexts,
|
|
450
460
|
admissionOpen,
|
|
451
461
|
admissionReserve: reserveAdmission,
|
|
@@ -455,7 +465,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
455
465
|
const sessionBrowserHost = appBrowserHostSupervisor
|
|
456
466
|
? createSupervisedAppSessionBrowserHost(durableDesktopBrowserHost, appBrowserHostSupervisor)
|
|
457
467
|
: createDesktopFirstSessionBrowserHost(durableDesktopBrowserHost, createHeadlessBrowserHost());
|
|
458
|
-
|
|
468
|
+
sessionBrowsers = new SessionBrowserService({
|
|
459
469
|
host: sessionBrowserHost,
|
|
460
470
|
sessions: {
|
|
461
471
|
exists: async (sessionId) => Boolean(sessionRegistry.get(sessionId) ??
|
|
@@ -556,6 +566,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
556
566
|
list: () => control.listAgents(),
|
|
557
567
|
},
|
|
558
568
|
pendingMessages: sessionPendingMessageStore,
|
|
569
|
+
pendingInputs: sessionPendingInputs,
|
|
559
570
|
resources: sessionResources,
|
|
560
571
|
forks: sessionForks,
|
|
561
572
|
execution: {
|
|
@@ -769,6 +780,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
769
780
|
status: async () => asDaemonChromeInspectionStatus(await initializedChromeInspectionManager.status()),
|
|
770
781
|
configure: async (input) => asDaemonChromeInspectionStatus(await initializedChromeInspectionManager.configure(input)),
|
|
771
782
|
};
|
|
783
|
+
revokeEmbeddedWritableSessionPortalGrants();
|
|
772
784
|
server = await startServer({
|
|
773
785
|
config: appConfig,
|
|
774
786
|
control,
|
|
@@ -801,8 +813,10 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
|
|
|
801
813
|
daemonInstanceId,
|
|
802
814
|
},
|
|
803
815
|
sessionPortalAuthorization: {
|
|
804
|
-
redeem: redeemSessionPortalTicket,
|
|
816
|
+
redeem: (ticket, embedOrigin) => redeemSessionPortalTicket(ticket, new Date(), embedOrigin),
|
|
805
817
|
authorize: authorizeSessionPortalGrant,
|
|
818
|
+
inspect: inspectSessionPortalGrant,
|
|
819
|
+
subscribeRevocations: onSessionPortalGrantRevoked,
|
|
806
820
|
ownsSession: sessionBelongsToPlugin,
|
|
807
821
|
},
|
|
808
822
|
});
|
package/dist/db.js
CHANGED
|
@@ -282,6 +282,17 @@ function migrate(conn) {
|
|
|
282
282
|
);
|
|
283
283
|
CREATE INDEX IF NOT EXISTS idx_session_portal_grants_expires
|
|
284
284
|
ON session_portal_grants(expires_at);
|
|
285
|
+
CREATE TABLE IF NOT EXISTS session_timeline_projections (
|
|
286
|
+
plugin_id TEXT NOT NULL,
|
|
287
|
+
session_id TEXT NOT NULL,
|
|
288
|
+
projection_id TEXT NOT NULL,
|
|
289
|
+
request_hash TEXT NOT NULL,
|
|
290
|
+
response_id TEXT NOT NULL,
|
|
291
|
+
item_id TEXT NOT NULL UNIQUE,
|
|
292
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'completed')),
|
|
293
|
+
created_at TEXT NOT NULL,
|
|
294
|
+
PRIMARY KEY(plugin_id, session_id, projection_id)
|
|
295
|
+
);
|
|
285
296
|
CREATE TABLE IF NOT EXISTS runtime_pairing_claims (
|
|
286
297
|
claim_id TEXT PRIMARY KEY,
|
|
287
298
|
secret_hash TEXT NOT NULL,
|
|
@@ -359,6 +370,17 @@ function migrate(conn) {
|
|
|
359
370
|
addColumnIfMissing(conn, "session_message_operations", "response_id", "TEXT");
|
|
360
371
|
addColumnIfMissing(conn, "session_message_operations", "message_item_id", "TEXT");
|
|
361
372
|
addColumnIfMissing(conn, "session_message_operations", "execution_snapshot", "TEXT");
|
|
373
|
+
addColumnIfMissing(conn, "session_portal_tickets", "grant_handle", "TEXT");
|
|
374
|
+
addColumnIfMissing(conn, "session_portal_tickets", "embed_origin", "TEXT");
|
|
375
|
+
addColumnIfMissing(conn, "session_portal_grants", "grant_handle", "TEXT");
|
|
376
|
+
addColumnIfMissing(conn, "session_portal_grants", "embed_origin", "TEXT");
|
|
377
|
+
addColumnIfMissing(conn, "session_timeline_projections", "state", "TEXT NOT NULL DEFAULT 'pending'");
|
|
378
|
+
conn.exec(`
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_session_portal_tickets_grant_handle
|
|
380
|
+
ON session_portal_tickets(plugin_id, grant_handle);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_session_portal_grants_grant_handle
|
|
382
|
+
ON session_portal_grants(plugin_id, grant_handle);
|
|
383
|
+
`);
|
|
362
384
|
}
|
|
363
385
|
/** Rebuild the original image-only table without losing committed resources. */
|
|
364
386
|
function migrateSessionResourceFiles(conn) {
|