agent-remnote 1.5.0 → 1.5.1

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/dist/main.js CHANGED
@@ -92343,29 +92343,235 @@ var WsClientLive = succeed10(WsClient, {
92343
92343
  })
92344
92344
  });
92345
92345
 
92346
- // src/lib/managedRuntimePaths.ts
92347
- import path19 from "node:path";
92348
- function resolveManagedStateFile(params3) {
92349
- const defaultPath = resolveUserFilePath(params3.defaultStateFilePath);
92350
- const explicitPath = params3.explicitStateFilePath ? resolveUserFilePath(params3.explicitStateFilePath) : undefined;
92351
- const candidatePath = params3.candidate ? resolveUserFilePath(params3.candidate) : undefined;
92352
- if (!candidatePath) {
92353
- return explicitPath ?? defaultPath;
92346
+ // src/lib/apiUrls.ts
92347
+ function normalizeApiBasePath2(basePath) {
92348
+ const trimmed2 = basePath.trim();
92349
+ if (!trimmed2)
92350
+ return "/v1";
92351
+ const normalized = trimmed2.startsWith("/") ? trimmed2 : `/${trimmed2}`;
92352
+ const withoutTrailing = normalized.replace(/\/+$/, "");
92353
+ return withoutTrailing && withoutTrailing !== "/" ? withoutTrailing : "/";
92354
+ }
92355
+ function normalizeBaseUrl(baseUrl) {
92356
+ return baseUrl.trim().replace(/\/+$/, "");
92357
+ }
92358
+ function resolveBasePrefix(baseUrl, fallbackBasePath) {
92359
+ const parsed = new URL(normalizeBaseUrl(baseUrl));
92360
+ const pathname = normalizeApiBasePath2(parsed.pathname);
92361
+ if (pathname && pathname !== "/")
92362
+ return normalizeApiBasePath2(pathname);
92363
+ return normalizeApiBasePath2(fallbackBasePath);
92364
+ }
92365
+ function normalizeRoutePath(routePath) {
92366
+ const trimmed2 = routePath.trim();
92367
+ if (!trimmed2)
92368
+ return "";
92369
+ return `/${trimmed2.replace(/^\/+/, "")}`;
92370
+ }
92371
+ function buildApiBaseUrl(baseUrl, fallbackBasePath = "/v1") {
92372
+ const parsed = new URL(normalizeBaseUrl(baseUrl));
92373
+ const prefix2 = resolveBasePrefix(baseUrl, fallbackBasePath);
92374
+ return prefix2 === "/" ? parsed.origin : `${parsed.origin}${prefix2}`;
92375
+ }
92376
+ function apiLocalBaseUrl(port3, basePath = "/v1") {
92377
+ return buildApiBaseUrl(`http://127.0.0.1:${port3}`, basePath);
92378
+ }
92379
+ function apiContainerBaseUrl(port3, basePath = "/v1") {
92380
+ return buildApiBaseUrl(`http://host.docker.internal:${port3}`, basePath);
92381
+ }
92382
+ function joinApiUrl(baseUrl, routePath, fallbackBasePath = "/v1") {
92383
+ return `${buildApiBaseUrl(baseUrl, fallbackBasePath)}${normalizeRoutePath(routePath)}`;
92384
+ }
92385
+
92386
+ // src/services/HostApiClient.ts
92387
+ function normalizeBaseUrl2(baseUrl) {
92388
+ return baseUrl.trim().replace(/\/+$/, "");
92389
+ }
92390
+ function apiTimeoutError(params3) {
92391
+ return new CliError({
92392
+ code: "API_TIMEOUT",
92393
+ message: `API timeout after ${params3.timeoutMs}ms`,
92394
+ exitCode: 1,
92395
+ details: { base_url: params3.baseUrl, path: params3.path, timeout_ms: params3.timeoutMs }
92396
+ });
92397
+ }
92398
+ function apiUnavailableError(params3) {
92399
+ return new CliError({
92400
+ code: "API_UNAVAILABLE",
92401
+ message: String(params3.error?.message || params3.error || "API request failed"),
92402
+ exitCode: 1,
92403
+ details: { base_url: params3.baseUrl, path: params3.path }
92404
+ });
92405
+ }
92406
+ function exitCodeFromRemoteCode(code2) {
92407
+ return code2 === "INVALID_ARGS" || code2 === "INVALID_PAYLOAD" || code2 === "PAYLOAD_TOO_LARGE" ? 2 : 1;
92408
+ }
92409
+ function parseEnvelope(raw4) {
92410
+ if (!raw4 || typeof raw4 !== "object") {
92411
+ return { ok: false, error: { code: "INTERNAL", message: "Invalid API response envelope" } };
92354
92412
  }
92355
- if (candidatePath === defaultPath)
92356
- return candidatePath;
92357
- if (explicitPath && candidatePath === explicitPath)
92358
- return candidatePath;
92359
- const pidRoot = path19.dirname(params3.pidFilePath);
92360
- const rel = path19.relative(pidRoot, candidatePath);
92361
- if (rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel)) {
92362
- return candidatePath;
92413
+ return raw4;
92414
+ }
92415
+ function buildQuery(params3) {
92416
+ const sp = new URLSearchParams;
92417
+ for (const [key, value8] of Object.entries(params3)) {
92418
+ if (value8 === undefined || value8 === null)
92419
+ continue;
92420
+ if (typeof value8 === "string") {
92421
+ if (!value8.trim())
92422
+ continue;
92423
+ sp.set(key, value8);
92424
+ continue;
92425
+ }
92426
+ if (typeof value8 === "number" || typeof value8 === "boolean") {
92427
+ sp.set(key, String(value8));
92428
+ }
92363
92429
  }
92364
- return explicitPath ?? defaultPath;
92430
+ const query = sp.toString();
92431
+ return query ? `?${query}` : "";
92432
+ }
92433
+ function requestJson(params3) {
92434
+ const timeoutMs = Math.max(1, params3.timeoutMs ?? 15000);
92435
+ const baseUrl = normalizeBaseUrl2(params3.baseUrl);
92436
+ const url2 = joinApiUrl(baseUrl, params3.path, params3.basePath);
92437
+ return async((resume2) => {
92438
+ const controller = new AbortController;
92439
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
92440
+ fetch(url2, {
92441
+ method: params3.method,
92442
+ headers: params3.method === "POST" ? { "content-type": "application/json" } : undefined,
92443
+ body: params3.body === undefined ? undefined : JSON.stringify(params3.body),
92444
+ signal: controller.signal
92445
+ }).then(async (response) => {
92446
+ clearTimeout(timer2);
92447
+ let parsed;
92448
+ try {
92449
+ parsed = parseEnvelope(await response.json());
92450
+ } catch (error4) {
92451
+ resume2(fail8(new CliError({
92452
+ code: "API_UNAVAILABLE",
92453
+ message: "API returned a non-JSON response",
92454
+ exitCode: 1,
92455
+ details: { url: url2, status: response.status, error: String(error4?.message || error4) }
92456
+ })));
92457
+ return;
92458
+ }
92459
+ if (parsed.ok === true) {
92460
+ resume2(succeed8(parsed.data));
92461
+ return;
92462
+ }
92463
+ const code2 = typeof parsed.error?.code === "string" ? parsed.error.code : "INTERNAL";
92464
+ const message2 = typeof parsed.error?.message === "string" ? parsed.error.message : "API request failed";
92465
+ const hint = Array.isArray(parsed.hint) ? parsed.hint.map(String) : undefined;
92466
+ resume2(fail8(new CliError({
92467
+ code: code2,
92468
+ message: message2,
92469
+ exitCode: exitCodeFromRemoteCode(code2),
92470
+ details: parsed.error?.details,
92471
+ hint
92472
+ })));
92473
+ }).catch((error4) => {
92474
+ clearTimeout(timer2);
92475
+ if (error4?.name === "AbortError") {
92476
+ resume2(fail8(apiTimeoutError({ baseUrl, path: params3.path, timeoutMs })));
92477
+ return;
92478
+ }
92479
+ resume2(fail8(apiUnavailableError({ baseUrl, path: params3.path, error: error4 })));
92480
+ });
92481
+ return sync3(() => {
92482
+ clearTimeout(timer2);
92483
+ controller.abort();
92484
+ });
92485
+ });
92365
92486
  }
92366
92487
 
92488
+ class HostApiClient extends Tag2("HostApiClient")() {
92489
+ }
92490
+ var HostApiClientLive = effect(HostApiClient, gen2(function* () {
92491
+ const cfg = yield* AppConfig;
92492
+ const basePath = cfg.apiBasePath ?? "/v1";
92493
+ const request = (params3) => requestJson({ ...params3, basePath });
92494
+ return {
92495
+ resolveRefValue: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/ref/resolve", method: "POST", body, timeoutMs }),
92496
+ resolvePlacement: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/placement/resolve", method: "POST", body, timeoutMs }),
92497
+ resolveStableSiblingRange: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/selection/stable-sibling-range", method: "POST", body, timeoutMs }),
92498
+ health: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/health", method: "GET", timeoutMs }),
92499
+ status: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/status", method: "GET", timeoutMs }),
92500
+ uiContextSnapshot: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92501
+ baseUrl,
92502
+ path: `/plugin/ui-context/snapshot${buildQuery({ stateFile, staleMs })}`,
92503
+ method: "GET",
92504
+ timeoutMs
92505
+ }),
92506
+ uiContextPage: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92507
+ baseUrl,
92508
+ path: `/plugin/ui-context/page${buildQuery({ stateFile, staleMs })}`,
92509
+ method: "GET",
92510
+ timeoutMs
92511
+ }),
92512
+ uiContextFocusedRem: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92513
+ baseUrl,
92514
+ path: `/plugin/ui-context/focused-rem${buildQuery({ stateFile, staleMs })}`,
92515
+ method: "GET",
92516
+ timeoutMs
92517
+ }),
92518
+ uiContextDescribe: ({ baseUrl, stateFile, staleMs, selectionLimit, timeoutMs }) => request({
92519
+ baseUrl,
92520
+ path: `/plugin/ui-context/describe${buildQuery({ stateFile, staleMs, selectionLimit })}`,
92521
+ method: "GET",
92522
+ timeoutMs
92523
+ }),
92524
+ selectionSnapshot: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92525
+ baseUrl,
92526
+ path: `/plugin/selection/snapshot${buildQuery({ stateFile, staleMs })}`,
92527
+ method: "GET",
92528
+ timeoutMs
92529
+ }),
92530
+ selectionRoots: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92531
+ baseUrl,
92532
+ path: `/plugin/selection/roots${buildQuery({ stateFile, staleMs })}`,
92533
+ method: "GET",
92534
+ timeoutMs
92535
+ }),
92536
+ selectionCurrent: ({ baseUrl, stateFile, staleMs, timeoutMs }) => request({
92537
+ baseUrl,
92538
+ path: `/plugin/selection/current${buildQuery({ stateFile, staleMs })}`,
92539
+ method: "GET",
92540
+ timeoutMs
92541
+ }),
92542
+ pluginCurrent: ({ baseUrl, stateFile, staleMs, selectionLimit, timeoutMs }) => request({
92543
+ baseUrl,
92544
+ path: `/plugin/current${buildQuery({ stateFile, staleMs, selectionLimit })}`,
92545
+ method: "GET",
92546
+ timeoutMs
92547
+ }),
92548
+ selectionOutline: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/plugin/selection/outline", method: "POST", body, timeoutMs }),
92549
+ uiContext: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/ui-context", method: "GET", timeoutMs }),
92550
+ selection: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/selection", method: "GET", timeoutMs }),
92551
+ searchDb: ({ baseUrl, ...body }) => request({ baseUrl, path: "/search/db", method: "POST", body }),
92552
+ searchPlugin: ({ baseUrl, ...body }) => request({ baseUrl, path: "/search/plugin", method: "POST", body }),
92553
+ writeApply: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/write/apply", method: "POST", body, timeoutMs }),
92554
+ readOutline: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/outline", method: "POST", body, timeoutMs }),
92555
+ readPageId: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/page-id", method: "POST", body, timeoutMs }),
92556
+ resolveRef: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/resolve-ref", method: "POST", body, timeoutMs }),
92557
+ byReference: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/by-reference", method: "POST", body, timeoutMs }),
92558
+ references: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/references", method: "POST", body, timeoutMs }),
92559
+ resolveQueryPowerup: ({ baseUrl, powerup, timeoutMs }) => request({ baseUrl, path: "/internal/query/resolve-powerup", method: "POST", body: { powerup }, timeoutMs }),
92560
+ query: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/query", method: "POST", body, timeoutMs }),
92561
+ dailyRemId: ({ baseUrl, date: date6, offsetDays, timeoutMs }) => request({
92562
+ baseUrl,
92563
+ path: `/daily/rem-id${buildQuery({ date: date6, offsetDays })}`,
92564
+ method: "GET",
92565
+ timeoutMs
92566
+ }),
92567
+ queueWait: ({ baseUrl, txnId, timeoutMs, pollMs }) => request({ baseUrl, path: "/queue/wait", method: "POST", body: { txnId, timeoutMs, pollMs } }),
92568
+ queueTxn: ({ baseUrl, txnId, timeoutMs }) => request({ baseUrl, path: `/queue/txns/${encodeURIComponent(txnId)}`, method: "GET", timeoutMs }),
92569
+ triggerSync: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/actions/trigger-sync", method: "POST", timeoutMs })
92570
+ };
92571
+ }));
92572
+
92367
92573
  // src/lib/pidTrust.ts
92368
- import path20 from "node:path";
92574
+ import path19 from "node:path";
92369
92575
  function normalizeToken(value8) {
92370
92576
  return value8.trim().replace(/\\/g, "/").toLowerCase();
92371
92577
  }
