@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
package/dist/cli.js CHANGED
@@ -52,8 +52,8 @@ Lifecycle:
52
52
 
53
53
  Plugins (static manifest + isolated runner):
54
54
  plugin manage list
55
- plugin manage install <npm|path|git|url> [--force] [--grant-all] [--allow-scripts] [--restart]
56
- plugin manage update <id> [--grant-all] [--allow-scripts]
55
+ plugin manage install <npm|path|git|url> [...] [--expect-digest <sha256-...>]
56
+ plugin manage update <id> [...] [--expect-digest <sha256-...>]
57
57
  plugin manage uninstall <id>
58
58
  plugin manage enable|disable <id>
59
59
  manage arbitrary installed plugins
@@ -147,8 +147,10 @@ async function pluginManageCommand(sub, arg) {
147
147
  const force = process.argv.includes("--force");
148
148
  const grantAll = process.argv.includes("--grant-all");
149
149
  const allowScripts = process.argv.includes("--allow-scripts");
150
+ const expectedDigest = digestOption(process.argv);
150
151
  const result = await installPlugin(arg, (line) => console.log(line), {
151
152
  allowScripts,
153
+ ...(expectedDigest ? { expectedDigest } : {}),
152
154
  onConflict: async (existing) => {
153
155
  if (force)
154
156
  return true;
@@ -197,9 +199,11 @@ async function pluginManageCommand(sub, arg) {
197
199
  if (!isRunnablePluginRecord(existing)) {
198
200
  fail(`plugin "${arg}" uses the removed legacy format; reinstall it from an explicit source`);
199
201
  }
202
+ const expectedDigest = digestOption(process.argv);
200
203
  const result = await installPlugin(existing.spec, (line) => console.log(line), {
201
204
  expectedId: existing.name,
202
205
  allowScripts: process.argv.includes("--allow-scripts"),
206
+ ...(expectedDigest ? { expectedDigest } : {}),
203
207
  onConflict: () => true,
204
208
  approveCapabilities: (manifest, current) => approvePluginCapabilities(manifest, current?.grants ?? [], process.argv.includes("--grant-all")),
205
209
  });
@@ -641,6 +645,13 @@ function optionValue(args, option) {
641
645
  fail(`${option}: missing value`);
642
646
  return value;
643
647
  }
648
+ function digestOption(args) {
649
+ const expected = optionValue(args, "--expect-digest");
650
+ const legacyAlias = optionValue(args, "--expected-digest");
651
+ if (expected && legacyAlias)
652
+ fail("pass only one digest expectation option");
653
+ return expected ?? legacyAlias;
654
+ }
644
655
  async function readStdinBounded(maxBytes) {
645
656
  const chunks = [];
646
657
  let bytes = 0;
@@ -1,4 +1,5 @@
1
1
  import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
2
+ import { type DaemonChromeInspectionConfigureInput, type DaemonChromeInspectionStatus, type DaemonShutdownIfIdleResult } from "@rynx-ai/protocol/control";
2
3
  import { type RemoteRuntimeRpcMethod, type RemoteRuntimeRpcParams, type RemoteRuntimeRpcResultFor } from "@rynx-ai/protocol/remote-runtime-rpc";
3
4
  import { type PairingOffer } from "@rynx-ai/protocol/direct-runtime";
4
5
  import { type RuntimeBrowserBootstrapCredential, type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -61,6 +62,14 @@ export declare function ensureResidentDaemon(): Promise<ResidentDaemon>;
61
62
  export declare function getResidentDaemonIdentity(): Promise<ResidentDaemonIdentity>;
62
63
  /** Read the transport-independent status of the resident daemon process. */
63
64
  export declare function getResidentDaemonRuntimeStatus(): Promise<DaemonStatus>;
65
+ /**
66
+ * Ask the resident daemon to perform one final activity check and stop only
67
+ * when idle. Busy is a normal result so the caller can ask the user to resolve
68
+ * the listed work and explicitly retry.
69
+ */
70
+ export declare function shutdownResidentDaemonIfIdle(): Promise<DaemonShutdownIfIdleResult>;
71
+ export declare function getResidentChromeInspectionStatus(): Promise<DaemonChromeInspectionStatus>;
72
+ export declare function configureResidentChromeInspection(input: DaemonChromeInspectionConfigureInput): Promise<DaemonChromeInspectionStatus>;
64
73
  /** Create one pairing offer on the resident daemon for a selected reachable route. */
65
74
  export declare function createResidentRemoteRuntimePairingOffer(input?: {
66
75
  clientLabel?: string;
@@ -1,6 +1,7 @@
1
1
  import { open } from "node:fs/promises";
2
2
  import { isAbsolute } from "node:path";
3
3
  import { isDaemonCoreCompatible, parseDaemonStatus, } from "@rynx-ai/protocol/remote-runtime";
4
+ import { DAEMON_CHROME_INSPECTION_PATH, DAEMON_SHUTDOWN_IF_IDLE_PATH, parseDaemonChromeInspectionConfigureInput, parseDaemonChromeInspectionStatus, parseDaemonShutdownIfIdleResult, } from "@rynx-ai/protocol/control";
4
5
  import { parseRemoteRuntimeRpcRequest, parseRemoteRuntimeRpcResponseForMethod, REMOTE_RUNTIME_RPC_MAX_FRAME_BYTES, REMOTE_RUNTIME_RPC_METHOD_METADATA, } from "@rynx-ai/protocol/remote-runtime-rpc";
5
6
  import { parsePairingOffer, } from "@rynx-ai/protocol/direct-runtime";
6
7
  import { RUNTIME_BROWSER_BOOTSTRAP_PATH, RUNTIME_BROWSER_CAPABILITY_ENV, RUNTIME_BROWSER_CAPABILITY_HEADER, RUNTIME_BROWSER_CONTEXT_FILE_ENV, RUNTIME_BROWSER_MANAGEMENT_PATH_PREFIX, RUNTIME_BROWSER_RPC_PATH, RUNTIME_BROWSER_SESSION_ID_ENV, RUNTIME_BROWSER_SESSION_ID_HEADER, parseRuntimeBrowserBootstrapCredential, parseRuntimeBrowserEndpointDescriptor, } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -9,6 +10,7 @@ import { ensureDaemonControlEndpoint } from "./control-endpoint.js";
9
10
  export { connectResidentDesktopBrowserHost, } from "./desktop-browser-host-client.js";
10
11
  const IDENTITY_TIMEOUT_MS = 5_000;
11
12
  const RUNTIME_STATUS_TIMEOUT_MS = 5_000;
13
+ const DAEMON_SHUTDOWN_TIMEOUT_MS = 5_000;
12
14
  const PLUGIN_CLI_TIMEOUT_MS = 120_000;
13
15
  const PLUGIN_CLI_BODY_MAX_BYTES = 512 * 1024;
14
16
  const PLUGIN_CLI_STDIN_MAX_BYTES = 256 * 1024;
@@ -17,6 +19,9 @@ const PLUGIN_CLI_ARGS_TOTAL_MAX_BYTES = 32 * 1024;
17
19
  const PLUGIN_CLI_ARGS_MAX_COUNT = 64;
18
20
  const IDENTITY_RESPONSE_MAX_BYTES = 16 * 1024;
19
21
  const RUNTIME_STATUS_RESPONSE_MAX_BYTES = 64 * 1024;
22
+ const DAEMON_SHUTDOWN_RESPONSE_MAX_BYTES = 4 * 1024;
23
+ const CHROME_INSPECTION_TIMEOUT_MS = 30_000;
24
+ const CHROME_INSPECTION_RESPONSE_MAX_BYTES = 256 * 1024;
20
25
  const REMOTE_RUNTIME_ADMIN_RESPONSE_MAX_BYTES = 64 * 1024;
21
26
  const RUNTIME_TARGET_RESPONSE_MAX_BYTES = 128 * 1024;
22
27
  const RUNTIME_TARGET_REQUEST_MAX_BYTES = 64 * 1024;
@@ -97,6 +102,45 @@ export async function getResidentDaemonRuntimeStatus() {
97
102
  }
98
103
  return status;
99
104
  }
105
+ /**
106
+ * Ask the resident daemon to perform one final activity check and stop only
107
+ * when idle. Busy is a normal result so the caller can ask the user to resolve
108
+ * the listed work and explicitly retry.
109
+ */
110
+ export async function shutdownResidentDaemonIfIdle() {
111
+ const endpoint = await ensureDaemonControlEndpoint();
112
+ assertLoopbackOrigin(endpoint.origin);
113
+ const response = await fetch(`${endpoint.origin}${DAEMON_SHUTDOWN_IF_IDLE_PATH}`, {
114
+ method: "POST",
115
+ headers: managementHeaders(endpoint.managementToken),
116
+ signal: AbortSignal.timeout(DAEMON_SHUTDOWN_TIMEOUT_MS),
117
+ });
118
+ const body = await readBoundedResponse(response, DAEMON_SHUTDOWN_RESPONSE_MAX_BYTES);
119
+ if (response.status !== 202 && response.status !== 409) {
120
+ throw daemonRequestError("daemon shutdown-if-idle request", response.status, body);
121
+ }
122
+ let result;
123
+ try {
124
+ result = parseDaemonShutdownIfIdleResult(JSON.parse(body));
125
+ }
126
+ catch (error) {
127
+ throw new Error(`daemon returned an invalid shutdown-if-idle response: ${messageOf(error)}`);
128
+ }
129
+ if (response.status === 202 && result.outcome !== "accepted" ||
130
+ response.status === 409 && result.outcome !== "busy") {
131
+ throw new Error("daemon returned a shutdown-if-idle outcome inconsistent with HTTP status");
132
+ }
133
+ return result;
134
+ }
135
+ export async function getResidentChromeInspectionStatus() {
136
+ const result = await chromeInspectionRequest("GET");
137
+ return parseDaemonChromeInspectionStatus(result);
138
+ }
139
+ export async function configureResidentChromeInspection(input) {
140
+ const normalized = parseDaemonChromeInspectionConfigureInput(input);
141
+ const result = await chromeInspectionRequest("PUT", normalized);
142
+ return parseDaemonChromeInspectionStatus(result);
143
+ }
100
144
  /** Create one pairing offer on the resident daemon for a selected reachable route. */
101
145
  export async function createResidentRemoteRuntimePairingOffer(input = {}) {
102
146
  const endpoint = await ensureDaemonControlEndpoint();
@@ -545,6 +589,25 @@ export async function invokeResidentPluginCommand(pluginId, args, options = {})
545
589
  function managementHeaders(token) {
546
590
  return { "x-rynx-management-token": token };
547
591
  }
592
+ async function chromeInspectionRequest(method, bodyValue) {
593
+ const endpoint = await ensureDaemonControlEndpoint();
594
+ assertLoopbackOrigin(endpoint.origin);
595
+ const body = bodyValue === undefined ? undefined : JSON.stringify(bodyValue);
596
+ const response = await fetch(`${endpoint.origin}${DAEMON_CHROME_INSPECTION_PATH}`, {
597
+ method,
598
+ headers: {
599
+ ...managementHeaders(endpoint.managementToken),
600
+ ...(body === undefined ? {} : { "content-type": "application/json" }),
601
+ },
602
+ ...(body === undefined ? {} : { body }),
603
+ signal: AbortSignal.timeout(CHROME_INSPECTION_TIMEOUT_MS),
604
+ });
605
+ const responseBody = await readBoundedResponse(response, CHROME_INSPECTION_RESPONSE_MAX_BYTES);
606
+ if (!response.ok) {
607
+ throw daemonRequestError("Chrome inspection request", response.status, responseBody);
608
+ }
609
+ return parseJsonObject(responseBody, "daemon returned an invalid Chrome inspection response");
610
+ }
548
611
  function assertLoopbackOrigin(origin) {
549
612
  const hostname = new URL(origin).hostname;
550
613
  if (hostname !== "127.0.0.1" && hostname !== "[::1]" && hostname !== "::1") {
@@ -0,0 +1,8 @@
1
+ export interface DaemonBuildStatus {
2
+ restartRequired: boolean;
3
+ }
4
+ /** Detect a same-installation rebuild that happened after this daemon
5
+ * incarnation started. Package path/version changes are handled by the PM2
6
+ * lifecycle wrapper; this is the development and in-place-update fallback. */
7
+ export declare function daemonEntryRestartRequired(entryPath: string, daemonStartedAtMs: number): boolean;
8
+ export declare function createDaemonBuildStatus(entryPath: string, daemonStartedAtMs: number): () => DaemonBuildStatus;
@@ -0,0 +1,18 @@
1
+ import { statSync } from "node:fs";
2
+ const FILE_TIMESTAMP_TOLERANCE_MS = 1_000;
3
+ /** Detect a same-installation rebuild that happened after this daemon
4
+ * incarnation started. Package path/version changes are handled by the PM2
5
+ * lifecycle wrapper; this is the development and in-place-update fallback. */
6
+ export function daemonEntryRestartRequired(entryPath, daemonStartedAtMs) {
7
+ try {
8
+ return statSync(entryPath).mtimeMs > daemonStartedAtMs + FILE_TIMESTAMP_TOLERANCE_MS;
9
+ }
10
+ catch {
11
+ return true;
12
+ }
13
+ }
14
+ export function createDaemonBuildStatus(entryPath, daemonStartedAtMs) {
15
+ return () => ({
16
+ restartRequired: daemonEntryRestartRequired(entryPath, daemonStartedAtMs),
17
+ });
18
+ }
@@ -1,7 +1,11 @@
1
1
  import { type AppConfig } from "@rynx-ai/core";
2
- import { startServer, startDirectRuntimeServer } from "@rynx-ai/server";
2
+ import { type DaemonActivitySnapshot } from "@rynx-ai/protocol/control";
3
+ import { SessionBrowserService, startServer, startDirectRuntimeServer, type DirectRuntimeBrowserInspectHost, type SessionBrowserHost } from "@rynx-ai/server";
4
+ import { AppBrowserHostSupervisor, type AppBrowserHostSupervisorOptions } from "./app-browser-host-supervisor.js";
5
+ import { ChromeInspectionManager, type ChromeInspectionManagerOptions } from "./chrome-inspection-manager.js";
3
6
  import { type DaemonOwnerLease } from "./daemon-owner.js";
4
7
  import { type DirectRuntimeConfig } from "./direct-runtime-config.js";
8
+ export declare function daemonActivityBlocksShutdown(activity: DaemonActivitySnapshot): boolean;
5
9
  export interface StartRynxDaemonServerOptions {
6
10
  config?: AppConfig;
7
11
  warn?: (message: string) => void;
@@ -13,5 +17,18 @@ export interface StartRynxDaemonServerOptions {
13
17
  loadDirectConfig?: () => Promise<DirectRuntimeConfig>;
14
18
  /** Test seam for listener lifecycle and bind failures. */
15
19
  startDirectServer?: typeof startDirectRuntimeServer;
20
+ /** Test seam for resident Chrome inspection settings and gateway lifecycle. */
21
+ createChromeInspectionManager?: (options: ChromeInspectionManagerOptions) => Promise<Pick<ChromeInspectionManager, "status" | "configure" | "shutdown">>;
22
+ /** Test seam for the App-only embedded Chromium process supervisor. */
23
+ createBrowserHostSupervisor?: (options: AppBrowserHostSupervisorOptions) => Pick<AppBrowserHostSupervisor, "ensureConnected" | "restart" | "stop">;
16
24
  }
17
- export declare function startRynxDaemonServer({ config, warn, writeEndpoint, acquireOwner, loadDirectConfig, startDirectServer, }?: StartRynxDaemonServerOptions): Promise<Awaited<ReturnType<typeof startServer>>>;
25
+ export declare function startRynxDaemonServer({ config, warn, writeEndpoint, acquireOwner, loadDirectConfig, startDirectServer, createChromeInspectionManager, createBrowserHostSupervisor, }?: StartRynxDaemonServerOptions): Promise<Awaited<ReturnType<typeof startServer>>>;
26
+ export declare function createSupervisedAppSessionBrowserHost(desktop: SessionBrowserHost, supervisor: Pick<AppBrowserHostSupervisor, "ensureConnected" | "restart">): SessionBrowserHost;
27
+ /**
28
+ * Resolve the authenticated semantic target inside its owning Runtime and
29
+ * attach directly to that Page's loopback CDP WebSocket. No endpoint is ever
30
+ * projected into the Direct protocol.
31
+ */
32
+ export declare function createDirectRuntimeBrowserInspectHost(browsers: Pick<SessionBrowserService, "getInspectionPageEndpoint">, options?: {
33
+ openTimeoutMs?: number;
34
+ }): DirectRuntimeBrowserInspectHost;