@rynx-ai/daemon 0.1.11-beta.2 → 0.1.11-beta.3

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 (49) hide show
  1. package/bundled-plugins/plugins/lark/dist/runtime.js +2 -5
  2. package/bundled-plugins/plugins/lark/dist/settings.js +2 -2
  3. package/bundled-plugins/plugins/lark/package.json +1 -1
  4. package/dist/browser-artifact-management.d.ts +4 -1
  5. package/dist/browser-artifact-management.js +18 -10
  6. package/dist/browser-artifacts.d.ts +1 -0
  7. package/dist/browser-artifacts.js +1 -0
  8. package/dist/chrome-for-testing-runtime.d.ts +27 -0
  9. package/dist/chrome-for-testing-runtime.js +193 -0
  10. package/dist/chrome-for-testing-store.d.ts +2 -0
  11. package/dist/chrome-for-testing-store.js +9 -0
  12. package/dist/daemon-server.d.ts +4 -1
  13. package/dist/daemon-server.js +137 -32
  14. package/dist/db.js +2 -0
  15. package/dist/entry-path.d.ts +3 -0
  16. package/dist/entry-path.js +12 -0
  17. package/dist/index-daemon.js +3 -87
  18. package/dist/lifecycle.d.ts +12 -0
  19. package/dist/lifecycle.js +16 -0
  20. package/dist/maintenance-lease.d.ts +39 -0
  21. package/dist/maintenance-lease.js +250 -0
  22. package/dist/plugin-host-rpc.d.ts +19 -1
  23. package/dist/plugin-host-rpc.js +163 -23
  24. package/dist/plugin-installer.js +59 -0
  25. package/dist/plugin-supervisor.d.ts +5 -0
  26. package/dist/plugin-supervisor.js +218 -29
  27. package/dist/pm2.d.ts +11 -2
  28. package/dist/pm2.js +116 -19
  29. package/dist/process.d.ts +12 -0
  30. package/dist/process.js +91 -0
  31. package/dist/session-launch-operations.d.ts +10 -1
  32. package/dist/session-launch-operations.js +15 -6
  33. package/dist/setup-service.d.ts +20 -0
  34. package/dist/setup-service.js +38 -0
  35. package/package.json +33 -20
  36. package/dist/browser-cli-args.d.ts +0 -20
  37. package/dist/browser-cli-args.js +0 -100
  38. package/dist/cli.d.ts +0 -2
  39. package/dist/cli.js +0 -550
  40. package/dist/control-client.d.ts +0 -122
  41. package/dist/control-client.js +0 -855
  42. package/dist/control-endpoint.d.ts +0 -14
  43. package/dist/control-endpoint.js +0 -124
  44. package/dist/desktop-browser-host-client.d.ts +0 -35
  45. package/dist/desktop-browser-host-client.js +0 -415
  46. package/dist/setup.d.ts +0 -20
  47. package/dist/setup.js +0 -386
  48. package/dist/update.d.ts +0 -25
  49. package/dist/update.js +0 -133
package/dist/pm2.js CHANGED
@@ -10,14 +10,18 @@
10
10
  import { spawn, spawnSync } from "node:child_process";
11
11
  import { mkdirSync, realpathSync } from "node:fs";
12
12
  import { createRequire } from "node:module";
13
- import { dirname, join, resolve } from "node:path";
14
- import { fileURLToPath } from "node:url";
13
+ import { userInfo } from "node:os";
14
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
15
15
  import { loadConfig, rynxHome } from "@rynx-ai/core";
16
16
  import { daemonEntryRestartRequired } from "./daemon-build-status.js";
17
+ import { resolveDaemonEntry } from "./entry-path.js";
17
18
  const require = createRequire(import.meta.url);
18
19
  /** pm2 app name — single resident daemon per machine. */
19
20
  const APP_NAME = "rynx";
20
21
  const PM2_COMMAND_TIMEOUT_MS = 5_000;
22
+ const LOGIN_SHELL_PATH_TIMEOUT_MS = 5_000;
23
+ const LOGIN_SHELL_PATH_MAX_BYTES = 256 * 1024;
24
+ const REFRESH_LOGIN_SHELL_PATH_ENV = "RYNX_REFRESH_LOGIN_SHELL_PATH";
21
25
  const DAEMON_PACKAGE_VERSION = require("../package.json").version;