@@ -92376,7 +92582,7 @@ function collectCommandTokens(raw4) {
92376
92582
  if (!normalized)
92377
92583
  continue;
92378
92584
  const unquoted = normalized.replace(/^['"]|['"]$/g, "");
92379
- const base = path20.basename(unquoted);
92585
+ const base = path19.basename(unquoted);
92380
92586
  if (unquoted.includes("agent-remnote") || base.includes("agent-remnote")) {
92381
92587
  out.add("agent-remnote");
92382
92588
  }
@@ -92434,32 +92640,870 @@ function requireTrustedPidRecord(params3) {
92434
92640
  });
92435
92641
  }
92436
92642
 
92437
- // src/lib/statuslineArtifacts.ts
92438
- import { promises as fs15 } from "node:fs";
92439
-
92440
- // src/services/StatusLineFile.ts
92441
- import { promises as fs14 } from "node:fs";
92442
- import path21 from "node:path";
92443
- class StatusLineFile extends Tag2("StatusLineFile")() {
92444
- }
92445
- function defaultTextFile() {
92446
- return path21.join(homeDir(), ".agent-remnote", "status-line.txt");
92447
- }
92448
- function defaultJsonFile() {
92449
- return path21.join(homeDir(), ".agent-remnote", "status-line.json");
92450
- }
92451
- function ensureDir8(p3) {
92452
- return fs14.mkdir(path21.dirname(p3), { recursive: true }).then(() => {
92453
- return;
92454
- });
92455
- }
92456
- async function readFileOrEmpty(filePath) {
92643
+ // src/lib/runtimeBuildInfo.ts
92644
+ import { createHash as createHash3 } from "node:crypto";
92645
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
92646
+ import path20 from "node:path";
92647
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
92648
+ function packageInfo() {
92457
92649
  try {
92458
- return await fs14.readFile(filePath, "utf8");
92459
- } catch (e) {
92460
- if (e?.code === "ENOENT")
92461
- return "";
92462
- throw e;
92650
+ const raw4 = readFileSync2(new URL("../../package.json", import.meta.url), "utf8");
92651
+ const parsed = JSON.parse(raw4);
92652
+ const name = typeof parsed?.name === "string" && parsed.name.trim() ? parsed.name.trim() : "agent-remnote";
92653
+ const version = typeof parsed?.version === "string" && parsed.version.trim() ? parsed.version.trim() : "0.0.0";
92654
+ return { name, version };
92655
+ } catch {
92656
+ return { name: "agent-remnote", version: "0.0.0" };
92657
+ }
92658
+ }
92659
+ function fileMtimeMs(targetPath) {
92660
+ try {
92661
+ return Math.floor(statSync(targetPath).mtimeMs);
92662
+ } catch {
92663
+ return 0;
92664
+ }
92665
+ }
92666
+ function latestMtimeMs(dirPath) {
92667
+ if (!existsSync2(dirPath))
92668
+ return 0;
92669
+ let max7 = 0;
92670
+ const stack = [dirPath];
92671
+ while (stack.length > 0) {
92672
+ const current2 = stack.pop();
92673
+ let entries2 = [];
92674
+ try {
92675
+ entries2 = readdirSync(current2);
92676
+ } catch {
92677
+ continue;
92678
+ }
92679
+ for (const entry of entries2) {
92680
+ const full = path20.join(current2, entry);
92681
+ let st;
92682
+ try {
92683
+ st = statSync(full);
92684
+ } catch {
92685
+ continue;
92686
+ }
92687
+ if (st.isDirectory()) {
92688
+ stack.push(full);
92689
+ } else if (st.isFile()) {
92690
+ max7 = Math.max(max7, Math.floor(st.mtimeMs));
92691
+ }
92692
+ }
92693
+ }
92694
+ return max7;
92695
+ }
92696
+ function detectMode() {
92697
+ const url2 = import.meta.url;
92698
+ if (url2.includes("/src/"))
92699
+ return "src";
92700
+ if (url2.includes("/dist/"))
92701
+ return "dist";
92702
+ return "unknown";
92703
+ }
92704
+ function computeDefaultBuildInfo() {
92705
+ const pkg = packageInfo();
92706
+ const mode = detectMode();
92707
+ const srcDir = new URL("../", import.meta.url);
92708
+ const packageJson = new URL("../../package.json", import.meta.url);
92709
+ const sourceStamp = Math.max(latestMtimeMs(fileURLToPath3(srcDir)), fileMtimeMs(fileURLToPath3(packageJson)));
92710
+ const buildIdInput = `${pkg.name}
92711
+ ${pkg.version}
92712
+ ${mode}
92713
+ ${sourceStamp}`;
92714
+ const buildId = createHash3("sha256").update(buildIdInput).digest("hex").slice(0, 12);
92715
+ return {
92716
+ name: pkg.name,
92717
+ version: pkg.version,
92718
+ build_id: `${pkg.version}:${buildId}`,
92719
+ built_at: sourceStamp || Date.now(),
92720
+ source_stamp: sourceStamp || Date.now(),
92721
+ mode
92722
+ };
92723
+ }
92724
+ var cached4;
92725
+ function parseEnvNumber(raw4, fallback) {
92726
+ const parsed = Number(raw4);
92727
+ return Number.isFinite(parsed) ? parsed : fallback;
92728
+ }
92729
+ function currentRuntimeBuildInfo() {
92730
+ if (cached4)
92731
+ return cached4;
92732
+ const defaultInfo = computeDefaultBuildInfo();
92733
+ cached4 = {
92734
+ name: process.env.AGENT_REMNOTE_NAME?.trim() || defaultInfo.name,
92735
+ version: process.env.AGENT_REMNOTE_VERSION?.trim() || defaultInfo.version,
92736
+ build_id: process.env.AGENT_REMNOTE_BUILD_ID?.trim() || defaultInfo.build_id,
92737
+ built_at: parseEnvNumber(process.env.AGENT_REMNOTE_BUILD_AT, defaultInfo.built_at),
92738
+ source_stamp: parseEnvNumber(process.env.AGENT_REMNOTE_SOURCE_STAMP, defaultInfo.source_stamp),
92739
+ mode: process.env.AGENT_REMNOTE_BUILD_MODE === "src" || process.env.AGENT_REMNOTE_BUILD_MODE === "dist" || process.env.AGENT_REMNOTE_BUILD_MODE === "unknown" ? process.env.AGENT_REMNOTE_BUILD_MODE : defaultInfo.mode
92740
+ };
92741
+ return cached4;
92742
+ }
92743
+ function runtimeVersionWarnings(params3) {
92744
+ const warnings = [];
92745
+ const compare2 = (label, other) => {
92746
+ if (!other)
92747
+ return;
92748
+ if (other.build_id !== params3.current.build_id) {
92749
+ warnings.push(`${label} build mismatch: current=${params3.current.build_id} live=${other.build_id}`);
92750
+ }
92751
+ };
92752
+ compare2("daemon", params3.daemon);
92753
+ compare2("plugin", params3.plugin);
92754
+ compare2("api", params3.api);
92755
+ return warnings;
92756
+ }
92757
+
92758
+ // src/commands/api/_shared.ts
92759
+ var API_HEALTH_TIMEOUT_MS = 2000;
92760
+ var API_START_WAIT_DEFAULT_MS = 15000;
92761
+ var API_STOP_WAIT_DEFAULT_MS = 5000;
92762
+ function childCommandLine(params3) {
92763
+ const command = process.argv[0];
92764
+ const script = process.argv[1];
92765
+ if (!command || !script) {
92766
+ throw new CliError({
92767
+ code: "INTERNAL",
92768
+ message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
92769
+ exitCode: 1,
92770
+ details: { argv: process.argv }
92771
+ });
92772
+ }
92773
+ const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
92774
+ const args2 = [...execArgv, script, "--daemon-url", params3.wsUrl, "--store-db", params3.storeDb];
92775
+ if (params3.remnoteDb)
92776
+ args2.push("--remnote-db", params3.remnoteDb);
92777
+ args2.push("--api-base-path", params3.basePath);
92778
+ args2.push("api", "serve", "--host", params3.host, "--port", String(params3.port), "--state-file", params3.stateFile);
92779
+ return { command, args: args2 };
92780
+ }
92781
+ function toPidFileValue(params3) {
92782
+ return {
92783
+ pid: params3.pid,
92784
+ build: currentRuntimeBuildInfo(),
92785
+ started_at: params3.startedAt,
92786
+ host: params3.host,
92787
+ port: params3.port,
92788
+ base_path: params3.basePath,
92789
+ log_file: params3.logFile,
92790
+ state_file: params3.stateFile,
92791
+ cmd: params3.cmd
92792
+ };
92793
+ }
92794
+ function waitForApiHealth(baseUrl, waitMs) {
92795
+ return gen2(function* () {
92796
+ if (!Number.isFinite(waitMs) || waitMs < 0) {
92797
+ return yield* fail8(new CliError({
92798
+ code: "INVALID_ARGS",
92799
+ message: "--wait must be a non-negative integer (ms)",
92800
+ exitCode: 2,
92801
+ details: { wait_ms: waitMs }
92802
+ }));
92803
+ }
92804
+ if (waitMs === 0)
92805
+ return;
92806
+ const api = yield* HostApiClient;
92807
+ const deadline = Date.now() + waitMs;
92808
+ while (Date.now() < deadline) {
92809
+ const remaining = Math.max(0, deadline - Date.now());
92810
+ const res = yield* api.health({ baseUrl, timeoutMs: Math.min(API_HEALTH_TIMEOUT_MS, Math.max(1, remaining)) }).pipe(either3);
92811
+ if (isRight2(res))
92812
+ return;
92813
+ yield* sleep4(millis(300));
92814
+ }
92815
+ return yield* fail8(new CliError({
92816
+ code: "API_TIMEOUT",
92817
+ message: `Timed out waiting for host API to become available (${waitMs}ms)`,
92818
+ exitCode: 1,
92819
+ details: { base_url: baseUrl, wait_ms: waitMs },
92820
+ hint: ["agent-remnote api status --json", "agent-remnote api logs", "agent-remnote stack status --json"]
92821
+ }));
92822
+ });
92823
+ }
92824
+ function startApiDaemon(params3) {
92825
+ return gen2(function* () {
92826
+ const cfg = yield* AppConfig;
92827
+ const api = yield* HostApiClient;
92828
+ const apiFiles = yield* ApiDaemonFiles;
92829
+ const proc = yield* Process;
92830
+ const host = params3.host ?? cfg.apiHost ?? "0.0.0.0";
92831
+ const port3 = params3.port ?? cfg.apiPort ?? 3000;
92832
+ const basePath = params3.basePath ?? cfg.apiBasePath ?? "/v1";
92833
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? apiFiles.defaultPidFile());
92834
+ const logFilePath = resolveUserFilePath(params3.logFile ?? apiFiles.defaultLogFile());
92835
+ const stateFilePath = resolveUserFilePath(params3.stateFile ?? apiFiles.defaultStateFile());
92836
+ const localBaseUrl = apiLocalBaseUrl(port3, basePath);
92837
+ const existing = yield* apiFiles.readPidFile(pidFilePath);
92838
+ if (existing) {
92839
+ const alive = yield* proc.isPidRunning(existing.pid);
92840
+ if (!alive) {
92841
+ yield* apiFiles.deletePidFile(pidFilePath);
92842
+ } else {
92843
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
92844
+ return {
92845
+ started: false,
92846
+ pid: existing.pid,
92847
+ pid_file: pidFilePath,
92848
+ log_file: logFilePath,
92849
+ state_file: stateFilePath,
92850
+ base_url: localBaseUrl
92851
+ };
92852
+ }
92853
+ }
92854
+ const pre = yield* api.health({ baseUrl: localBaseUrl, timeoutMs: API_HEALTH_TIMEOUT_MS }).pipe(either3);
92855
+ if (isRight2(pre)) {
92856
+ return {
92857
+ started: false,
92858
+ pid_file: pidFilePath,
92859
+ log_file: logFilePath,
92860
+ state_file: stateFilePath,
92861
+ base_url: localBaseUrl
92862
+ };
92863
+ }
92864
+ const cmd = yield* try_3({
92865
+ try: () => childCommandLine({
92866
+ wsUrl: cfg.wsUrl,
92867
+ storeDb: cfg.storeDb,
92868
+ remnoteDb: cfg.remnoteDb,
92869
+ host,
92870
+ port: port3,
92871
+ basePath,
92872
+ stateFile: stateFilePath
92873
+ }),
92874
+ catch: (e) => isCliError(e) ? e : new CliError({
92875
+ code: "INTERNAL",
92876
+ message: "Failed to start host api",
92877
+ exitCode: 1,
92878
+ details: { error: String(e?.message || e) }
92879
+ })
92880
+ });
92881
+ const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
92882
+ yield* apiFiles.writePidFile(pidFilePath, toPidFileValue({
92883
+ pid,
92884
+ startedAt: Date.now(),
92885
+ host,
92886
+ port: port3,
92887
+ basePath,
92888
+ logFile: logFilePath,
92889
+ stateFile: stateFilePath,
92890
+ cmd: [cmd.command, ...cmd.args]
92891
+ }));
92892
+ yield* waitForApiHealth(localBaseUrl, params3.waitMs);
92893
+ return {
92894
+ started: true,
92895
+ pid,
92896
+ pid_file: pidFilePath,
92897
+ log_file: logFilePath,
92898
+ state_file: stateFilePath,
92899
+ base_url: localBaseUrl
92900
+ };
92901
+ });
92902
+ }
92903
+ function ensureApiDaemon(params3) {
92904
+ return gen2(function* () {
92905
+ const cfg = yield* AppConfig;
92906
+ const api = yield* HostApiClient;
92907
+ const apiFiles = yield* ApiDaemonFiles;
92908
+ const proc = yield* Process;
92909
+ const port3 = params3.port ?? cfg.apiPort ?? 3000;
92910
+ const basePath = cfg.apiBasePath ?? "/v1";
92911
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? apiFiles.defaultPidFile());
92912
+ const logFilePath = resolveUserFilePath(params3.logFile ?? apiFiles.defaultLogFile());
92913
+ const stateFilePath = resolveUserFilePath(params3.stateFile ?? apiFiles.defaultStateFile());
92914
+ const localBaseUrl = apiLocalBaseUrl(port3, basePath);
92915
+ const pre = yield* api.health({ baseUrl: localBaseUrl, timeoutMs: API_HEALTH_TIMEOUT_MS }).pipe(either3);
92916
+ if (isRight2(pre)) {
92917
+ const existing = yield* apiFiles.readPidFile(pidFilePath);
92918
+ if (existing) {
92919
+ const alive = yield* proc.isPidRunning(existing.pid);
92920
+ if (alive) {
92921
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
92922
+ return {
92923
+ started: false,
92924
+ pid: existing.pid,
92925
+ pid_file: pidFilePath,
92926
+ log_file: existing.log_file ?? logFilePath,
92927
+ state_file: existing.state_file ?? stateFilePath,
92928
+ base_url: localBaseUrl
92929
+ };
92930
+ }
92931
+ }
92932
+ return {
92933
+ started: false,
92934
+ pid_file: pidFilePath,
92935
+ log_file: logFilePath,
92936
+ state_file: stateFilePath,
92937
+ base_url: localBaseUrl
92938
+ };
92939
+ }
92940
+ return yield* startApiDaemon(params3);
92941
+ });
92942
+ }
92943
+
92944
+ // src/lib/pluginServerHealth.ts
92945
+ function checkPluginServerHealth(baseUrl, timeoutMs) {
92946
+ return tryPromise2({
92947
+ try: async () => {
92948
+ const controller = new AbortController;
92949
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
92950
+ try {
92951
+ const res = await fetch(`${baseUrl}/manifest.json`, { signal: controller.signal });
92952
+ if (!res.ok) {
92953
+ throw new Error(`Unexpected response status: ${res.status}`);
92954
+ }
92955
+ return { base_url: baseUrl };
92956
+ } finally {
92957
+ clearTimeout(timer2);
92958
+ }
92959
+ },
92960
+ catch: (error4) => new CliError({
92961
+ code: "PLUGIN_UNAVAILABLE",
92962
+ message: "Plugin server is unavailable",
92963
+ exitCode: 1,
92964
+ details: { base_url: baseUrl, error: String(error4?.message || error4) }
92965
+ })
92966
+ });
92967
+ }
92968
+ function waitForPluginServerHealth(baseUrl, waitMs, timeoutMs) {
92969
+ return gen2(function* () {
92970
+ if (!Number.isFinite(waitMs) || waitMs < 0) {
92971
+ return yield* fail8(new CliError({
92972
+ code: "INVALID_ARGS",
92973
+ message: "--wait must be a non-negative integer (ms)",
92974
+ exitCode: 2,
92975
+ details: { wait_ms: waitMs }
92976
+ }));
92977
+ }
92978
+ if (waitMs === 0)
92979
+ return;
92980
+ const deadline = Date.now() + waitMs;
92981
+ while (Date.now() < deadline) {
92982
+ const remaining = Math.max(0, deadline - Date.now());
92983
+ const res = yield* checkPluginServerHealth(baseUrl, Math.min(timeoutMs, Math.max(1, remaining))).pipe(either3);
92984
+ if (isRight2(res))
92985
+ return;
92986
+ yield* sleep4(millis(300));
92987
+ }
92988
+ return yield* fail8(new CliError({
92989
+ code: "PLUGIN_UNAVAILABLE",
92990
+ message: `Timed out waiting for plugin server to become available (${waitMs}ms)`,
92991
+ exitCode: 1,
92992
+ details: { base_url: baseUrl, wait_ms: waitMs },
92993
+ hint: ["agent-remnote plugin status --json", "agent-remnote plugin logs --lines 200"]
92994
+ }));
92995
+ });
92996
+ }
92997
+
92998
+ // src/commands/plugin/_shared.ts
92999
+ var PLUGIN_SERVER_HEALTH_TIMEOUT_MS = 2000;
93000
+ var PLUGIN_SERVER_START_WAIT_DEFAULT_MS = 15000;
93001
+ var PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS = 5000;
93002
+ var PLUGIN_SERVER_DEFAULT_HOST = "127.0.0.1";
93003
+ var PLUGIN_SERVER_DEFAULT_PORT = 8080;
93004
+ function pluginServerLocalBaseUrl(host, port3) {
93005
+ const normalizedHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
93006
+ return `http://${normalizedHost}:${port3}`;
93007
+ }
93008
+ function childCommandLine2(params3) {
93009
+ const command = process.argv[0];
93010
+ const script = process.argv[1];
93011
+ if (!command || !script) {
93012
+ throw new CliError({
93013
+ code: "INTERNAL",
93014
+ message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
93015
+ exitCode: 1,
93016
+ details: { argv: process.argv }
93017
+ });
93018
+ }
93019
+ const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
93020
+ const args2 = [...execArgv, script, "plugin", "serve", "--host", params3.host, "--port", String(params3.port), "--state-file", params3.stateFile];
93021
+ return { command, args: args2 };
93022
+ }
93023
+ function toPidFileValue2(params3) {
93024
+ return {
93025
+ pid: params3.pid,
93026
+ build: currentRuntimeBuildInfo(),
93027
+ started_at: params3.startedAt,
93028
+ host: params3.host,
93029
+ port: params3.port,
93030
+ log_file: params3.logFile,
93031
+ state_file: params3.stateFile,
93032
+ cmd: params3.cmd
93033
+ };
93034
+ }
93035
+ function startPluginServer(params3) {
93036
+ return gen2(function* () {
93037
+ const files = yield* PluginServerFiles;
93038
+ const proc = yield* Process;
93039
+ const host = params3.host ?? PLUGIN_SERVER_DEFAULT_HOST;
93040
+ const port3 = params3.port ?? PLUGIN_SERVER_DEFAULT_PORT;
93041
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? files.defaultPidFile());
93042
+ const logFilePath = resolveUserFilePath(params3.logFile ?? files.defaultLogFile());
93043
+ const stateFilePath = resolveUserFilePath(params3.stateFile ?? files.defaultStateFile());
93044
+ const baseUrl = pluginServerLocalBaseUrl(host, port3);
93045
+ const existing = yield* files.readPidFile(pidFilePath);
93046
+ if (existing) {
93047
+ const alive = yield* proc.isPidRunning(existing.pid);
93048
+ if (!alive) {
93049
+ yield* files.deletePidFile(pidFilePath);
93050
+ } else {
93051
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
93052
+ return {
93053
+ started: false,
93054
+ pid: existing.pid,
93055
+ pid_file: pidFilePath,
93056
+ log_file: existing.log_file ?? logFilePath,
93057
+ state_file: existing.state_file ?? stateFilePath,
93058
+ base_url: baseUrl
93059
+ };
93060
+ }
93061
+ }
93062
+ const pre = yield* checkPluginServerHealth(baseUrl, PLUGIN_SERVER_HEALTH_TIMEOUT_MS).pipe(either3);
93063
+ if (isRight2(pre)) {
93064
+ return {
93065
+ started: false,
93066
+ pid_file: pidFilePath,
93067
+ log_file: logFilePath,
93068
+ state_file: stateFilePath,
93069
+ base_url: baseUrl
93070
+ };
93071
+ }
93072
+ const cmd = yield* try_3({
93073
+ try: () => childCommandLine2({ host, port: port3, stateFile: stateFilePath }),
93074
+ catch: (error4) => isCliError(error4) ? error4 : new CliError({
93075
+ code: "INTERNAL",
93076
+ message: "Failed to start plugin server",
93077
+ exitCode: 1,
93078
+ details: { error: String(error4?.message || error4) }
93079
+ })
93080
+ });
93081
+ const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
93082
+ yield* files.writePidFile(pidFilePath, toPidFileValue2({
93083
+ pid,
93084
+ startedAt: Date.now(),
93085
+ host,
93086
+ port: port3,
93087
+ logFile: logFilePath,
93088
+ stateFile: stateFilePath,
93089
+ cmd: [cmd.command, ...cmd.args]
93090
+ }));
93091
+ yield* waitForPluginServerHealth(baseUrl, params3.waitMs, PLUGIN_SERVER_HEALTH_TIMEOUT_MS);
93092
+ return {
93093
+ started: true,
93094
+ pid,
93095
+ pid_file: pidFilePath,
93096
+ log_file: logFilePath,
93097
+ state_file: stateFilePath,
93098
+ base_url: baseUrl
93099
+ };
93100
+ });
93101
+ }
93102
+ function ensurePluginServer(params3) {
93103
+ return gen2(function* () {
93104
+ const files = yield* PluginServerFiles;
93105
+ const proc = yield* Process;
93106
+ const host = params3.host ?? PLUGIN_SERVER_DEFAULT_HOST;
93107
+ const port3 = params3.port ?? PLUGIN_SERVER_DEFAULT_PORT;
93108
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? files.defaultPidFile());
93109
+ const logFilePath = resolveUserFilePath(params3.logFile ?? files.defaultLogFile());
93110
+ const stateFilePath = resolveUserFilePath(params3.stateFile ?? files.defaultStateFile());
93111
+ const baseUrl = pluginServerLocalBaseUrl(host, port3);
93112
+ const pre = yield* checkPluginServerHealth(baseUrl, PLUGIN_SERVER_HEALTH_TIMEOUT_MS).pipe(either3);
93113
+ if (isRight2(pre)) {
93114
+ const existing = yield* files.readPidFile(pidFilePath);
93115
+ if (existing) {
93116
+ const alive = yield* proc.isPidRunning(existing.pid);
93117
+ if (alive) {
93118
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
93119
+ return {
93120
+ started: false,
93121
+ pid: existing.pid,
93122
+ pid_file: pidFilePath,
93123
+ log_file: existing.log_file ?? logFilePath,
93124
+ state_file: existing.state_file ?? stateFilePath,
93125
+ base_url: baseUrl
93126
+ };
93127
+ }
93128
+ }
93129
+ return {
93130
+ started: false,
93131
+ pid_file: pidFilePath,
93132
+ log_file: logFilePath,
93133
+ state_file: stateFilePath,
93134
+ base_url: baseUrl
93135
+ };
93136
+ }
93137
+ return yield* startPluginServer(params3);
93138
+ });
93139
+ }
93140
+
93141
+ // src/commands/ws/_shared.ts
93142
+ import path21 from "node:path";
93143
+ var WS_HEALTH_TIMEOUT_MS = 2000;
93144
+ var WS_START_WAIT_DEFAULT_MS = 15000;
93145
+ var WS_STOP_WAIT_DEFAULT_MS = 5000;
93146
+ function supervisorCommandLine(params3) {
93147
+ const command = process.argv[0];
93148
+ const script = process.argv[1];
93149
+ if (!command || !script) {
93150
+ throw new CliError({
93151
+ code: "INTERNAL",
93152
+ message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
93153
+ exitCode: 1,
93154
+ details: { argv: process.argv }
93155
+ });
93156
+ }
93157
+ const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
93158
+ return {
93159
+ command,
93160
+ args: [
93161
+ ...execArgv,
93162
+ script,
93163
+ "--daemon-url",
93164
+ params3.wsUrl,
93165
+ "--store-db",
93166
+ params3.storeDb,
93167
+ "daemon",
93168
+ "supervisor",
93169
+ "--pid-file",
93170
+ params3.pidFile,
93171
+ "--log-file",
93172
+ params3.logFile,
93173
+ "--state-file",
93174
+ params3.stateFile
93175
+ ]
93176
+ };
93177
+ }
93178
+ function toPidFileValue3(params3) {
93179
+ return {
93180
+ pid: params3.pid,
93181
+ build: currentRuntimeBuildInfo(),
93182
+ started_at: params3.startedAt,
93183
+ ws_url: params3.wsUrl,
93184
+ log_file: params3.logFile,
93185
+ cmd: params3.cmd,
93186
+ ws_bridge_state_file: params3.wsBridgeStateFile,
93187
+ status_line_file: params3.statusLineFile,
93188
+ status_line_json_file: params3.statusLineJsonFile
93189
+ };
93190
+ }
93191
+ function waitForHealth(url2, waitMs) {
93192
+ return gen2(function* () {
93193
+ if (!Number.isFinite(waitMs) || waitMs < 0) {
93194
+ return yield* fail8(new CliError({
93195
+ code: "INVALID_ARGS",
93196
+ message: "--wait must be a non-negative integer (ms)",
93197
+ exitCode: 2,
93198
+ details: { wait_ms: waitMs }
93199
+ }));
93200
+ }
93201
+ if (waitMs === 0)
93202
+ return;
93203
+ const ws = yield* WsClient;
93204
+ const deadline = Date.now() + waitMs;
93205
+ while (Date.now() < deadline) {
93206
+ const remaining = Math.max(0, deadline - Date.now());
93207
+ const res = yield* ws.health({ url: url2, timeoutMs: Math.min(WS_HEALTH_TIMEOUT_MS, Math.max(1, remaining)) }).pipe(either3);
93208
+ if (isRight2(res))
93209
+ return;
93210
+ yield* sleep4(millis(300));
93211
+ }
93212
+ return yield* fail8(new CliError({
93213
+ code: "WS_TIMEOUT",
93214
+ message: `Timed out waiting for WS to become available (${waitMs}ms)`,
93215
+ exitCode: 1,
93216
+ details: { url: url2, wait_ms: waitMs },
93217
+ hint: ["agent-remnote daemon status", "agent-remnote daemon logs", "agent-remnote daemon health --json"]
93218
+ }));
93219
+ });
93220
+ }
93221
+ function toInitialSupervisorState(now2) {
93222
+ return {
93223
+ status: "running",
93224
+ restart_count: 0,
93225
+ restart_window_started_at: now2,
93226
+ backoff_until: null,
93227
+ last_exit: null,
93228
+ failed_reason: null
93229
+ };
93230
+ }
93231
+ function defaultStateFilePathFromPidFile(pidFilePath) {
93232
+ return path21.join(path21.dirname(pidFilePath), "ws.state.json");
93233
+ }
93234
+ function startWsSupervisor(params3) {
93235
+ return gen2(function* () {
93236
+ const cfg = yield* AppConfig;
93237
+ const daemonFiles = yield* DaemonFiles;
93238
+ const supervisorState = yield* SupervisorState;
93239
+ const proc = yield* Process;
93240
+ const ws = yield* WsClient;
93241
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? daemonFiles.defaultPidFile());
93242
+ const logFilePath = resolveUserFilePath(params3.logFile ?? daemonFiles.defaultLogFile());
93243
+ const stateFilePath = defaultStateFilePathFromPidFile(pidFilePath);
93244
+ const existingPidFile = yield* daemonFiles.readPidFile(pidFilePath);
93245
+ if (existingPidFile) {
93246
+ const alive = yield* proc.isPidRunning(existingPidFile.pid);
93247
+ if (!alive) {
93248
+ yield* daemonFiles.deletePidFile(pidFilePath);
93249
+ yield* supervisorState.deleteStateFile(stateFilePath);
93250
+ } else {
93251
+ yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
93252
+ return {
93253
+ started: false,
93254
+ pid: existingPidFile.pid,
93255
+ pid_file: pidFilePath,
93256
+ log_file: existingPidFile.log_file ?? logFilePath
93257
+ };
93258
+ }
93259
+ }
93260
+ const pre = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93261
+ if (isRight2(pre)) {
93262
+ return { started: false, pid_file: pidFilePath, log_file: logFilePath };
93263
+ }
93264
+ const cmd = yield* try_3({
93265
+ try: () => supervisorCommandLine({
93266
+ wsUrl: cfg.wsUrl,
93267
+ storeDb: cfg.storeDb,
93268
+ pidFile: pidFilePath,
93269
+ logFile: logFilePath,
93270
+ stateFile: stateFilePath
93271
+ }),
93272
+ catch: (e) => isCliError(e) ? e : new CliError({
93273
+ code: "INTERNAL",
93274
+ message: "Failed to start supervisor",
93275
+ exitCode: 1,
93276
+ details: { error: String(e?.message || e) }
93277
+ })
93278
+ });
93279
+ const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
93280
+ const now2 = Date.now();
93281
+ yield* daemonFiles.writePidFile(pidFilePath, {
93282
+ ...toPidFileValue3({
93283
+ pid,
93284
+ startedAt: now2,
93285
+ wsUrl: cfg.wsUrl,
93286
+ logFile: logFilePath,
93287
+ cmd: [cmd.command, ...cmd.args],
93288
+ wsBridgeStateFile: cfg.wsStateFile.path,
93289
+ statusLineFile: cfg.statusLineFile,
93290
+ statusLineJsonFile: cfg.statusLineJsonFile
93291
+ }),
93292
+ mode: "supervisor",
93293
+ child_pid: null,
93294
+ state_file: stateFilePath
93295
+ });
93296
+ yield* supervisorState.writeStateFile(stateFilePath, toInitialSupervisorState(now2));
93297
+ yield* waitForHealth(cfg.wsUrl, params3.waitMs);
93298
+ return { started: true, pid, pid_file: pidFilePath, log_file: logFilePath };
93299
+ });
93300
+ }
93301
+ function ensureWsSupervisor(params3) {
93302
+ return gen2(function* () {
93303
+ const cfg = yield* AppConfig;
93304
+ const ws = yield* WsClient;
93305
+ const pre = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93306
+ if (isRight2(pre)) {
93307
+ const daemonFiles = yield* DaemonFiles;
93308
+ const proc = yield* Process;
93309
+ const pidFilePath = resolveUserFilePath(params3.pidFile ?? daemonFiles.defaultPidFile());
93310
+ const logFilePath = resolveUserFilePath(params3.logFile ?? daemonFiles.defaultLogFile());
93311
+ const existingPidFile = yield* daemonFiles.readPidFile(pidFilePath);
93312
+ if (existingPidFile) {
93313
+ const alive = yield* proc.isPidRunning(existingPidFile.pid);
93314
+ if (alive) {
93315
+ yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
93316
+ return {
93317
+ started: false,
93318
+ pid: existingPidFile.pid,
93319
+ pid_file: pidFilePath,
93320
+ log_file: existingPidFile.log_file ?? logFilePath
93321
+ };
93322
+ }
93323
+ }
93324
+ return {
93325
+ started: false,
93326
+ pid_file: pidFilePath,
93327
+ log_file: logFilePath
93328
+ };
93329
+ }
93330
+ return yield* startWsSupervisor(params3);
93331
+ });
93332
+ }
93333
+
93334
+ // src/lib/managedRuntimePaths.ts
93335
+ import path22 from "node:path";
93336
+ function resolveManagedStateFile(params3) {
93337
+ const defaultPath = resolveUserFilePath(params3.defaultStateFilePath);
93338
+ const explicitPath = params3.explicitStateFilePath ? resolveUserFilePath(params3.explicitStateFilePath) : undefined;
93339
+ const candidatePath = params3.candidate ? resolveUserFilePath(params3.candidate) : undefined;
93340
+ if (!candidatePath) {
93341
+ return explicitPath ?? defaultPath;
93342
+ }
93343
+ if (candidatePath === defaultPath)
93344
+ return candidatePath;
93345
+ if (explicitPath && candidatePath === explicitPath)
93346
+ return candidatePath;
93347
+ const pidRoot = path22.dirname(params3.pidFilePath);
93348
+ const rel = path22.relative(pidRoot, candidatePath);
93349
+ if (rel === "" || !rel.startsWith("..") && !path22.isAbsolute(rel)) {
93350
+ return candidatePath;
93351
+ }
93352
+ return explicitPath ?? defaultPath;
93353
+ }
93354
+
93355
+ // src/lib/pluginBuildInfo.ts
93356
+ import { readFileSync as readFileSync3 } from "node:fs";
93357
+ import path24 from "node:path";
93358
+
93359
+ // src/lib/pluginArtifacts.ts
93360
+ import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
93361
+ import path23 from "node:path";
93362
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
93363
+ function currentDir(moduleUrl) {
93364
+ return path23.dirname(fileURLToPath4(moduleUrl));
93365
+ }
93366
+ function isDirectory(targetPath) {
93367
+ try {
93368
+ return statSync2(targetPath).isDirectory();
93369
+ } catch {
93370
+ return false;
93371
+ }
93372
+ }
93373
+ function isFile(targetPath) {
93374
+ try {
93375
+ return statSync2(targetPath).isFile();
93376
+ } catch {
93377
+ return false;
93378
+ }
93379
+ }
93380
+ function distCandidates(moduleUrl) {
93381
+ const dir2 = currentDir(moduleUrl);
93382
+ return [
93383
+ path23.resolve(dir2, "../../plugin-artifacts/dist"),
93384
+ path23.resolve(dir2, "../plugin-artifacts/dist"),
93385
+ path23.resolve(dir2, "../../../plugin/dist"),
93386
+ path23.resolve(dir2, "../../plugin/dist")
93387
+ ];
93388
+ }
93389
+ function zipCandidates(moduleUrl) {
93390
+ const dir2 = currentDir(moduleUrl);
93391
+ return [
93392
+ path23.resolve(dir2, "../../plugin-artifacts/PluginZip.zip"),
93393
+ path23.resolve(dir2, "../plugin-artifacts/PluginZip.zip"),
93394
+ path23.resolve(dir2, "../../../plugin/PluginZip.zip"),
93395
+ path23.resolve(dir2, "../../plugin/PluginZip.zip")
93396
+ ];
93397
+ }
93398
+ function validateDistPath(targetPath) {
93399
+ return isDirectory(targetPath) && existsSync3(path23.join(targetPath, "manifest.json"));
93400
+ }
93401
+ function hasBuildInfo(targetPath) {
93402
+ return isFile(path23.join(targetPath, "build-info.json"));
93403
+ }
93404
+ function resolvePluginDistPath(moduleUrl = import.meta.url) {
93405
+ for (const candidate of distCandidates(moduleUrl)) {
93406
+ if (validateDistPath(candidate) && hasBuildInfo(candidate))
93407
+ return candidate;
93408
+ }
93409
+ for (const candidate of distCandidates(moduleUrl)) {
93410
+ if (validateDistPath(candidate))
93411
+ return candidate;
93412
+ }
93413
+ throw new CliError({
93414
+ code: "DEPENDENCY_MISSING",
93415
+ message: "Plugin build artifacts are unavailable",
93416
+ exitCode: 1,
93417
+ details: { candidates: distCandidates(moduleUrl) },
93418
+ hint: [
93419
+ "Run npm run build --workspace @remnote/plugin in the repository checkout",
93420
+ "Or install a packaged agent-remnote release that includes plugin artifacts"
93421
+ ]
93422
+ });
93423
+ }
93424
+ function resolvePluginZipPath(moduleUrl = import.meta.url) {
93425
+ for (const candidate of zipCandidates(moduleUrl)) {
93426
+ if (isFile(candidate))
93427
+ return candidate;
93428
+ }
93429
+ throw new CliError({
93430
+ code: "DEPENDENCY_MISSING",
93431
+ message: "Plugin zip artifact is unavailable",
93432
+ exitCode: 1,
93433
+ details: { candidates: zipCandidates(moduleUrl) },
93434
+ hint: [
93435
+ "Run npm run build --workspace @remnote/plugin in the repository checkout",
93436
+ "Or install a packaged agent-remnote release that includes plugin artifacts"
93437
+ ]
93438
+ });
93439
+ }
93440
+
93441
+ // src/lib/pluginBuildInfo.ts
93442
+ function readPluginDistBuildInfo(distPath) {
93443
+ const normalized = typeof distPath === "string" ? distPath.trim() : "";
93444
+ if (!normalized)
93445
+ return null;
93446
+ const target2 = path24.join(normalized, "build-info.json");
93447
+ try {
93448
+ const raw4 = readFileSync3(target2, "utf8");
93449
+ const parsed = JSON.parse(raw4);
93450
+ if (typeof parsed?.name === "string" && typeof parsed?.version === "string" && typeof parsed?.build_id === "string" && typeof parsed?.built_at === "number" && typeof parsed?.source_stamp === "number") {
93451
+ return {
93452
+ name: parsed.name,
93453
+ version: parsed.version,
93454
+ build_id: parsed.build_id,
93455
+ built_at: parsed.built_at,
93456
+ source_stamp: parsed.source_stamp,
93457
+ mode: parsed.mode === "src" || parsed.mode === "dist" || parsed.mode === "unknown" ? parsed.mode : "dist"
93458
+ };
93459
+ }
93460
+ } catch {}
93461
+ return null;
93462
+ }
93463
+ function currentExpectedPluginBuildInfo() {
93464
+ try {
93465
+ const dist = resolvePluginDistPath();
93466
+ return readPluginDistBuildInfo(dist);
93467
+ } catch {
93468
+ return null;
93469
+ }
93470
+ }
93471
+ function pluginBuildWarnings(params3) {
93472
+ if (!params3.expected || !params3.live)
93473
+ return [];
93474
+ if (params3.expected.build_id === params3.live.build_id)
93475
+ return [];
93476
+ return [
93477
+ `plugin build mismatch: expected=${params3.expected.build_id} live=${params3.live.build_id}`
93478
+ ];
93479
+ }
93480
+
93481
+ // src/lib/statuslineArtifacts.ts
93482
+ import { promises as fs15 } from "node:fs";
93483
+
93484
+ // src/services/StatusLineFile.ts
93485
+ import { promises as fs14 } from "node:fs";
93486
+ import path25 from "node:path";
93487
+ class StatusLineFile extends Tag2("StatusLineFile")() {
93488
+ }
93489
+ function defaultTextFile() {
93490
+ return path25.join(homeDir(), ".agent-remnote", "status-line.txt");
93491
+ }
93492
+ function defaultJsonFile() {
93493
+ return path25.join(homeDir(), ".agent-remnote", "status-line.json");
93494
+ }
93495
+ function ensureDir8(p3) {
93496
+ return fs14.mkdir(path25.dirname(p3), { recursive: true }).then(() => {
93497
+ return;
93498
+ });
93499
+ }
93500
+ async function readFileOrEmpty(filePath) {
93501
+ try {
93502
+ return await fs14.readFile(filePath, "utf8");
93503
+ } catch (e) {
93504
+ if (e?.code === "ENOENT")
93505
+ return "";
93506
+ throw e;
92463
93507
  }
92464
93508
  }