22
26
  /**
23
27
  * Build a dialable control-console URL from the daemon's bind host/port. Bind-all
@@ -49,10 +53,6 @@ function pm2Bin() {
49
53
  // pm2 ships its CLI at bin/pm2; run it with our own node.
50
54
  return require.resolve("pm2/bin/pm2");
51
55
  }
52
- /** `index-daemon.js` sits next to this file in `dist/`. */
53
- function daemonEntryPath() {
54
- return join(dirname(fileURLToPath(import.meta.url)), "index-daemon.js");
55
- }
56
56
  function logsDir() {
57
57
  const dir = join(rynxHome(), "logs");
58
58
  mkdirSync(dir, { recursive: true });
@@ -71,11 +71,58 @@ function nodeRuntimeEnv() {
71
71
  : {}),
72
72
  };
73
73
  }
74
+ /**
75
+ * A detached plugin updater intentionally receives a reduced environment and
76
+ * therefore has no SHELL. Starting the replacement daemon with that reduced
77
+ * PATH would make user-installed runtimes (for example an fnm-managed Codex)
78
+ * disappear after every upgrade. Recover the user's login-shell PATH only at
79
+ * this boundary; normal interactive starts keep their exact caller environment.
80
+ */
81
+ function daemonStartEnv() {
82
+ const env = nodeRuntimeEnv();
83
+ const refreshLoginPath = env[REFRESH_LOGIN_SHELL_PATH_ENV] === "1";
84
+ delete env[REFRESH_LOGIN_SHELL_PATH_ENV];
85
+ if (!refreshLoginPath)
86
+ return env;
87
+ const shell = userInfo().shell?.trim();
88
+ if (!shell) {
89
+ throw new Error("cannot refresh daemon PATH: the user login shell is unavailable");
90
+ }
91
+ const result = spawnSync(shell, ["-lic", `printf '\\036%s\\037' "$PATH"`], {
92
+ encoding: "utf8",
93
+ // Do not feed an fnm/nvm "multishell" directory inherited from the old
94
+ // daemon back into the next login shell. Version managers prepend a new
95
+ // directory on every shell startup, so reusing the old PATH would grow
96
+ // it indefinitely across upgrades.
97
+ env: { ...env, SHELL: shell, PATH: loginShellBaselinePath() },
98
+ timeout: LOGIN_SHELL_PATH_TIMEOUT_MS,
99
+ killSignal: "SIGTERM",
100
+ maxBuffer: LOGIN_SHELL_PATH_MAX_BYTES,
101
+ });
102
+ if (result.error) {
103
+ throw new Error(`cannot refresh daemon PATH: ${result.error.message}`);
104
+ }
105
+ if (result.status !== 0) {
106
+ throw new Error(`cannot refresh daemon PATH: login shell exited ${result.status ?? "without status"}`);
107
+ }
108
+ const path = extractFramedPath(result.stdout);
109
+ if (!path) {
110
+ throw new Error("cannot refresh daemon PATH: login shell returned no framed PATH");
111
+ }
112
+ const npmPrefix = env.NPM_CONFIG_PREFIX?.trim();
113
+ const npmBin = npmPrefix && isAbsolute(npmPrefix)
114
+ ? join(npmPrefix, "bin")
115
+ : "";
116
+ const refreshedPath = npmBin === ""
117
+ ? path
118
+ : [npmBin, ...path.split(delimiter).filter((entry) => entry !== npmBin)].join(delimiter);
119
+ return { ...env, PATH: refreshedPath, SHELL: shell };
120
+ }
74
121
  /** Run a pm2 command inheriting stdio; returns its exit code. */