92465
93509
  async function writeTextAtomic(filePath, content) {
@@ -92544,6 +93588,26 @@ function cleanupStatuslineArtifacts(paths) {
92544
93588
  }
92545
93589
 
92546
93590
  // src/lib/doctor/fixes.ts
93591
+ function stopTrustedRuntime(params3) {
93592
+ return gen2(function* () {
93593
+ const proc = yield* Process;
93594
+ yield* proc.kill(params3.pid, "SIGTERM");
93595
+ const exited = yield* proc.waitForExit({ pid: params3.pid, timeoutMs: params3.stopWaitMs });
93596
+ if (!exited) {
93597
+ yield* proc.kill(params3.pid, "SIGKILL");
93598
+ const killed = yield* proc.waitForExit({ pid: params3.pid, timeoutMs: params3.stopWaitMs });
93599
+ if (!killed) {
93600
+ return yield* fail8(new CliError({
93601
+ code: "INTERNAL",
93602
+ message: `Failed to stop mismatched ${params3.service} runtime`,
93603
+ exitCode: 1,
93604
+ details: { pid: params3.pid, pid_file: params3.pidFilePath, state_file: params3.stateFilePath }
93605
+ }));
93606
+ }
93607
+ }
93608
+ yield* params3.cleanup;
93609
+ });
93610
+ }
92547
93611
  function applyDoctorFixes() {
92548
93612
  return gen2(function* () {
92549
93613
  const cfg = yield* AppConfig;
@@ -92557,6 +93621,7 @@ function applyDoctorFixes() {
92557
93621
  const fixes = [];
92558
93622
  const cleaned = [];
92559
93623
  const cleanupFailures = [];
93624
+ const current2 = currentRuntimeBuildInfo();
92560
93625
  const daemonPidFile = daemonFiles.defaultPidFile();
92561
93626
  const daemonPidInfo = yield* daemonFiles.readPidFile(daemonPidFile).pipe(orElseSucceed2(() => {
92562
93627
  return;
@@ -92639,6 +93704,14 @@ function applyDoctorFixes() {
92639
93704
  });
92640
93705
  }
92641
93706
  }
93707
+ const resolvedPluginStateFile = resolveManagedStateFile({
93708
+ pidFilePath: pluginPidFile,
93709
+ defaultStateFilePath: pluginFiles.defaultStateFile(),
93710
+ candidate: pluginPidInfo?.state_file
93711
+ });
93712
+ const pluginStateInfo = yield* pluginFiles.readStateFile(resolvedPluginStateFile).pipe(orElseSucceed2(() => {
93713
+ return;
93714
+ }));
92642
93715
  fixes.push({
92643
93716
  id: "runtime.cleanup_stale_artifacts",
92644
93717
  ok: cleanupFailures.length === 0,
@@ -92673,17 +93746,155 @@ function applyDoctorFixes() {
92673
93746
  details: { error: configRepair.left.message }
92674
93747
  });
92675
93748
  }
93749
+ const restartAttempted = [];
93750
+ const restartRestarted = [];
93751
+ const restartSkipped = [];
93752
+ const restartFailed = [];
93753
+ const expectedPlugin = currentExpectedPluginBuildInfo();
93754
+ const daemonNeedsRestart = daemonPidInfo?.pid && daemonPidInfo.build?.build_id && daemonPidInfo.build.build_id !== current2.build_id && daemonPidInfo.mode === "supervisor" && (yield* isTrustedPidRecord(daemonPidInfo));
93755
+ if (daemonNeedsRestart) {
93756
+ const daemonStateFile = resolveManagedStateFile({
93757
+ pidFilePath: daemonPidFile,
93758
+ defaultStateFilePath: supervisorState.defaultStateFile(),
93759
+ candidate: daemonPidInfo.state_file
93760
+ });
93761
+ restartAttempted.push("daemon");
93762
+ const stopped = yield* stopTrustedRuntime({
93763
+ service: "daemon",
93764
+ pid: daemonPidInfo.pid,
93765
+ pidFilePath: daemonPidFile,
93766
+ stateFilePath: daemonStateFile,
93767
+ stopWaitMs: WS_STOP_WAIT_DEFAULT_MS,
93768
+ cleanup: gen2(function* () {
93769
+ yield* daemonFiles.deletePidFile(daemonPidFile);
93770
+ yield* supervisorState.deleteStateFile(daemonStateFile);
93771
+ yield* cleanupStatuslineArtifacts(resolveStatuslineArtifactPaths({ cfg, pidInfo: daemonPidInfo }));
93772
+ })
93773
+ }).pipe(either3);
93774
+ if (stopped._tag === "Right") {
93775
+ changed = true;
93776
+ const started = yield* startWsSupervisor({
93777
+ waitMs: WS_START_WAIT_DEFAULT_MS,
93778
+ pidFile: daemonPidFile,
93779
+ logFile: daemonPidInfo.log_file
93780
+ }).pipe(either3);
93781
+ if (started._tag === "Right") {
93782
+ if (started.right.started) {
93783
+ restartRestarted.push("daemon");
93784
+ } else {
93785
+ restartSkipped.push("daemon_already_healthy_after_restart_attempt");
93786
+ }
93787
+ } else {
93788
+ restartFailed.push({ service: "daemon", error: started.left.message });
93789
+ }
93790
+ } else {
93791
+ restartFailed.push({ service: "daemon", error: stopped.left.message });
93792
+ }
93793
+ } else if (daemonPidInfo?.build?.build_id && daemonPidInfo.build.build_id !== current2.build_id) {
93794
+ restartSkipped.push("daemon_restart_not_safe");
93795
+ }
93796
+ const apiNeedsRestart = apiPidInfo?.pid && apiPidInfo.build?.build_id && apiPidInfo.build.build_id !== current2.build_id && (yield* isTrustedPidRecord(apiPidInfo));
93797
+ if (apiNeedsRestart) {
93798
+ const apiStateFile = resolveManagedStateFile({
93799
+ pidFilePath: apiPidFile,
93800
+ defaultStateFilePath: apiFiles.defaultStateFile(),
93801
+ candidate: apiPidInfo.state_file
93802
+ });
93803
+ restartAttempted.push("api");
93804
+ const stopped = yield* stopTrustedRuntime({
93805
+ service: "api",
93806
+ pid: apiPidInfo.pid,
93807
+ pidFilePath: apiPidFile,
93808
+ stateFilePath: apiStateFile,
93809
+ stopWaitMs: API_STOP_WAIT_DEFAULT_MS,
93810
+ cleanup: gen2(function* () {
93811
+ yield* apiFiles.deletePidFile(apiPidFile);
93812
+ yield* apiFiles.deleteStateFile(apiStateFile);
93813
+ })
93814
+ }).pipe(either3);
93815
+ if (stopped._tag === "Right") {
93816
+ changed = true;
93817
+ const started = yield* startApiDaemon({
93818
+ host: apiPidInfo.host,
93819
+ port: apiPidInfo.port,
93820
+ basePath: apiPidInfo.base_path,
93821
+ waitMs: API_START_WAIT_DEFAULT_MS,
93822
+ pidFile: apiPidFile,
93823
+ logFile: apiPidInfo.log_file,
93824
+ stateFile: apiStateFile
93825
+ }).pipe(either3);
93826
+ if (started._tag === "Right") {
93827
+ if (started.right.started) {
93828
+ restartRestarted.push("api");
93829
+ } else {
93830
+ restartSkipped.push("api_already_healthy_after_restart_attempt");
93831
+ }
93832
+ } else {
93833
+ restartFailed.push({ service: "api", error: started.left.message });
93834
+ }
93835
+ } else {
93836
+ restartFailed.push({ service: "api", error: stopped.left.message });
93837
+ }
93838
+ } else if (apiPidInfo?.build?.build_id && apiPidInfo.build.build_id !== current2.build_id) {
93839
+ restartSkipped.push("api_restart_not_safe");
93840
+ }
93841
+ const pluginArtifactMismatch = expectedPlugin && pluginStateInfo?.plugin_build?.build_id && pluginStateInfo.plugin_build.build_id !== expectedPlugin.build_id;
93842
+ const pluginRuntimeMismatch = pluginPidInfo?.build?.build_id && pluginPidInfo.build.build_id !== current2.build_id;
93843
+ const pluginNeedsRestart = pluginPidInfo?.pid && (pluginRuntimeMismatch || pluginArtifactMismatch) && (yield* isTrustedPidRecord(pluginPidInfo));
93844
+ if (pluginNeedsRestart) {
93845
+ const pluginStateFile = resolveManagedStateFile({
93846
+ pidFilePath: pluginPidFile,
93847
+ defaultStateFilePath: pluginFiles.defaultStateFile(),
93848
+ candidate: pluginPidInfo.state_file
93849
+ });
93850
+ restartAttempted.push("plugin");
93851
+ const stopped = yield* stopTrustedRuntime({
93852
+ service: "plugin",
93853
+ pid: pluginPidInfo.pid,
93854
+ pidFilePath: pluginPidFile,
93855
+ stateFilePath: pluginStateFile,
93856
+ stopWaitMs: PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS,
93857
+ cleanup: gen2(function* () {
93858
+ yield* pluginFiles.deletePidFile(pluginPidFile);
93859
+ yield* pluginFiles.deleteStateFile(pluginStateFile);
93860
+ })
93861
+ }).pipe(either3);
93862
+ if (stopped._tag === "Right") {
93863
+ changed = true;
93864
+ const started = yield* startPluginServer({
93865
+ host: pluginPidInfo.host,
93866
+ port: pluginPidInfo.port,
93867
+ waitMs: PLUGIN_SERVER_START_WAIT_DEFAULT_MS,
93868
+ pidFile: pluginPidFile,
93869
+ logFile: pluginPidInfo.log_file,
93870
+ stateFile: pluginStateFile
93871
+ }).pipe(either3);
93872
+ if (started._tag === "Right") {
93873
+ if (started.right.started) {
93874
+ restartRestarted.push("plugin");
93875
+ } else {
93876
+ restartSkipped.push("plugin_already_healthy_after_restart_attempt");
93877
+ }
93878
+ } else {
93879
+ restartFailed.push({ service: "plugin", error: started.left.message });
93880
+ }
93881
+ } else {
93882
+ restartFailed.push({ service: "plugin", error: stopped.left.message });
93883
+ }
93884
+ } else if (pluginRuntimeMismatch || pluginArtifactMismatch) {
93885
+ restartSkipped.push("plugin_restart_not_safe");
93886
+ }
92676
93887
  const restartSummary = {
92677
- attempted: [],
92678
- restarted: [],
92679
- skipped: ["safe_restart_disabled"],
92680
- failed: []
93888
+ attempted: restartAttempted,
93889
+ restarted: restartRestarted,
93890
+ skipped: restartSkipped,
93891
+ failed: restartFailed
92681
93892
  };
92682
93893
  fixes.push({
92683
93894
  id: "runtime.restart_mismatched_services",
92684
- ok: true,
92685
- changed: false,
92686
- summary: "Skipped automatic runtime restart inside doctor --fix to preserve the safe repair boundary",
93895
+ ok: restartFailed.length === 0,
93896
+ changed: restartRestarted.length > 0,
93897
+ summary: restartAttempted.length === 0 ? "No trusted runtime build mismatches required automatic restart" : restartFailed.length === 0 ? `Restarted ${restartRestarted.length} mismatched runtime service(s)` : `Restarted ${restartRestarted.length} mismatched runtime service(s); ${restartFailed.length} restart(s) failed`,
92687
93898
  details: restartSummary
92688
93899
  });
92689
93900
  return { fixes, changed, restartSummary };
@@ -92691,40 +93902,40 @@ function applyDoctorFixes() {
92691
93902
  }
92692
93903
 
92693
93904
  // src/lib/doctor/checks.ts
92694
- import path26 from "node:path";
93905
+ import path27 from "node:path";
92695
93906
 
92696
93907
  // src/lib/builtin-scenarios/index.ts
92697
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
92698
- import path22 from "node:path";
92699
- import { fileURLToPath as fileURLToPath3 } from "node:url";
92700
- var MODULE_DIR = path22.dirname(fileURLToPath3(import.meta.url));
93908
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
93909
+ import path26 from "node:path";
93910
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
93911
+ var MODULE_DIR = path26.dirname(fileURLToPath5(import.meta.url));
92701
93912
  function locateBuiltinSourceRoot() {
92702
93913
  const packageRootCandidates = [
92703
- path22.resolve(MODULE_DIR, "../../../"),
92704
- path22.resolve(MODULE_DIR, "..")
93914
+ path26.resolve(MODULE_DIR, "../../../"),
93915
+ path26.resolve(MODULE_DIR, "..")
92705
93916
  ];
92706
93917
  for (const packageRoot of packageRootCandidates) {
92707
- const builtinSourceRoot = path22.join(packageRoot, "builtin-scenarios");
92708
- if (existsSync2(path22.join(builtinSourceRoot, "catalog.json"))) {
93918
+ const builtinSourceRoot = path26.join(packageRoot, "builtin-scenarios");
93919
+ if (existsSync4(path26.join(builtinSourceRoot, "catalog.json"))) {
92709
93920
  return { packageRoot, builtinSourceRoot };
92710
93921
  }
92711
93922
  }
92712
93923
  throw new Error(`Unable to locate builtin-scenarios catalog from ${MODULE_DIR}`);
92713
93924
  }
92714
93925
  var { packageRoot: PACKAGE_ROOT, builtinSourceRoot: BUILTIN_SOURCE_ROOT } = locateBuiltinSourceRoot();
92715
- var REPO_ROOT = path22.resolve(PACKAGE_ROOT, "../..");
92716
- var BUILTIN_CATALOG_PATH = path22.join(BUILTIN_SOURCE_ROOT, "catalog.json");
93926
+ var REPO_ROOT = path26.resolve(PACKAGE_ROOT, "../..");
93927
+ var BUILTIN_CATALOG_PATH = path26.join(BUILTIN_SOURCE_ROOT, "catalog.json");
92717
93928
  var BUILTIN_SOURCE_PREFIX = "packages/agent-remnote/builtin-scenarios/";
92718
93929
  function readJsonFile(filePath) {
92719
- return JSON.parse(readFileSync2(filePath, "utf8"));
93930
+ return JSON.parse(readFileSync4(filePath, "utf8"));
92720
93931
  }
92721
93932
  function resolveRepoPath(relPath) {
92722
- return path22.resolve(REPO_ROOT, relPath);
93933
+ return path26.resolve(REPO_ROOT, relPath);
92723
93934
  }
92724
93935
  function resolveBuiltinPackagePath(relPath) {
92725
93936
  const normalized = relPath.replaceAll("\\", "/");
92726
93937
  if (normalized.startsWith(BUILTIN_SOURCE_PREFIX)) {
92727
- return path22.resolve(BUILTIN_SOURCE_ROOT, normalized.slice(BUILTIN_SOURCE_PREFIX.length));
93938
+ return path26.resolve(BUILTIN_SOURCE_ROOT, normalized.slice(BUILTIN_SOURCE_PREFIX.length));
92728
93939
  }
92729
93940
  return resolveRepoPath(relPath);
92730
93941
  }
@@ -92732,275 +93943,34 @@ var builtinScenarioCatalog = readJsonFile(BUILTIN_CATALOG_PATH);
92732
93943
  var builtinScenarioPackageCache = new Map;
92733
93944
  var builtinScenarioPackagesTarget = {};
92734
93945
  function loadBuiltinScenarioPackage(entry) {
92735
- const cached4 = builtinScenarioPackageCache.get(entry.package_id);
92736
- if (cached4)
92737
- return cached4;
93946
+ const cached5 = builtinScenarioPackageCache.get(entry.package_id);
93947
+ if (cached5)
93948
+ return cached5;
92738
93949
  const loaded = readJsonFile(resolveBuiltinPackagePath(entry.package_path));
92739
93950
  builtinScenarioPackageCache.set(entry.package_id, loaded);
92740
93951
  return loaded;
92741
93952
  }
92742
- for (const entry of builtinScenarioCatalog) {
92743
- Object.defineProperty(builtinScenarioPackagesTarget, entry.package_id, {
92744
- enumerable: true,
92745
- configurable: false,
92746
- get: () => loadBuiltinScenarioPackage(entry)
92747
- });
92748
- }
92749
- var builtinScenarioPackages = Object.freeze(builtinScenarioPackagesTarget);
92750
- function getBuiltinScenarioPackage(id2) {
92751
- const entry = builtinScenarioCatalog.find((item) => item.package_id === id2);
92752
- if (!entry) {
92753
- throw new Error(`Unknown builtin scenario package: ${id2}`);
92754
- }
92755
- return loadBuiltinScenarioPackage(entry);
92756
- }
92757
- function getBuiltinScenarioPackageSourcePath(id2) {
92758
- const entry = builtinScenarioCatalog.find((item) => item.package_id === id2);
92759
- if (!entry) {
92760
- throw new Error(`Unknown builtin scenario source: ${id2}`);
92761
- }
92762
- return resolveBuiltinPackagePath(entry.package_path);
92763
- }
92764
-
92765
- // src/lib/pluginBuildInfo.ts
92766
- import { readFileSync as readFileSync3 } from "node:fs";
92767
- import path24 from "node:path";
92768
-
92769
- // src/lib/pluginArtifacts.ts
92770
- import { existsSync as existsSync3, statSync } from "node:fs";
92771
- import path23 from "node:path";
92772
- import { fileURLToPath as fileURLToPath4 } from "node:url";
92773
- function currentDir(moduleUrl) {
92774
- return path23.dirname(fileURLToPath4(moduleUrl));
92775
- }
92776
- function isDirectory(targetPath) {
92777
- try {
92778
- return statSync(targetPath).isDirectory();
92779
- } catch {
92780
- return false;
92781
- }
92782
- }
92783
- function isFile(targetPath) {
92784
- try {
92785
- return statSync(targetPath).isFile();
92786
- } catch {
92787
- return false;
92788
- }
92789
- }
92790
- function distCandidates(moduleUrl) {
92791
- const dir2 = currentDir(moduleUrl);
92792
- return [
92793
- path23.resolve(dir2, "../../plugin-artifacts/dist"),
92794
- path23.resolve(dir2, "../plugin-artifacts/dist"),
92795
- path23.resolve(dir2, "../../../plugin/dist"),
92796
- path23.resolve(dir2, "../../plugin/dist")
92797
- ];
92798
- }
92799
- function zipCandidates(moduleUrl) {
92800
- const dir2 = currentDir(moduleUrl);
92801
- return [
92802
- path23.resolve(dir2, "../../plugin-artifacts/PluginZip.zip"),
92803
- path23.resolve(dir2, "../plugin-artifacts/PluginZip.zip"),
92804
- path23.resolve(dir2, "../../../plugin/PluginZip.zip"),
92805
- path23.resolve(dir2, "../../plugin/PluginZip.zip")
92806
- ];
92807
- }
92808
- function validateDistPath(targetPath) {
92809
- return isDirectory(targetPath) && existsSync3(path23.join(targetPath, "manifest.json"));
92810
- }
92811
- function hasBuildInfo(targetPath) {
92812
- return isFile(path23.join(targetPath, "build-info.json"));
92813
- }
92814
- function resolvePluginDistPath(moduleUrl = import.meta.url) {
92815
- for (const candidate of distCandidates(moduleUrl)) {
92816
- if (validateDistPath(candidate) && hasBuildInfo(candidate))
92817
- return candidate;
92818
- }
92819
- for (const candidate of distCandidates(moduleUrl)) {
92820
- if (validateDistPath(candidate))
92821
- return candidate;
92822
- }
92823
- throw new CliError({
92824
- code: "DEPENDENCY_MISSING",
92825
- message: "Plugin build artifacts are unavailable",
92826
- exitCode: 1,
92827
- details: { candidates: distCandidates(moduleUrl) },
92828
- hint: [
92829
- "Run npm run build --workspace @remnote/plugin in the repository checkout",
92830
- "Or install a packaged agent-remnote release that includes plugin artifacts"
92831
- ]
92832
- });
92833
- }
92834
- function resolvePluginZipPath(moduleUrl = import.meta.url) {
92835
- for (const candidate of zipCandidates(moduleUrl)) {
92836
- if (isFile(candidate))
92837
- return candidate;
92838
- }
92839
- throw new CliError({
92840
- code: "DEPENDENCY_MISSING",
92841
- message: "Plugin zip artifact is unavailable",
92842
- exitCode: 1,
92843
- details: { candidates: zipCandidates(moduleUrl) },
92844
- hint: [
92845
- "Run npm run build --workspace @remnote/plugin in the repository checkout",
92846
- "Or install a packaged agent-remnote release that includes plugin artifacts"
92847
- ]
92848
- });
92849
- }
92850
-
92851
- // src/lib/pluginBuildInfo.ts
92852
- function readPluginDistBuildInfo(distPath) {
92853
- const normalized = typeof distPath === "string" ? distPath.trim() : "";
92854
- if (!normalized)
92855
- return null;
92856
- const target2 = path24.join(normalized, "build-info.json");
92857
- try {
92858
- const raw4 = readFileSync3(target2, "utf8");
92859
- const parsed = JSON.parse(raw4);
92860
- if (typeof parsed?.name === "string" && typeof parsed?.version === "string" && typeof parsed?.build_id === "string" && typeof parsed?.built_at === "number" && typeof parsed?.source_stamp === "number") {
92861
- return {
92862
- name: parsed.name,
92863
- version: parsed.version,
92864
- build_id: parsed.build_id,
92865
- built_at: parsed.built_at,
92866
- source_stamp: parsed.source_stamp,
92867
- mode: parsed.mode === "src" || parsed.mode === "dist" || parsed.mode === "unknown" ? parsed.mode : "dist"
92868
- };
92869
- }
92870
- } catch {}
92871
- return null;
92872
- }
92873
- function currentExpectedPluginBuildInfo() {
92874
- try {
92875
- const dist = resolvePluginDistPath();
92876
- return readPluginDistBuildInfo(dist);
92877
- } catch {
92878
- return null;
92879
- }
92880
- }
92881
- function pluginBuildWarnings(params3) {
92882
- if (!params3.expected || !params3.live)
92883
- return [];
92884
- if (params3.expected.build_id === params3.live.build_id)
92885
- return [];
92886
- return [
92887
- `plugin build mismatch: expected=${params3.expected.build_id} live=${params3.live.build_id}`
92888
- ];
92889
- }
92890
-
92891
- // src/lib/runtimeBuildInfo.ts
92892
- import { createHash as createHash3 } from "node:crypto";
92893
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, statSync as statSync2 } from "node:fs";
92894
- import path25 from "node:path";
92895
- import { fileURLToPath as fileURLToPath5 } from "node:url";
92896
- function packageInfo() {
92897
- try {
92898
- const raw4 = readFileSync4(new URL("../../package.json", import.meta.url), "utf8");
92899
- const parsed = JSON.parse(raw4);
92900
- const name = typeof parsed?.name === "string" && parsed.name.trim() ? parsed.name.trim() : "agent-remnote";
92901
- const version = typeof parsed?.version === "string" && parsed.version.trim() ? parsed.version.trim() : "0.0.0";
92902
- return { name, version };
92903
- } catch {
92904
- return { name: "agent-remnote", version: "0.0.0" };
92905
- }
92906
- }
92907
- function fileMtimeMs(targetPath) {
92908
- try {
92909
- return Math.floor(statSync2(targetPath).mtimeMs);
92910
- } catch {
92911
- return 0;
92912
- }
92913
- }
92914
- function latestMtimeMs(dirPath) {
92915
- if (!existsSync4(dirPath))
92916
- return 0;
92917
- let max7 = 0;
92918
- const stack = [dirPath];
92919
- while (stack.length > 0) {
92920
- const current2 = stack.pop();
92921
- let entries2 = [];
92922
- try {
92923
- entries2 = readdirSync(current2);
92924
- } catch {
92925
- continue;
92926
- }
92927
- for (const entry of entries2) {
92928
- const full = path25.join(current2, entry);
92929
- let st;
92930
- try {
92931
- st = statSync2(full);
92932
- } catch {
92933
- continue;
92934
- }
92935
- if (st.isDirectory()) {
92936
- stack.push(full);
92937
- } else if (st.isFile()) {
92938
- max7 = Math.max(max7, Math.floor(st.mtimeMs));
92939
- }
92940
- }
92941
- }
92942
- return max7;
92943
- }
92944
- function detectMode() {
92945
- const url2 = import.meta.url;
92946
- if (url2.includes("/src/"))
92947
- return "src";
92948
- if (url2.includes("/dist/"))
92949
- return "dist";
92950
- return "unknown";
92951
- }
92952
- function computeDefaultBuildInfo() {
92953
- const pkg = packageInfo();
92954
- const mode = detectMode();
92955
- const srcDir = new URL("../", import.meta.url);
92956
- const packageJson = new URL("../../package.json", import.meta.url);
92957
- const sourceStamp = Math.max(latestMtimeMs(fileURLToPath5(srcDir)), fileMtimeMs(fileURLToPath5(packageJson)));
92958
- const buildIdInput = `${pkg.name}
92959
- ${pkg.version}
92960
- ${mode}
92961
- ${sourceStamp}`;
92962
- const buildId = createHash3("sha256").update(buildIdInput).digest("hex").slice(0, 12);
92963
- return {
92964
- name: pkg.name,
92965
- version: pkg.version,
92966
- build_id: `${pkg.version}:${buildId}`,
92967
- built_at: sourceStamp || Date.now(),
92968
- source_stamp: sourceStamp || Date.now(),
92969
- mode
92970
- };
92971
- }
92972
- var cached4;
92973
- function parseEnvNumber(raw4, fallback) {
92974
- const parsed = Number(raw4);
92975
- return Number.isFinite(parsed) ? parsed : fallback;
93953
+ for (const entry of builtinScenarioCatalog) {
93954
+ Object.defineProperty(builtinScenarioPackagesTarget, entry.package_id, {
93955
+ enumerable: true,
93956
+ configurable: false,
93957
+ get: () => loadBuiltinScenarioPackage(entry)
93958
+ });
92976
93959
  }
92977
- function currentRuntimeBuildInfo() {
92978
- if (cached4)
92979
- return cached4;
92980
- const defaultInfo = computeDefaultBuildInfo();
92981
- cached4 = {
92982
- name: process.env.AGENT_REMNOTE_NAME?.trim() || defaultInfo.name,
92983
- version: process.env.AGENT_REMNOTE_VERSION?.trim() || defaultInfo.version,
92984
- build_id: process.env.AGENT_REMNOTE_BUILD_ID?.trim() || defaultInfo.build_id,
92985
- built_at: parseEnvNumber(process.env.AGENT_REMNOTE_BUILD_AT, defaultInfo.built_at),
92986
- source_stamp: parseEnvNumber(process.env.AGENT_REMNOTE_SOURCE_STAMP, defaultInfo.source_stamp),
92987
- mode: process.env.AGENT_REMNOTE_BUILD_MODE === "src" || process.env.AGENT_REMNOTE_BUILD_MODE === "dist" || process.env.AGENT_REMNOTE_BUILD_MODE === "unknown" ? process.env.AGENT_REMNOTE_BUILD_MODE : defaultInfo.mode
92988
- };
92989
- return cached4;
93960
+ var builtinScenarioPackages = Object.freeze(builtinScenarioPackagesTarget);
93961
+ function getBuiltinScenarioPackage(id2) {
93962
+ const entry = builtinScenarioCatalog.find((item) => item.package_id === id2);
93963
+ if (!entry) {
93964
+ throw new Error(`Unknown builtin scenario package: ${id2}`);
93965
+ }
93966
+ return loadBuiltinScenarioPackage(entry);
92990
93967
  }
92991
- function runtimeVersionWarnings(params3) {
92992
- const warnings = [];
92993
- const compare2 = (label, other) => {
92994
- if (!other)
92995
- return;
92996
- if (other.build_id !== params3.current.build_id) {
92997
- warnings.push(`${label} build mismatch: current=${params3.current.build_id} live=${other.build_id}`);
92998
- }
92999
- };
93000
- compare2("daemon", params3.daemon);
93001
- compare2("plugin", params3.plugin);
93002
- compare2("api", params3.api);
93003
- return warnings;
93968
+ function getBuiltinScenarioPackageSourcePath(id2) {
93969
+ const entry = builtinScenarioCatalog.find((item) => item.package_id === id2);
93970
+ if (!entry) {
93971
+ throw new Error(`Unknown builtin scenario source: ${id2}`);
93972
+ }
93973
+ return resolveBuiltinPackagePath(entry.package_path);
93004
93974
  }
93005
93975
 
93006
93976
  // src/lib/doctor/checks.ts
@@ -93021,7 +93991,7 @@ function collectDoctorChecks() {
93021
93991
  staleArtifacts.push({
93022
93992
  service: "daemon",
93023
93993
  pidFile: daemonPidFile,
93024
- stateFile: daemonPidInfo.state_file ?? path26.join(path26.dirname(daemonPidFile), "ws.state.json"),
93994
+ stateFile: daemonPidInfo.state_file ?? path27.join(path27.dirname(daemonPidFile), "ws.state.json"),
93025
93995
  pid: daemonPidInfo.pid
93026
93996
  });
93027
93997
  }
@@ -93049,13 +94019,16 @@ function collectDoctorChecks() {
93049
94019
  pid: pluginPidInfo.pid
93050
94020
  });
93051
94021
  }
94022
+ const pluginStateInfo = yield* pluginFiles.readStateFile(pluginPidInfo?.state_file ?? pluginFiles.defaultStateFile()).pipe(orElseSucceed2(() => {
94023
+ return;
94024
+ }));
93052
94025
  const current2 = currentRuntimeBuildInfo();
93053
94026
  const expectedPlugin = currentExpectedPluginBuildInfo();
93054
94027
  const mismatches = [
93055
94028
  daemonPidInfo?.build?.build_id && daemonPidInfo.build.build_id !== current2.build_id ? { service: "daemon", live: daemonPidInfo.build.build_id } : null,
93056
94029
  apiPidInfo?.build?.build_id && apiPidInfo.build.build_id !== current2.build_id ? { service: "api", live: apiPidInfo.build.build_id } : null,
93057
94030
  pluginPidInfo?.build?.build_id && pluginPidInfo.build.build_id !== current2.build_id ? { service: "plugin", live: pluginPidInfo.build.build_id } : null,
93058
- expectedPlugin && pluginPidInfo?.build?.build_id && pluginPidInfo.build.build_id !== expectedPlugin.build_id ? { service: "plugin-artifact", live: pluginPidInfo.build.build_id, expected: expectedPlugin.build_id } : null
94031
+ expectedPlugin && pluginStateInfo?.plugin_build?.build_id && pluginStateInfo.plugin_build.build_id !== expectedPlugin.build_id ? { service: "plugin-artifact", live: pluginStateInfo.plugin_build.build_id, expected: expectedPlugin.build_id } : null
93059
94032
  ].filter(Boolean);
93060
94033
  const configPreview = yield* userConfig.previewRepair().pipe(either3);
93061
94034
  const configChanged = configPreview._tag === "Right" ? configPreview.right.changed : false;
@@ -93069,269 +94042,76 @@ function collectDoctorChecks() {
93069
94042
  return { ok: true, details: undefined };
93070
94043
  } catch (error4) {
93071
94044
  return { ok: false, details: { error: String(error4?.message || error4) } };
93072
- }
93073
- });
93074
- const pluginArtifactsCheck = yield* sync3(() => {
93075
- try {
93076
- return { ok: true, details: { distPath: resolvePluginDistPath(), zipPath: resolvePluginZipPath() } };
93077
- } catch (error4) {
93078
- return { ok: false, details: { error: String(error4?.message || error4) } };
93079
- }
93080
- });
93081
- const pidWritable = yield* fsAccess.canWritePath(daemonPidFile);
93082
- const logWritable = yield* fsAccess.canWritePath(daemonFiles.defaultLogFile());
93083
- const storeWritable = yield* fsAccess.checkWritableFile(cfg.storeDb);
93084
- const pathOk = pidWritable && logWritable && storeWritable.ok;
93085
- return [
93086
- {
93087
- id: "runtime.stale_pid_or_state",
93088
- ok: staleArtifacts.length === 0,
93089
- severity: staleArtifacts.length === 0 ? "info" : "warning",
93090
- summary: staleArtifacts.length === 0 ? "No stale runtime pid/state artifacts" : `Found ${staleArtifacts.length} stale runtime artifact set(s)`,
93091
- details: staleArtifacts,
93092
- repairable: staleArtifacts.length > 0
93093
- },
93094
- {
93095
- id: "runtime.version_mismatch",
93096
- ok: mismatches.length === 0,
93097
- severity: mismatches.length === 0 ? "info" : "warning",
93098
- summary: mismatches.length === 0 ? "No runtime build mismatch detected" : `Found ${mismatches.length} runtime build mismatch(es)`,
93099
- details: mismatches,
93100
- repairable: mismatches.length > 0
93101
- },
93102
- {
93103
- id: "config.migration_needed",
93104
- ok: configValid && !configChanged,
93105
- severity: !configValid ? "error" : configChanged ? "warning" : "info",
93106
- summary: !configValid ? "User config is invalid or conflicting" : configChanged ? "User config can be canonicalized" : "User config already canonical",
93107
- details: configDetails,
93108
- repairable: configRepairable
93109
- },
93110
- {
93111
- id: "package.builtin_scenarios_broken",
93112
- ok: packageCheck.ok,
93113
- severity: packageCheck.ok ? "info" : "error",
93114
- summary: packageCheck.ok ? "Builtin scenarios are loadable" : "Builtin scenario package loading failed",
93115
- details: packageCheck.details,
93116
- repairable: !packageCheck.ok
93117
- },
93118
- {
93119
- id: "package.plugin_artifacts_unavailable",
93120
- ok: pluginArtifactsCheck.ok,
93121
- severity: pluginArtifactsCheck.ok ? "info" : "error",
93122
- summary: pluginArtifactsCheck.ok ? "Plugin artifacts are available" : "Plugin artifacts are unavailable",
93123
- details: pluginArtifactsCheck.details,
93124
- repairable: false
93125
- },
93126
- {
93127
- id: "env.path_or_permission_problem",
93128
- ok: pathOk,
93129
- severity: pathOk ? "info" : "error",
93130
- summary: pathOk ? "Required writable paths are available" : "One or more required paths are not writable",
93131
- details: {
93132
- daemon_pid_file: daemonPidFile,
93133
- daemon_log_file: daemonFiles.defaultLogFile(),
93134
- pid_writable: pidWritable,
93135
- log_writable: logWritable,
93136
- store_db: cfg.storeDb,
93137
- store_writable: storeWritable
93138
- },
93139
- repairable: false
93140
- }
93141
- ];
93142
- });
93143
- }
93144
-
93145
- // src/commands/ws/_shared.ts
93146
- import path27 from "node:path";
93147
- var WS_HEALTH_TIMEOUT_MS = 2000;
93148
- var WS_START_WAIT_DEFAULT_MS = 15000;
93149
- var WS_STOP_WAIT_DEFAULT_MS = 5000;
93150
- function supervisorCommandLine(params3) {
93151
- const command = process.argv[0];
93152
- const script = process.argv[1];
93153
- if (!command || !script) {
93154
- throw new CliError({
93155
- code: "INTERNAL",
93156
- message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
93157
- exitCode: 1,
93158
- details: { argv: process.argv }
93159
- });
93160
- }
93161
- const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
93162
- return {
93163
- command,
93164
- args: [
93165
- ...execArgv,
93166
- script,
93167
- "--daemon-url",
93168
- params3.wsUrl,
93169
- "--store-db",
93170
- params3.storeDb,
93171
- "daemon",
93172
- "supervisor",
93173
- "--pid-file",
93174
- params3.pidFile,
93175
- "--log-file",
93176
- params3.logFile,
93177
- "--state-file",
93178
- params3.stateFile
93179
- ]
93180
- };
93181
- }
93182
- function toPidFileValue(params3) {
93183
- return {
93184
- pid: params3.pid,
93185
- build: currentRuntimeBuildInfo(),
93186
- started_at: params3.startedAt,
93187
- ws_url: params3.wsUrl,
93188
- log_file: params3.logFile,
93189
- cmd: params3.cmd,
93190
- ws_bridge_state_file: params3.wsBridgeStateFile,
93191
- status_line_file: params3.statusLineFile,
93192
- status_line_json_file: params3.statusLineJsonFile
93193
- };
93194
- }
93195
- function waitForHealth(url2, waitMs) {
93196
- return gen2(function* () {
93197
- if (!Number.isFinite(waitMs) || waitMs < 0) {
93198
- return yield* fail8(new CliError({
93199
- code: "INVALID_ARGS",
93200
- message: "--wait must be a non-negative integer (ms)",
93201
- exitCode: 2,
93202
- details: { wait_ms: waitMs }
93203
- }));
93204
- }
93205
- if (waitMs === 0)
93206
- return;
93207
- const ws = yield* WsClient;
93208
- const deadline = Date.now() + waitMs;
93209
- while (Date.now() < deadline) {
93210
- const remaining = Math.max(0, deadline - Date.now());
93211
- const res = yield* ws.health({ url: url2, timeoutMs: Math.min(WS_HEALTH_TIMEOUT_MS, Math.max(1, remaining)) }).pipe(either3);
93212
- if (isRight2(res))
93213
- return;
93214
- yield* sleep4(millis(300));
93215
- }
93216
- return yield* fail8(new CliError({
93217
- code: "WS_TIMEOUT",
93218
- message: `Timed out waiting for WS to become available (${waitMs}ms)`,
93219
- exitCode: 1,
93220
- details: { url: url2, wait_ms: waitMs },
93221
- hint: ["agent-remnote daemon status", "agent-remnote daemon logs", "agent-remnote daemon health --json"]
93222
- }));
93223
- });
93224
- }
93225
- function toInitialSupervisorState(now2) {
93226
- return {
93227
- status: "running",
93228
- restart_count: 0,
93229
- restart_window_started_at: now2,
93230
- backoff_until: null,
93231
- last_exit: null,
93232
- failed_reason: null
93233
- };
93234
- }
93235
- function defaultStateFilePathFromPidFile(pidFilePath) {
93236
- return path27.join(path27.dirname(pidFilePath), "ws.state.json");
93237
- }
93238
- function startWsSupervisor(params3) {
93239
- return gen2(function* () {
93240
- const cfg = yield* AppConfig;
93241
- const daemonFiles = yield* DaemonFiles;
93242
- const supervisorState = yield* SupervisorState;
93243
- const proc = yield* Process;
93244
- const ws = yield* WsClient;
93245
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? daemonFiles.defaultPidFile());
93246
- const logFilePath = resolveUserFilePath(params3.logFile ?? daemonFiles.defaultLogFile());
93247
- const stateFilePath = defaultStateFilePathFromPidFile(pidFilePath);
93248
- const existingPidFile = yield* daemonFiles.readPidFile(pidFilePath);
93249
- if (existingPidFile) {
93250
- const alive = yield* proc.isPidRunning(existingPidFile.pid);
93251
- if (!alive) {
93252
- yield* daemonFiles.deletePidFile(pidFilePath);
93253
- yield* supervisorState.deleteStateFile(stateFilePath);
93254
- } else {
93255
- yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
93256
- return {
93257
- started: false,
93258
- pid: existingPidFile.pid,
93259
- pid_file: pidFilePath,
93260
- log_file: existingPidFile.log_file ?? logFilePath
93261
- };
93262
- }
93263
- }
93264
- const pre = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93265
- if (isRight2(pre)) {
93266
- return { started: false, pid_file: pidFilePath, log_file: logFilePath };
93267
- }
93268
- const cmd = yield* try_3({
93269
- try: () => supervisorCommandLine({
93270
- wsUrl: cfg.wsUrl,
93271
- storeDb: cfg.storeDb,
93272
- pidFile: pidFilePath,
93273
- logFile: logFilePath,
93274
- stateFile: stateFilePath
93275
- }),
93276
- catch: (e) => isCliError(e) ? e : new CliError({
93277
- code: "INTERNAL",
93278
- message: "Failed to start supervisor",
93279
- exitCode: 1,
93280
- details: { error: String(e?.message || e) }
93281
- })
94045
+ }
93282
94046
  });
93283
- const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
93284
- const now2 = Date.now();
93285
- yield* daemonFiles.writePidFile(pidFilePath, {
93286
- ...toPidFileValue({
93287
- pid,
93288
- startedAt: now2,
93289
- wsUrl: cfg.wsUrl,
93290
- logFile: logFilePath,
93291
- cmd: [cmd.command, ...cmd.args],
93292
- wsBridgeStateFile: cfg.wsStateFile.path,
93293
- statusLineFile: cfg.statusLineFile,
93294
- statusLineJsonFile: cfg.statusLineJsonFile
93295
- }),
93296
- mode: "supervisor",
93297
- child_pid: null,
93298
- state_file: stateFilePath
94047
+ const pluginArtifactsCheck = yield* sync3(() => {
94048
+ try {
94049
+ return { ok: true, details: { distPath: resolvePluginDistPath(), zipPath: resolvePluginZipPath() } };
94050
+ } catch (error4) {
94051
+ return { ok: false, details: { error: String(error4?.message || error4) } };
94052
+ }
93299
94053
  });
93300
- yield* supervisorState.writeStateFile(stateFilePath, toInitialSupervisorState(now2));
93301
- yield* waitForHealth(cfg.wsUrl, params3.waitMs);
93302
- return { started: true, pid, pid_file: pidFilePath, log_file: logFilePath };
93303
- });
93304
- }
93305
- function ensureWsSupervisor(params3) {
93306
- return gen2(function* () {
93307
- const cfg = yield* AppConfig;
93308
- const ws = yield* WsClient;
93309
- const pre = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93310
- if (isRight2(pre)) {
93311
- const daemonFiles = yield* DaemonFiles;
93312
- const proc = yield* Process;
93313
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? daemonFiles.defaultPidFile());
93314
- const logFilePath = resolveUserFilePath(params3.logFile ?? daemonFiles.defaultLogFile());
93315
- const existingPidFile = yield* daemonFiles.readPidFile(pidFilePath);
93316
- if (existingPidFile) {
93317
- const alive = yield* proc.isPidRunning(existingPidFile.pid);
93318
- if (alive) {
93319
- yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
93320
- return {
93321
- started: false,
93322
- pid: existingPidFile.pid,
93323
- pid_file: pidFilePath,
93324
- log_file: existingPidFile.log_file ?? logFilePath
93325
- };
93326
- }
94054
+ const pidWritable = yield* fsAccess.canWritePath(daemonPidFile);
94055
+ const logWritable = yield* fsAccess.canWritePath(daemonFiles.defaultLogFile());
94056
+ const storeWritable = yield* fsAccess.checkWritableFile(cfg.storeDb);
94057
+ const pathOk = pidWritable && logWritable && storeWritable.ok;
94058
+ return [
94059
+ {
94060
+ id: "runtime.stale_pid_or_state",
94061
+ ok: staleArtifacts.length === 0,
94062
+ severity: staleArtifacts.length === 0 ? "info" : "warning",
94063
+ summary: staleArtifacts.length === 0 ? "No stale runtime pid/state artifacts" : `Found ${staleArtifacts.length} stale runtime artifact set(s)`,
94064
+ details: staleArtifacts,
94065
+ repairable: staleArtifacts.length > 0
94066
+ },
94067
+ {
94068
+ id: "runtime.version_mismatch",
94069
+ ok: mismatches.length === 0,
94070
+ severity: mismatches.length === 0 ? "info" : "warning",
94071
+ summary: mismatches.length === 0 ? "No runtime build mismatch detected" : `Found ${mismatches.length} runtime build mismatch(es)`,
94072
+ details: mismatches,
94073
+ repairable: mismatches.length > 0
94074
+ },
94075
+ {
94076
+ id: "config.migration_needed",
94077
+ ok: configValid && !configChanged,
94078
+ severity: !configValid ? "error" : configChanged ? "warning" : "info",
94079
+ summary: !configValid ? "User config is invalid or conflicting" : configChanged ? "User config can be canonicalized" : "User config already canonical",
94080
+ details: configDetails,
94081
+ repairable: configRepairable
94082
+ },
94083
+ {
94084
+ id: "package.builtin_scenarios_broken",
94085
+ ok: packageCheck.ok,
94086
+ severity: packageCheck.ok ? "info" : "error",
94087
+ summary: packageCheck.ok ? "Builtin scenarios are loadable" : "Builtin scenario package loading failed",
94088
+ details: packageCheck.details,
94089
+ repairable: !packageCheck.ok
94090
+ },
94091
+ {
94092
+ id: "package.plugin_artifacts_unavailable",
94093
+ ok: pluginArtifactsCheck.ok,
94094
+ severity: pluginArtifactsCheck.ok ? "info" : "error",
94095
+ summary: pluginArtifactsCheck.ok ? "Plugin artifacts are available" : "Plugin artifacts are unavailable",
94096
+ details: pluginArtifactsCheck.details,
94097
+ repairable: false
94098
+ },
94099
+ {
94100
+ id: "env.path_or_permission_problem",
94101
+ ok: pathOk,
94102
+ severity: pathOk ? "info" : "error",
94103
+ summary: pathOk ? "Required writable paths are available" : "One or more required paths are not writable",
94104
+ details: {
94105
+ daemon_pid_file: daemonPidFile,
94106
+ daemon_log_file: daemonFiles.defaultLogFile(),
94107
+ pid_writable: pidWritable,
94108
+ log_writable: logWritable,
94109
+ store_db: cfg.storeDb,
94110
+ store_writable: storeWritable
94111
+ },
94112
+ repairable: false
93327
94113
  }
93328
- return {
93329
- started: false,
93330
- pid_file: pidFilePath,
93331
- log_file: logFilePath
93332
- };
93333
- }
93334
- return yield* startWsSupervisor(params3);
94114
+ ];
93335
94115
  });
93336
94116
  }
93337
94117
 
@@ -95429,7 +96209,7 @@ var baseBackoffMs = integer7("backoff-ms").pipe(withDefault5(500));
95429
96209
  var maxBackoffMs = integer7("max-backoff-ms").pipe(withDefault5(1e4));
95430
96210
  var logMaxBytes = integer7("log-max-bytes").pipe(withDefault5(0));
95431
96211
  var logKeep = integer7("log-keep").pipe(withDefault5(5));
95432
- function childCommandLine(params3) {
96212
+ function childCommandLine3(params3) {
95433
96213
  const command = process.argv[0];
95434
96214
  const script = process.argv[1];
95435
96215
  if (!command || !script) {
@@ -95454,7 +96234,7 @@ var wsSupervisorCommand = exports_Command.make("supervisor", { pidFile: pidFile7
95454
96234
  const logFilePath = resolveUserFilePath(logFile5 ?? daemonFiles.defaultLogFile());
95455
96235
  const stateFilePath = resolveUserFilePath(stateFile3 ?? supervisorState.defaultStateFile());
95456
96236
  const childCmd = yield* try_3({
95457
- try: () => childCommandLine({ wsUrl: cfg.wsUrl, storeDb: cfg.storeDb }),
96237
+ try: () => childCommandLine3({ wsUrl: cfg.wsUrl, storeDb: cfg.storeDb }),
95458
96238
  catch: (e) => isCliError(e) ? e : new CliError({
95459
96239
  code: "INTERNAL",
95460
96240
  message: "Failed to build child command line",
@@ -95770,238 +96550,11 @@ function waitForTxn(params3) {
95770
96550
  ]
95771
96551
  }));
95772
96552
  }
95773
- yield* sleep4(millis(resolvedPollMs));
95774
- }
95775
- });
95776
- }
95777
-
95778
- // src/lib/apiUrls.ts
95779
- function normalizeApiBasePath2(basePath) {
95780
- const trimmed2 = basePath.trim();
95781
- if (!trimmed2)
95782
- return "/v1";
95783
- const normalized = trimmed2.startsWith("/") ? trimmed2 : `/${trimmed2}`;
95784
- const withoutTrailing = normalized.replace(/\/+$/, "");
95785
- return withoutTrailing && withoutTrailing !== "/" ? withoutTrailing : "/";
95786
- }
95787
- function normalizeBaseUrl(baseUrl) {
95788
- return baseUrl.trim().replace(/\/+$/, "");
95789
- }
95790
- function resolveBasePrefix(baseUrl, fallbackBasePath) {
95791
- const parsed = new URL(normalizeBaseUrl(baseUrl));
95792
- const pathname = normalizeApiBasePath2(parsed.pathname);
95793
- if (pathname && pathname !== "/")
95794
- return normalizeApiBasePath2(pathname);
95795
- return normalizeApiBasePath2(fallbackBasePath);
95796
- }
95797
- function normalizeRoutePath(routePath) {
95798
- const trimmed2 = routePath.trim();
95799
- if (!trimmed2)
95800
- return "";
95801
- return `/${trimmed2.replace(/^\/+/, "")}`;
95802
- }
95803
- function buildApiBaseUrl(baseUrl, fallbackBasePath = "/v1") {
95804
- const parsed = new URL(normalizeBaseUrl(baseUrl));
95805
- const prefix2 = resolveBasePrefix(baseUrl, fallbackBasePath);
95806
- return prefix2 === "/" ? parsed.origin : `${parsed.origin}${prefix2}`;
95807
- }
95808
- function apiLocalBaseUrl(port3, basePath = "/v1") {
95809
- return buildApiBaseUrl(`http://127.0.0.1:${port3}`, basePath);
95810
- }
95811
- function apiContainerBaseUrl(port3, basePath = "/v1") {
95812
- return buildApiBaseUrl(`http://host.docker.internal:${port3}`, basePath);
95813
- }
95814
- function joinApiUrl(baseUrl, routePath, fallbackBasePath = "/v1") {
95815
- return `${buildApiBaseUrl(baseUrl, fallbackBasePath)}${normalizeRoutePath(routePath)}`;
95816
- }
95817
-
95818
- // src/services/HostApiClient.ts
95819
- function normalizeBaseUrl2(baseUrl) {
95820
- return baseUrl.trim().replace(/\/+$/, "");
95821
- }
95822
- function apiTimeoutError(params3) {
95823
- return new CliError({
95824
- code: "API_TIMEOUT",
95825
- message: `API timeout after ${params3.timeoutMs}ms`,
95826
- exitCode: 1,
95827
- details: { base_url: params3.baseUrl, path: params3.path, timeout_ms: params3.timeoutMs }
95828
- });
95829
- }
95830
- function apiUnavailableError(params3) {
95831
- return new CliError({
95832
- code: "API_UNAVAILABLE",
95833
- message: String(params3.error?.message || params3.error || "API request failed"),
95834
- exitCode: 1,
95835
- details: { base_url: params3.baseUrl, path: params3.path }
95836
- });
95837
- }
95838
- function exitCodeFromRemoteCode(code2) {
95839
- return code2 === "INVALID_ARGS" || code2 === "INVALID_PAYLOAD" || code2 === "PAYLOAD_TOO_LARGE" ? 2 : 1;
95840
- }
95841
- function parseEnvelope(raw4) {
95842
- if (!raw4 || typeof raw4 !== "object") {
95843
- return { ok: false, error: { code: "INTERNAL", message: "Invalid API response envelope" } };
95844
- }
95845
- return raw4;
95846
- }
95847
- function buildQuery(params3) {
95848
- const sp = new URLSearchParams;
95849
- for (const [key, value8] of Object.entries(params3)) {
95850
- if (value8 === undefined || value8 === null)
95851
- continue;
95852
- if (typeof value8 === "string") {
95853
- if (!value8.trim())
95854
- continue;
95855
- sp.set(key, value8);
95856
- continue;
95857
- }
95858
- if (typeof value8 === "number" || typeof value8 === "boolean") {
95859
- sp.set(key, String(value8));
95860
- }
95861
- }
95862
- const query = sp.toString();
95863
- return query ? `?${query}` : "";
95864
- }
95865
- function requestJson(params3) {
95866
- const timeoutMs = Math.max(1, params3.timeoutMs ?? 15000);
95867
- const baseUrl = normalizeBaseUrl2(params3.baseUrl);
95868
- const url2 = joinApiUrl(baseUrl, params3.path, params3.basePath);
95869
- return async((resume2) => {
95870
- const controller = new AbortController;
95871
- const timer2 = setTimeout(() => controller.abort(), timeoutMs);
95872
- fetch(url2, {
95873
- method: params3.method,
95874
- headers: params3.method === "POST" ? { "content-type": "application/json" } : undefined,
95875
- body: params3.body === undefined ? undefined : JSON.stringify(params3.body),
95876
- signal: controller.signal
95877
- }).then(async (response) => {
95878
- clearTimeout(timer2);
95879
- let parsed;
95880
- try {
95881
- parsed = parseEnvelope(await response.json());
95882
- } catch (error4) {
95883
- resume2(fail8(new CliError({
95884
- code: "API_UNAVAILABLE",
95885
- message: "API returned a non-JSON response",
95886
- exitCode: 1,
95887
- details: { url: url2, status: response.status, error: String(error4?.message || error4) }
95888
- })));
95889
- return;
95890
- }
95891
- if (parsed.ok === true) {
95892
- resume2(succeed8(parsed.data));
95893
- return;
95894
- }
95895
- const code2 = typeof parsed.error?.code === "string" ? parsed.error.code : "INTERNAL";
95896
- const message2 = typeof parsed.error?.message === "string" ? parsed.error.message : "API request failed";
95897
- const hint = Array.isArray(parsed.hint) ? parsed.hint.map(String) : undefined;
95898
- resume2(fail8(new CliError({
95899
- code: code2,
95900
- message: message2,
95901
- exitCode: exitCodeFromRemoteCode(code2),
95902
- details: parsed.error?.details,
95903
- hint
95904
- })));
95905
- }).catch((error4) => {
95906
- clearTimeout(timer2);
95907
- if (error4?.name === "AbortError") {
95908
- resume2(fail8(apiTimeoutError({ baseUrl, path: params3.path, timeoutMs })));
95909
- return;
95910
- }
95911
- resume2(fail8(apiUnavailableError({ baseUrl, path: params3.path, error: error4 })));
95912
- });
95913
- return sync3(() => {
95914
- clearTimeout(timer2);
95915
- controller.abort();
95916
- });
96553
+ yield* sleep4(millis(resolvedPollMs));
96554
+ }
95917
96555
  });
95918
96556
  }
95919
96557
 
95920
- class HostApiClient extends Tag2("HostApiClient")() {
95921
- }
95922
- var HostApiClientLive = effect(HostApiClient, gen2(function* () {
95923
- const cfg = yield* AppConfig;
95924
- const basePath = cfg.apiBasePath ?? "/v1";
95925
- const request = (params3) => requestJson({ ...params3, basePath });
95926
- return {
95927
- resolveRefValue: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/ref/resolve", method: "POST", body, timeoutMs }),
95928
- resolvePlacement: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/placement/resolve", method: "POST", body, timeoutMs }),
95929
- resolveStableSiblingRange: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/selection/stable-sibling-range", method: "POST", body, timeoutMs }),
95930
- health: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/health", method: "GET", timeoutMs }),
95931
- status: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/status", method: "GET", timeoutMs }),
95932
- uiContextSnapshot: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95933
- baseUrl,
95934
- path: `/plugin/ui-context/snapshot${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95935
- method: "GET",
95936
- timeoutMs
95937
- }),
95938
- uiContextPage: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95939
- baseUrl,
95940
- path: `/plugin/ui-context/page${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95941
- method: "GET",
95942
- timeoutMs
95943
- }),
95944
- uiContextFocusedRem: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95945
- baseUrl,
95946
- path: `/plugin/ui-context/focused-rem${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95947
- method: "GET",
95948
- timeoutMs
95949
- }),
95950
- uiContextDescribe: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, selectionLimit, timeoutMs }) => request({
95951
- baseUrl,
95952
- path: `/plugin/ui-context/describe${buildQuery({ stateFile: stateFile3, staleMs: staleMs2, selectionLimit })}`,
95953
- method: "GET",
95954
- timeoutMs
95955
- }),
95956
- selectionSnapshot: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95957
- baseUrl,
95958
- path: `/plugin/selection/snapshot${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95959
- method: "GET",
95960
- timeoutMs
95961
- }),
95962
- selectionRoots: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95963
- baseUrl,
95964
- path: `/plugin/selection/roots${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95965
- method: "GET",
95966
- timeoutMs
95967
- }),
95968
- selectionCurrent: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, timeoutMs }) => request({
95969
- baseUrl,
95970
- path: `/plugin/selection/current${buildQuery({ stateFile: stateFile3, staleMs: staleMs2 })}`,
95971
- method: "GET",
95972
- timeoutMs
95973
- }),
95974
- pluginCurrent: ({ baseUrl, stateFile: stateFile3, staleMs: staleMs2, selectionLimit, timeoutMs }) => request({
95975
- baseUrl,
95976
- path: `/plugin/current${buildQuery({ stateFile: stateFile3, staleMs: staleMs2, selectionLimit })}`,
95977
- method: "GET",
95978
- timeoutMs
95979
- }),
95980
- selectionOutline: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/plugin/selection/outline", method: "POST", body, timeoutMs }),
95981
- uiContext: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/ui-context", method: "GET", timeoutMs }),
95982
- selection: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/selection", method: "GET", timeoutMs }),
95983
- searchDb: ({ baseUrl, ...body }) => request({ baseUrl, path: "/search/db", method: "POST", body }),
95984
- searchPlugin: ({ baseUrl, ...body }) => request({ baseUrl, path: "/search/plugin", method: "POST", body }),
95985
- writeApply: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/write/apply", method: "POST", body, timeoutMs }),
95986
- readOutline: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/outline", method: "POST", body, timeoutMs }),
95987
- readPageId: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/page-id", method: "POST", body, timeoutMs }),
95988
- resolveRef: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/resolve-ref", method: "POST", body, timeoutMs }),
95989
- byReference: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/by-reference", method: "POST", body, timeoutMs }),
95990
- references: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/references", method: "POST", body, timeoutMs }),
95991
- resolveQueryPowerup: ({ baseUrl, powerup, timeoutMs }) => request({ baseUrl, path: "/internal/query/resolve-powerup", method: "POST", body: { powerup }, timeoutMs }),
95992
- query: ({ baseUrl, body, timeoutMs }) => request({ baseUrl, path: "/read/query", method: "POST", body, timeoutMs }),
95993
- dailyRemId: ({ baseUrl, date: date6, offsetDays, timeoutMs }) => request({
95994
- baseUrl,
95995
- path: `/daily/rem-id${buildQuery({ date: date6, offsetDays })}`,
95996
- method: "GET",
95997
- timeoutMs
95998
- }),
95999
- queueWait: ({ baseUrl, txnId, timeoutMs, pollMs }) => request({ baseUrl, path: "/queue/wait", method: "POST", body: { txnId, timeoutMs, pollMs } }),
96000
- queueTxn: ({ baseUrl, txnId, timeoutMs }) => request({ baseUrl, path: `/queue/txns/${encodeURIComponent(txnId)}`, method: "GET", timeoutMs }),
96001
- triggerSync: ({ baseUrl, timeoutMs }) => request({ baseUrl, path: "/actions/trigger-sync", method: "POST", timeoutMs })
96002
- };
96003
- }));
96004
-
96005
96558
  // src/lib/workspaceResolver.ts
96006
96559
  import fs19 from "node:fs";
96007
96560
 
@@ -101420,232 +101973,46 @@ var applyCommand = exports_Command.make("apply", {
101420
101973
  const resolvedIdempotencyKey = idempotencyKey3 ?? compiled.idempotencyKey;
101421
101974
  if (dryRun) {
101422
101975
  yield* writeSuccess({
101423
- data: {
101424
- dry_run: true,
101425
- kind: compiled.kind,
101426
- ops: compiled.ops,
101427
- alias_map: compiled.aliasMap,
101428
- meta: metaValue ? payloadSvc.normalizeKeys(metaValue) : undefined
101429
- },
101430
- md: `- dry_run: true
101431
- - kind: ${compiled.kind}
101432
- - ops: ${compiled.ops.length}
101433
- `
101434
- });
101435
- return;
101436
- }
101437
- yield* validateOptionMutationOps({ scopeLabel: "generic", ops: compiled.ops });
101438
- const data = yield* invokeWave1Capability("write.apply", {
101439
- body: {
101440
- ...expanded,
101441
- priority: resolvedPriority,
101442
- clientId: resolvedClientId,
101443
- idempotencyKey: resolvedIdempotencyKey,
101444
- meta: metaValue,
101445
- notify: notify3 ?? compiled.notify ?? true,
101446
- ensureDaemon: ensureDaemon4 ?? compiled.ensureDaemon ?? true
101447
- },
101448
- wait: wait3,
101449
- timeoutMs: timeoutMs3,
101450
- pollMs: pollMs3
101451
- });
101452
- const out = compiled.kind === "actions" ? { ...data, alias_map: data?.alias_map ?? compiled.aliasMap } : data;
101453
- yield* writeSuccess({
101454
- data: out,
101455
- ids: [data.txn_id, ...data.op_ids],
101456
- md: `- txn_id: ${data.txn_id}
101457
- - op_ids: ${data.op_ids.length}
101458
- - notified: ${data.notified}
101459
- - sent: ${data.sent ?? ""}
101460
- `
101461
- });
101462
- }).pipe(catchAll2(writeFailure)));
101463
-
101464
- // src/commands/api/_shared.ts
101465
- var API_HEALTH_TIMEOUT_MS = 2000;
101466
- var API_START_WAIT_DEFAULT_MS = 15000;
101467
- var API_STOP_WAIT_DEFAULT_MS = 5000;
101468
- function childCommandLine2(params3) {
101469
- const command = process.argv[0];
101470
- const script = process.argv[1];
101471
- if (!command || !script) {
101472
- throw new CliError({
101473
- code: "INTERNAL",
101474
- message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
101475
- exitCode: 1,
101476
- details: { argv: process.argv }
101477
- });
101478
- }
101479
- const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
101480
- const args2 = [...execArgv, script, "--daemon-url", params3.wsUrl, "--store-db", params3.storeDb];
101481
- if (params3.remnoteDb)
101482
- args2.push("--remnote-db", params3.remnoteDb);
101483
- args2.push("--api-base-path", params3.basePath);
101484
- args2.push("api", "serve", "--host", params3.host, "--port", String(params3.port), "--state-file", params3.stateFile);
101485
- return { command, args: args2 };
101486
- }
101487
- function toPidFileValue2(params3) {
101488
- return {
101489
- pid: params3.pid,
101490
- build: currentRuntimeBuildInfo(),
101491
- started_at: params3.startedAt,
101492
- host: params3.host,
101493
- port: params3.port,
101494
- base_path: params3.basePath,
101495
- log_file: params3.logFile,
101496
- state_file: params3.stateFile,
101497
- cmd: params3.cmd
101498
- };
101499
- }
101500
- function waitForApiHealth(baseUrl, waitMs) {
101501
- return gen2(function* () {
101502
- if (!Number.isFinite(waitMs) || waitMs < 0) {
101503
- return yield* fail8(new CliError({
101504
- code: "INVALID_ARGS",
101505
- message: "--wait must be a non-negative integer (ms)",
101506
- exitCode: 2,
101507
- details: { wait_ms: waitMs }
101508
- }));
101509
- }
101510
- if (waitMs === 0)
101511
- return;
101512
- const api = yield* HostApiClient;
101513
- const deadline = Date.now() + waitMs;
101514
- while (Date.now() < deadline) {
101515
- const remaining = Math.max(0, deadline - Date.now());
101516
- const res = yield* api.health({ baseUrl, timeoutMs: Math.min(API_HEALTH_TIMEOUT_MS, Math.max(1, remaining)) }).pipe(either3);
101517
- if (isRight2(res))
101518
- return;
101519
- yield* sleep4(millis(300));
101520
- }
101521
- return yield* fail8(new CliError({
101522
- code: "API_TIMEOUT",
101523
- message: `Timed out waiting for host API to become available (${waitMs}ms)`,
101524
- exitCode: 1,
101525
- details: { base_url: baseUrl, wait_ms: waitMs },
101526
- hint: ["agent-remnote api status --json", "agent-remnote api logs", "agent-remnote stack status --json"]
101527
- }));
101528
- });
101529
- }
101530
- function startApiDaemon(params3) {
101531
- return gen2(function* () {
101532
- const cfg = yield* AppConfig;
101533
- const api = yield* HostApiClient;
101534
- const apiFiles = yield* ApiDaemonFiles;
101535
- const proc = yield* Process;
101536
- const host = params3.host ?? cfg.apiHost ?? "0.0.0.0";
101537
- const port3 = params3.port ?? cfg.apiPort ?? 3000;
101538
- const basePath2 = cfg.apiBasePath ?? "/v1";
101539
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? apiFiles.defaultPidFile());
101540
- const logFilePath = resolveUserFilePath(params3.logFile ?? apiFiles.defaultLogFile());
101541
- const stateFilePath = resolveUserFilePath(params3.stateFile ?? apiFiles.defaultStateFile());
101542
- const localBaseUrl = apiLocalBaseUrl(port3, basePath2);
101543
- const existing = yield* apiFiles.readPidFile(pidFilePath);
101544
- if (existing) {
101545
- const alive = yield* proc.isPidRunning(existing.pid);
101546
- if (!alive) {
101547
- yield* apiFiles.deletePidFile(pidFilePath);
101548
- } else {
101549
- yield* requireTrustedPidRecord({ record: existing, pidFilePath });
101550
- return {
101551
- started: false,
101552
- pid: existing.pid,
101553
- pid_file: pidFilePath,
101554
- log_file: logFilePath,
101555
- state_file: stateFilePath,
101556
- base_url: localBaseUrl
101557
- };
101558
- }
101559
- }
101560
- const pre = yield* api.health({ baseUrl: localBaseUrl, timeoutMs: API_HEALTH_TIMEOUT_MS }).pipe(either3);
101561
- if (isRight2(pre)) {
101562
- return {
101563
- started: false,
101564
- pid_file: pidFilePath,
101565
- log_file: logFilePath,
101566
- state_file: stateFilePath,
101567
- base_url: localBaseUrl
101568
- };
101569
- }
101570
- const cmd = yield* try_3({
101571
- try: () => childCommandLine2({
101572
- wsUrl: cfg.wsUrl,
101573
- storeDb: cfg.storeDb,
101574
- remnoteDb: cfg.remnoteDb,
101575
- host,
101576
- port: port3,
101577
- basePath: basePath2,
101578
- stateFile: stateFilePath
101579
- }),
101580
- catch: (e) => isCliError(e) ? e : new CliError({
101581
- code: "INTERNAL",
101582
- message: "Failed to start host api",
101583
- exitCode: 1,
101584
- details: { error: String(e?.message || e) }
101585
- })
101586
- });
101587
- const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
101588
- yield* apiFiles.writePidFile(pidFilePath, toPidFileValue2({
101589
- pid,
101590
- startedAt: Date.now(),
101591
- host,
101592
- port: port3,
101593
- basePath: basePath2,
101594
- logFile: logFilePath,
101595
- stateFile: stateFilePath,
101596
- cmd: [cmd.command, ...cmd.args]
101597
- }));
101598
- yield* waitForApiHealth(localBaseUrl, params3.waitMs);
101599
- return {
101600
- started: true,
101601
- pid,
101602
- pid_file: pidFilePath,
101603
- log_file: logFilePath,
101604
- state_file: stateFilePath,
101605
- base_url: localBaseUrl
101606
- };
101976
+ data: {
101977
+ dry_run: true,
101978
+ kind: compiled.kind,
101979
+ ops: compiled.ops,
101980
+ alias_map: compiled.aliasMap,
101981
+ meta: metaValue ? payloadSvc.normalizeKeys(metaValue) : undefined
101982
+ },
101983
+ md: `- dry_run: true
101984
+ - kind: ${compiled.kind}
101985
+ - ops: ${compiled.ops.length}
101986
+ `
101987
+ });
101988
+ return;
101989
+ }
101990
+ yield* validateOptionMutationOps({ scopeLabel: "generic", ops: compiled.ops });
101991
+ const data = yield* invokeWave1Capability("write.apply", {
101992
+ body: {
101993
+ ...expanded,
101994
+ priority: resolvedPriority,
101995
+ clientId: resolvedClientId,
101996
+ idempotencyKey: resolvedIdempotencyKey,
101997
+ meta: metaValue,
101998
+ notify: notify3 ?? compiled.notify ?? true,
101999
+ ensureDaemon: ensureDaemon4 ?? compiled.ensureDaemon ?? true
102000
+ },
102001
+ wait: wait3,
102002
+ timeoutMs: timeoutMs3,
102003
+ pollMs: pollMs3
101607
102004
  });
101608
- }
101609
- function ensureApiDaemon(params3) {
101610
- return gen2(function* () {
101611
- const cfg = yield* AppConfig;
101612
- const api = yield* HostApiClient;
101613
- const apiFiles = yield* ApiDaemonFiles;
101614
- const proc = yield* Process;
101615
- const port3 = params3.port ?? cfg.apiPort ?? 3000;
101616
- const basePath2 = cfg.apiBasePath ?? "/v1";
101617
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? apiFiles.defaultPidFile());
101618
- const logFilePath = resolveUserFilePath(params3.logFile ?? apiFiles.defaultLogFile());
101619
- const stateFilePath = resolveUserFilePath(params3.stateFile ?? apiFiles.defaultStateFile());
101620
- const localBaseUrl = apiLocalBaseUrl(port3, basePath2);
101621
- const pre = yield* api.health({ baseUrl: localBaseUrl, timeoutMs: API_HEALTH_TIMEOUT_MS }).pipe(either3);
101622
- if (isRight2(pre)) {
101623
- const existing = yield* apiFiles.readPidFile(pidFilePath);
101624
- if (existing) {
101625
- const alive = yield* proc.isPidRunning(existing.pid);
101626
- if (alive) {
101627
- yield* requireTrustedPidRecord({ record: existing, pidFilePath });
101628
- return {
101629
- started: false,
101630
- pid: existing.pid,
101631
- pid_file: pidFilePath,
101632
- log_file: existing.log_file ?? logFilePath,
101633
- state_file: existing.state_file ?? stateFilePath,
101634
- base_url: localBaseUrl
101635
- };
101636
- }
101637
- }
101638
- return {
101639
- started: false,
101640
- pid_file: pidFilePath,
101641
- log_file: logFilePath,
101642
- state_file: stateFilePath,
101643
- base_url: localBaseUrl
101644
- };
101645
- }
101646
- return yield* startApiDaemon(params3);
102005
+ const out = compiled.kind === "actions" ? { ...data, alias_map: data?.alias_map ?? compiled.aliasMap } : data;
102006
+ yield* writeSuccess({
102007
+ data: out,
102008
+ ids: [data.txn_id, ...data.op_ids],
102009
+ md: `- txn_id: ${data.txn_id}
102010
+ - op_ids: ${data.op_ids.length}
102011
+ - notified: ${data.notified}
102012
+ - sent: ${data.sent ?? ""}
102013
+ `
101647
102014
  });
101648
- }
102015
+ }).pipe(catchAll2(writeFailure)));
101649
102016
 
101650
102017
  // src/commands/api/ensure.ts
101651
102018
  function optionToUndefined17(opt) {
@@ -103017,203 +103384,6 @@ var pluginCurrentCommand = exports_Command.make("current", { stateFile: stateFil
103017
103384
  yield* writeSuccess({ data: out, ids: ids3, md });
103018
103385
  }).pipe(catchAll2(writeFailure)));
103019
103386
 
103020
- // src/lib/pluginServerHealth.ts
103021
- function checkPluginServerHealth(baseUrl, timeoutMs3) {
103022
- return tryPromise2({
103023
- try: async () => {
103024
- const controller = new AbortController;
103025
- const timer2 = setTimeout(() => controller.abort(), timeoutMs3);
103026
- try {
103027
- const res = await fetch(`${baseUrl}/manifest.json`, { signal: controller.signal });
103028
- if (!res.ok) {
103029
- throw new Error(`Unexpected response status: ${res.status}`);
103030
- }
103031
- return { base_url: baseUrl };
103032
- } finally {
103033
- clearTimeout(timer2);
103034
- }
103035
- },
103036
- catch: (error4) => new CliError({
103037
- code: "PLUGIN_UNAVAILABLE",
103038
- message: "Plugin server is unavailable",
103039
- exitCode: 1,
103040
- details: { base_url: baseUrl, error: String(error4?.message || error4) }
103041
- })
103042
- });
103043
- }
103044
- function waitForPluginServerHealth(baseUrl, waitMs, timeoutMs3) {
103045
- return gen2(function* () {
103046
- if (!Number.isFinite(waitMs) || waitMs < 0) {
103047
- return yield* fail8(new CliError({
103048
- code: "INVALID_ARGS",
103049
- message: "--wait must be a non-negative integer (ms)",
103050
- exitCode: 2,
103051
- details: { wait_ms: waitMs }
103052
- }));
103053
- }
103054
- if (waitMs === 0)
103055
- return;
103056
- const deadline = Date.now() + waitMs;
103057
- while (Date.now() < deadline) {
103058
- const remaining = Math.max(0, deadline - Date.now());
103059
- const res = yield* checkPluginServerHealth(baseUrl, Math.min(timeoutMs3, Math.max(1, remaining))).pipe(either3);
103060
- if (isRight2(res))
103061
- return;
103062
- yield* sleep4(millis(300));
103063
- }
103064
- return yield* fail8(new CliError({
103065
- code: "PLUGIN_UNAVAILABLE",
103066
- message: `Timed out waiting for plugin server to become available (${waitMs}ms)`,
103067
- exitCode: 1,
103068
- details: { base_url: baseUrl, wait_ms: waitMs },
103069
- hint: ["agent-remnote plugin status --json", "agent-remnote plugin logs --lines 200"]
103070
- }));
103071
- });
103072
- }
103073
-
103074
- // src/commands/plugin/_shared.ts
103075
- var PLUGIN_SERVER_HEALTH_TIMEOUT_MS = 2000;
103076
- var PLUGIN_SERVER_START_WAIT_DEFAULT_MS = 15000;
103077
- var PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS = 5000;
103078
- var PLUGIN_SERVER_DEFAULT_HOST = "127.0.0.1";
103079
- var PLUGIN_SERVER_DEFAULT_PORT = 8080;
103080
- function pluginServerLocalBaseUrl(host5, port7) {
103081
- const normalizedHost = host5 === "0.0.0.0" || host5 === "::" ? "127.0.0.1" : host5;
103082
- return `http://${normalizedHost}:${port7}`;
103083
- }
103084
- function childCommandLine3(params3) {
103085
- const command = process.argv[0];
103086
- const script = process.argv[1];
103087
- if (!command || !script) {
103088
- throw new CliError({
103089
- code: "INTERNAL",
103090
- message: "Unable to determine the current executable entrypoint (process.argv is incomplete)",
103091
- exitCode: 1,
103092
- details: { argv: process.argv }
103093
- });
103094
- }
103095
- const execArgv = Array.isArray(process.execArgv) ? process.execArgv : [];
103096
- const args2 = [...execArgv, script, "plugin", "serve", "--host", params3.host, "--port", String(params3.port), "--state-file", params3.stateFile];
103097
- return { command, args: args2 };
103098
- }
103099
- function toPidFileValue3(params3) {
103100
- return {
103101
- pid: params3.pid,
103102
- build: currentRuntimeBuildInfo(),
103103
- started_at: params3.startedAt,
103104
- host: params3.host,
103105
- port: params3.port,
103106
- log_file: params3.logFile,
103107
- state_file: params3.stateFile,
103108
- cmd: params3.cmd
103109
- };
103110
- }
103111
- function startPluginServer(params3) {
103112
- return gen2(function* () {
103113
- const files = yield* PluginServerFiles;
103114
- const proc = yield* Process;
103115
- const host5 = params3.host ?? PLUGIN_SERVER_DEFAULT_HOST;
103116
- const port7 = params3.port ?? PLUGIN_SERVER_DEFAULT_PORT;
103117
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? files.defaultPidFile());
103118
- const logFilePath = resolveUserFilePath(params3.logFile ?? files.defaultLogFile());
103119
- const stateFilePath = resolveUserFilePath(params3.stateFile ?? files.defaultStateFile());
103120
- const baseUrl = pluginServerLocalBaseUrl(host5, port7);
103121
- const existing = yield* files.readPidFile(pidFilePath);
103122
- if (existing) {
103123
- const alive = yield* proc.isPidRunning(existing.pid);
103124
- if (!alive) {
103125
- yield* files.deletePidFile(pidFilePath);
103126
- } else {
103127
- yield* requireTrustedPidRecord({ record: existing, pidFilePath });
103128
- return {
103129
- started: false,
103130
- pid: existing.pid,
103131
- pid_file: pidFilePath,
103132
- log_file: existing.log_file ?? logFilePath,
103133
- state_file: existing.state_file ?? stateFilePath,
103134
- base_url: baseUrl
103135
- };
103136
- }
103137
- }
103138
- const pre = yield* checkPluginServerHealth(baseUrl, PLUGIN_SERVER_HEALTH_TIMEOUT_MS).pipe(either3);
103139
- if (isRight2(pre)) {
103140
- return {
103141
- started: false,
103142
- pid_file: pidFilePath,
103143
- log_file: logFilePath,
103144
- state_file: stateFilePath,
103145
- base_url: baseUrl
103146
- };
103147
- }
103148
- const cmd = yield* try_3({
103149
- try: () => childCommandLine3({ host: host5, port: port7, stateFile: stateFilePath }),
103150
- catch: (error4) => isCliError(error4) ? error4 : new CliError({
103151
- code: "INTERNAL",
103152
- message: "Failed to start plugin server",
103153
- exitCode: 1,
103154
- details: { error: String(error4?.message || error4) }
103155
- })
103156
- });
103157
- const pid = yield* proc.spawnDetached({ command: cmd.command, args: cmd.args, logFile: logFilePath });
103158
- yield* files.writePidFile(pidFilePath, toPidFileValue3({
103159
- pid,
103160
- startedAt: Date.now(),
103161
- host: host5,
103162
- port: port7,
103163
- logFile: logFilePath,
103164
- stateFile: stateFilePath,
103165
- cmd: [cmd.command, ...cmd.args]
103166
- }));
103167
- yield* waitForPluginServerHealth(baseUrl, params3.waitMs, PLUGIN_SERVER_HEALTH_TIMEOUT_MS);
103168
- return {
103169
- started: true,
103170
- pid,
103171
- pid_file: pidFilePath,
103172
- log_file: logFilePath,
103173
- state_file: stateFilePath,
103174
- base_url: baseUrl
103175
- };
103176
- });
103177
- }
103178
- function ensurePluginServer(params3) {
103179
- return gen2(function* () {
103180
- const files = yield* PluginServerFiles;
103181
- const proc = yield* Process;
103182
- const host5 = params3.host ?? PLUGIN_SERVER_DEFAULT_HOST;
103183
- const port7 = params3.port ?? PLUGIN_SERVER_DEFAULT_PORT;
103184
- const pidFilePath = resolveUserFilePath(params3.pidFile ?? files.defaultPidFile());
103185
- const logFilePath = resolveUserFilePath(params3.logFile ?? files.defaultLogFile());
103186
- const stateFilePath = resolveUserFilePath(params3.stateFile ?? files.defaultStateFile());
103187
- const baseUrl = pluginServerLocalBaseUrl(host5, port7);
103188
- const pre = yield* checkPluginServerHealth(baseUrl, PLUGIN_SERVER_HEALTH_TIMEOUT_MS).pipe(either3);
103189
- if (isRight2(pre)) {
103190
- const existing = yield* files.readPidFile(pidFilePath);
103191
- if (existing) {
103192
- const alive = yield* proc.isPidRunning(existing.pid);
103193
- if (alive) {
103194
- yield* requireTrustedPidRecord({ record: existing, pidFilePath });
103195
- return {
103196
- started: false,
103197
- pid: existing.pid,
103198
- pid_file: pidFilePath,
103199
- log_file: existing.log_file ?? logFilePath,
103200
- state_file: existing.state_file ?? stateFilePath,
103201
- base_url: baseUrl
103202
- };
103203
- }
103204
- }
103205
- return {
103206
- started: false,
103207
- pid_file: pidFilePath,
103208
- log_file: logFilePath,
103209
- state_file: stateFilePath,
103210
- base_url: baseUrl
103211
- };
103212
- }
103213
- return yield* startPluginServer(params3);
103214
- });
103215
- }
103216
-
103217
103387
  // src/commands/plugin/ensure.ts
103218
103388
  function optionToUndefined33(opt) {
103219
103389
  return isSome2(opt) ? opt.value : undefined;