75
- function runPm2(args, quiet = false) {
122
+ function runPm2(args, quiet = false, env = nodeRuntimeEnv()) {
76
123
  const res = spawnSync(process.execPath, [pm2Bin(), ...args], {
77
124
  stdio: quiet ? "ignore" : "inherit",
78
- env: nodeRuntimeEnv(),
125
+ env,
79
126
  timeout: PM2_COMMAND_TIMEOUT_MS,
80
127
  killSignal: "SIGTERM",
81
128
  });
@@ -117,7 +164,7 @@ function pm2ErrorMessage(error) {
117
164
  * entry from scratch (delete + start) so config flags are always fresh; the
118
165
  * `force === false` path short-circuits when it's already online.
119
166
  */
120
- export function startDaemon({ force, quiet = false }) {
167
+ export function startDaemon({ force, quiet = false, launchEnvironment, }) {
121
168
  let existing;
122
169
  try {
123
170
  existing = describe();
@@ -139,6 +186,20 @@ export function startDaemon({ force, quiet = false }) {
139
186
  if (staleBuild && !quiet) {
140
187
  console.log("A newer daemon build is present on disk; restarting Rynx.");
141
188
  }
189
+ let startEnv;
190
+ try {
191
+ // Resolve the replacement environment before deleting an existing app.
192
+ // A broken/hung login shell must leave the known-good daemon untouched.
193
+ startEnv = {
194
+ ...daemonStartEnv(),
195
+ ...launchEnvironment,
196
+ };
197
+ }
198
+ catch (error) {
199
+ if (!quiet)
200
+ console.error(error instanceof Error ? error.message : String(error));
201
+ return 1;
202
+ }
142
203
  const dir = logsDir();
143
204
  if (existing) {
144
205
  const removed = runPm2(["delete", APP_NAME], quiet);
@@ -147,7 +208,7 @@ export function startDaemon({ force, quiet = false }) {
147
208
  }
148
209
  const status = runPm2([
149
210
  "start",
150
- daemonEntryPath(),
211
+ resolveDaemonEntry(),
151
212
  "--name",
152
213
  APP_NAME,
153
214
  // App and CLI share one daemon-owned state/workspace root.
@@ -163,7 +224,7 @@ export function startDaemon({ force, quiet = false }) {
163
224
  join(dir, "err.log"),
164
225
  "--merge-logs",
165
226
  "--time",
166
- ], quiet);
227
+ ], quiet, startEnv);
167
228
  if (status === 0 && !quiet) {
168
229
  console.log(`rynx ${force ? "restarted" : "started"} (pm2 app "${APP_NAME}", logs: ${dir}).`);
169
230
  console.log(`Control console: ${styleUrl(controlConsoleUrl())}`);
@@ -173,16 +234,35 @@ export function startDaemon({ force, quiet = false }) {
173
234
  export function isPm2DaemonOnline() {
174
235
  return describe()?.pm2_env?.status === "online";
175
236
  }
176
- /** Stop and remove the daemon from pm2's process list. */
237
+ /**
238
+ * Stop the daemon and terminate its dedicated pm2 supervisor.
239
+ *
240
+ * PM2_HOME lives below RYNX_HOME, so this supervisor cannot own unrelated
241
+ * applications. Leaving it alive across an npm replacement is unsafe: the
242
+ * daemon process container path is resolved inside the old pm2 package. Once
243
+ * npm swaps @rynx-ai/daemon, that cached path may no longer exist and every
244
+ * subsequent `rynx start` enters a restart loop before our code can run.
245
+ */
177
246
  export function stopDaemon() {
178
- if (!describe()) {
247
+ const existing = describe();
248
+ if (!existing) {
249
+ // Even an empty process list can belong to a resident PM2 daemon whose
250
+ // module path is about to be replaced. `pm2 kill` is idempotent and is
251
+ // the only reliable package-upgrade boundary.
252
+ const killed = runPm2(["kill"], true);
253
+ if (killed !== 0)
254
+ return killed;
179
255
  console.log("rynx is not running.");
180
256
  return 0;
181
257
  }
182
- const status = runPm2(["delete", APP_NAME]);
183
- if (status === 0)
184
- console.log("rynx stopped.");
185
- return status;
258
+ const removed = runPm2(["delete", APP_NAME]);
259
+ if (removed !== 0)
260
+ return removed;
261
+ const killed = runPm2(["kill"]);
262
+ if (killed !== 0)
263
+ return killed;
264
+ console.log("rynx stopped.");
265
+ return 0;
186
266
  }
187
267
  /** Print a one-line status; exit 0 only when online. */
188
268
  export function statusDaemon() {
@@ -209,7 +289,7 @@ export function streamLogs() {
209
289
  }
210
290
  function residentBuildRestartRequired(app) {
211
291
  const runningEntry = app.pm2_env?.pm_exec_path;
212
- if (runningEntry && !sameEntryPath(runningEntry, daemonEntryPath()))
292
+ if (runningEntry && !sameEntryPath(runningEntry, resolveDaemonEntry()))
213
293
  return true;
214
294
  const runningVersion = app.pm2_env?.version;
215
295
  if (runningVersion &&
@@ -220,7 +300,7 @@ function residentBuildRestartRequired(app) {
220
300
  const startedAt = app.pm2_env?.pm_uptime;
221
301
  return typeof startedAt === "number" &&
222
302
  Number.isFinite(startedAt) &&
223
- daemonEntryRestartRequired(daemonEntryPath(), startedAt);
303
+ daemonEntryRestartRequired(resolveDaemonEntry(), startedAt);
224
304
  }
225
305
  function sameEntryPath(left, right) {
226
306
  try {
@@ -230,3 +310,20 @@ function sameEntryPath(left, right) {
230
310
  return resolve(left) === resolve(right);
231
311
  }
232
312
  }
313
+ function extractFramedPath(stdout) {
314
+ if (!stdout)
315
+ return undefined;
316
+ const start = stdout.lastIndexOf("\x1e");
317
+ if (start < 0)
318
+ return undefined;
319
+ const end = stdout.indexOf("\x1f", start + 1);
320
+ if (end < 0)
321
+ return undefined;
322
+ const value = stdout.slice(start + 1, end);
323
+ return value.trim() ? value : undefined;
324
+ }
325
+ function loginShellBaselinePath() {
326
+ return process.platform === "darwin"
327
+ ? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
328
+ : "/usr/local/bin:/usr/bin:/bin";
329
+ }
@@ -0,0 +1,12 @@
1
+ interface SupervisorProcess {
2
+ connected?: boolean;
3
+ disconnect?: () => void;
4
+ }
5
+ /** Release a parent/supervisor IPC handle after graceful daemon shutdown. */
6
+ export declare function disconnectSupervisorIpc(target?: SupervisorProcess): void;
7
+ /**
8
+ * Run the daemon in the current process. Process ownership and executable
9
+ * selection belong to the caller (CLI standalone lifecycle or App lifecycle).
10
+ */
11
+ export declare function runDaemonProcess(): Promise<void>;
12
+ export {};
@@ -0,0 +1,91 @@
1
+ import { startRynxDaemonServer } from "./daemon-server.js";
2
+ import { ensureBundledOfficialPlugins } from "./bundled-plugins.js";
3
+ import { installStdioEpipeGuard } from "./stdio-epipe-guard.js";
4
+ /** Release a parent/supervisor IPC handle after graceful daemon shutdown. */
5
+ export function disconnectSupervisorIpc(target = process) {
6
+ if (target.connected && typeof target.disconnect === "function") {
7
+ target.disconnect();
8
+ }
9
+ }
10
+ /**
11
+ * Run the daemon in the current process. Process ownership and executable
12
+ * selection belong to the caller (CLI standalone lifecycle or App lifecycle).
13
+ */
14
+ export async function runDaemonProcess() {
15
+ installStdioEpipeGuard();
16
+ const followsApp = process.env.RYNX_DAEMON_LIFECYCLE === "follow-app";
17
+ let parentDisconnected = followsApp && !process.connected;
18
+ const recordParentDisconnect = () => {
19
+ parentDisconnected = true;
20
+ };
21
+ if (followsApp)
22
+ process.once("disconnect", recordParentDisconnect);
23
+ const preparePlugins = async () => {
24
+ try {
25
+ const bundled = await ensureBundledOfficialPlugins({ reclaimHostLeases: true });
26
+ if (bundled.status === "ready") {
27
+ console.log(JSON.stringify({
28
+ level: "info",
29
+ type: "bundled_plugins_ready",
30
+ installed: bundled.installed,
31
+ updated: bundled.updated,
32
+ }));
33
+ }
34
+ }
35
+ catch (error) {
36
+ console.error(JSON.stringify({
37
+ level: "error",
38
+ type: "bundled_plugins_failed",
39
+ error: error instanceof Error ? error.message : String(error),
40
+ }));
41
+ }
42
+ };
43
+ let server;
44
+ try {
45
+ server = await startRynxDaemonServer({
46
+ preparePlugins,
47
+ onShutdownComplete: () => disconnectSupervisorIpc(),
48
+ });
49
+ }
50
+ catch (error) {
51
+ process.off("disconnect", recordParentDisconnect);
52
+ throw error;
53
+ }
54
+ let shutdownPromise;
55
+ const shutdown = (signal) => {
56
+ if (shutdownPromise)
57
+ return shutdownPromise;
58
+ process.off("SIGINT", onSigint);
59
+ process.off("SIGTERM", onSigterm);
60
+ process.off("disconnect", onDisconnect);
61
+ shutdownPromise = (async () => {
62
+ console.log(JSON.stringify({ level: "info", type: "shutdown", signal }));
63
+ await server.shutdown();
64
+ })();
65
+ shutdownPromise.catch((error) => {
66
+ process.exitCode = 1;
67
+ console.error(`Shutdown failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
68
+ });
69
+ return shutdownPromise;
70
+ };
71
+ const onSigint = () => {
72
+ void shutdown("SIGINT");
73
+ };
74
+ const onSigterm = () => {
75
+ void shutdown("SIGTERM");
76
+ };
77
+ const onDisconnect = () => {
78
+ if (process.env.RYNX_DAEMON_LIFECYCLE === "follow-app") {
79
+ void shutdown("APP_DISCONNECT");
80
+ }
81
+ };
82
+ process.once("SIGINT", onSigint);
83
+ process.once("SIGTERM", onSigterm);
84
+ if (followsApp) {
85
+ process.once("disconnect", onDisconnect);
86
+ process.off("disconnect", recordParentDisconnect);
87
+ if (parentDisconnected || !process.connected) {
88
+ void shutdown("APP_DISCONNECT");
89
+ }
90
+ }
91
+ }
@@ -9,13 +9,22 @@ export interface EnsurePluginSessionLaunchInput {
9
9
  operationId: string;
10
10
  /** SHA-256 of the host-normalized, execution-affecting launch request. */
11
11
  requestHash: string;
12
+ /** SHA-256 before mutable filesystem selectors are canonicalized. */
13
+ rawRequestHash?: string;
12
14
  session: PluginSessionLaunchMeta;
13
15
  }
14
16
  export interface EnsurePluginSessionLaunchResult {
15
17
  sessionId: string;
16
18
  disposition: "created" | "replayed";
17
19
  }
18
- export type ReplayPluginSessionLaunchInput = Omit<EnsurePluginSessionLaunchInput, "session">;
20
+ export type ReplayPluginSessionLaunchInput = Omit<EnsurePluginSessionLaunchInput, "session"> & {
21
+ /**
22
+ * A caller doing a pre-canonicalization lookup may treat a hash mismatch as
23
+ * a cache miss, then retry with the canonical hash. The canonical lookup
24
+ * must keep the default strict conflict behavior.
25
+ */
26
+ allowRequestHashMismatch?: boolean;
27
+ };
19
28
  export declare class SessionLaunchOperationConflictError extends Error {
20
29
  readonly pluginId: string;
21
30
  readonly scopeId: string;
@@ -48,7 +48,10 @@ export function replayPluginSessionLaunch(input) {
48
48
  const existing = readOperation(pluginId, scopeId, operationId);
49
49
  if (!existing)
50
50
  return undefined;
51
- if (existing.request_hash !== requestHash) {
51
+ if (existing.request_hash !== requestHash &&
52
+ existing.raw_request_hash !== requestHash) {
53
+ if (input.allowRequestHashMismatch)
54
+ return undefined;
52
55
  throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
53
56
  }
54
57
  if (existing.state === "deleted") {
@@ -75,10 +78,16 @@ export function ensurePluginSessionLaunch(input) {
75
78
  const scopeId = requiredId(input.scopeId, "scopeId");
76
79
  const operationId = requiredId(input.operationId, "operationId");
77
80
  const requestHash = requiredRequestHash(input.requestHash);
81
+ const rawRequestHash = input.rawRequestHash
82
+ ? requiredRequestHash(input.rawRequestHash)
83
+ : requestHash;
78
84
  const ensure = db().transaction(() => {
79
85
  const existing = readOperation(pluginId, scopeId, operationId);
80
86
  if (existing) {
81
- if (existing.request_hash !== requestHash) {
87
+ if (existing.request_hash !== requestHash &&
88
+ existing.raw_request_hash !== requestHash &&
89
+ existing.request_hash !== rawRequestHash &&
90
+ existing.raw_request_hash !== rawRequestHash) {
82
91
  throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
83
92
  }
84
93
  if (existing.state === "deleted") {
@@ -110,10 +119,10 @@ export function ensurePluginSessionLaunch(input) {
110
119
  }
111
120
  db()
112
121
  .prepare(`INSERT INTO plugin_session_launch_operations
113
- (plugin_id, scope_id, operation_id, request_hash, session_id, state,
122
+ (plugin_id, scope_id, operation_id, request_hash, raw_request_hash, session_id, state,
114
123
  created_at, updated_at, deleted_at)
115
- VALUES (?, ?, ?, ?, ?, 'active', ?, ?, NULL)`)
116
- .run(pluginId, scopeId, operationId, requestHash, sessionId, now, now);
124
+ VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, NULL)`)
125
+ .run(pluginId, scopeId, operationId, requestHash, rawRequestHash, sessionId, now, now);
117
126
  return { disposition: "created", sessionId };
118
127
  });
119
128
  const result = ensure.immediate();
@@ -124,7 +133,7 @@ export function ensurePluginSessionLaunch(input) {
124
133
  }
125
134
  function readOperation(pluginId, scopeId, operationId) {
126
135
  return db()
127
- .prepare(`SELECT request_hash, session_id, state
136
+ .prepare(`SELECT request_hash, raw_request_hash, session_id, state
128
137
  FROM plugin_session_launch_operations
129
138
  WHERE plugin_id = ? AND scope_id = ? AND operation_id = ?`)
130
139
  .get(pluginId, scopeId, operationId);
@@ -0,0 +1,20 @@
1
+ export interface DaemonDiagnosticSnapshot {
2
+ databasePath: string;
3
+ databaseExists: boolean;
4
+ pluginCount: number;
5
+ agentCount: number;
6
+ browser: {
7
+ supported: false;
8
+ } | {
9
+ supported: true;
10
+ installedVersion?: string;
11
+ error?: string;
12
+ };
13
+ }
14
+ export declare function prepareBundledPlugins(): Promise<{
15
+ status: "ready" | "unavailable";
16
+ installed: string[];
17
+ updated: string[];
18
+ unchanged: string[];
19
+ }>;
20
+ export declare function inspectDaemonDiagnostics(): Promise<DaemonDiagnosticSnapshot>;
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { listAgentSpecs } from "@rynx-ai/core";
3
+ import { createChromeForTestingArtifactStore, isChromeForTestingHostSupported } from "./chrome-for-testing-store.js";
4
+ import { ensureBundledOfficialPlugins } from "./bundled-plugins.js";
5
+ import { dbPath } from "./db.js";
6
+ import { listPlugins } from "./plugin-store.js";
7
+ export async function prepareBundledPlugins() {
8
+ return ensureBundledOfficialPlugins();
9
+ }
10
+ export async function inspectDaemonDiagnostics() {
11
+ const databasePath = dbPath();
12
+ let browser;
13
+ if (!isChromeForTestingHostSupported()) {
14
+ browser = { supported: false };
15
+ }
16
+ else {
17
+ try {
18
+ const installed = createChromeForTestingArtifactStore().readActive();
19
+ browser = {
20
+ supported: true,
21
+ ...(installed ? { installedVersion: installed.version } : {}),
22
+ };
23
+ }
24
+ catch (error) {
25
+ browser = {
26
+ supported: true,
27
+ error: error instanceof Error ? error.message : String(error),
28
+ };
29
+ }
30
+ }
31
+ return {
32
+ databasePath,
33
+ databaseExists: existsSync(databasePath),
34
+ pluginCount: existsSync(databasePath) ? listPlugins().length : 0,
35
+ agentCount: (await listAgentSpecs()).length,
36
+ browser,
37
+ };
38
+ }
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@rynx-ai/daemon",
3
- "version": "0.1.11-beta.2",
3
+ "version": "0.1.11-beta.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
7
7
  "directory": "packages/daemon"
8
8
  },
9
- "description": "Rynx daemon CLI: supervises the local agent control plane and isolated optional plugin runtimes.",
9
+ "description": "Rynx resident control-plane service and standalone lifecycle implementation.",
10
10
  "type": "module",
11
+ "engines": {
12
+ "node": ">=22"
13
+ },
11
14
  "publishConfig": {
12
15
  "registry": "https://registry.npmjs.org/",
13
16
  "access": "public"
@@ -16,38 +19,48 @@
16
19
  "dist",
17
20
  "bundled-plugins"
18
21
  ],
19
- "bin": {
20
- "rynx": "./dist/cli.js"
21
- },
22
- "main": "./dist/index-daemon.js",
23
22
  "exports": {
24
- "./client": {
25
- "types": "./dist/control-client.d.ts",
26
- "default": "./dist/control-client.js"
23
+ "./entry-path": {
24
+ "types": "./dist/entry-path.d.ts",
25
+ "default": "./dist/entry-path.js"
26
+ },
27
+ "./lifecycle": {
28
+ "types": "./dist/lifecycle.d.ts",
29
+ "default": "./dist/lifecycle.js"
30
+ },
31
+ "./browser-artifacts": {
32
+ "types": "./dist/browser-artifacts.d.ts",
33
+ "default": "./dist/browser-artifacts.js"
34
+ },
35
+ "./setup-service": {
36
+ "types": "./dist/setup-service.d.ts",
37
+ "default": "./dist/setup-service.js"
38
+ },
39
+ "./internal/process": {
40
+ "types": "./dist/process.d.ts",
41
+ "default": "./dist/process.js"
27
42
  }
28
43
  },
29
44
  "dependencies": {
30
- "@clack/prompts": "^1.6.0",
31
45
  "better-sqlite3": "^12.11.1",
32
46
  "extract-zip": "2.0.1",
33
47
  "pm2": "^6.0.0",
34
48
  "tar": "^7.5.19",
35
49
  "ws": "^8.21.0",
36
- "@rynx-ai/cli": "0.1.11-beta.2",
37
- "@rynx-ai/core": "0.1.11-beta.2",
38
- "@rynx-ai/emulator": "0.1.11-beta.2",
39
- "@rynx-ai/plugin-runner": "0.1.11-beta.2",
40
- "@rynx-ai/plugin-sdk": "0.1.11-beta.2",
41
- "@rynx-ai/protocol": "0.1.11-beta.2",
42
- "@rynx-ai/remote-runtime-client": "0.1.11-beta.2",
43
- "@rynx-ai/server": "0.1.11-beta.2"
50
+ "@rynx-ai/core": "0.1.11-beta.3",
51
+ "@rynx-ai/emulator": "0.1.11-beta.3",
52
+ "@rynx-ai/plugin-runner": "0.1.11-beta.3",
53
+ "@rynx-ai/protocol": "0.1.11-beta.3",
54
+ "@rynx-ai/remote-runtime-client": "0.1.11-beta.3",
55
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.3",
56
+ "@rynx-ai/server": "0.1.11-beta.3"
44
57
  },
45
58
  "devDependencies": {
46
59
  "@types/ws": "^8.18.1",
47
- "@rynx-ai/plugin-channel-lark": "0.1.11"
60
+ "@rynx-ai/plugin-channel-lark": "0.1.11-beta.3"
48
61
  },
49
62
  "scripts": {
50
- "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/cli.js dist/index-daemon.js && node ../../scripts/stage-bundled-plugins.mjs",
63
+ "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node ../../scripts/stage-bundled-plugins.mjs",
51
64
  "dev": "tsx watch --tsconfig ../../tsconfig.json src/index-daemon.ts"
52
65
  }
53
66
  }
@@ -1,20 +0,0 @@
1
- export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "close";
2
- export interface BrowserCliArgs {
3
- subcommand: BrowserCliSubcommand;
4
- json: boolean;
5
- ensure: boolean;
6
- channel?: string;
7
- version?: string;
8
- runtime?: string;
9
- session?: string;
10
- url?: string;
11
- }
12
- export interface BrowserCliTarget {
13
- runtimeSelector: string;
14
- sessionId: string;
15
- credential?: RuntimeBrowserBootstrapCredential;
16
- }
17
- export declare function parseBrowserCliArgs(args: readonly string[]): BrowserCliArgs;
18
- /** Resolve authority without ever turning a managed Session into an operator. */
19
- export declare function resolveBrowserCliTarget(args: BrowserCliArgs, managedCredential: RuntimeBrowserBootstrapCredential | undefined): BrowserCliTarget;
20
- import type { RuntimeBrowserBootstrapCredential } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -1,100 +0,0 @@
1
- const SPECS = {
2
- install: { values: ["--channel", "--version"], flags: ["--json"] },
3
- update: { values: ["--channel"], flags: ["--json"] },
4
- version: { values: [], flags: ["--json"] },
5
- clean: { values: [], flags: ["--json"] },
6
- open: {
7
- values: ["--url", "--session", "--runtime"],
8
- flags: ["--json"],
9
- positionalUrl: true,
10
- },
11
- status: { values: ["--session", "--runtime"], flags: ["--json"] },
12
- pages: { values: ["--session", "--runtime"], flags: ["--json"] },
13
- endpoint: { values: ["--session"], flags: ["--ensure", "--json"] },
14
- close: { values: ["--session", "--runtime"], flags: ["--json"] },
15
- };
16
- export function parseBrowserCliArgs(args) {
17
- const subcommand = args[0];
18
- if (!isBrowserSubcommand(subcommand)) {
19
- throw new Error("browser: expected install, update, version, clean, open, status, pages, endpoint, or close");
20
- }
21
- const spec = SPECS[subcommand];
22
- const values = new Map();
23
- const flags = new Set();
24
- const positionals = [];
25
- for (let index = 1; index < args.length; index += 1) {
26
- const arg = args[index];
27
- if (arg.startsWith("--")) {
28
- if (spec.flags.includes(arg)) {
29
- if (flags.has(arg))
30
- throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
31
- flags.add(arg);
32
- continue;
33
- }
34
- if (spec.values.includes(arg)) {
35
- if (values.has(arg))
36
- throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
37
- const value = args[index + 1];
38
- if (!value || value.startsWith("--")) {
39
- throw new Error(`browser ${subcommand}: ${arg} requires a value`);
40
- }
41
- values.set(arg, value);
42
- index += 1;
43
- continue;
44
- }
45
- throw new Error(`browser ${subcommand}: unknown option ${arg}`);
46
- }
47
- positionals.push(arg);
48
- }
49
- if (!spec.positionalUrl && positionals.length > 0) {
50
- throw new Error(`browser ${subcommand}: unexpected argument ${positionals[0]}`);
51
- }
52
- if (positionals.length > 1) {
53
- throw new Error(`browser ${subcommand}: unexpected extra argument ${positionals[1]}`);
54
- }
55
- if (positionals.length === 1 && values.has("--url")) {
56
- throw new Error("browser open: pass the URL once, either positionally or with --url");
57
- }
58
- return {
59
- subcommand,
60
- json: flags.has("--json"),
61
- ensure: flags.has("--ensure"),
62
- ...(values.has("--channel") ? { channel: values.get("--channel") } : {}),
63
- ...(values.has("--version") ? { version: values.get("--version") } : {}),
64
- ...(values.has("--runtime") ? { runtime: values.get("--runtime") } : {}),
65
- ...(values.has("--session") ? { session: values.get("--session") } : {}),
66
- ...(values.has("--url") || positionals[0]
67
- ? { url: values.get("--url") ?? positionals[0] }
68
- : {}),
69
- };
70
- }
71
- /** Resolve authority without ever turning a managed Session into an operator. */
72
- export function resolveBrowserCliTarget(args, managedCredential) {
73
- const runtimeSelector = args.runtime ?? "local";
74
- if (runtimeSelector !== "local") {
75
- if (managedCredential) {
76
- throw new Error("browser: a managed Session cannot select another Runtime");
77
- }
78
- if (!args.session) {
79
- throw new Error("browser: --session is required when selecting a remote Runtime");
80
- }
81
- return { runtimeSelector, sessionId: args.session };
82
- }
83
- if (managedCredential) {
84
- if (args.session && args.session !== managedCredential.sessionId) {
85
- throw new Error("browser: a managed Session cannot control another Session with --session");
86
- }
87
- return {
88
- runtimeSelector: "local",
89
- sessionId: managedCredential.sessionId,
90
- credential: managedCredential,
91
- };
92
- }
93
- if (!args.session) {
94
- throw new Error("browser: run inside a managed Rynx Session or pass --session for operator access");
95
- }
96
- return { runtimeSelector: "local", sessionId: args.session };
97
- }
98
- function isBrowserSubcommand(value) {
99
- return value !== undefined && Object.hasOwn(SPECS, value);
100
- }
package/dist/cli.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};