agent-remnote 1.4.0 → 1.5.0

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
@@ -43214,6 +43214,7 @@ var option = (self) => matchEffect(self, {
43214
43214
  onSuccess: (a) => succeed(some2(a))
43215
43215
  });
43216
43216
  var orElseFail = /* @__PURE__ */ dual(2, (self, evaluate2) => orElse2(self, () => failSync(evaluate2)));
43217
+ var orElseSucceed = /* @__PURE__ */ dual(2, (self, evaluate2) => orElse2(self, () => sync(evaluate2)));
43217
43218
  var patchFiberRefs = (patch11) => updateFiberRefs((fiberId2, fiberRefs3) => pipe(patch11, patch9(fiberId2, fiberRefs3)));
43218
43219
  var promise = (evaluate2) => evaluate2.length >= 1 ? async_((resolve, signal) => {
43219
43220
  try {
@@ -48400,6 +48401,7 @@ var withUnhandledErrorLogLevel2 = withUnhandledErrorLogLevel;
48400
48401
  var orDie2 = orDie;
48401
48402
  var orElse4 = orElse2;
48402
48403
  var orElseFail2 = orElseFail;
48404
+ var orElseSucceed2 = orElseSucceed;
48403
48405
  var runtime4 = runtime3;
48404
48406
  var unsafeMakeSemaphore2 = unsafeMakeSemaphore;
48405
48407
  var makeSemaphore2 = makeSemaphore;
@@ -73990,6 +73992,7 @@ var platformRunnerImpl = /* @__PURE__ */ PlatformRunner2.of({
73990
73992
  // src/main.ts
73991
73993
  import { readFileSync as readFileSync5 } from "node:fs";
73992
73994
  import { format as format10 } from "node:util";
73995
+ import { isMainThread as isMainThread2, parentPort as parentPort3, workerData as workerData2 } from "node:worker_threads";
73993
73996
 
73994
73997
  // src/services/UserConfigFile.ts
73995
73998
  import { promises as fs4 } from "node:fs";
@@ -74323,6 +74326,41 @@ function isConfigCommandInvocation(argv) {
74323
74326
  }
74324
74327
  return tokens[i] === "config";
74325
74328
  }
74329
+ function isDoctorCommandInvocation(argv) {
74330
+ const tokens = argv.slice(2);
74331
+ let i = 0;
74332
+ while (i < tokens.length) {
74333
+ const raw4 = String(tokens[i] ?? "");
74334
+ if (!raw4)
74335
+ break;
74336
+ if (raw4 === "--") {
74337
+ i += 1;
74338
+ break;
74339
+ }
74340
+ if (!raw4.startsWith("-"))
74341
+ break;
74342
+ const { flag, inlineValue } = splitFlagInlineValue(raw4);
74343
+ if (ROOT_VALUE_FLAGS.has(flag)) {
74344
+ i += inlineValue !== null ? 1 : 2;
74345
+ continue;
74346
+ }
74347
+ if (ROOT_BOOL_FLAGS.has(flag) || BUILTIN_BOOL_FLAGS.has(flag)) {
74348
+ if (inlineValue !== null) {
74349
+ i += 1;
74350
+ continue;
74351
+ }
74352
+ const next4 = tokens[i + 1];
74353
+ if (typeof next4 === "string" && isBooleanLiteralToken(next4)) {
74354
+ i += 2;
74355
+ continue;
74356
+ }
74357
+ i += 1;
74358
+ continue;
74359
+ }
74360
+ break;
74361
+ }
74362
+ return tokens[i] === "doctor";
74363
+ }
74326
74364
  function normalizeApiBasePath(raw4) {
74327
74365
  const trimmed2 = raw4.trim();
74328
74366
  if (!trimmed2)
@@ -74534,7 +74572,9 @@ function readUserConfigFile(configFile) {
74534
74572
  const nestedHost = apiObject?.host;
74535
74573
  const nestedPort = apiObject?.port;
74536
74574
  const nestedBasePath = apiObject?.basePath;
74537
- const apiBaseUrl = config3.apiBaseUrl ?? nestedBaseUrl;
74575
+ const hasRootApiBaseUrl = Object.prototype.hasOwnProperty.call(config3, "apiBaseUrl");
74576
+ const hasNestedBaseUrl = apiObject ? Object.prototype.hasOwnProperty.call(apiObject, "baseUrl") : false;
74577
+ const apiBaseUrl = hasRootApiBaseUrl ? config3.apiBaseUrl : nestedBaseUrl;
74538
74578
  const apiHostRaw = config3.apiHost ?? nestedHost;
74539
74579
  const apiPortRaw = config3.apiPort ?? nestedPort;
74540
74580
  const apiBasePathRaw = config3.apiBasePath ?? nestedBasePath;
@@ -74546,6 +74586,20 @@ function readUserConfigFile(configFile) {
74546
74586
  details: { config_file: file6 }
74547
74587
  });
74548
74588
  }
74589
+ const normalizedApiBaseUrl = apiBaseUrl === undefined ? undefined : (() => {
74590
+ const normalized = normalizeApiBaseUrl(apiBaseUrl);
74591
+ const rootNormalized = typeof config3.apiBaseUrl === "string" ? normalizeApiBaseUrl(config3.apiBaseUrl) : undefined;
74592
+ const nestedNormalized = typeof nestedBaseUrl === "string" ? normalizeApiBaseUrl(nestedBaseUrl) : undefined;
74593
+ if (hasRootApiBaseUrl && hasNestedBaseUrl && rootNormalized !== nestedNormalized) {
74594
+ throw new CliError({
74595
+ code: "INVALID_ARGS",
74596
+ message: `Config keys apiBaseUrl and api.baseUrl conflict: ${file6}`,
74597
+ exitCode: 2,
74598
+ details: { config_file: file6 }
74599
+ });
74600
+ }
74601
+ return normalized;
74602
+ })();
74549
74603
  const apiHost = apiHostRaw === undefined ? undefined : (() => {
74550
74604
  if (typeof apiHostRaw !== "string") {
74551
74605
  throw new CliError({
@@ -74555,7 +74609,18 @@ function readUserConfigFile(configFile) {
74555
74609
  details: { config_file: file6 }
74556
74610
  });
74557
74611
  }
74558
- return normalizeApiHost(apiHostRaw);
74612
+ const normalized = normalizeApiHost(apiHostRaw);
74613
+ const rootNormalized = typeof config3.apiHost === "string" ? normalizeApiHost(config3.apiHost) : undefined;
74614
+ const nestedNormalized = typeof nestedHost === "string" ? normalizeApiHost(nestedHost) : undefined;
74615
+ if (rootNormalized !== undefined && nestedNormalized !== undefined && rootNormalized !== nestedNormalized) {
74616
+ throw new CliError({
74617
+ code: "INVALID_ARGS",
74618
+ message: `Config keys apiHost and api.host conflict: ${file6}`,
74619
+ exitCode: 2,
74620
+ details: { config_file: file6 }
74621
+ });
74622
+ }
74623
+ return normalized;
74559
74624
  })();
74560
74625
  const apiPort = apiPortRaw === undefined ? undefined : (() => {
74561
74626
  if (typeof apiPortRaw !== "number" && typeof apiPortRaw !== "string") {
@@ -74566,7 +74631,18 @@ function readUserConfigFile(configFile) {
74566
74631
  details: { config_file: file6 }
74567
74632
  });
74568
74633
  }
74569
- return normalizeApiPort(apiPortRaw);
74634
+ const normalized = normalizeApiPort(apiPortRaw);
74635
+ const rootNormalized = typeof config3.apiPort === "number" || typeof config3.apiPort === "string" ? normalizeApiPort(config3.apiPort) : undefined;
74636
+ const nestedNormalized = typeof nestedPort === "number" || typeof nestedPort === "string" ? normalizeApiPort(nestedPort) : undefined;
74637
+ if (rootNormalized !== undefined && nestedNormalized !== undefined && rootNormalized !== nestedNormalized) {
74638
+ throw new CliError({
74639
+ code: "INVALID_ARGS",
74640
+ message: `Config keys apiPort and api.port conflict: ${file6}`,
74641
+ exitCode: 2,
74642
+ details: { config_file: file6 }
74643
+ });
74644
+ }
74645
+ return normalized;
74570
74646
  })();
74571
74647
  const apiBasePath = apiBasePathRaw === undefined ? undefined : (() => {
74572
74648
  if (typeof apiBasePathRaw !== "string") {
@@ -74577,12 +74653,23 @@ function readUserConfigFile(configFile) {
74577
74653
  details: { config_file: file6 }
74578
74654
  });
74579
74655
  }
74580
- return normalizeApiBasePath(apiBasePathRaw);
74656
+ const normalized = normalizeApiBasePath(apiBasePathRaw);
74657
+ const rootNormalized = typeof config3.apiBasePath === "string" ? normalizeApiBasePath(config3.apiBasePath) : undefined;
74658
+ const nestedNormalized = typeof nestedBasePath === "string" ? normalizeApiBasePath(nestedBasePath) : undefined;
74659
+ if (rootNormalized !== undefined && nestedNormalized !== undefined && rootNormalized !== nestedNormalized) {
74660
+ throw new CliError({
74661
+ code: "INVALID_ARGS",
74662
+ message: `Config keys apiBasePath and api.basePath conflict: ${file6}`,
74663
+ exitCode: 2,
74664
+ details: { config_file: file6 }
74665
+ });
74666
+ }
74667
+ return normalized;
74581
74668
  })();
74582
74669
  if (apiBaseUrl === undefined && apiHost === undefined && apiPort === undefined && apiBasePath === undefined) {
74583
74670
  return {};
74584
74671
  }
74585
- return { apiBaseUrl, apiHost, apiPort, apiBasePath };
74672
+ return { apiBaseUrl: normalizedApiBaseUrl, apiHost, apiPort, apiBasePath };
74586
74673
  }
74587
74674
  function resolveConfig() {
74588
74675
  return gen2(function* () {
@@ -74597,7 +74684,7 @@ function resolveConfig() {
74597
74684
  details: { config_file: configFile, error: String(error4?.message || error4) }
74598
74685
  })
74599
74686
  }));
74600
- const userConfig = userConfigResult._tag === "Right" ? userConfigResult.right : isConfigCommandInvocation(process.argv) ? {} : yield* fail8(userConfigResult.left);
74687
+ const userConfig = userConfigResult._tag === "Right" ? userConfigResult.right : isConfigCommandInvocation(process.argv) || isDoctorCommandInvocation(process.argv) ? {} : yield* fail8(userConfigResult.left);
74601
74688
  const wsStateFile = resolveWsStateFile(raw4.wsStateFile);
74602
74689
  const remnoteDb = optionalTrimmed(raw4.remnoteDb) ? resolveUserFilePath(raw4.remnoteDb) : inferRemnoteDbFromWsState({ wsStateFile, wsStateStaleMs: raw4.wsStateStaleMs }) || undefined;
74603
74690
  const storeDb = resolveUserFilePath(raw4.storeDb);
@@ -74697,8 +74784,22 @@ function readApiBaseUrl(doc) {
74697
74784
  const root = doc.apiBaseUrl;
74698
74785
  const api = doc.api;
74699
74786
  const nested4 = isPlainObject(api) ? api.baseUrl : undefined;
74700
- const rootValue = root === undefined ? undefined : typeof root === "string" ? root : (errors3.push("Config key apiBaseUrl must be a string"), undefined);
74701
- const nestedValue = nested4 === undefined ? undefined : typeof nested4 === "string" ? nested4 : (errors3.push("Config key api.baseUrl must be a string"), undefined);
74787
+ const rootValue = root === undefined ? undefined : typeof root === "string" ? (() => {
74788
+ try {
74789
+ return normalizeApiBaseUrl(root);
74790
+ } catch (error4) {
74791
+ errors3.push(isCliError(error4) ? error4.message : "Config key apiBaseUrl must be a valid URL");
74792
+ return;
74793
+ }
74794
+ })() : (errors3.push("Config key apiBaseUrl must be a string"), undefined);
74795
+ const nestedValue = nested4 === undefined ? undefined : typeof nested4 === "string" ? (() => {
74796
+ try {
74797
+ return normalizeApiBaseUrl(nested4);
74798
+ } catch (error4) {
74799
+ errors3.push(isCliError(error4) ? error4.message : "Config key api.baseUrl must be a valid URL");
74800
+ return;
74801
+ }
74802
+ })() : (errors3.push("Config key api.baseUrl must be a string"), undefined);
74702
74803
  if (rootValue && nestedValue && rootValue !== nestedValue) {
74703
74804
  errors3.push("Config keys apiBaseUrl and api.baseUrl conflict");
74704
74805
  }
@@ -74811,6 +74912,34 @@ function inspectDoc(configFile, exists3, doc) {
74811
74912
  valid: errors3.length === 0
74812
74913
  };
74813
74914
  }
74915
+ function canonicalizeDoc(doc) {
74916
+ const next4 = cloneDoc(doc);
74917
+ const inspection = inspectDoc("", true, next4);
74918
+ if (!inspection.valid) {
74919
+ return next4;
74920
+ }
74921
+ if (inspection.values.apiBaseUrl !== undefined) {
74922
+ next4.apiBaseUrl = inspection.values.apiBaseUrl;
74923
+ }
74924
+ if (inspection.values.apiHost !== undefined) {
74925
+ next4.apiHost = inspection.values.apiHost;
74926
+ }
74927
+ if (inspection.values.apiPort !== undefined) {
74928
+ next4.apiPort = inspection.values.apiPort;
74929
+ }
74930
+ if (inspection.values.apiBasePath !== undefined) {
74931
+ next4.apiBasePath = inspection.values.apiBasePath;
74932
+ }
74933
+ const api = next4.api;
74934
+ if (isPlainObject(api)) {
74935
+ delete api.baseUrl;
74936
+ delete api.host;
74937
+ delete api.port;
74938
+ delete api.basePath;
74939
+ removeEmptyApiObject(next4);
74940
+ }
74941
+ return next4;
74942
+ }
74814
74943
  async function ensureDir(filePath) {
74815
74944
  await fs4.mkdir(path7.dirname(filePath), { recursive: true });
74816
74945
  }
@@ -75002,6 +75131,69 @@ var UserConfigFileLive = succeed10(UserConfigFile, {
75002
75131
  }
75003
75132
  return inspectDoc(parsed.configFile, parsed.exists, parsed.doc);
75004
75133
  }),
75134
+ previewRepair: () => gen2(function* () {
75135
+ const cfg = yield* AppConfig;
75136
+ const parsed = yield* promise2(() => readDocDetailed(cfg.configFile));
75137
+ const { configFile, doc, exists: exists3 } = yield* try_3({
75138
+ try: () => requireParsedDoc(parsed),
75139
+ catch: (error4) => toCliFailure(error4, "Invalid config file")
75140
+ });
75141
+ const before2 = inspectDoc(configFile, exists3, doc);
75142
+ const nextDoc = canonicalizeDoc(doc);
75143
+ const after3 = inspectDoc(configFile, exists3, nextDoc);
75144
+ return {
75145
+ configFile,
75146
+ exists: exists3,
75147
+ changed: JSON.stringify(doc) !== JSON.stringify(nextDoc),
75148
+ before: before2,
75149
+ after: after3,
75150
+ nextDoc
75151
+ };
75152
+ }),
75153
+ repair: () => gen2(function* () {
75154
+ const cfg = yield* AppConfig;
75155
+ const parsed = yield* promise2(() => readDocDetailed(cfg.configFile));
75156
+ const { configFile, doc, exists: exists3 } = yield* try_3({
75157
+ try: () => requireParsedDoc(parsed),
75158
+ catch: (error4) => toCliFailure(error4, "Invalid config file")
75159
+ });
75160
+ const before2 = inspectDoc(configFile, exists3, doc);
75161
+ const nextDoc = canonicalizeDoc(doc);
75162
+ const after3 = inspectDoc(configFile, exists3, nextDoc);
75163
+ const preview = {
75164
+ configFile,
75165
+ exists: exists3,
75166
+ changed: JSON.stringify(doc) !== JSON.stringify(nextDoc),
75167
+ before: before2,
75168
+ after: after3,
75169
+ nextDoc
75170
+ };
75171
+ if (!preview.changed) {
75172
+ return {
75173
+ ...preview,
75174
+ written: false
75175
+ };
75176
+ }
75177
+ if (!preview.before.valid) {
75178
+ return {
75179
+ ...preview,
75180
+ written: false
75181
+ };
75182
+ }
75183
+ yield* tryPromise2({
75184
+ try: async () => await writeDoc(preview.configFile, preview.nextDoc),
75185
+ catch: (error4) => new CliError({
75186
+ code: "INTERNAL",
75187
+ message: "Failed to write config file",
75188
+ exitCode: 1,
75189
+ details: { config_file: preview.configFile, error: String(error4?.message || error4) }
75190
+ })
75191
+ });
75192
+ return {
75193
+ ...preview,
75194
+ written: true
75195
+ };
75196
+ }),
75005
75197
  get: (key) => gen2(function* () {
75006
75198
  const targetKey = yield* try_3({
75007
75199
  try: () => canonicalKey(key),
@@ -75715,35 +75907,181 @@ var configCommand = exports_Command.make("config", {}).pipe(exports_Command.with
75715
75907
  configValidateCommand
75716
75908
  ]));
75717
75909
 
75718
- // src/services/FsAccess.ts
75719
- import { constants as FS_CONSTANTS, promises as fs8 } from "node:fs";
75910
+ // src/services/ApiDaemonFiles.ts
75911
+ import { promises as fs8 } from "node:fs";
75720
75912
  import path10 from "node:path";
75913
+ class ApiDaemonFiles extends Tag2("ApiDaemonFiles")() {
75914
+ }
75915
+ function ensureDir4(p3) {
75916
+ return fs8.mkdir(path10.dirname(p3), { recursive: true }).then(() => {
75917
+ return;
75918
+ });
75919
+ }
75920
+ function defaultPidFile2() {
75921
+ const envPidFile = process.env.REMNOTE_API_PID_FILE;
75922
+ if (typeof envPidFile === "string" && envPidFile.trim())
75923
+ return resolveUserFilePath(envPidFile);
75924
+ return path10.join(homeDir(), ".agent-remnote", "api.pid");
75925
+ }
75926
+ function defaultLogFile2() {
75927
+ const envLogFile = process.env.REMNOTE_API_LOG_FILE;
75928
+ if (typeof envLogFile === "string" && envLogFile.trim())
75929
+ return resolveUserFilePath(envLogFile);
75930
+ return path10.join(homeDir(), ".agent-remnote", "api.log");
75931
+ }
75932
+ function defaultStateFile2() {
75933
+ const envStateFile = process.env.REMNOTE_API_STATE_FILE;
75934
+ if (typeof envStateFile === "string" && envStateFile.trim())
75935
+ return resolveUserFilePath(envStateFile);
75936
+ return path10.join(homeDir(), ".agent-remnote", "api.state.json");
75937
+ }
75938
+ function parseJson2(raw4, filePath) {
75939
+ try {
75940
+ return JSON.parse(raw4);
75941
+ } catch (error4) {
75942
+ throw new CliError({
75943
+ code: "INTERNAL",
75944
+ message: `Failed to parse JSON file: ${filePath}`,
75945
+ exitCode: 1,
75946
+ details: { file_path: filePath, error: String(error4?.message || error4) }
75947
+ });
75948
+ }
75949
+ }
75950
+ var ApiDaemonFilesLive = succeed10(ApiDaemonFiles, {
75951
+ defaultPidFile: defaultPidFile2,
75952
+ defaultLogFile: defaultLogFile2,
75953
+ defaultStateFile: defaultStateFile2,
75954
+ readPidFile: (pidFilePath) => tryPromise2({
75955
+ try: async () => {
75956
+ const resolved = resolveUserFilePath(pidFilePath);
75957
+ const raw4 = await fs8.readFile(resolved, "utf8");
75958
+ return parseJson2(raw4, resolved);
75959
+ },
75960
+ catch: (error4) => {
75961
+ const code2 = error4?.code;
75962
+ if (code2 === "ENOENT")
75963
+ return;
75964
+ if (isCliError(error4))
75965
+ return error4;
75966
+ return new CliError({
75967
+ code: "INTERNAL",
75968
+ message: "Failed to read api pid file",
75969
+ exitCode: 1,
75970
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
75971
+ });
75972
+ }
75973
+ }).pipe(catchAll2((error4) => {
75974
+ if (error4 === undefined)
75975
+ return succeed8(undefined);
75976
+ return fail8(error4);
75977
+ })),
75978
+ writePidFile: (pidFilePath, value8) => tryPromise2({
75979
+ try: async () => {
75980
+ const resolved = resolveUserFilePath(pidFilePath);
75981
+ await ensureDir4(resolved);
75982
+ await fs8.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
75983
+ `, "utf8");
75984
+ },
75985
+ catch: (error4) => new CliError({
75986
+ code: "INTERNAL",
75987
+ message: "Failed to write api pid file",
75988
+ exitCode: 1,
75989
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
75990
+ })
75991
+ }),
75992
+ deletePidFile: (pidFilePath) => tryPromise2({
75993
+ try: async () => {
75994
+ const resolved = resolveUserFilePath(pidFilePath);
75995
+ await fs8.rm(resolved, { force: true });
75996
+ },
75997
+ catch: (error4) => new CliError({
75998
+ code: "INTERNAL",
75999
+ message: "Failed to delete api pid file",
76000
+ exitCode: 1,
76001
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
76002
+ })
76003
+ }),
76004
+ readStateFile: (stateFilePath) => tryPromise2({
76005
+ try: async () => {
76006
+ const resolved = resolveUserFilePath(stateFilePath);
76007
+ const raw4 = await fs8.readFile(resolved, "utf8");
76008
+ return parseJson2(raw4, resolved);
76009
+ },
76010
+ catch: (error4) => {
76011
+ const code2 = error4?.code;
76012
+ if (code2 === "ENOENT")
76013
+ return;
76014
+ if (isCliError(error4))
76015
+ return error4;
76016
+ return new CliError({
76017
+ code: "INTERNAL",
76018
+ message: "Failed to read api state file",
76019
+ exitCode: 1,
76020
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76021
+ });
76022
+ }
76023
+ }).pipe(catchAll2((error4) => {
76024
+ if (error4 === undefined)
76025
+ return succeed8(undefined);
76026
+ return fail8(error4);
76027
+ })),
76028
+ writeStateFile: (stateFilePath, value8) => tryPromise2({
76029
+ try: async () => {
76030
+ const resolved = resolveUserFilePath(stateFilePath);
76031
+ await ensureDir4(resolved);
76032
+ await fs8.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
76033
+ `, "utf8");
76034
+ },
76035
+ catch: (error4) => new CliError({
76036
+ code: "INTERNAL",
76037
+ message: "Failed to write api state file",
76038
+ exitCode: 1,
76039
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76040
+ })
76041
+ }),
76042
+ deleteStateFile: (stateFilePath) => tryPromise2({
76043
+ try: async () => {
76044
+ const resolved = resolveUserFilePath(stateFilePath);
76045
+ await fs8.rm(resolved, { force: true });
76046
+ },
76047
+ catch: (error4) => new CliError({
76048
+ code: "INTERNAL",
76049
+ message: "Failed to delete api state file",
76050
+ exitCode: 1,
76051
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76052
+ })
76053
+ })
76054
+ });
76055
+
76056
+ // src/services/FsAccess.ts
76057
+ import { constants as FS_CONSTANTS, promises as fs9 } from "node:fs";
76058
+ import path11 from "node:path";
75721
76059
 
75722
76060
  class FsAccess extends Tag2("FsAccess")() {
75723
76061
  }
75724
76062
  async function canWritePath(filePath) {
75725
76063
  try {
75726
- await fs8.mkdir(path10.dirname(filePath), { recursive: true });
75727
- await fs8.access(path10.dirname(filePath), FS_CONSTANTS.W_OK);
76064
+ await fs9.mkdir(path11.dirname(filePath), { recursive: true });
76065
+ await fs9.access(path11.dirname(filePath), FS_CONSTANTS.W_OK);
75728
76066
  return true;
75729
76067
  } catch {
75730
76068
  return false;
75731
76069
  }
75732
76070
  }
75733
76071
  async function checkWritableFile(filePath) {
75734
- const dir2 = path10.dirname(filePath);
76072
+ const dir2 = path11.dirname(filePath);
75735
76073
  try {
75736
- await fs8.mkdir(dir2, { recursive: true });
76074
+ await fs9.mkdir(dir2, { recursive: true });
75737
76075
  } catch (e) {
75738
76076
  return { ok: false, reason: `failed_to_create_dir: ${String(e?.message || e)}` };
75739
76077
  }
75740
76078
  try {
75741
- await fs8.access(dir2, FS_CONSTANTS.W_OK);
76079
+ await fs9.access(dir2, FS_CONSTANTS.W_OK);
75742
76080
  } catch (e) {
75743
76081
  return { ok: false, reason: `dir_not_writable: ${String(e?.message || e)}` };
75744
76082
  }
75745
76083
  try {
75746
- await fs8.access(filePath, FS_CONSTANTS.W_OK);
76084
+ await fs9.access(filePath, FS_CONSTANTS.W_OK);
75747
76085
  return { ok: true };
75748
76086
  } catch {
75749
76087
  return { ok: true };
@@ -75752,7 +76090,7 @@ async function checkWritableFile(filePath) {
75752
76090
  var FsAccessLive = succeed10(FsAccess, {
75753
76091
  isFile: (filePath) => tryPromise2({
75754
76092
  try: async () => {
75755
- const st = await fs8.stat(filePath);
76093
+ const st = await fs9.stat(filePath);
75756
76094
  return st.isFile();
75757
76095
  },
75758
76096
  catch: (e) => e
@@ -75767,8 +76105,270 @@ var FsAccessLive = succeed10(FsAccess, {
75767
76105
  }).pipe(catchAll2(() => succeed8({ ok: false, reason: "unknown_error" })))
75768
76106
  });
75769
76107
 
76108
+ // src/services/PluginServerFiles.ts
76109
+ import { promises as fs10 } from "node:fs";
76110
+ import path12 from "node:path";
76111
+ class PluginServerFiles extends Tag2("PluginServerFiles")() {
76112
+ }
76113
+ function ensureDir5(p3) {
76114
+ return fs10.mkdir(path12.dirname(p3), { recursive: true }).then(() => {
76115
+ return;
76116
+ });
76117
+ }
76118
+ function defaultPidFile3() {
76119
+ const envPidFile = process.env.REMNOTE_PLUGIN_SERVER_PID_FILE;
76120
+ if (typeof envPidFile === "string" && envPidFile.trim())
76121
+ return resolveUserFilePath(envPidFile);
76122
+ return path12.join(homeDir(), ".agent-remnote", "plugin-server.pid");
76123
+ }
76124
+ function defaultLogFile3() {
76125
+ const envLogFile = process.env.REMNOTE_PLUGIN_SERVER_LOG_FILE;
76126
+ if (typeof envLogFile === "string" && envLogFile.trim())
76127
+ return resolveUserFilePath(envLogFile);
76128
+ return path12.join(homeDir(), ".agent-remnote", "plugin-server.log");
76129
+ }
76130
+ function defaultStateFile3() {
76131
+ const envStateFile = process.env.REMNOTE_PLUGIN_SERVER_STATE_FILE;
76132
+ if (typeof envStateFile === "string" && envStateFile.trim())
76133
+ return resolveUserFilePath(envStateFile);
76134
+ return path12.join(homeDir(), ".agent-remnote", "plugin-server.state.json");
76135
+ }
76136
+ function parseJson3(raw4, filePath) {
76137
+ try {
76138
+ return JSON.parse(raw4);
76139
+ } catch (error4) {
76140
+ throw new CliError({
76141
+ code: "INTERNAL",
76142
+ message: `Failed to parse JSON file: ${filePath}`,
76143
+ exitCode: 1,
76144
+ details: { file_path: filePath, error: String(error4?.message || error4) }
76145
+ });
76146
+ }
76147
+ }
76148
+ var PluginServerFilesLive = succeed10(PluginServerFiles, {
76149
+ defaultPidFile: defaultPidFile3,
76150
+ defaultLogFile: defaultLogFile3,
76151
+ defaultStateFile: defaultStateFile3,
76152
+ readPidFile: (pidFilePath) => tryPromise2({
76153
+ try: async () => {
76154
+ const resolved = resolveUserFilePath(pidFilePath);
76155
+ const raw4 = await fs10.readFile(resolved, "utf8");
76156
+ return parseJson3(raw4, resolved);
76157
+ },
76158
+ catch: (error4) => {
76159
+ const code2 = error4?.code;
76160
+ if (code2 === "ENOENT")
76161
+ return;
76162
+ if (isCliError(error4))
76163
+ return error4;
76164
+ return new CliError({
76165
+ code: "INTERNAL",
76166
+ message: "Failed to read plugin server pid file",
76167
+ exitCode: 1,
76168
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
76169
+ });
76170
+ }
76171
+ }).pipe(catchAll2((error4) => {
76172
+ if (error4 === undefined)
76173
+ return succeed8(undefined);
76174
+ return fail8(error4);
76175
+ })),
76176
+ writePidFile: (pidFilePath, value8) => tryPromise2({
76177
+ try: async () => {
76178
+ const resolved = resolveUserFilePath(pidFilePath);
76179
+ await ensureDir5(resolved);
76180
+ await fs10.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
76181
+ `, "utf8");
76182
+ },
76183
+ catch: (error4) => new CliError({
76184
+ code: "INTERNAL",
76185
+ message: "Failed to write plugin server pid file",
76186
+ exitCode: 1,
76187
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
76188
+ })
76189
+ }),
76190
+ deletePidFile: (pidFilePath) => tryPromise2({
76191
+ try: async () => {
76192
+ const resolved = resolveUserFilePath(pidFilePath);
76193
+ await fs10.rm(resolved, { force: true });
76194
+ },
76195
+ catch: (error4) => new CliError({
76196
+ code: "INTERNAL",
76197
+ message: "Failed to delete plugin server pid file",
76198
+ exitCode: 1,
76199
+ details: { file_path: pidFilePath, error: String(error4?.message || error4) }
76200
+ })
76201
+ }),
76202
+ readStateFile: (stateFilePath) => tryPromise2({
76203
+ try: async () => {
76204
+ const resolved = resolveUserFilePath(stateFilePath);
76205
+ const raw4 = await fs10.readFile(resolved, "utf8");
76206
+ return parseJson3(raw4, resolved);
76207
+ },
76208
+ catch: (error4) => {
76209
+ const code2 = error4?.code;
76210
+ if (code2 === "ENOENT")
76211
+ return;
76212
+ if (isCliError(error4))
76213
+ return error4;
76214
+ return new CliError({
76215
+ code: "INTERNAL",
76216
+ message: "Failed to read plugin server state file",
76217
+ exitCode: 1,
76218
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76219
+ });
76220
+ }
76221
+ }).pipe(catchAll2((error4) => {
76222
+ if (error4 === undefined)
76223
+ return succeed8(undefined);
76224
+ return fail8(error4);
76225
+ })),
76226
+ writeStateFile: (stateFilePath, value8) => tryPromise2({
76227
+ try: async () => {
76228
+ const resolved = resolveUserFilePath(stateFilePath);
76229
+ await ensureDir5(resolved);
76230
+ await fs10.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
76231
+ `, "utf8");
76232
+ },
76233
+ catch: (error4) => new CliError({
76234
+ code: "INTERNAL",
76235
+ message: "Failed to write plugin server state file",
76236
+ exitCode: 1,
76237
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76238
+ })
76239
+ }),
76240
+ deleteStateFile: (stateFilePath) => tryPromise2({
76241
+ try: async () => {
76242
+ const resolved = resolveUserFilePath(stateFilePath);
76243
+ await fs10.rm(resolved, { force: true });
76244
+ },
76245
+ catch: (error4) => new CliError({
76246
+ code: "INTERNAL",
76247
+ message: "Failed to delete plugin server state file",
76248
+ exitCode: 1,
76249
+ details: { file_path: stateFilePath, error: String(error4?.message || error4) }
76250
+ })
76251
+ })
76252
+ });
76253
+
76254
+ // src/services/Process.ts
76255
+ import { execFile, spawn as spawn2 } from "node:child_process";
76256
+ import fs11 from "node:fs";
76257
+ import path13 from "node:path";
76258
+ class Process extends Tag2("Process")() {
76259
+ }
76260
+ function ensureDirSync(filePath) {
76261
+ fs11.mkdirSync(path13.dirname(filePath), { recursive: true });
76262
+ }
76263
+ var ProcessLive = succeed10(Process, {
76264
+ isPidRunning: (pid) => sync3(() => {
76265
+ if (!Number.isFinite(pid) || pid <= 0)
76266
+ return false;
76267
+ try {
76268
+ process.kill(pid, 0);
76269
+ return true;
76270
+ } catch (error4) {
76271
+ if (error4?.code === "EPERM")
76272
+ return true;
76273
+ return false;
76274
+ }
76275
+ }),
76276
+ getCommandLine: (pid) => async((resume2) => {
76277
+ if (!Number.isFinite(pid) || pid <= 0) {
76278
+ resume2(succeed8(undefined));
76279
+ return;
76280
+ }
76281
+ const finish = (value8) => resume2(succeed8(value8));
76282
+ if (process.platform === "win32") {
76283
+ execFile("powershell.exe", ["-NoProfile", "-Command", `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CommandLine`], { windowsHide: true }, (error4, stdout2) => {
76284
+ if (error4) {
76285
+ finish(undefined);
76286
+ return;
76287
+ }
76288
+ const text15 = String(stdout2 ?? "").trim();
76289
+ finish(text15 || undefined);
76290
+ });
76291
+ return;
76292
+ }
76293
+ execFile("ps", ["-p", String(pid), "-o", "command="], (error4, stdout2) => {
76294
+ if (error4) {
76295
+ finish(undefined);
76296
+ return;
76297
+ }
76298
+ const text15 = String(stdout2 ?? "").trim();
76299
+ finish(text15 || undefined);
76300
+ });
76301
+ }),
76302
+ spawnDetached: (params3) => try_3({
76303
+ try: () => {
76304
+ ensureDirSync(params3.logFile);
76305
+ const fd = fs11.openSync(params3.logFile, "a");
76306
+ const child = spawn2(params3.command, [...params3.args], {
76307
+ cwd: params3.cwd,
76308
+ env: params3.env,
76309
+ detached: true,
76310
+ stdio: ["ignore", fd, fd]
76311
+ });
76312
+ child.unref();
76313
+ return child.pid;
76314
+ },
76315
+ catch: (error4) => {
76316
+ if (isCliError(error4))
76317
+ return error4;
76318
+ return new CliError({
76319
+ code: "INTERNAL",
76320
+ message: "Failed to start background process",
76321
+ exitCode: 1,
76322
+ details: { error: String(error4?.message || error4) }
76323
+ });
76324
+ }
76325
+ }).pipe(flatMap9((pid) => {
76326
+ if (typeof pid !== "number" || !Number.isFinite(pid)) {
76327
+ return fail8(new CliError({
76328
+ code: "INTERNAL",
76329
+ message: "Failed to start background process (pid is unavailable)",
76330
+ exitCode: 1
76331
+ }));
76332
+ }
76333
+ return succeed8(pid);
76334
+ })),
76335
+ kill: (pid, signal) => try_3({
76336
+ try: () => {
76337
+ process.kill(pid, signal);
76338
+ },
76339
+ catch: (error4) => new CliError({
76340
+ code: "INTERNAL",
76341
+ message: `Failed to send signal (${signal})`,
76342
+ exitCode: 1,
76343
+ details: { pid, signal, error: String(error4?.message || error4) }
76344
+ })
76345
+ }),
76346
+ waitForExit: ({ pid, timeoutMs }) => async((resume2) => {
76347
+ const deadline = Date.now() + Math.max(0, timeoutMs);
76348
+ const tick2 = () => {
76349
+ let alive = false;
76350
+ try {
76351
+ process.kill(pid, 0);
76352
+ alive = true;
76353
+ } catch (error4) {
76354
+ alive = error4?.code === "EPERM";
76355
+ }
76356
+ if (!alive) {
76357
+ resume2(succeed8(true));
76358
+ return;
76359
+ }
76360
+ if (Date.now() >= deadline) {
76361
+ resume2(succeed8(false));
76362
+ return;
76363
+ }
76364
+ setTimeout(tick2, 100);
76365
+ };
76366
+ tick2();
76367
+ })
76368
+ });
76369
+
75770
76370
  // src/lib/tmux.ts
75771
- import { spawn as spawn2, spawnSync } from "node:child_process";
76371
+ import { spawn as spawn3, spawnSync } from "node:child_process";
75772
76372
  function envTmuxRefreshEnabled() {
75773
76373
  const raw4 = String(process.env.REMNOTE_TMUX_REFRESH || process.env.TMUX_REFRESH || "").trim().toLowerCase();
75774
76374
  if (raw4 === "0" || raw4 === "false" || raw4 === "off" || raw4 === "no")
@@ -75805,13 +76405,13 @@ function refreshNow() {
75805
76405
  try {
75806
76406
  if (clients) {
75807
76407
  for (const clientName of clients) {
75808
- const child = spawn2("tmux", ["refresh-client", "-S", "-t", clientName], { stdio: "ignore" });
76408
+ const child = spawn3("tmux", ["refresh-client", "-S", "-t", clientName], { stdio: "ignore" });
75809
76409
  child.on("error", () => {});
75810
76410
  child.unref();
75811
76411
  }
75812
76412
  return;
75813
76413
  }
75814
- const fallback = spawn2("tmux", ["refresh-client", "-S"], { stdio: "ignore" });
76414
+ const fallback = spawn3("tmux", ["refresh-client", "-S"], { stdio: "ignore" });
75815
76415
  fallback.on("error", () => {});
75816
76416
  fallback.unref();
75817
76417
  } catch {}
@@ -75860,21 +76460,21 @@ function buildDbFallbackNextAction(queryText) {
75860
76460
  }
75861
76461
 
75862
76462
  // src/lib/wsDebug.ts
75863
- import fs9 from "node:fs";
75864
- import path11 from "node:path";
76463
+ import fs12 from "node:fs";
76464
+ import path14 from "node:path";
75865
76465
  function envDebug() {
75866
76466
  const v = (process.env.REMNOTE_WS_DEBUG || process.env.WS_DEBUG || "").toLowerCase();
75867
76467
  return v === "1" || v === "true";
75868
76468
  }
75869
76469
  function envLogFilePath(params3) {
75870
76470
  const p3 = process.env.REMNOTE_WS_LOGFILE || process.env.WS_LOGFILE || "";
75871
- const def = params3.debug ? path11.join(homeDir(), ".agent-remnote", "ws-debug.log") : "";
76471
+ const def = params3.debug ? path14.join(homeDir(), ".agent-remnote", "ws-debug.log") : "";
75872
76472
  const out = p3 && p3.trim() || def;
75873
76473
  if (!out)
75874
76474
  return;
75875
76475
  try {
75876
76476
  const resolved = resolveUserFilePath(out);
75877
- fs9.mkdirSync(path11.dirname(resolved), { recursive: true });
76477
+ fs12.mkdirSync(path14.dirname(resolved), { recursive: true });
75878
76478
  return resolved;
75879
76479
  } catch {
75880
76480
  return;
@@ -75906,19 +76506,19 @@ function wsLog(level, msg, ctx) {
75906
76506
  if (!LOG_FILE)
75907
76507
  return;
75908
76508
  try {
75909
- fs9.appendFileSync(LOG_FILE, `${new Date().toISOString()} ${line4}
76509
+ fs12.appendFileSync(LOG_FILE, `${new Date().toISOString()} ${line4}
75910
76510
  `);
75911
76511
  } catch {}
75912
76512
  }
75913
76513
 
75914
76514
  // src/internal/queue/db.ts
75915
- import path13 from "node:path";
76515
+ import path16 from "node:path";
75916
76516
 
75917
76517
  // src/internal/store/db.ts
75918
76518
  import Database from "better-sqlite3";
75919
76519
  import { createHash, randomUUID } from "node:crypto";
75920
76520
  import { constants as constants2, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
75921
- import path12 from "node:path";
76521
+ import path15 from "node:path";
75922
76522
 
75923
76523
  // src/internal/store/migrations/0001-baseline.ts
75924
76524
  var migration = {
@@ -76439,7 +77039,7 @@ CREATE INDEX IF NOT EXISTS idx_task_runs_queue_txn_id
76439
77039
  ON task_runs(queue_txn_id);
76440
77040
  `;
76441
77041
  function absoluteDefaultStorePath() {
76442
- return path12.join(homeDir(), ".agent-remnote", "store.sqlite");
77042
+ return path15.join(homeDir(), ".agent-remnote", "store.sqlite");
76443
77043
  }
76444
77044
  function defaultStorePath() {
76445
77045
  const env2 = process.env.REMNOTE_STORE_DB || process.env.STORE_DB;
@@ -76451,15 +77051,15 @@ function defaultLegacyQueuePath() {
76451
77051
  const env2 = process.env.REMNOTE_QUEUE_DB || process.env.QUEUE_DB;
76452
77052
  if (typeof env2 === "string" && env2.trim())
76453
77053
  return resolveUserFilePath(env2);
76454
- return path12.join(homeDir(), ".agent-remnote", "queue.sqlite");
77054
+ return path15.join(homeDir(), ".agent-remnote", "queue.sqlite");
76455
77055
  }
76456
- function ensureDir4(filePath) {
76457
- const dir2 = path12.dirname(filePath);
77056
+ function ensureDir6(filePath) {
77057
+ const dir2 = path15.dirname(filePath);
76458
77058
  mkdirSync(dir2, { recursive: true });
76459
77059
  }
76460
77060
  function openStoreDb(dbPath = defaultStorePath()) {
76461
77061
  const resolvedPath = resolveUserFilePath(dbPath);
76462
- ensureDir4(resolvedPath);
77062
+ ensureDir6(resolvedPath);
76463
77063
  maybeInitializeDefaultStoreFromLegacy(resolvedPath, { callerProvidedPath: dbPath });
76464
77064
  const db = new Database(resolvedPath);
76465
77065
  db.pragma("busy_timeout = 5000");
@@ -76796,7 +77396,7 @@ function maybeInitializeDefaultStoreFromLegacy(resolvedStorePath, params3) {
76796
77396
  if (!existsSync(legacyPath))
76797
77397
  return;
76798
77398
  const tmpPath = `${resolvedStorePath}.tmp.${process.pid}.${randomUUID()}`;
76799
- ensureDir4(tmpPath);
77399
+ ensureDir6(tmpPath);
76800
77400
  try {
76801
77401
  const source = new Database(legacyPath, { readonly: true });
76802
77402
  try {
@@ -76931,10 +77531,10 @@ function defaultQueuePath() {
76931
77531
  const env2 = process.env.REMNOTE_QUEUE_DB || process.env.QUEUE_DB;
76932
77532
  if (typeof env2 === "string" && env2.trim())
76933
77533
  return resolveUserFilePath(env2);
76934
- return path13.join(homeDir(), ".agent-remnote", "queue.sqlite");
77534
+ return path16.join(homeDir(), ".agent-remnote", "queue.sqlite");
76935
77535
  }
76936
- function ensureDir5(filePath) {
76937
- ensureDir4(filePath);
77536
+ function ensureDir7(filePath) {
77537
+ ensureDir6(filePath);
76938
77538
  }
76939
77539
  function queueErrorFromStoreSchema(dbPath, error4) {
76940
77540
  const code2 = error4.code === "STORE_SCHEMA_NEWER" ? "QUEUE_SCHEMA_NEWER" : error4.code === "STORE_SCHEMA_INVALID" ? "QUEUE_SCHEMA_INVALID" : "QUEUE_SCHEMA_UNKNOWN";
@@ -76947,7 +77547,7 @@ function queueErrorFromStoreSchema(dbPath, error4) {
76947
77547
  }
76948
77548
  function openQueueDb(dbPath = defaultQueuePath()) {
76949
77549
  const resolvedPath = resolveUserFilePath(dbPath);
76950
- ensureDir5(resolvedPath);
77550
+ ensureDir7(resolvedPath);
76951
77551
  try {
76952
77552
  return openStoreDb(resolvedPath);
76953
77553
  } catch (e) {
@@ -77305,18 +77905,18 @@ function getFirstString(payload, keys8) {
77305
77905
  }
77306
77906
  return null;
77307
77907
  }
77308
- function getFirstStringFromNested(payload, path14) {
77908
+ function getFirstStringFromNested(payload, path17) {
77309
77909
  let cur = payload;
77310
- for (const k of path14) {
77910
+ for (const k of path17) {
77311
77911
  if (!cur || typeof cur !== "object")
77312
77912
  return null;
77313
77913
  cur = cur[k];
77314
77914
  }
77315
77915
  return asNonEmptyString(cur);
77316
77916
  }
77317
- function getStringArrayFromNested(payload, path14) {
77917
+ function getStringArrayFromNested(payload, path17) {
77318
77918
  let cur = payload;
77319
- for (const k of path14) {
77919
+ for (const k of path17) {
77320
77920
  if (!cur || typeof cur !== "object")
77321
77921
  return [];
77322
77922
  cur = cur[k];
@@ -78660,38 +79260,38 @@ function idFieldPathsForOpType(opTypeRaw) {
78660
79260
  return Array.isArray(entry.id_fields) ? entry.id_fields : [];
78661
79261
  }
78662
79262
  // src/kernel/op-catalog/pathWalk.ts
78663
- function parsePathTokens(path14) {
78664
- return path14.split(".").map((part) => part.trim()).filter(Boolean).map((part) => {
79263
+ function parsePathTokens(path17) {
79264
+ return path17.split(".").map((part) => part.trim()).filter(Boolean).map((part) => {
78665
79265
  const isArray2 = part.endsWith("[]");
78666
79266
  const key = isArray2 ? part.slice(0, -2).trim() : part;
78667
79267
  return { key, isArray: isArray2 };
78668
79268
  }).filter((token) => token.key.length > 0);
78669
79269
  }
78670
- function collectLeafValues(value8, path14, idx = 0) {
78671
- if (idx >= path14.length)
79270
+ function collectLeafValues(value8, path17, idx = 0) {
79271
+ if (idx >= path17.length)
78672
79272
  return [value8];
78673
79273
  if (!value8 || typeof value8 !== "object")
78674
79274
  return [];
78675
- const token = path14[idx];
79275
+ const token = path17[idx];
78676
79276
  const next4 = value8[token.key];
78677
79277
  if (token.isArray) {
78678
79278
  if (!Array.isArray(next4))
78679
79279
  return [];
78680
79280
  const out = [];
78681
79281
  for (const item of next4) {
78682
- out.push(...collectLeafValues(item, path14, idx + 1));
79282
+ out.push(...collectLeafValues(item, path17, idx + 1));
78683
79283
  }
78684
79284
  return out;
78685
79285
  }
78686
- return collectLeafValues(next4, path14, idx + 1);
79286
+ return collectLeafValues(next4, path17, idx + 1);
78687
79287
  }
78688
- function mapLeafValuesInPlace(value8, path14, mapFn, idx = 0) {
78689
- if (idx >= path14.length)
79288
+ function mapLeafValuesInPlace(value8, path17, mapFn, idx = 0) {
79289
+ if (idx >= path17.length)
78690
79290
  return;
78691
79291
  if (!value8 || typeof value8 !== "object")
78692
79292
  return;
78693
- const token = path14[idx];
78694
- const isLeaf = idx === path14.length - 1;
79293
+ const token = path17[idx];
79294
+ const isLeaf = idx === path17.length - 1;
78695
79295
  if (token.isArray) {
78696
79296
  const next4 = value8[token.key];
78697
79297
  if (!Array.isArray(next4))
@@ -78701,7 +79301,7 @@ function mapLeafValuesInPlace(value8, path14, mapFn, idx = 0) {
78701
79301
  return;
78702
79302
  }
78703
79303
  for (const item of next4) {
78704
- mapLeafValuesInPlace(item, path14, mapFn, idx + 1);
79304
+ mapLeafValuesInPlace(item, path17, mapFn, idx + 1);
78705
79305
  }
78706
79306
  return;
78707
79307
  }
@@ -78709,7 +79309,7 @@ function mapLeafValuesInPlace(value8, path14, mapFn, idx = 0) {
78709
79309
  value8[token.key] = mapFn(value8[token.key]);
78710
79310
  return;
78711
79311
  }
78712
- mapLeafValuesInPlace(value8[token.key], path14, mapFn, idx + 1);
79312
+ mapLeafValuesInPlace(value8[token.key], path17, mapFn, idx + 1);
78713
79313
  }
78714
79314
 
78715
79315
  // src/kernel/op-catalog/substituteIds.ts
@@ -78731,8 +79331,8 @@ function collectTempIdsFromPayload(opTypeRaw, payload) {
78731
79331
  if (!payload || typeof payload !== "object")
78732
79332
  return [];
78733
79333
  const out = [];
78734
- for (const path14 of idFieldPathsForOpType(opTypeRaw)) {
78735
- const tokens = parsePathTokens(path14);
79334
+ for (const path17 of idFieldPathsForOpType(opTypeRaw)) {
79335
+ const tokens = parsePathTokens(path17);
78736
79336
  if (tokens.length === 0)
78737
79337
  continue;
78738
79338
  const values5 = collectLeafValues(payload, tokens);
@@ -78747,8 +79347,8 @@ function substituteTempIdsInPayload(opTypeRaw, payload, idMap) {
78747
79347
  if (!payload || typeof payload !== "object")
78748
79348
  return payload;
78749
79349
  const out = cloneJson(payload);
78750
- for (const path14 of idFieldPathsForOpType(opTypeRaw)) {
78751
- const tokens = parsePathTokens(path14);
79350
+ for (const path17 of idFieldPathsForOpType(opTypeRaw)) {
79351
+ const tokens = parsePathTokens(path17);
78752
79352
  if (tokens.length === 0)
78753
79353
  continue;
78754
79354
  mapLeafValuesInPlace(out, tokens, (value8) => {
@@ -80149,8 +80749,8 @@ function toWsBridgeClient2(client) {
80149
80749
  var GLOBAL_BRIDGE_KEY = Symbol.for("__REMNOTE_WS_BRIDGE__");
80150
80750
  // src/internal/remdb-tools/shared.ts
80151
80751
  import BetterSqlite3 from "better-sqlite3";
80152
- import { promises as fs10 } from "fs";
80153
- import path14 from "path";
80752
+ import { promises as fs13 } from "fs";
80753
+ import path17 from "path";
80154
80754
 
80155
80755
  // ../../node_modules/date-fns/constants.js
80156
80756
  var daysInYear = 365.2425;
@@ -81639,18 +82239,18 @@ async function withResolvedDatabase(dbPath, fn) {
81639
82239
  async function resolveDatabasePath(dbPath) {
81640
82240
  if (dbPath) {
81641
82241
  const expanded = expandHome2(dbPath.trim());
81642
- const stat3 = await fs10.stat(expanded);
82242
+ const stat3 = await fs13.stat(expanded);
81643
82243
  if (!stat3.isFile()) {
81644
82244
  throw new Error(`${expanded} is not a file`);
81645
82245
  }
81646
82246
  return {
81647
82247
  dbPath: expanded,
81648
82248
  source: "explicit",
81649
- baseDir: path14.dirname(expanded)
82249
+ baseDir: path17.dirname(expanded)
81650
82250
  };
81651
82251
  }
81652
- const baseDir = path14.join(homeDir(), REMNOTE_RELATIVE_DIR);
81653
- const entries2 = await fs10.readdir(baseDir, { withFileTypes: true }).catch(() => {
82252
+ const baseDir = path17.join(homeDir(), REMNOTE_RELATIVE_DIR);
82253
+ const entries2 = await fs13.readdir(baseDir, { withFileTypes: true }).catch(() => {
81654
82254
  throw new Error(`RemNote directory ${baseDir} not found – please specify dbPath manually`);
81655
82255
  });
81656
82256
  const candidates = [];
@@ -81663,9 +82263,9 @@ async function resolveDatabasePath(dbPath) {
81663
82263
  const isSecondary = SECONDARY_DIRS.has(entry.name);
81664
82264
  if (!isPrimary && !isSecondary)
81665
82265
  continue;
81666
- const candidatePath = path14.join(baseDir, entry.name, REMNOTE_DB_NAME);
82266
+ const candidatePath = path17.join(baseDir, entry.name, REMNOTE_DB_NAME);
81667
82267
  try {
81668
- const stat3 = await fs10.stat(candidatePath);
82268
+ const stat3 = await fs13.stat(candidatePath);
81669
82269
  candidates.push({
81670
82270
  dbPath: candidatePath,
81671
82271
  dirName: entry.name,
@@ -81696,18 +82296,18 @@ async function discoverBackups(basePath) {
81696
82296
  const backups = [];
81697
82297
  let entries2;
81698
82298
  try {
81699
- entries2 = await fs10.readdir(basePath, { withFileTypes: true });
82299
+ entries2 = await fs13.readdir(basePath, { withFileTypes: true });
81700
82300
  } catch (error4) {
81701
82301
  throw new Error(`Unable to read ${basePath}: ${String(error4)}`);
81702
82302
  }
81703
82303
  for (const entry of entries2) {
81704
82304
  if (!entry.isDirectory())
81705
82305
  continue;
81706
- const accountDir = path14.join(basePath, entry.name);
81707
- const backupDir = path14.join(accountDir, "backups");
82306
+ const accountDir = path17.join(basePath, entry.name);
82307
+ const backupDir = path17.join(accountDir, "backups");
81708
82308
  let backupFiles;
81709
82309
  try {
81710
- backupFiles = await fs10.readdir(backupDir, { withFileTypes: true });
82310
+ backupFiles = await fs13.readdir(backupDir, { withFileTypes: true });
81711
82311
  } catch {
81712
82312
  continue;
81713
82313
  }
@@ -81716,8 +82316,8 @@ async function discoverBackups(basePath) {
81716
82316
  continue;
81717
82317
  if (!file6.name.endsWith(".db") && !file6.name.endsWith(".db.zip"))
81718
82318
  continue;
81719
- const fullPath = path14.join(backupDir, file6.name);
81720
- const stat3 = await fs10.stat(fullPath);
82319
+ const fullPath = path17.join(backupDir, file6.name);
82320
+ const stat3 = await fs13.stat(fullPath);
81721
82321
  backups.push({
81722
82322
  accountDir: entry.name,
81723
82323
  file: file6.name,
@@ -81838,8 +82438,8 @@ function parseOrThrow(schema2, input, options6) {
81838
82438
  throw new Error(prefix2 + lines3.join("; "));
81839
82439
  }
81840
82440
  function formatZodIssueEN(issue) {
81841
- const path15 = issue.path && issue.path.length > 0 ? issue.path.join(".") : undefined;
81842
- const where = path15 ? `field ${path15}: ` : "";
82441
+ const path18 = issue.path && issue.path.length > 0 ? issue.path.join(".") : undefined;
82442
+ const where = path18 ? `field ${path18}: ` : "";
81843
82443
  switch (issue.code) {
81844
82444
  case "invalid_type": {
81845
82445
  const expected = toENType(issue.expected);
@@ -82291,8 +82891,8 @@ function getErrorMap() {
82291
82891
  return overrideErrorMap;
82292
82892
  }
82293
82893
  var makeIssue = (params3) => {
82294
- const { data, path: path15, errorMaps, issueData } = params3;
82295
- const fullPath = [...path15, ...issueData.path || []];
82894
+ const { data, path: path18, errorMaps, issueData } = params3;
82895
+ const fullPath = [...path18, ...issueData.path || []];
82296
82896
  const fullIssue = {
82297
82897
  ...issueData,
82298
82898
  path: fullPath
@@ -82420,11 +83020,11 @@ var _ZodEnum_cache;
82420
83020
  var _ZodNativeEnum_cache;
82421
83021
 
82422
83022
  class ParseInputLazyPath {
82423
- constructor(parent, value8, path15, key) {
83023
+ constructor(parent, value8, path18, key) {
82424
83024
  this._cachedPath = [];
82425
83025
  this.parent = parent;
82426
83026
  this.data = value8;
82427
- this._path = path15;
83027
+ this._path = path18;
82428
83028
  this._key = key;
82429
83029
  }
82430
83030
  get path() {
@@ -85801,7 +86401,7 @@ var z = /* @__PURE__ */ Object.freeze({
85801
86401
  });
85802
86402
 
85803
86403
  // src/internal/remdb-tools/listRemBackups.ts
85804
- import path15 from "node:path";
86404
+ import path18 from "node:path";
85805
86405
  var inputShape = {
85806
86406
  basePath: z.string().optional().describe("RemNote base directory (default: ~/remnote)"),
85807
86407
  limit: z.number().int().min(1).max(200).optional().describe("Max backups to return (default 50)")
@@ -85809,7 +86409,7 @@ var inputShape = {
85809
86409
  var listRemBackupsSchema = z.object(inputShape);
85810
86410
  async function executeListRemBackups(params3) {
85811
86411
  const parsed = parseOrThrow(listRemBackupsSchema, params3, { label: "list_rem_backups" });
85812
- const basePath = expandHome2(parsed.basePath ?? path15.join(homeDir(), REMNOTE_RELATIVE_DIR));
86412
+ const basePath = expandHome2(parsed.basePath ?? path18.join(homeDir(), REMNOTE_RELATIVE_DIR));
85813
86413
  const backups = await discoverBackups(basePath);
85814
86414
  const limit = parsed.limit ?? 50;
85815
86415
  return {
@@ -85819,6 +86419,7 @@ async function executeListRemBackups(params3) {
85819
86419
  };
85820
86420
  }
85821
86421
  // src/internal/remdb-tools/searchRemOverview.ts
86422
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
85822
86423
  import { isMainThread, parentPort as parentPort2, workerData } from "node:worker_threads";
85823
86424
 
85824
86425
  // src/internal/remdb-tools/timeFilters.ts
@@ -86098,6 +86699,21 @@ class HardTimeoutError extends Error {
86098
86699
  this.timeoutMs = timeoutMs;
86099
86700
  }
86100
86701
  }
86702
+ async function runSearchRemOverviewWorkerJob(job, port3) {
86703
+ if (job.kind === "search_rem_overview") {
86704
+ try {
86705
+ const result = await executeSearchRemOverviewDirect(job.input);
86706
+ port3.postMessage({ ok: true, result });
86707
+ } catch (e) {
86708
+ port3.postMessage({
86709
+ ok: false,
86710
+ error: { message: asErrorMessage(e), stack: e?.stack }
86711
+ });
86712
+ }
86713
+ return;
86714
+ }
86715
+ port3.postMessage({ ok: false, error: { message: "Unknown worker job" } });
86716
+ }
86101
86717
  function asErrorMessage(e) {
86102
86718
  return String(e?.message || e || "Unknown error");
86103
86719
  }
@@ -86108,7 +86724,8 @@ async function executeSearchRemOverviewDirect(params3) {
86108
86724
  }
86109
86725
  async function executeSearchRemOverviewWithHardTimeout(params3, timeoutMs) {
86110
86726
  const selfUrl = new URL(import.meta.url);
86111
- const isSourceMode = selfUrl.protocol === "file:" && selfUrl.pathname.endsWith(".ts");
86727
+ const selfFilePath = selfUrl.protocol === "file:" ? fileURLToPath2(selfUrl) : "";
86728
+ const isSourceMode = selfFilePath.endsWith(".ts");
86112
86729
  if (isSourceMode) {
86113
86730
  return await executeSearchRemOverviewDirect(params3);
86114
86731
  }
@@ -86125,18 +86742,7 @@ if (!isMainThread) {
86125
86742
  throw new Error("Worker parentPort is unavailable");
86126
86743
  }
86127
86744
  const job = workerData ?? { kind: "unknown" };
86128
- if (job.kind === "search_rem_overview") {
86129
- executeSearchRemOverviewDirect(job.input).then((result) => {
86130
- port3.postMessage({ ok: true, result });
86131
- }, (e) => {
86132
- port3.postMessage({
86133
- ok: false,
86134
- error: { message: asErrorMessage(e), stack: e?.stack }
86135
- });
86136
- });
86137
- } else {
86138
- port3.postMessage({ ok: false, error: { message: "Unknown worker job" } });
86139
- }
86745
+ runSearchRemOverviewWorkerJob(job, port3);
86140
86746
  }
86141
86747
  async function executeSearchRemOverview(params3) {
86142
86748
  const parsed = parseOrThrow(searchRemOverviewSchema, params3, { label: "search_rem_overview" });
@@ -87644,10 +88250,10 @@ function fetchDocs(db, rootId, includeDescendants, maxDepth) {
87644
88250
  }
87645
88251
  return rows.map((row) => convertRow(row.id, row.doc));
87646
88252
  }
87647
- function collectReferences(value8, remId, path16, into3) {
88253
+ function collectReferences(value8, remId, path19, into3) {
87648
88254
  if (Array.isArray(value8)) {
87649
88255
  value8.forEach((item, index) => {
87650
- collectReferences(item, remId, [...path16, index], into3);
88256
+ collectReferences(item, remId, [...path19, index], into3);
87651
88257
  });
87652
88258
  return;
87653
88259
  }
@@ -87657,7 +88263,7 @@ function collectReferences(value8, remId, path16, into3) {
87657
88263
  into3.push({
87658
88264
  refId: maybeRef._id,
87659
88265
  remId,
87660
- path: formatPath2(path16),
88266
+ path: formatPath2(path19),
87661
88267
  tokenKind: classifyToken(maybeRef)
87662
88268
  });
87663
88269
  return;
@@ -87666,13 +88272,13 @@ function collectReferences(value8, remId, path16, into3) {
87666
88272
  into3.push({
87667
88273
  refId: maybeRef._id,
87668
88274
  remId,
87669
- path: formatPath2(path16),
88275
+ path: formatPath2(path19),
87670
88276
  tokenKind: classifyToken(maybeRef)
87671
88277
  });
87672
88278
  return;
87673
88279
  }
87674
88280
  for (const [key, child] of Object.entries(maybeRef)) {
87675
- collectReferences(child, remId, [...path16, key], into3);
88281
+ collectReferences(child, remId, [...path19, key], into3);
87676
88282
  }
87677
88283
  }
87678
88284
  }
@@ -87789,8 +88395,8 @@ function buildReferencesMarkdown(remId, guidance, outbound, inbound, includeInbo
87789
88395
  const tokenKind = Array.isArray(ref.tokenKinds) ? `type: ${formatTokenKinds(ref.tokenKinds)}` : "";
87790
88396
  const ancestor = ref.ancestor ? `, ancestor: ${ref.ancestor}` : "";
87791
88397
  const count4 = typeof ref.count === "number" ? `${ref.count} occurrences` : "";
87792
- const path16 = ref.occurrences ? ref.occurrences[0]?.path ?? null : ref.representativePath;
87793
- const samplePath = path16 ? `, sample path: ${path16}` : "";
88398
+ const path19 = ref.occurrences ? ref.occurrences[0]?.path ?? null : ref.representativePath;
88399
+ const samplePath = path19 ? `, sample path: ${path19}` : "";
87794
88400
  lines3.push(`- **${name}** (ID: ${ref.refId}, ${count4}${ancestor}${samplePath}${tokenKind ? `, ${tokenKind}` : ""})`);
87795
88401
  });
87796
88402
  }
@@ -91737,107 +92343,559 @@ var WsClientLive = succeed10(WsClient, {
91737
92343
  })
91738
92344
  });
91739
92345
 
91740
- // src/commands/ws/_shared.ts
91741
- import path18 from "node:path";
91742
-
91743
- // src/services/Process.ts
91744
- import { spawn as spawn3 } from "node:child_process";
91745
- import fs11 from "node:fs";
91746
- import path16 from "node:path";
91747
- class Process extends Tag2("Process")() {
91748
- }
91749
- function ensureDirSync(filePath) {
91750
- fs11.mkdirSync(path16.dirname(filePath), { recursive: true });
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;
92354
+ }
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;
92363
+ }
92364
+ return explicitPath ?? defaultPath;
92365
+ }
92366
+
92367
+ // src/lib/pidTrust.ts
92368
+ import path20 from "node:path";
92369
+ function normalizeToken(value8) {
92370
+ return value8.trim().replace(/\\/g, "/").toLowerCase();
91751
92371
  }
91752
- var ProcessLive = succeed10(Process, {
91753
- isPidRunning: (pid) => sync3(() => {
91754
- if (!Number.isFinite(pid) || pid <= 0)
92372
+ function collectCommandTokens(raw4) {
92373
+ const out = new Set;
92374
+ for (const item of raw4) {
92375
+ const normalized = normalizeToken(String(item ?? ""));
92376
+ if (!normalized)
92377
+ continue;
92378
+ const unquoted = normalized.replace(/^['"]|['"]$/g, "");
92379
+ const base = path20.basename(unquoted);
92380
+ if (unquoted.includes("agent-remnote") || base.includes("agent-remnote")) {
92381
+ out.add("agent-remnote");
92382
+ }
92383
+ if (base === "node" || base === "node.exe")
92384
+ out.add("node");
92385
+ if (base === "tsx" || base === "tsx.cmd")
92386
+ out.add("tsx");
92387
+ if (base.endsWith(".js") || base.endsWith(".ts"))
92388
+ out.add(base);
92389
+ if (!unquoted.startsWith("-") && !unquoted.includes("/"))
92390
+ out.add(unquoted);
92391
+ }
92392
+ out.add("agent-remnote");
92393
+ return Array.from(out);
92394
+ }
92395
+ function expectedTokens(record2) {
92396
+ const raw4 = Array.isArray(record2.cmd) ? record2.cmd : [];
92397
+ return collectCommandTokens(raw4);
92398
+ }
92399
+ function actualTokens(commandLine) {
92400
+ const parts2 = commandLine.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
92401
+ return new Set(collectCommandTokens(parts2));
92402
+ }
92403
+ function isTrustedPidRecord(record2) {
92404
+ return gen2(function* () {
92405
+ const proc = yield* Process;
92406
+ const alive = yield* proc.isPidRunning(record2.pid);
92407
+ if (!alive)
91755
92408
  return false;
91756
- try {
91757
- process.kill(pid, 0);
91758
- return true;
91759
- } catch (error4) {
91760
- if (error4?.code === "EPERM")
91761
- return true;
92409
+ if (!proc.getCommandLine)
91762
92410
  return false;
91763
- }
91764
- }),
91765
- spawnDetached: (params3) => try_3({
91766
- try: () => {
91767
- ensureDirSync(params3.logFile);
91768
- const fd = fs11.openSync(params3.logFile, "a");
91769
- const child = spawn3(params3.command, [...params3.args], {
91770
- cwd: params3.cwd,
91771
- env: params3.env,
91772
- detached: true,
91773
- stdio: ["ignore", fd, fd]
91774
- });
91775
- child.unref();
91776
- return child.pid;
92411
+ const commandLine = yield* proc.getCommandLine(record2.pid);
92412
+ if (!commandLine)
92413
+ return false;
92414
+ const expected = expectedTokens(record2);
92415
+ const actual = actualTokens(commandLine);
92416
+ return expected.every((token) => actual.has(token));
92417
+ });
92418
+ }
92419
+ function requireTrustedPidRecord(params3) {
92420
+ return gen2(function* () {
92421
+ const trusted = yield* isTrustedPidRecord(params3.record);
92422
+ if (trusted)
92423
+ return;
92424
+ return yield* fail8(new CliError({
92425
+ code: "INTERNAL",
92426
+ message: "Refusing to operate on a pidfile that does not match a live agent-remnote process",
92427
+ exitCode: 1,
92428
+ details: {
92429
+ pid: params3.record.pid,
92430
+ pid_file: params3.pidFilePath,
92431
+ cmd: params3.record.cmd ?? []
92432
+ }
92433
+ }));
92434
+ });
92435
+ }
92436
+
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) {
92457
+ try {
92458
+ return await fs14.readFile(filePath, "utf8");
92459
+ } catch (e) {
92460
+ if (e?.code === "ENOENT")
92461
+ return "";
92462
+ throw e;
92463
+ }
92464
+ }
92465
+ async function writeTextAtomic(filePath, content) {
92466
+ await ensureDir8(filePath);
92467
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
92468
+ await fs14.writeFile(tmp, content, "utf8");
92469
+ await fs14.rename(tmp, filePath);
92470
+ }
92471
+ var StatusLineFileLive = succeed10(StatusLineFile, {
92472
+ defaultTextFile: () => defaultTextFile(),
92473
+ defaultJsonFile: () => defaultJsonFile(),
92474
+ write: ({ text: text15, textFilePath, debug: debug2, jsonFilePath, json: json4 }) => tryPromise2({
92475
+ try: async () => {
92476
+ const resolvedTextFile = resolveUserFilePath(textFilePath ?? defaultTextFile());
92477
+ const normalizedText = text15.trimEnd();
92478
+ const desired = normalizedText.length > 0 ? `${normalizedText}
92479
+ ` : "";
92480
+ const existing = await readFileOrEmpty(resolvedTextFile);
92481
+ if (existing === desired)
92482
+ return { wrote: false, textFilePath: resolvedTextFile };
92483
+ await writeTextAtomic(resolvedTextFile, desired);
92484
+ if (debug2 === true) {
92485
+ const resolvedJsonFile = resolveUserFilePath(jsonFilePath ?? defaultJsonFile());
92486
+ const payload = json4 !== undefined ? json4 : { text: normalizedText };
92487
+ await writeTextAtomic(resolvedJsonFile, `${JSON.stringify(payload)}
92488
+ `);
92489
+ }
92490
+ return { wrote: true, textFilePath: resolvedTextFile };
91777
92491
  },
91778
92492
  catch: (error4) => {
91779
92493
  if (isCliError(error4))
91780
92494
  return error4;
91781
92495
  return new CliError({
91782
92496
  code: "INTERNAL",
91783
- message: "Failed to start background process",
92497
+ message: "Failed to write status line file",
91784
92498
  exitCode: 1,
91785
92499
  details: { error: String(error4?.message || error4) }
91786
92500
  });
91787
92501
  }
91788
- }).pipe(flatMap9((pid) => {
91789
- if (typeof pid !== "number" || !Number.isFinite(pid)) {
91790
- return fail8(new CliError({
91791
- code: "INTERNAL",
91792
- message: "Failed to start background process (pid is unavailable)",
91793
- exitCode: 1
91794
- }));
92502
+ })
92503
+ });
92504
+
92505
+ // src/lib/statuslineArtifacts.ts
92506
+ function normalizeOptionalPath(value8) {
92507
+ if (typeof value8 !== "string")
92508
+ return;
92509
+ const t = value8.trim();
92510
+ return t.length > 0 ? resolveUserFilePath(t) : undefined;
92511
+ }
92512
+ function resolveStatuslineArtifactPaths(params3) {
92513
+ const pidInfo = params3.pidInfo;
92514
+ const wsBridgeStateFilePath = normalizeOptionalPath(pidInfo?.ws_bridge_state_file) ?? params3.cfg.wsStateFile.path;
92515
+ const statusLineFilePath = normalizeOptionalPath(pidInfo?.status_line_file) ?? params3.cfg.statusLineFile;
92516
+ const statusLineJsonFilePath = normalizeOptionalPath(pidInfo?.status_line_json_file) ?? params3.cfg.statusLineJsonFile;
92517
+ return { wsBridgeStateFilePath, statusLineFilePath, statusLineJsonFilePath };
92518
+ }
92519
+ function deleteFileIfExists(filePath) {
92520
+ return promise2(async () => {
92521
+ try {
92522
+ await fs15.unlink(filePath);
92523
+ return { action: "deleted", file: filePath };
92524
+ } catch (e) {
92525
+ if (e?.code === "ENOENT")
92526
+ return { action: "skipped", file: filePath };
92527
+ return { action: "failed", file: filePath, error: String(e?.message || e) };
91795
92528
  }
91796
- return succeed8(pid);
91797
- })),
91798
- kill: (pid, signal) => try_3({
91799
- try: () => {
91800
- process.kill(pid, signal);
91801
- },
91802
- catch: (error4) => new CliError({
91803
- code: "INTERNAL",
91804
- message: `Failed to send signal (${signal})`,
91805
- exitCode: 1,
91806
- details: { pid, signal, error: String(error4?.message || error4) }
91807
- })
91808
- }),
91809
- waitForExit: ({ pid, timeoutMs }) => async((resume2) => {
91810
- const deadline = Date.now() + Math.max(0, timeoutMs);
91811
- const tick2 = () => {
91812
- let alive = false;
91813
- try {
91814
- process.kill(pid, 0);
91815
- alive = true;
91816
- } catch (error4) {
91817
- alive = error4?.code === "EPERM";
92529
+ });
92530
+ }
92531
+ function cleanupStatuslineArtifacts(paths) {
92532
+ return gen2(function* () {
92533
+ const statusLineFile = yield* StatusLineFile;
92534
+ const wsBridgeStateFile = yield* deleteFileIfExists(paths.wsBridgeStateFilePath);
92535
+ const cleared = yield* statusLineFile.write({ text: "", textFilePath: paths.statusLineFilePath, debug: false }).pipe(either3);
92536
+ const statusLineFileOutcome = cleared._tag === "Right" ? cleared.right.wrote ? { action: "cleared", file: cleared.right.textFilePath } : { action: "skipped", file: cleared.right.textFilePath } : {
92537
+ action: "failed",
92538
+ file: paths.statusLineFilePath,
92539
+ error: String(cleared.left?.message || cleared.left)
92540
+ };
92541
+ const statusLineJsonFile = yield* deleteFileIfExists(paths.statusLineJsonFilePath);
92542
+ return { wsBridgeStateFile, statusLineFile: statusLineFileOutcome, statusLineJsonFile };
92543
+ });
92544
+ }
92545
+
92546
+ // src/lib/doctor/fixes.ts
92547
+ function applyDoctorFixes() {
92548
+ return gen2(function* () {
92549
+ const cfg = yield* AppConfig;
92550
+ const daemonFiles = yield* DaemonFiles;
92551
+ const apiFiles = yield* ApiDaemonFiles;
92552
+ const pluginFiles = yield* PluginServerFiles;
92553
+ const supervisorState = yield* SupervisorState;
92554
+ const proc = yield* Process;
92555
+ const userConfig = yield* UserConfigFile;
92556
+ let changed = false;
92557
+ const fixes = [];
92558
+ const cleaned = [];
92559
+ const cleanupFailures = [];
92560
+ const daemonPidFile = daemonFiles.defaultPidFile();
92561
+ const daemonPidInfo = yield* daemonFiles.readPidFile(daemonPidFile).pipe(orElseSucceed2(() => {
92562
+ return;
92563
+ }));
92564
+ if (daemonPidInfo?.pid && !(yield* isTrustedPidRecord(daemonPidInfo))) {
92565
+ const daemonStateFile = resolveManagedStateFile({
92566
+ pidFilePath: daemonPidFile,
92567
+ defaultStateFilePath: supervisorState.defaultStateFile(),
92568
+ candidate: daemonPidInfo.state_file
92569
+ });
92570
+ const result = yield* gen2(function* () {
92571
+ yield* daemonFiles.deletePidFile(daemonPidFile);
92572
+ yield* supervisorState.deleteStateFile(daemonStateFile);
92573
+ return yield* cleanupStatuslineArtifacts(resolveStatuslineArtifactPaths({ cfg, pidInfo: daemonPidInfo }));
92574
+ }).pipe(either3);
92575
+ if (result._tag === "Right") {
92576
+ cleaned.push({ service: "daemon", pidFile: daemonPidFile, stateFile: daemonStateFile, pid: daemonPidInfo.pid });
92577
+ changed = true;
92578
+ } else {
92579
+ cleanupFailures.push({
92580
+ service: "daemon",
92581
+ pidFile: daemonPidFile,
92582
+ stateFile: daemonStateFile,
92583
+ pid: daemonPidInfo.pid,
92584
+ error: result.left.message
92585
+ });
91818
92586
  }
91819
- if (!alive) {
91820
- resume2(succeed8(true));
91821
- return;
92587
+ }
92588
+ const apiPidFile = apiFiles.defaultPidFile();
92589
+ const apiPidInfo = yield* apiFiles.readPidFile(apiPidFile).pipe(orElseSucceed2(() => {
92590
+ return;
92591
+ }));
92592
+ if (apiPidInfo?.pid && !(yield* isTrustedPidRecord(apiPidInfo))) {
92593
+ const apiStateFile = resolveManagedStateFile({
92594
+ pidFilePath: apiPidFile,
92595
+ defaultStateFilePath: apiFiles.defaultStateFile(),
92596
+ candidate: apiPidInfo.state_file
92597
+ });
92598
+ const result = yield* gen2(function* () {
92599
+ yield* apiFiles.deletePidFile(apiPidFile);
92600
+ yield* apiFiles.deleteStateFile(apiStateFile);
92601
+ }).pipe(either3);
92602
+ if (result._tag === "Right") {
92603
+ cleaned.push({ service: "api", pidFile: apiPidFile, stateFile: apiStateFile, pid: apiPidInfo.pid });
92604
+ changed = true;
92605
+ } else {
92606
+ cleanupFailures.push({
92607
+ service: "api",
92608
+ pidFile: apiPidFile,
92609
+ stateFile: apiStateFile,
92610
+ pid: apiPidInfo.pid,
92611
+ error: result.left.message
92612
+ });
91822
92613
  }
91823
- if (Date.now() >= deadline) {
91824
- resume2(succeed8(false));
91825
- return;
92614
+ }
92615
+ const pluginPidFile = pluginFiles.defaultPidFile();
92616
+ const pluginPidInfo = yield* pluginFiles.readPidFile(pluginPidFile).pipe(orElseSucceed2(() => {
92617
+ return;
92618
+ }));
92619
+ if (pluginPidInfo?.pid && !(yield* isTrustedPidRecord(pluginPidInfo))) {
92620
+ const pluginStateFile = resolveManagedStateFile({
92621
+ pidFilePath: pluginPidFile,
92622
+ defaultStateFilePath: pluginFiles.defaultStateFile(),
92623
+ candidate: pluginPidInfo.state_file
92624
+ });
92625
+ const result = yield* gen2(function* () {
92626
+ yield* pluginFiles.deletePidFile(pluginPidFile);
92627
+ yield* pluginFiles.deleteStateFile(pluginStateFile);
92628
+ }).pipe(either3);
92629
+ if (result._tag === "Right") {
92630
+ cleaned.push({ service: "plugin", pidFile: pluginPidFile, stateFile: pluginStateFile, pid: pluginPidInfo.pid });
92631
+ changed = true;
92632
+ } else {
92633
+ cleanupFailures.push({
92634
+ service: "plugin",
92635
+ pidFile: pluginPidFile,
92636
+ stateFile: pluginStateFile,
92637
+ pid: pluginPidInfo.pid,
92638
+ error: result.left.message
92639
+ });
91826
92640
  }
91827
- setTimeout(tick2, 100);
92641
+ }
92642
+ fixes.push({
92643
+ id: "runtime.cleanup_stale_artifacts",
92644
+ ok: cleanupFailures.length === 0,
92645
+ changed: cleaned.length > 0,
92646
+ summary: cleaned.length === 0 && cleanupFailures.length === 0 ? "No stale runtime artifacts needed cleanup" : cleanupFailures.length === 0 ? `Cleaned ${cleaned.length} stale runtime artifact set(s)` : `Cleaned ${cleaned.length} stale runtime artifact set(s); ${cleanupFailures.length} cleanup failure(s) need manual follow-up`,
92647
+ details: {
92648
+ cleaned,
92649
+ failed: cleanupFailures
92650
+ }
92651
+ });
92652
+ const configRepair = yield* userConfig.repair().pipe(either3);
92653
+ if (configRepair._tag === "Right") {
92654
+ if (configRepair.right.changed && configRepair.right.before.valid)
92655
+ changed = true;
92656
+ fixes.push({
92657
+ id: "config.rewrite_canonical_user_config",
92658
+ ok: configRepair.right.before.valid,
92659
+ changed: configRepair.right.before.valid ? configRepair.right.changed : false,
92660
+ summary: !configRepair.right.before.valid ? "Skipped config rewrite because the current config is invalid or conflicting" : configRepair.right.changed ? "Rewrote user config into canonical form" : "User config already canonical",
92661
+ details: {
92662
+ config_file: configRepair.right.configFile,
92663
+ before: configRepair.right.before,
92664
+ after: configRepair.right.after
92665
+ }
92666
+ });
92667
+ } else {
92668
+ fixes.push({
92669
+ id: "config.rewrite_canonical_user_config",
92670
+ ok: false,
92671
+ changed: false,
92672
+ summary: "Failed to rewrite user config",
92673
+ details: { error: configRepair.left.message }
92674
+ });
92675
+ }
92676
+ const restartSummary = {
92677
+ attempted: [],
92678
+ restarted: [],
92679
+ skipped: ["safe_restart_disabled"],
92680
+ failed: []
91828
92681
  };
91829
- tick2();
91830
- })
91831
- });
92682
+ fixes.push({
92683
+ 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",
92687
+ details: restartSummary
92688
+ });
92689
+ return { fixes, changed, restartSummary };
92690
+ });
92691
+ }
92692
+
92693
+ // src/lib/doctor/checks.ts
92694
+ import path26 from "node:path";
92695
+
92696
+ // 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));
92701
+ function locateBuiltinSourceRoot() {
92702
+ const packageRootCandidates = [
92703
+ path22.resolve(MODULE_DIR, "../../../"),
92704
+ path22.resolve(MODULE_DIR, "..")
92705
+ ];
92706
+ for (const packageRoot of packageRootCandidates) {
92707
+ const builtinSourceRoot = path22.join(packageRoot, "builtin-scenarios");
92708
+ if (existsSync2(path22.join(builtinSourceRoot, "catalog.json"))) {
92709
+ return { packageRoot, builtinSourceRoot };
92710
+ }
92711
+ }
92712
+ throw new Error(`Unable to locate builtin-scenarios catalog from ${MODULE_DIR}`);
92713
+ }
92714
+ 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");
92717
+ var BUILTIN_SOURCE_PREFIX = "packages/agent-remnote/builtin-scenarios/";
92718
+ function readJsonFile(filePath) {
92719
+ return JSON.parse(readFileSync2(filePath, "utf8"));
92720
+ }
92721
+ function resolveRepoPath(relPath) {
92722
+ return path22.resolve(REPO_ROOT, relPath);
92723
+ }
92724
+ function resolveBuiltinPackagePath(relPath) {
92725
+ const normalized = relPath.replaceAll("\\", "/");
92726
+ if (normalized.startsWith(BUILTIN_SOURCE_PREFIX)) {
92727
+ return path22.resolve(BUILTIN_SOURCE_ROOT, normalized.slice(BUILTIN_SOURCE_PREFIX.length));
92728
+ }
92729
+ return resolveRepoPath(relPath);
92730
+ }
92731
+ var builtinScenarioCatalog = readJsonFile(BUILTIN_CATALOG_PATH);
92732
+ var builtinScenarioPackageCache = new Map;
92733
+ var builtinScenarioPackagesTarget = {};
92734
+ function loadBuiltinScenarioPackage(entry) {
92735
+ const cached4 = builtinScenarioPackageCache.get(entry.package_id);
92736
+ if (cached4)
92737
+ return cached4;
92738
+ const loaded = readJsonFile(resolveBuiltinPackagePath(entry.package_path));
92739
+ builtinScenarioPackageCache.set(entry.package_id, loaded);
92740
+ return loaded;
92741
+ }
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
+ }
91832
92890
 
91833
92891
  // src/lib/runtimeBuildInfo.ts
91834
92892
  import { createHash as createHash3 } from "node:crypto";
91835
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
91836
- import path17 from "node:path";
91837
- import { fileURLToPath as fileURLToPath2 } from "node:url";
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";
91838
92896
  function packageInfo() {
91839
92897
  try {
91840
- const raw4 = readFileSync2(new URL("../../package.json", import.meta.url), "utf8");
92898
+ const raw4 = readFileSync4(new URL("../../package.json", import.meta.url), "utf8");
91841
92899
  const parsed = JSON.parse(raw4);
91842
92900
  const name = typeof parsed?.name === "string" && parsed.name.trim() ? parsed.name.trim() : "agent-remnote";
91843
92901
  const version = typeof parsed?.version === "string" && parsed.version.trim() ? parsed.version.trim() : "0.0.0";
@@ -91848,13 +92906,13 @@ function packageInfo() {
91848
92906
  }
91849
92907
  function fileMtimeMs(targetPath) {
91850
92908
  try {
91851
- return Math.floor(statSync(targetPath).mtimeMs);
92909
+ return Math.floor(statSync2(targetPath).mtimeMs);
91852
92910
  } catch {
91853
92911
  return 0;
91854
92912
  }
91855
92913
  }
91856
92914
  function latestMtimeMs(dirPath) {
91857
- if (!existsSync2(dirPath))
92915
+ if (!existsSync4(dirPath))
91858
92916
  return 0;
91859
92917
  let max7 = 0;
91860
92918
  const stack = [dirPath];
@@ -91867,10 +92925,10 @@ function latestMtimeMs(dirPath) {
91867
92925
  continue;
91868
92926
  }
91869
92927
  for (const entry of entries2) {
91870
- const full = path17.join(current2, entry);
92928
+ const full = path25.join(current2, entry);
91871
92929
  let st;
91872
92930
  try {
91873
- st = statSync(full);
92931
+ st = statSync2(full);
91874
92932
  } catch {
91875
92933
  continue;
91876
92934
  }
@@ -91896,7 +92954,7 @@ function computeDefaultBuildInfo() {
91896
92954
  const mode = detectMode();
91897
92955
  const srcDir = new URL("../", import.meta.url);
91898
92956
  const packageJson = new URL("../../package.json", import.meta.url);
91899
- const sourceStamp = Math.max(latestMtimeMs(fileURLToPath2(srcDir)), fileMtimeMs(fileURLToPath2(packageJson)));
92957
+ const sourceStamp = Math.max(latestMtimeMs(fileURLToPath5(srcDir)), fileMtimeMs(fileURLToPath5(packageJson)));
91900
92958
  const buildIdInput = `${pkg.name}
91901
92959
  ${pkg.version}
91902
92960
  ${mode}
@@ -91945,7 +93003,147 @@ function runtimeVersionWarnings(params3) {
91945
93003
  return warnings;
91946
93004
  }
91947
93005
 
93006
+ // src/lib/doctor/checks.ts
93007
+ function collectDoctorChecks() {
93008
+ return gen2(function* () {
93009
+ const cfg = yield* AppConfig;
93010
+ const daemonFiles = yield* DaemonFiles;
93011
+ const apiFiles = yield* ApiDaemonFiles;
93012
+ const pluginFiles = yield* PluginServerFiles;
93013
+ const userConfig = yield* UserConfigFile;
93014
+ const fsAccess = yield* FsAccess;
93015
+ const staleArtifacts = [];
93016
+ const daemonPidFile = daemonFiles.defaultPidFile();
93017
+ const daemonPidInfo = yield* daemonFiles.readPidFile(daemonPidFile).pipe(orElseSucceed2(() => {
93018
+ return;
93019
+ }));
93020
+ if (daemonPidInfo?.pid && !(yield* isTrustedPidRecord(daemonPidInfo))) {
93021
+ staleArtifacts.push({
93022
+ service: "daemon",
93023
+ pidFile: daemonPidFile,
93024
+ stateFile: daemonPidInfo.state_file ?? path26.join(path26.dirname(daemonPidFile), "ws.state.json"),
93025
+ pid: daemonPidInfo.pid
93026
+ });
93027
+ }
93028
+ const apiPidFile = apiFiles.defaultPidFile();
93029
+ const apiPidInfo = yield* apiFiles.readPidFile(apiPidFile).pipe(orElseSucceed2(() => {
93030
+ return;
93031
+ }));
93032
+ if (apiPidInfo?.pid && !(yield* isTrustedPidRecord(apiPidInfo))) {
93033
+ staleArtifacts.push({
93034
+ service: "api",
93035
+ pidFile: apiPidFile,
93036
+ stateFile: apiPidInfo.state_file ?? apiFiles.defaultStateFile(),
93037
+ pid: apiPidInfo.pid
93038
+ });
93039
+ }
93040
+ const pluginPidFile = pluginFiles.defaultPidFile();
93041
+ const pluginPidInfo = yield* pluginFiles.readPidFile(pluginPidFile).pipe(orElseSucceed2(() => {
93042
+ return;
93043
+ }));
93044
+ if (pluginPidInfo?.pid && !(yield* isTrustedPidRecord(pluginPidInfo))) {
93045
+ staleArtifacts.push({
93046
+ service: "plugin",
93047
+ pidFile: pluginPidFile,
93048
+ stateFile: pluginPidInfo.state_file ?? pluginFiles.defaultStateFile(),
93049
+ pid: pluginPidInfo.pid
93050
+ });
93051
+ }
93052
+ const current2 = currentRuntimeBuildInfo();
93053
+ const expectedPlugin = currentExpectedPluginBuildInfo();
93054
+ const mismatches = [
93055
+ daemonPidInfo?.build?.build_id && daemonPidInfo.build.build_id !== current2.build_id ? { service: "daemon", live: daemonPidInfo.build.build_id } : null,
93056
+ apiPidInfo?.build?.build_id && apiPidInfo.build.build_id !== current2.build_id ? { service: "api", live: apiPidInfo.build.build_id } : null,
93057
+ 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
93059
+ ].filter(Boolean);
93060
+ const configPreview = yield* userConfig.previewRepair().pipe(either3);
93061
+ const configChanged = configPreview._tag === "Right" ? configPreview.right.changed : false;
93062
+ const configValid = configPreview._tag === "Right" ? configPreview.right.before.valid : false;
93063
+ const configRepairable = configPreview._tag === "Right" ? configPreview.right.before.valid && configPreview.right.changed : false;
93064
+ const configDetails = configPreview._tag === "Right" ? configPreview.right : { error: configPreview.left.message };
93065
+ const packageCheck = yield* sync3(() => {
93066
+ try {
93067
+ getBuiltinScenarioPackage("dn_recent_todos_to_today_move");
93068
+ getBuiltinScenarioPackage("dn_recent_todos_to_today_portal");
93069
+ return { ok: true, details: undefined };
93070
+ } catch (error4) {
93071
+ 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
+
91948
93145
  // src/commands/ws/_shared.ts
93146
+ import path27 from "node:path";
91949
93147
  var WS_HEALTH_TIMEOUT_MS = 2000;
91950
93148
  var WS_START_WAIT_DEFAULT_MS = 15000;
91951
93149
  var WS_STOP_WAIT_DEFAULT_MS = 5000;
@@ -92035,7 +93233,7 @@ function toInitialSupervisorState(now2) {
92035
93233
  };
92036
93234
  }
92037
93235
  function defaultStateFilePathFromPidFile(pidFilePath) {
92038
- return path18.join(path18.dirname(pidFilePath), "ws.state.json");
93236
+ return path27.join(path27.dirname(pidFilePath), "ws.state.json");
92039
93237
  }
92040
93238
  function startWsSupervisor(params3) {
92041
93239
  return gen2(function* () {
@@ -92054,6 +93252,7 @@ function startWsSupervisor(params3) {
92054
93252
  yield* daemonFiles.deletePidFile(pidFilePath);
92055
93253
  yield* supervisorState.deleteStateFile(stateFilePath);
92056
93254
  } else {
93255
+ yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
92057
93256
  return {
92058
93257
  started: false,
92059
93258
  pid: existingPidFile.pid,
@@ -92117,6 +93316,7 @@ function ensureWsSupervisor(params3) {
92117
93316
  if (existingPidFile) {
92118
93317
  const alive = yield* proc.isPidRunning(existingPidFile.pid);
92119
93318
  if (alive) {
93319
+ yield* requireTrustedPidRecord({ record: existingPidFile, pidFilePath });
92120
93320
  return {
92121
93321
  started: false,
92122
93322
  pid: existingPidFile.pid,
@@ -92136,75 +93336,99 @@ function ensureWsSupervisor(params3) {
92136
93336
  }
92137
93337
 
92138
93338
  // src/commands/doctor.ts
92139
- var doctorCommand = exports_Command.make("doctor", {}, () => gen2(function* () {
93339
+ var doctorCommand = exports_Command.make("doctor", { fix: boolean8("fix") }, ({ fix }) => gen2(function* () {
92140
93340
  const cfg = yield* AppConfig;
92141
93341
  const queue = yield* Queue;
92142
93342
  const remDb = yield* RemDb;
92143
93343
  const ws = yield* WsClient;
92144
93344
  const daemonFiles = yield* DaemonFiles;
92145
93345
  const fsAccess = yield* FsAccess;
92146
- const queueStats2 = yield* queue.stats({ dbPath: cfg.storeDb }).pipe(either3);
92147
- const remnote = yield* remDb.withDb(cfg.remnoteDb, (db) => {
92148
- db.prepare("SELECT 1 FROM quanta LIMIT 1").get();
92149
- const hasSearchInfos = !!db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='remsSearchInfos' LIMIT 1`).get();
92150
- const hasContents = !!db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='remsContents' LIMIT 1`).get();
92151
- return { has_search_index: hasSearchInfos && hasContents };
92152
- }).pipe(either3);
92153
- const schema2 = yield* try_3({
92154
- try: () => {
92155
- const db = openStoreDb(cfg.storeDb);
92156
- try {
92157
- return readStoreSchemaStatus(db);
92158
- } finally {
92159
- db.close();
93346
+ yield* ApiDaemonFiles;
93347
+ yield* PluginServerFiles;
93348
+ yield* Process;
93349
+ yield* SupervisorState;
93350
+ yield* UserConfigFile;
93351
+ const collectSnapshot = () => gen2(function* () {
93352
+ const queueStats2 = yield* queue.stats({ dbPath: cfg.storeDb }).pipe(either3);
93353
+ const remnote = yield* remDb.withDb(cfg.remnoteDb, (db) => {
93354
+ db.prepare("SELECT 1 FROM quanta LIMIT 1").get();
93355
+ const hasSearchInfos = !!db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='remsSearchInfos' LIMIT 1`).get();
93356
+ const hasContents = !!db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='remsContents' LIMIT 1`).get();
93357
+ return { has_search_index: hasSearchInfos && hasContents };
93358
+ }).pipe(either3);
93359
+ const schema2 = yield* try_3({
93360
+ try: () => {
93361
+ const db = openStoreDb(cfg.storeDb);
93362
+ try {
93363
+ return readStoreSchemaStatus(db);
93364
+ } finally {
93365
+ db.close();
93366
+ }
93367
+ },
93368
+ catch: (error4) => new CliError({
93369
+ code: "DB_UNAVAILABLE",
93370
+ message: `Failed to read store schema: ${String(error4?.message || error4)}`,
93371
+ exitCode: 1
93372
+ })
93373
+ }).pipe(either3);
93374
+ const wsHealth = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93375
+ const wsClients = yield* ws.queryClients({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
93376
+ const pidFilePath = daemonFiles.defaultPidFile();
93377
+ const logFilePath = daemonFiles.defaultLogFile();
93378
+ const pidWritable = yield* fsAccess.canWritePath(pidFilePath);
93379
+ const logWritable = yield* fsAccess.canWritePath(logFilePath);
93380
+ const storeDbWritable = yield* fsAccess.checkWritableFile(cfg.storeDb);
93381
+ return {
93382
+ queue: {
93383
+ ok: queueStats2._tag === "Right",
93384
+ db_path: cfg.storeDb,
93385
+ schema: schema2._tag === "Right" ? schema2.right : undefined,
93386
+ schema_error: schema2._tag === "Left" ? schema2.left.message : undefined,
93387
+ writable: storeDbWritable.ok,
93388
+ writable_reason: storeDbWritable.reason,
93389
+ stats: queueStats2._tag === "Right" ? queueStats2.right : undefined,
93390
+ error: queueStats2._tag === "Left" ? queueStats2.left.message : undefined
93391
+ },
93392
+ remnote_db: {
93393
+ ok: remnote._tag === "Right",
93394
+ db_path: remnote._tag === "Right" ? remnote.right.info.dbPath : cfg.remnoteDb,
93395
+ resolution: remnote._tag === "Right" ? remnote.right.info.source : undefined,
93396
+ has_search_index: remnote._tag === "Right" ? remnote.right.result.has_search_index : undefined,
93397
+ error: remnote._tag === "Left" ? remnote.left.message : undefined
93398
+ },
93399
+ ws: {
93400
+ ok: wsHealth._tag === "Right",
93401
+ url: cfg.wsUrl,
93402
+ rtt_ms: wsHealth._tag === "Right" ? wsHealth.right.rtt_ms : undefined,
93403
+ error: wsHealth._tag === "Left" ? wsHealth.left.message : undefined,
93404
+ clients: wsClients._tag === "Right" ? wsClients.right.clients : []
93405
+ },
93406
+ daemon_files: {
93407
+ pid_file: pidFilePath,
93408
+ log_file: logFilePath,
93409
+ pid_writable: pidWritable,
93410
+ log_writable: logWritable
92160
93411
  }
92161
- },
92162
- catch: (error4) => new CliError({
92163
- code: "DB_UNAVAILABLE",
92164
- message: `Failed to read store schema: ${String(error4?.message || error4)}`,
92165
- exitCode: 1
92166
- })
92167
- }).pipe(either3);
92168
- const wsHealth = yield* ws.health({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
92169
- const wsClients = yield* ws.queryClients({ url: cfg.wsUrl, timeoutMs: WS_HEALTH_TIMEOUT_MS }).pipe(either3);
92170
- const pidFilePath = daemonFiles.defaultPidFile();
92171
- const logFilePath = daemonFiles.defaultLogFile();
92172
- const pidWritable = yield* fsAccess.canWritePath(pidFilePath);
92173
- const logWritable = yield* fsAccess.canWritePath(logFilePath);
92174
- const storeDbWritable = yield* fsAccess.checkWritableFile(cfg.storeDb);
93412
+ };
93413
+ });
93414
+ const snapshotBefore = yield* collectSnapshot();
93415
+ const checksBefore = yield* collectDoctorChecks();
93416
+ const fixResult = fix ? yield* applyDoctorFixes() : { fixes: [], changed: false, restartSummary: { attempted: [], restarted: [], skipped: [], failed: [] } };
93417
+ const snapshotAfter = fix ? yield* collectSnapshot() : snapshotBefore;
93418
+ const checks = fix ? yield* collectDoctorChecks() : checksBefore;
92175
93419
  const data = {
92176
- queue: {
92177
- ok: queueStats2._tag === "Right",
92178
- db_path: cfg.storeDb,
92179
- schema: schema2._tag === "Right" ? schema2.right : undefined,
92180
- schema_error: schema2._tag === "Left" ? schema2.left.message : undefined,
92181
- writable: storeDbWritable.ok,
92182
- writable_reason: storeDbWritable.reason,
92183
- stats: queueStats2._tag === "Right" ? queueStats2.right : undefined,
92184
- error: queueStats2._tag === "Left" ? queueStats2.left.message : undefined
92185
- },
92186
- remnote_db: {
92187
- ok: remnote._tag === "Right",
92188
- db_path: remnote._tag === "Right" ? remnote.right.info.dbPath : cfg.remnoteDb,
92189
- resolution: remnote._tag === "Right" ? remnote.right.info.source : undefined,
92190
- has_search_index: remnote._tag === "Right" ? remnote.right.result.has_search_index : undefined,
92191
- error: remnote._tag === "Left" ? remnote.left.message : undefined
92192
- },
92193
- ws: {
92194
- ok: wsHealth._tag === "Right",
92195
- url: cfg.wsUrl,
92196
- rtt_ms: wsHealth._tag === "Right" ? wsHealth.right.rtt_ms : undefined,
92197
- error: wsHealth._tag === "Left" ? wsHealth.left.message : undefined,
92198
- clients: wsClients._tag === "Right" ? wsClients.right.clients : []
92199
- },
92200
- daemon_files: {
92201
- pid_file: pidFilePath,
92202
- log_file: logFilePath,
92203
- pid_writable: pidWritable,
92204
- log_writable: logWritable
92205
- }
92206
- };
92207
- const overallOk = data.queue.ok && data.remnote_db.ok && data.ws.ok && data.daemon_files.pid_writable && data.daemon_files.log_writable;
93420
+ queue: snapshotAfter.queue,
93421
+ remnote_db: snapshotAfter.remnote_db,
93422
+ ws: snapshotAfter.ws,
93423
+ daemon_files: snapshotAfter.daemon_files,
93424
+ before: fix ? snapshotBefore : undefined,
93425
+ changed: fixResult.changed,
93426
+ checks_before: checksBefore,
93427
+ checks,
93428
+ fixes: fixResult.fixes,
93429
+ restart_summary: fixResult.restartSummary
93430
+ };
93431
+ const overallOk = data.queue.ok && data.remnote_db.ok && data.ws.ok && data.daemon_files.pid_writable && data.daemon_files.log_writable && checks.every((check2) => check2.ok);
92208
93432
  const hints = [];
92209
93433
  if (!data.ws.ok)
92210
93434
  hints.push("Try: agent-remnote daemon ensure / agent-remnote daemon status");
@@ -92243,6 +93467,9 @@ var doctorCommand = exports_Command.make("doctor", {}, () => gen2(function* () {
92243
93467
  `- pid_file: ${data.daemon_files.pid_file}`,
92244
93468
  `- log_file_writable: ${data.daemon_files.log_writable}`,
92245
93469
  `- log_file: ${data.daemon_files.log_file}`,
93470
+ `- changed: ${data.changed}`,
93471
+ `- checks_total: ${data.checks.length}`,
93472
+ `- fixes_total: ${data.fixes.length}`,
92246
93473
  hints.length > 0 ? `
92247
93474
  ## Hint` : "",
92248
93475
  ...hints.map((h) => `- ${h}`)
@@ -92564,121 +93791,37 @@ var wsLogsCommand = exports_Command.make("logs", {
92564
93791
  }).pipe(catchAll2(writeFailure)));
92565
93792
 
92566
93793
  // src/commands/ws/restart.ts
92567
- import path20 from "node:path";
92568
-
92569
- // src/lib/statuslineArtifacts.ts
92570
- import { promises as fs13 } from "node:fs";
92571
-
92572
- // src/services/StatusLineFile.ts
92573
- import { promises as fs12 } from "node:fs";
92574
- import path19 from "node:path";
92575
- class StatusLineFile extends Tag2("StatusLineFile")() {
92576
- }
92577
- function defaultTextFile() {
92578
- return path19.join(homeDir(), ".agent-remnote", "status-line.txt");
92579
- }
92580
- function defaultJsonFile() {
92581
- return path19.join(homeDir(), ".agent-remnote", "status-line.json");
92582
- }
92583
- function ensureDir6(p3) {
92584
- return fs12.mkdir(path19.dirname(p3), { recursive: true }).then(() => {
92585
- return;
92586
- });
92587
- }
92588
- async function readFileOrEmpty(filePath) {
92589
- try {
92590
- return await fs12.readFile(filePath, "utf8");
92591
- } catch (e) {
92592
- if (e?.code === "ENOENT")
92593
- return "";
92594
- throw e;
92595
- }
92596
- }
92597
- async function writeTextAtomic(filePath, content) {
92598
- await ensureDir6(filePath);
92599
- const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
92600
- await fs12.writeFile(tmp, content, "utf8");
92601
- await fs12.rename(tmp, filePath);
92602
- }
92603
- var StatusLineFileLive = succeed10(StatusLineFile, {
92604
- defaultTextFile: () => defaultTextFile(),
92605
- defaultJsonFile: () => defaultJsonFile(),
92606
- write: ({ text: text15, textFilePath, debug: debug2, jsonFilePath, json: json4 }) => tryPromise2({
92607
- try: async () => {
92608
- const resolvedTextFile = resolveUserFilePath(textFilePath ?? defaultTextFile());
92609
- const normalizedText = text15.trimEnd();
92610
- const desired = normalizedText.length > 0 ? `${normalizedText}
92611
- ` : "";
92612
- const existing = await readFileOrEmpty(resolvedTextFile);
92613
- if (existing === desired)
92614
- return { wrote: false, textFilePath: resolvedTextFile };
92615
- await writeTextAtomic(resolvedTextFile, desired);
92616
- if (debug2 === true) {
92617
- const resolvedJsonFile = resolveUserFilePath(jsonFilePath ?? defaultJsonFile());
92618
- const payload = json4 !== undefined ? json4 : { text: normalizedText };
92619
- await writeTextAtomic(resolvedJsonFile, `${JSON.stringify(payload)}
92620
- `);
92621
- }
92622
- return { wrote: true, textFilePath: resolvedTextFile };
92623
- },
92624
- catch: (error4) => {
92625
- if (isCliError(error4))
92626
- return error4;
92627
- return new CliError({
92628
- code: "INTERNAL",
92629
- message: "Failed to write status line file",
92630
- exitCode: 1,
92631
- details: { error: String(error4?.message || error4) }
92632
- });
92633
- }
92634
- })
92635
- });
92636
-
92637
- // src/lib/statuslineArtifacts.ts
92638
- function normalizeOptionalPath(value8) {
92639
- if (typeof value8 !== "string")
92640
- return;
92641
- const t = value8.trim();
92642
- return t.length > 0 ? resolveUserFilePath(t) : undefined;
92643
- }
92644
- function resolveStatuslineArtifactPaths(params3) {
92645
- const pidInfo = params3.pidInfo;
92646
- const wsBridgeStateFilePath = normalizeOptionalPath(pidInfo?.ws_bridge_state_file) ?? params3.cfg.wsStateFile.path;
92647
- const statusLineFilePath = normalizeOptionalPath(pidInfo?.status_line_file) ?? params3.cfg.statusLineFile;
92648
- const statusLineJsonFilePath = normalizeOptionalPath(pidInfo?.status_line_json_file) ?? params3.cfg.statusLineJsonFile;
92649
- return { wsBridgeStateFilePath, statusLineFilePath, statusLineJsonFilePath };
92650
- }
92651
- function deleteFileIfExists(filePath) {
92652
- return promise2(async () => {
92653
- try {
92654
- await fs13.unlink(filePath);
92655
- return { action: "deleted", file: filePath };
92656
- } catch (e) {
92657
- if (e?.code === "ENOENT")
92658
- return { action: "skipped", file: filePath };
92659
- return { action: "failed", file: filePath, error: String(e?.message || e) };
92660
- }
92661
- });
92662
- }
92663
- function cleanupStatuslineArtifacts(paths) {
92664
- return gen2(function* () {
92665
- const statusLineFile = yield* StatusLineFile;
92666
- const wsBridgeStateFile = yield* deleteFileIfExists(paths.wsBridgeStateFilePath);
92667
- const cleared = yield* statusLineFile.write({ text: "", textFilePath: paths.statusLineFilePath, debug: false }).pipe(either3);
92668
- const statusLineFileOutcome = cleared._tag === "Right" ? cleared.right.wrote ? { action: "cleared", file: cleared.right.textFilePath } : { action: "skipped", file: cleared.right.textFilePath } : {
92669
- action: "failed",
92670
- file: paths.statusLineFilePath,
92671
- error: String(cleared.left?.message || cleared.left)
92672
- };
92673
- const statusLineJsonFile = yield* deleteFileIfExists(paths.statusLineJsonFilePath);
92674
- return { wsBridgeStateFile, statusLineFile: statusLineFileOutcome, statusLineJsonFile };
92675
- });
92676
- }
92677
-
92678
- // src/commands/ws/restart.ts
93794
+ import path28 from "node:path";
92679
93795
  function optionToUndefined3(opt) {
92680
93796
  return isSome2(opt) ? opt.value : undefined;
92681
93797
  }
93798
+ function resolveManagedStateFile2(params3) {
93799
+ const candidate = params3.candidate ? resolveUserFilePath(params3.candidate) : undefined;
93800
+ if (!candidate)
93801
+ return resolveUserFilePath(params3.defaultStateFilePath);
93802
+ if (candidate === resolveUserFilePath(params3.defaultStateFilePath))
93803
+ return candidate;
93804
+ return path28.dirname(candidate) === path28.dirname(params3.pidFilePath) ? candidate : resolveUserFilePath(params3.defaultStateFilePath);
93805
+ }
93806
+ function isWithinRoot(rootDir, targetPath) {
93807
+ const rel = path28.relative(rootDir, targetPath);
93808
+ return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
93809
+ }
93810
+ function sanitizePidInfoForArtifacts(params3) {
93811
+ const rootDir = path28.dirname(params3.pidFilePath);
93812
+ const normalizeMaybe = (value8) => {
93813
+ if (typeof value8 !== "string" || !value8.trim())
93814
+ return;
93815
+ const resolved = resolveUserFilePath(value8);
93816
+ return isWithinRoot(rootDir, resolved) ? resolved : undefined;
93817
+ };
93818
+ return {
93819
+ ...params3.pidInfo,
93820
+ ws_bridge_state_file: normalizeMaybe(params3.pidInfo.ws_bridge_state_file),
93821
+ status_line_file: normalizeMaybe(params3.pidInfo.status_line_file),
93822
+ status_line_json_file: normalizeMaybe(params3.pidInfo.status_line_json_file)
93823
+ };
93824
+ }
92682
93825
  var pidFile3 = text10("pid-file").pipe(optional6, map34(optionToUndefined3));
92683
93826
  var logFile2 = text10("log-file").pipe(optional6, map34(optionToUndefined3));
92684
93827
  var wsRestartCommand = exports_Command.make("restart", {
@@ -92694,7 +93837,11 @@ var wsRestartCommand = exports_Command.make("restart", {
92694
93837
  const pidFilePath = resolveUserFilePath(pidFile4 ?? daemonFiles.defaultPidFile());
92695
93838
  const existing = yield* daemonFiles.readPidFile(pidFilePath);
92696
93839
  let stopResult = { stopped: true, pid_file: pidFilePath };
92697
- const stateFilePath = resolveUserFilePath(existing?.state_file ?? path20.join(path20.dirname(pidFilePath), "ws.state.json"));
93840
+ const stateFilePath = resolveManagedStateFile2({
93841
+ pidFilePath,
93842
+ defaultStateFilePath: supervisorState.defaultStateFile(),
93843
+ candidate: existing?.state_file
93844
+ });
92698
93845
  if (existing) {
92699
93846
  const alive = yield* proc.isPidRunning(existing.pid);
92700
93847
  if (!alive) {
@@ -92702,6 +93849,7 @@ var wsRestartCommand = exports_Command.make("restart", {
92702
93849
  yield* supervisorState.deleteStateFile(stateFilePath);
92703
93850
  stopResult = { stopped: true, stale: true, pid: existing.pid, pid_file: pidFilePath };
92704
93851
  } else {
93852
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
92705
93853
  yield* proc.kill(existing.pid, "SIGTERM");
92706
93854
  const exited = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: WS_STOP_WAIT_DEFAULT_MS });
92707
93855
  if (!exited) {
@@ -92729,7 +93877,10 @@ var wsRestartCommand = exports_Command.make("restart", {
92729
93877
  stopResult = { stopped: true, pid: existing.pid, pid_file: pidFilePath };
92730
93878
  }
92731
93879
  }
92732
- const cleanup = yield* cleanupStatuslineArtifacts(resolveStatuslineArtifactPaths({ cfg, pidInfo: existing }));
93880
+ const cleanup = yield* cleanupStatuslineArtifacts(resolveStatuslineArtifactPaths({
93881
+ cfg,
93882
+ pidInfo: existing ? sanitizePidInfoForArtifacts({ pidFilePath, pidInfo: existing }) : undefined
93883
+ }));
92733
93884
  yield* sync3(() => refreshTmuxStatusLine());
92734
93885
  stopResult = { ...stopResult, cleanup };
92735
93886
  const startResult = yield* startWsSupervisor({ waitMs: wait, pidFile: pidFile4, logFile: logFile3 });
@@ -92766,10 +93917,10 @@ function safeJsonParse2(text15) {
92766
93917
  }
92767
93918
  }
92768
93919
  var WsBridgeServerLive = succeed10(WsBridgeServer, {
92769
- listen: ({ port: port3, path: path21, host }) => acquireRelease2(gen2(function* () {
93920
+ listen: ({ port: port3, path: path29, host }) => acquireRelease2(gen2(function* () {
92770
93921
  const events = yield* unbounded5();
92771
93922
  const sockets = new Map;
92772
- const wss = new import_websocket_server.default({ port: port3, host, path: path21 });
93923
+ const wss = new import_websocket_server.default({ port: port3, host, path: path29 });
92773
93924
  wss.on("connection", (ws, req) => {
92774
93925
  const connId = randomUUID4();
92775
93926
  const remoteAddr = safeStringHeader(req?.socket?.remoteAddress);
@@ -92803,7 +93954,7 @@ var WsBridgeServerLive = succeed10(WsBridgeServer, {
92803
93954
  });
92804
93955
  const handle = {
92805
93956
  events,
92806
- serverInfo: { port: port3, path: path21 },
93957
+ serverInfo: { port: port3, path: path29 },
92807
93958
  sendText: (connId, text15) => sync3(() => {
92808
93959
  const ws = sockets.get(connId);
92809
93960
  if (!ws)
@@ -92865,23 +94016,23 @@ var WsBridgeServerLive = succeed10(WsBridgeServer, {
92865
94016
  code: "WS_UNAVAILABLE",
92866
94017
  message: "Failed to start ws bridge server",
92867
94018
  exitCode: 1,
92868
- details: { error: String(error4?.message || error4), port: port3, path: path21, host }
94019
+ details: { error: String(error4?.message || error4), port: port3, path: path29, host }
92869
94020
  }))))
92870
94021
  });
92871
94022
 
92872
94023
  // src/services/WsBridgeStateFile.ts
92873
- import { promises as fs14 } from "node:fs";
92874
- import path21 from "node:path";
94024
+ import { promises as fs16 } from "node:fs";
94025
+ import path29 from "node:path";
92875
94026
  class WsBridgeStateFile extends Tag2("WsBridgeStateFile")() {
92876
94027
  }
92877
- async function ensureDir7(filePath) {
92878
- await fs14.mkdir(path21.dirname(filePath), { recursive: true });
94028
+ async function ensureDir9(filePath) {
94029
+ await fs16.mkdir(path29.dirname(filePath), { recursive: true });
92879
94030
  }
92880
94031
  async function writeTextAtomic2(filePath, content) {
92881
- await ensureDir7(filePath);
94032
+ await ensureDir9(filePath);
92882
94033
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
92883
- await fs14.writeFile(tmp, content, "utf8");
92884
- await fs14.rename(tmp, filePath);
94034
+ await fs16.writeFile(tmp, content, "utf8");
94035
+ await fs16.rename(tmp, filePath);
92885
94036
  }
92886
94037
  var WsBridgeStateFileLive = succeed10(WsBridgeStateFile, {
92887
94038
  write: ({ filePath, json: json4 }) => tryPromise2({
@@ -92987,7 +94138,7 @@ var TmuxLive = scoped3(Tmux, gen2(function* () {
92987
94138
  }));
92988
94139
 
92989
94140
  // src/services/WsBridgeState.ts
92990
- import { promises as fs15 } from "node:fs";
94141
+ import { promises as fs17 } from "node:fs";
92991
94142
  class WsBridgeState extends Tag2("WsBridgeState")() {
92992
94143
  }
92993
94144
  function toNonNegativeInt3(value8) {
@@ -93013,7 +94164,7 @@ var WsBridgeStateLive = succeed10(WsBridgeState, {
93013
94164
  return { connection: "off", selection: { kind: "none" } };
93014
94165
  }
93015
94166
  const raw4 = yield* tryPromise2({
93016
- try: async () => await fs15.readFile(cfg.wsStateFile.path, "utf8"),
94167
+ try: async () => await fs17.readFile(cfg.wsStateFile.path, "utf8"),
93017
94168
  catch: (e) => {
93018
94169
  if (e?.code === "ENOENT")
93019
94170
  return null;
@@ -93427,18 +94578,18 @@ function parseWsUrl(url2) {
93427
94578
  }
93428
94579
  const port3 = u.port ? Number(u.port) : u.protocol === "wss:" ? 443 : 80;
93429
94580
  const host = u.hostname && u.hostname.length > 0 ? u.hostname : "localhost";
93430
- const path22 = u.pathname && u.pathname.length > 0 ? u.pathname : "/ws";
94581
+ const path30 = u.pathname && u.pathname.length > 0 ? u.pathname : "/ws";
93431
94582
  if (!Number.isFinite(port3) || port3 <= 0) {
93432
94583
  throw new CliError({ code: "INVALID_ARGS", message: `Invalid wsUrl port: ${url2}`, exitCode: 2 });
93433
94584
  }
93434
- if (!path22.startsWith("/")) {
94585
+ if (!path30.startsWith("/")) {
93435
94586
  throw new CliError({ code: "INVALID_ARGS", message: `Invalid wsUrl path: ${url2}`, exitCode: 2 });
93436
94587
  }
93437
- return { host, port: port3, path: path22 };
94588
+ return { host, port: port3, path: path30 };
93438
94589
  }
93439
94590
  var wsServeCommand = exports_Command.make("serve", {}, () => gen2(function* () {
93440
94591
  const cfg = yield* AppConfig;
93441
- const { host, port: port3, path: path22 } = yield* try_3({
94592
+ const { host, port: port3, path: path30 } = yield* try_3({
93442
94593
  try: () => parseWsUrl(cfg.wsUrl),
93443
94594
  catch: (e) => isCliError(e) ? e : new CliError({
93444
94595
  code: "INVALID_ARGS",
@@ -93447,7 +94598,7 @@ var wsServeCommand = exports_Command.make("serve", {}, () => gen2(function* () {
93447
94598
  details: { ws_url: cfg.wsUrl, error: String(e?.message || e) }
93448
94599
  })
93449
94600
  });
93450
- yield* runWsBridgeRuntime({ host, port: port3, path: path22 });
94601
+ yield* runWsBridgeRuntime({ host, port: port3, path: path30 });
93451
94602
  }).pipe(catchAll2(writeFailure)));
93452
94603
 
93453
94604
  // src/commands/ws/start.ts
@@ -93463,14 +94614,14 @@ function parseWsUrl2(url2) {
93463
94614
  }
93464
94615
  const port3 = u.port ? Number(u.port) : u.protocol === "wss:" ? 443 : 80;
93465
94616
  const host = u.hostname && u.hostname.length > 0 ? u.hostname : "localhost";
93466
- const path22 = u.pathname && u.pathname.length > 0 ? u.pathname : "/ws";
94617
+ const path30 = u.pathname && u.pathname.length > 0 ? u.pathname : "/ws";
93467
94618
  if (!Number.isFinite(port3) || port3 <= 0) {
93468
94619
  throw new CliError({ code: "INVALID_ARGS", message: `Invalid wsUrl port: ${url2}`, exitCode: 2 });
93469
94620
  }
93470
- if (!path22.startsWith("/")) {
94621
+ if (!path30.startsWith("/")) {
93471
94622
  throw new CliError({ code: "INVALID_ARGS", message: `Invalid wsUrl path: ${url2}`, exitCode: 2 });
93472
94623
  }
93473
- return { host, port: port3, path: path22 };
94624
+ return { host, port: port3, path: path30 };
93474
94625
  }
93475
94626
  var pidFile4 = text10("pid-file").pipe(optional6, map34(optionToUndefined4));
93476
94627
  var logFile3 = text10("log-file").pipe(optional6, map34(optionToUndefined4));
@@ -93482,7 +94633,7 @@ var wsStartCommand = exports_Command.make("start", {
93482
94633
  }, ({ foreground, wait, pidFile: pidFile5, logFile: logFile4 }) => gen2(function* () {
93483
94634
  const cfg = yield* AppConfig;
93484
94635
  if (foreground) {
93485
- const { host, port: port3, path: path22 } = yield* try_3({
94636
+ const { host, port: port3, path: path30 } = yield* try_3({
93486
94637
  try: () => parseWsUrl2(cfg.wsUrl),
93487
94638
  catch: (e) => isCliError(e) ? e : new CliError({
93488
94639
  code: "INVALID_ARGS",
@@ -93491,7 +94642,7 @@ var wsStartCommand = exports_Command.make("start", {
93491
94642
  details: { ws_url: cfg.wsUrl, error: String(e?.message || e) }
93492
94643
  })
93493
94644
  });
93494
- yield* runWsBridgeRuntime({ host, port: port3, path: path22 });
94645
+ yield* runWsBridgeRuntime({ host, port: port3, path: path30 });
93495
94646
  return;
93496
94647
  }
93497
94648
  const result = yield* startWsSupervisor({ waitMs: wait, pidFile: pidFile5, logFile: logFile4 });
@@ -93506,110 +94657,7 @@ var wsStartCommand = exports_Command.make("start", {
93506
94657
  }).pipe(catchAll2(writeFailure)));
93507
94658
 
93508
94659
  // src/commands/ws/status.ts
93509
- import path24 from "node:path";
93510
-
93511
- // src/lib/pluginBuildInfo.ts
93512
- import { readFileSync as readFileSync3 } from "node:fs";
93513
- import path23 from "node:path";
93514
-
93515
- // src/lib/pluginArtifacts.ts
93516
- import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
93517
- import path22 from "node:path";
93518
- import { fileURLToPath as fileURLToPath3 } from "node:url";
93519
- function currentDir(moduleUrl) {
93520
- return path22.dirname(fileURLToPath3(moduleUrl));
93521
- }
93522
- function isDirectory(targetPath) {
93523
- try {
93524
- return statSync2(targetPath).isDirectory();
93525
- } catch {
93526
- return false;
93527
- }
93528
- }
93529
- function isFile(targetPath) {
93530
- try {
93531
- return statSync2(targetPath).isFile();
93532
- } catch {
93533
- return false;
93534
- }
93535
- }
93536
- function distCandidates(moduleUrl) {
93537
- const dir2 = currentDir(moduleUrl);
93538
- return [
93539
- path22.resolve(dir2, "../../plugin-artifacts/dist"),
93540
- path22.resolve(dir2, "../plugin-artifacts/dist"),
93541
- path22.resolve(dir2, "../../../plugin/dist"),
93542
- path22.resolve(dir2, "../../plugin/dist")
93543
- ];
93544
- }
93545
- function validateDistPath(targetPath) {
93546
- return isDirectory(targetPath) && existsSync3(path22.join(targetPath, "manifest.json"));
93547
- }
93548
- function hasBuildInfo(targetPath) {
93549
- return isFile(path22.join(targetPath, "build-info.json"));
93550
- }
93551
- function resolvePluginDistPath(moduleUrl = import.meta.url) {
93552
- for (const candidate of distCandidates(moduleUrl)) {
93553
- if (validateDistPath(candidate) && hasBuildInfo(candidate))
93554
- return candidate;
93555
- }
93556
- for (const candidate of distCandidates(moduleUrl)) {
93557
- if (validateDistPath(candidate))
93558
- return candidate;
93559
- }
93560
- throw new CliError({
93561
- code: "DEPENDENCY_MISSING",
93562
- message: "Plugin build artifacts are unavailable",
93563
- exitCode: 1,
93564
- details: { candidates: distCandidates(moduleUrl) },
93565
- hint: [
93566
- "Run npm run build --workspace @remnote/plugin in the repository checkout",
93567
- "Or install a packaged agent-remnote release that includes plugin artifacts"
93568
- ]
93569
- });
93570
- }
93571
-
93572
- // src/lib/pluginBuildInfo.ts
93573
- function readPluginDistBuildInfo(distPath) {
93574
- const normalized = typeof distPath === "string" ? distPath.trim() : "";
93575
- if (!normalized)
93576
- return null;
93577
- const target2 = path23.join(normalized, "build-info.json");
93578
- try {
93579
- const raw4 = readFileSync3(target2, "utf8");
93580
- const parsed = JSON.parse(raw4);
93581
- if (typeof parsed?.name === "string" && typeof parsed?.version === "string" && typeof parsed?.build_id === "string" && typeof parsed?.built_at === "number" && typeof parsed?.source_stamp === "number") {
93582
- return {
93583
- name: parsed.name,
93584
- version: parsed.version,
93585
- build_id: parsed.build_id,
93586
- built_at: parsed.built_at,
93587
- source_stamp: parsed.source_stamp,
93588
- mode: parsed.mode === "src" || parsed.mode === "dist" || parsed.mode === "unknown" ? parsed.mode : "dist"
93589
- };
93590
- }
93591
- } catch {}
93592
- return null;
93593
- }
93594
- function currentExpectedPluginBuildInfo() {
93595
- try {
93596
- const dist = resolvePluginDistPath();
93597
- return readPluginDistBuildInfo(dist);
93598
- } catch {
93599
- return null;
93600
- }
93601
- }
93602
- function pluginBuildWarnings(params3) {
93603
- if (!params3.expected || !params3.live)
93604
- return [];
93605
- if (params3.expected.build_id === params3.live.build_id)
93606
- return [];
93607
- return [
93608
- `plugin build mismatch: expected=${params3.expected.build_id} live=${params3.live.build_id}`
93609
- ];
93610
- }
93611
-
93612
- // src/commands/ws/status.ts
94660
+ import path30 from "node:path";
93613
94661
  function optionToUndefined5(opt) {
93614
94662
  return isSome2(opt) ? opt.value : undefined;
93615
94663
  }
@@ -93628,14 +94676,14 @@ var wsStatusCommand = exports_Command.make("status", { pidFile: pidFile5 }, ({ p
93628
94676
  if (!alive) {
93629
94677
  const stalePidInfo = pidInfo;
93630
94678
  yield* daemonFiles.deletePidFile(pidFilePath);
93631
- const staleStateFilePath = resolveUserFilePath(pidInfo.state_file ?? path24.join(path24.dirname(pidFilePath), "ws.state.json"));
94679
+ const staleStateFilePath = resolveUserFilePath(pidInfo.state_file ?? path30.join(path30.dirname(pidFilePath), "ws.state.json"));
93632
94680
  yield* supervisorState.deleteStateFile(staleStateFilePath);
93633
94681
  selfHealCleanup = yield* cleanupStatuslineArtifacts(resolveStatuslineArtifactPaths({ cfg, pidInfo: stalePidInfo }));
93634
94682
  yield* sync3(() => refreshTmuxStatusLine());
93635
94683
  pidInfo = undefined;
93636
94684
  }
93637
94685
  }
93638
- const stateFilePath = resolveUserFilePath(pidInfo?.state_file ?? path24.join(path24.dirname(pidFilePath), "ws.state.json"));
94686
+ const stateFilePath = resolveUserFilePath(pidInfo?.state_file ?? path30.join(path30.dirname(pidFilePath), "ws.state.json"));
93639
94687
  const state = yield* supervisorState.readStateFile(stateFilePath);
93640
94688
  const mode = pidInfo ? pidInfo.mode ?? "legacy" : "supervisor";
93641
94689
  const supervisorPid = pidInfo?.pid;
@@ -93818,10 +94866,37 @@ var wsStatusLineCommand = exports_Command.make("status-line", { stateFile, stale
93818
94866
  }).pipe(catchAll2(writeFailure)));
93819
94867
 
93820
94868
  // src/commands/ws/stop.ts
93821
- import path25 from "node:path";
94869
+ import path31 from "node:path";
93822
94870
  function optionToUndefined7(opt) {
93823
94871
  return isSome2(opt) ? opt.value : undefined;
93824
94872
  }
94873
+ function resolveManagedStateFile3(params3) {
94874
+ const candidate = params3.candidate ? resolveUserFilePath(params3.candidate) : undefined;
94875
+ if (!candidate)
94876
+ return resolveUserFilePath(params3.defaultStateFilePath);
94877
+ if (candidate === resolveUserFilePath(params3.defaultStateFilePath))
94878
+ return candidate;
94879
+ return path31.dirname(candidate) === path31.dirname(params3.pidFilePath) ? candidate : resolveUserFilePath(params3.defaultStateFilePath);
94880
+ }
94881
+ function isWithinRoot2(rootDir, targetPath) {
94882
+ const rel = path31.relative(rootDir, targetPath);
94883
+ return rel === "" || !rel.startsWith("..") && !path31.isAbsolute(rel);
94884
+ }
94885
+ function sanitizePidInfoForArtifacts2(params3) {
94886
+ const rootDir = path31.dirname(params3.pidFilePath);
94887
+ const normalizeMaybe = (value8) => {
94888
+ if (typeof value8 !== "string" || !value8.trim())
94889
+ return;
94890
+ const resolved = resolveUserFilePath(value8);
94891
+ return isWithinRoot2(rootDir, resolved) ? resolved : undefined;
94892
+ };
94893
+ return {
94894
+ ...params3.pidInfo,
94895
+ ws_bridge_state_file: normalizeMaybe(params3.pidInfo.ws_bridge_state_file),
94896
+ status_line_file: normalizeMaybe(params3.pidInfo.status_line_file),
94897
+ status_line_json_file: normalizeMaybe(params3.pidInfo.status_line_json_file)
94898
+ };
94899
+ }
93825
94900
  var pidFile6 = text10("pid-file").pipe(optional6, map34(optionToUndefined7));
93826
94901
  var wsStopCommand = exports_Command.make("stop", { force: boolean8("force"), pidFile: pidFile6 }, ({ force, pidFile: pidFile7 }) => gen2(function* () {
93827
94902
  const cfg = yield* AppConfig;
@@ -93831,7 +94906,10 @@ var wsStopCommand = exports_Command.make("stop", { force: boolean8("force"), pid
93831
94906
  const pidFilePath = resolveUserFilePath(pidFile7 ?? daemonFiles.defaultPidFile());
93832
94907
  const existing = yield* daemonFiles.readPidFile(pidFilePath);
93833
94908
  const cleanupDisplayArtifacts = (pidInfo) => gen2(function* () {
93834
- const paths = resolveStatuslineArtifactPaths({ cfg, pidInfo });
94909
+ const paths = resolveStatuslineArtifactPaths({
94910
+ cfg,
94911
+ pidInfo: pidInfo ? sanitizePidInfoForArtifacts2({ pidFilePath, pidInfo }) : undefined
94912
+ });
93835
94913
  const cleanup2 = yield* cleanupStatuslineArtifacts(paths);
93836
94914
  yield* sync3(() => refreshTmuxStatusLine());
93837
94915
  return cleanup2;
@@ -93846,7 +94924,11 @@ var wsStopCommand = exports_Command.make("stop", { force: boolean8("force"), pid
93846
94924
  });
93847
94925
  return;
93848
94926
  }
93849
- const stateFilePath = resolveUserFilePath(existing.state_file ?? path25.join(path25.dirname(pidFilePath), "ws.state.json"));
94927
+ const stateFilePath = resolveManagedStateFile3({
94928
+ pidFilePath,
94929
+ defaultStateFilePath: supervisorState.defaultStateFile(),
94930
+ candidate: existing.state_file
94931
+ });
93850
94932
  const alive = yield* proc.isPidRunning(existing.pid);
93851
94933
  if (!alive) {
93852
94934
  yield* daemonFiles.deletePidFile(pidFilePath);
@@ -93862,6 +94944,7 @@ var wsStopCommand = exports_Command.make("stop", { force: boolean8("force"), pid
93862
94944
  });
93863
94945
  return;
93864
94946
  }
94947
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
93865
94948
  yield* proc.kill(existing.pid, "SIGTERM");
93866
94949
  const exited = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: WS_STOP_WAIT_DEFAULT_MS });
93867
94950
  if (!exited) {
@@ -94040,8 +95123,8 @@ var ChildProcessLive = succeed10(ChildProcess2, {
94040
95123
  });
94041
95124
 
94042
95125
  // src/services/LogWriter.ts
94043
- import { promises as fs16 } from "node:fs";
94044
- import path26 from "node:path";
95126
+ import { promises as fs18 } from "node:fs";
95127
+ import path32 from "node:path";
94045
95128
  class LogWriterFactory extends Tag2("LogWriterFactory")() {
94046
95129
  }
94047
95130
  function sanitizePositiveInt(value8, fallback) {
@@ -94052,12 +95135,12 @@ function sanitizeMaxBytes(value8, fallback) {
94052
95135
  return null;
94053
95136
  return Number.isFinite(value8) && value8 > 0 ? Math.floor(value8) : fallback;
94054
95137
  }
94055
- async function ensureDir8(filePath) {
94056
- await fs16.mkdir(path26.dirname(filePath), { recursive: true });
95138
+ async function ensureDir10(filePath) {
95139
+ await fs18.mkdir(path32.dirname(filePath), { recursive: true });
94057
95140
  }
94058
95141
  async function safeStat(filePath) {
94059
95142
  try {
94060
- const s = await fs16.stat(filePath);
95143
+ const s = await fs18.stat(filePath);
94061
95144
  return { size: s.size };
94062
95145
  } catch {
94063
95146
  return;
@@ -94066,25 +95149,25 @@ async function safeStat(filePath) {
94066
95149
  async function rotateFiles(filePath, keep) {
94067
95150
  if (keep <= 0)
94068
95151
  return;
94069
- const dir2 = path26.dirname(filePath);
94070
- const base = path26.basename(filePath);
95152
+ const dir2 = path32.dirname(filePath);
95153
+ const base = path32.basename(filePath);
94071
95154
  let entries2 = [];
94072
95155
  try {
94073
- entries2 = await fs16.readdir(dir2);
95156
+ entries2 = await fs18.readdir(dir2);
94074
95157
  } catch {
94075
95158
  return;
94076
95159
  }
94077
- const rotated = entries2.filter((name) => name.startsWith(`${base}.`)).map((name) => path26.join(dir2, name)).sort().reverse();
95160
+ const rotated = entries2.filter((name) => name.startsWith(`${base}.`)).map((name) => path32.join(dir2, name)).sort().reverse();
94078
95161
  for (const p3 of rotated.slice(keep)) {
94079
95162
  try {
94080
- await fs16.unlink(p3);
95163
+ await fs18.unlink(p3);
94081
95164
  } catch {}
94082
95165
  }
94083
95166
  }
94084
95167
  async function createLogWriter(filePath, options6) {
94085
95168
  const maxBytes = sanitizeMaxBytes(options6.maxBytes, 20 * 1024 * 1024);
94086
95169
  const keep = sanitizePositiveInt(options6.keep, 5);
94087
- await ensureDir8(filePath);
95170
+ await ensureDir10(filePath);
94088
95171
  let currentSize = (await safeStat(filePath))?.size ?? 0;
94089
95172
  let handle;
94090
95173
  let closing = false;
@@ -94093,7 +95176,7 @@ async function createLogWriter(filePath, options6) {
94093
95176
  async function openIfNeeded() {
94094
95177
  if (handle)
94095
95178
  return handle;
94096
- handle = await fs16.open(filePath, "a");
95179
+ handle = await fs18.open(filePath, "a");
94097
95180
  return handle;
94098
95181
  }
94099
95182
  async function rotateIfNeeded(nextBytes) {
@@ -94107,7 +95190,7 @@ async function createLogWriter(filePath, options6) {
94107
95190
  handle = undefined;
94108
95191
  const rotatedPath = `${filePath}.${Date.now()}`;
94109
95192
  try {
94110
- await fs16.rename(filePath, rotatedPath);
95193
+ await fs18.rename(filePath, rotatedPath);
94111
95194
  } catch {}
94112
95195
  currentSize = 0;
94113
95196
  await rotateFiles(filePath, keep);
@@ -94920,7 +96003,7 @@ var HostApiClientLive = effect(HostApiClient, gen2(function* () {
94920
96003
  }));
94921
96004
 
94922
96005
  // src/lib/workspaceResolver.ts
94923
- import fs17 from "node:fs";
96006
+ import fs19 from "node:fs";
94924
96007
 
94925
96008
  // src/lib/business-semantics/uiContextResolution.ts
94926
96009
  function loadBridgeUiContextSnapshot(params3) {
@@ -95226,7 +96309,7 @@ function pathExists(filePath) {
95226
96309
  if (!filePath)
95227
96310
  return false;
95228
96311
  try {
95229
- return fs17.statSync(filePath).isFile();
96312
+ return fs19.statSync(filePath).isFile();
95230
96313
  } catch {
95231
96314
  return false;
95232
96315
  }
@@ -96727,7 +97810,7 @@ function isSingleRootOutlineMarkdown(input) {
96727
97810
  }
96728
97811
 
96729
97812
  // src/services/Payload.ts
96730
- import { promises as fs18 } from "node:fs";
97813
+ import { promises as fs20 } from "node:fs";
96731
97814
  class Payload extends Tag2("Payload")() {
96732
97815
  }
96733
97816
  var MAX_PAYLOAD_BYTES = 5 * 1024 * 1024;
@@ -96768,7 +97851,7 @@ async function readPayloadSpec(spec) {
96768
97851
  if (!filePath) {
96769
97852
  throw new CliError({ code: "INVALID_ARGS", message: "Payload file path cannot be empty", exitCode: 2 });
96770
97853
  }
96771
- return await fs18.readFile(filePath, "utf8");
97854
+ return await fs20.readFile(filePath, "utf8");
96772
97855
  }
96773
97856
  return spec;
96774
97857
  }
@@ -97078,8 +98161,8 @@ function resolveRefsInPayload(params3) {
97078
98161
  const out = structuredClone(params3.payload);
97079
98162
  const resolvedRefCache = new Map;
97080
98163
  const idPaths = idFieldPathsForOpType(params3.opType);
97081
- for (const path27 of idPaths) {
97082
- const tokens = parsePathTokens(path27);
98164
+ for (const path33 of idPaths) {
98165
+ const tokens = parsePathTokens(path33);
97083
98166
  if (tokens.length === 0)
97084
98167
  continue;
97085
98168
  const leaves = collectLeafValues(out, tokens);
@@ -97698,7 +98781,7 @@ function validateOptionMutationOps(params3) {
97698
98781
 
97699
98782
  // src/services/RefResolver.ts
97700
98783
  import { homedir } from "node:os";
97701
- import path27 from "node:path";
98784
+ import path33 from "node:path";
97702
98785
  class RefResolver extends Tag2("RefResolver")() {
97703
98786
  }
97704
98787
  function stripQuotes(s) {
@@ -97778,8 +98861,8 @@ function normalizeDbPathInput(dbPath) {
97778
98861
  const raw4 = typeof dbPath === "string" ? dbPath.trim() : "";
97779
98862
  if (!raw4)
97780
98863
  return;
97781
- const expanded = raw4 === "~" ? homedir() : raw4.startsWith("~/") || raw4.startsWith("~\\") ? path27.join(homedir(), raw4.slice(2)) : raw4;
97782
- return path27.resolve(path27.normalize(expanded));
98864
+ const expanded = raw4 === "~" ? homedir() : raw4.startsWith("~/") || raw4.startsWith("~\\") ? path33.join(homedir(), raw4.slice(2)) : raw4;
98865
+ return path33.resolve(path33.normalize(expanded));
97783
98866
  }
97784
98867
  var RefResolverLive = succeed10(RefResolver, {
97785
98868
  resolve: (ref, options6) => gen2(function* () {
@@ -100378,152 +101461,6 @@ var applyCommand = exports_Command.make("apply", {
100378
101461
  });
100379
101462
  }).pipe(catchAll2(writeFailure)));
100380
101463
 
100381
- // src/services/ApiDaemonFiles.ts
100382
- import { promises as fs19 } from "node:fs";
100383
- import path28 from "node:path";
100384
- class ApiDaemonFiles extends Tag2("ApiDaemonFiles")() {
100385
- }
100386
- function ensureDir9(p3) {
100387
- return fs19.mkdir(path28.dirname(p3), { recursive: true }).then(() => {
100388
- return;
100389
- });
100390
- }
100391
- function defaultPidFile2() {
100392
- const envPidFile = process.env.REMNOTE_API_PID_FILE;
100393
- if (typeof envPidFile === "string" && envPidFile.trim())
100394
- return resolveUserFilePath(envPidFile);
100395
- return path28.join(homeDir(), ".agent-remnote", "api.pid");
100396
- }
100397
- function defaultLogFile2() {
100398
- const envLogFile = process.env.REMNOTE_API_LOG_FILE;
100399
- if (typeof envLogFile === "string" && envLogFile.trim())
100400
- return resolveUserFilePath(envLogFile);
100401
- return path28.join(homeDir(), ".agent-remnote", "api.log");
100402
- }
100403
- function defaultStateFile2() {
100404
- const envStateFile = process.env.REMNOTE_API_STATE_FILE;
100405
- if (typeof envStateFile === "string" && envStateFile.trim())
100406
- return resolveUserFilePath(envStateFile);
100407
- return path28.join(homeDir(), ".agent-remnote", "api.state.json");
100408
- }
100409
- function parseJson2(raw4, filePath) {
100410
- try {
100411
- return JSON.parse(raw4);
100412
- } catch (error4) {
100413
- throw new CliError({
100414
- code: "INTERNAL",
100415
- message: `Failed to parse JSON file: ${filePath}`,
100416
- exitCode: 1,
100417
- details: { file_path: filePath, error: String(error4?.message || error4) }
100418
- });
100419
- }
100420
- }
100421
- var ApiDaemonFilesLive = succeed10(ApiDaemonFiles, {
100422
- defaultPidFile: defaultPidFile2,
100423
- defaultLogFile: defaultLogFile2,
100424
- defaultStateFile: defaultStateFile2,
100425
- readPidFile: (pidFilePath) => tryPromise2({
100426
- try: async () => {
100427
- const resolved = resolveUserFilePath(pidFilePath);
100428
- const raw4 = await fs19.readFile(resolved, "utf8");
100429
- return parseJson2(raw4, resolved);
100430
- },
100431
- catch: (error4) => {
100432
- const code2 = error4?.code;
100433
- if (code2 === "ENOENT")
100434
- return;
100435
- if (isCliError(error4))
100436
- return error4;
100437
- return new CliError({
100438
- code: "INTERNAL",
100439
- message: "Failed to read api pid file",
100440
- exitCode: 1,
100441
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
100442
- });
100443
- }
100444
- }).pipe(catchAll2((error4) => {
100445
- if (error4 === undefined)
100446
- return succeed8(undefined);
100447
- return fail8(error4);
100448
- })),
100449
- writePidFile: (pidFilePath, value8) => tryPromise2({
100450
- try: async () => {
100451
- const resolved = resolveUserFilePath(pidFilePath);
100452
- await ensureDir9(resolved);
100453
- await fs19.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
100454
- `, "utf8");
100455
- },
100456
- catch: (error4) => new CliError({
100457
- code: "INTERNAL",
100458
- message: "Failed to write api pid file",
100459
- exitCode: 1,
100460
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
100461
- })
100462
- }),
100463
- deletePidFile: (pidFilePath) => tryPromise2({
100464
- try: async () => {
100465
- const resolved = resolveUserFilePath(pidFilePath);
100466
- await fs19.rm(resolved, { force: true });
100467
- },
100468
- catch: (error4) => new CliError({
100469
- code: "INTERNAL",
100470
- message: "Failed to delete api pid file",
100471
- exitCode: 1,
100472
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
100473
- })
100474
- }),
100475
- readStateFile: (stateFilePath) => tryPromise2({
100476
- try: async () => {
100477
- const resolved = resolveUserFilePath(stateFilePath);
100478
- const raw4 = await fs19.readFile(resolved, "utf8");
100479
- return parseJson2(raw4, resolved);
100480
- },
100481
- catch: (error4) => {
100482
- const code2 = error4?.code;
100483
- if (code2 === "ENOENT")
100484
- return;
100485
- if (isCliError(error4))
100486
- return error4;
100487
- return new CliError({
100488
- code: "INTERNAL",
100489
- message: "Failed to read api state file",
100490
- exitCode: 1,
100491
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
100492
- });
100493
- }
100494
- }).pipe(catchAll2((error4) => {
100495
- if (error4 === undefined)
100496
- return succeed8(undefined);
100497
- return fail8(error4);
100498
- })),
100499
- writeStateFile: (stateFilePath, value8) => tryPromise2({
100500
- try: async () => {
100501
- const resolved = resolveUserFilePath(stateFilePath);
100502
- await ensureDir9(resolved);
100503
- await fs19.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
100504
- `, "utf8");
100505
- },
100506
- catch: (error4) => new CliError({
100507
- code: "INTERNAL",
100508
- message: "Failed to write api state file",
100509
- exitCode: 1,
100510
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
100511
- })
100512
- }),
100513
- deleteStateFile: (stateFilePath) => tryPromise2({
100514
- try: async () => {
100515
- const resolved = resolveUserFilePath(stateFilePath);
100516
- await fs19.rm(resolved, { force: true });
100517
- },
100518
- catch: (error4) => new CliError({
100519
- code: "INTERNAL",
100520
- message: "Failed to delete api state file",
100521
- exitCode: 1,
100522
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
100523
- })
100524
- })
100525
- });
100526
-
100527
101464
  // src/commands/api/_shared.ts
100528
101465
  var API_HEALTH_TIMEOUT_MS = 2000;
100529
101466
  var API_START_WAIT_DEFAULT_MS = 15000;
@@ -100609,6 +101546,7 @@ function startApiDaemon(params3) {
100609
101546
  if (!alive) {
100610
101547
  yield* apiFiles.deletePidFile(pidFilePath);
100611
101548
  } else {
101549
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
100612
101550
  return {
100613
101551
  started: false,
100614
101552
  pid: existing.pid,
@@ -100686,6 +101624,7 @@ function ensureApiDaemon(params3) {
100686
101624
  if (existing) {
100687
101625
  const alive = yield* proc.isPidRunning(existing.pid);
100688
101626
  if (alive) {
101627
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
100689
101628
  return {
100690
101629
  started: false,
100691
101630
  pid: existing.pid,
@@ -100817,20 +101756,43 @@ var apiRestartCommand = exports_Command.make("restart", {
100817
101756
  const proc = yield* Process;
100818
101757
  const pidFilePath = resolveUserFilePath(pidFile11 ?? apiFiles.defaultPidFile());
100819
101758
  const existing = yield* apiFiles.readPidFile(pidFilePath);
101759
+ const stateFilePath = resolveManagedStateFile({
101760
+ pidFilePath,
101761
+ defaultStateFilePath: apiFiles.defaultStateFile(),
101762
+ explicitStateFilePath: stateFile5,
101763
+ candidate: existing?.state_file
101764
+ });
100820
101765
  let stoppedPid;
100821
101766
  if (existing) {
100822
101767
  const alive = yield* proc.isPidRunning(existing.pid);
100823
101768
  if (alive) {
101769
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
100824
101770
  yield* proc.kill(existing.pid, "SIGTERM");
100825
101771
  const exited = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: API_STOP_WAIT_DEFAULT_MS });
100826
- if (!exited && force) {
101772
+ if (!exited) {
101773
+ if (!force) {
101774
+ return yield* fail8(new CliError({
101775
+ code: "INTERNAL",
101776
+ message: `Host API did not exit within ${API_STOP_WAIT_DEFAULT_MS}ms; use --force`,
101777
+ exitCode: 1,
101778
+ details: { pid: existing.pid, pid_file: pidFilePath }
101779
+ }));
101780
+ }
100827
101781
  yield* proc.kill(existing.pid, "SIGKILL");
100828
- yield* proc.waitForExit({ pid: existing.pid, timeoutMs: API_STOP_WAIT_DEFAULT_MS });
101782
+ const killed = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: API_STOP_WAIT_DEFAULT_MS });
101783
+ if (!killed) {
101784
+ return yield* fail8(new CliError({
101785
+ code: "INTERNAL",
101786
+ message: "Force stop failed (process is still alive)",
101787
+ exitCode: 1,
101788
+ details: { pid: existing.pid, pid_file: pidFilePath }
101789
+ }));
101790
+ }
100829
101791
  }
100830
101792
  stoppedPid = existing.pid;
100831
101793
  }
100832
101794
  yield* apiFiles.deletePidFile(pidFilePath).pipe(catchAll2(() => _void));
100833
- yield* apiFiles.deleteStateFile(existing.state_file ?? resolveUserFilePath(stateFile5 ?? apiFiles.defaultStateFile())).pipe(catchAll2(() => _void));
101795
+ yield* apiFiles.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
100834
101796
  }
100835
101797
  const started = yield* startApiDaemon({ host: host3, port: port5, waitMs: wait3, pidFile: pidFile11, logFile: logFile7, stateFile: stateFile5 });
100836
101798
  yield* writeSuccess({
@@ -101647,8 +102609,13 @@ var apiStopCommand = exports_Command.make("stop", { force: boolean8("force"), pi
101647
102609
  const apiFiles = yield* ApiDaemonFiles;
101648
102610
  const proc = yield* Process;
101649
102611
  const pidFilePath = resolveUserFilePath(pidFile14 ?? apiFiles.defaultPidFile());
101650
- const stateFilePath = resolveUserFilePath(stateFile9 ?? apiFiles.defaultStateFile());
101651
102612
  const existing = yield* apiFiles.readPidFile(pidFilePath);
102613
+ const stateFilePath = resolveManagedStateFile({
102614
+ pidFilePath,
102615
+ defaultStateFilePath: apiFiles.defaultStateFile(),
102616
+ explicitStateFilePath: stateFile9,
102617
+ candidate: existing?.state_file
102618
+ });
101652
102619
  if (!existing) {
101653
102620
  yield* apiFiles.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
101654
102621
  yield* writeSuccess({
@@ -101662,7 +102629,7 @@ var apiStopCommand = exports_Command.make("stop", { force: boolean8("force"), pi
101662
102629
  const alive = yield* proc.isPidRunning(existing.pid);
101663
102630
  if (!alive) {
101664
102631
  yield* apiFiles.deletePidFile(pidFilePath);
101665
- yield* apiFiles.deleteStateFile(existing.state_file ?? stateFilePath).pipe(catchAll2(() => _void));
102632
+ yield* apiFiles.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
101666
102633
  yield* writeSuccess({
101667
102634
  data: { stopped: true, stale: true, pid: existing.pid, pid_file: pidFilePath },
101668
102635
  md: `- stopped: true
@@ -101673,6 +102640,7 @@ var apiStopCommand = exports_Command.make("stop", { force: boolean8("force"), pi
101673
102640
  });
101674
102641
  return;
101675
102642
  }
102643
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
101676
102644
  yield* proc.kill(existing.pid, "SIGTERM");
101677
102645
  const exited = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: API_STOP_WAIT_DEFAULT_MS });
101678
102646
  if (!exited) {
@@ -101696,7 +102664,7 @@ var apiStopCommand = exports_Command.make("stop", { force: boolean8("force"), pi
101696
102664
  }
101697
102665
  }
101698
102666
  yield* apiFiles.deletePidFile(pidFilePath);
101699
- yield* apiFiles.deleteStateFile(existing.state_file ?? stateFilePath).pipe(catchAll2(() => _void));
102667
+ yield* apiFiles.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
101700
102668
  yield* writeSuccess({
101701
102669
  data: { stopped: true, pid: existing.pid, pid_file: pidFilePath },
101702
102670
  md: `- stopped: true
@@ -101753,13 +102721,13 @@ var readSelectionCurrentCommand = exports_Command.make("current", { stateFile: s
101753
102721
  }).pipe(catchAll2(writeFailure)));
101754
102722
 
101755
102723
  // src/commands/read/selection/outline.ts
101756
- import path29 from "node:path";
102724
+ import path34 from "node:path";
101757
102725
  function optionToUndefined25(opt) {
101758
102726
  return isSome2(opt) ? opt.value : undefined;
101759
102727
  }
101760
102728
  function normalizeOptionalStateFile(stateFile10) {
101761
102729
  const trimmed2 = typeof stateFile10 === "string" ? stateFile10.trim() : "";
101762
- return trimmed2 ? path29.resolve(resolveUserFilePath(trimmed2)) : undefined;
102730
+ return trimmed2 ? path34.resolve(resolveUserFilePath(trimmed2)) : undefined;
101763
102731
  }
101764
102732
  var stateFile10 = text10("state-file").pipe(optional6, map34(optionToUndefined25));
101765
102733
  var staleMs3 = integer7("stale-ms").pipe(optional6, map34(optionToUndefined25));
@@ -102103,152 +103071,6 @@ function waitForPluginServerHealth(baseUrl, waitMs, timeoutMs3) {
102103
103071
  });
102104
103072
  }
102105
103073
 
102106
- // src/services/PluginServerFiles.ts
102107
- import { promises as fs20 } from "node:fs";
102108
- import path30 from "node:path";
102109
- class PluginServerFiles extends Tag2("PluginServerFiles")() {
102110
- }
102111
- function ensureDir10(p3) {
102112
- return fs20.mkdir(path30.dirname(p3), { recursive: true }).then(() => {
102113
- return;
102114
- });
102115
- }
102116
- function defaultPidFile3() {
102117
- const envPidFile = process.env.REMNOTE_PLUGIN_SERVER_PID_FILE;
102118
- if (typeof envPidFile === "string" && envPidFile.trim())
102119
- return resolveUserFilePath(envPidFile);
102120
- return path30.join(homeDir(), ".agent-remnote", "plugin-server.pid");
102121
- }
102122
- function defaultLogFile3() {
102123
- const envLogFile = process.env.REMNOTE_PLUGIN_SERVER_LOG_FILE;
102124
- if (typeof envLogFile === "string" && envLogFile.trim())
102125
- return resolveUserFilePath(envLogFile);
102126
- return path30.join(homeDir(), ".agent-remnote", "plugin-server.log");
102127
- }
102128
- function defaultStateFile3() {
102129
- const envStateFile = process.env.REMNOTE_PLUGIN_SERVER_STATE_FILE;
102130
- if (typeof envStateFile === "string" && envStateFile.trim())
102131
- return resolveUserFilePath(envStateFile);
102132
- return path30.join(homeDir(), ".agent-remnote", "plugin-server.state.json");
102133
- }
102134
- function parseJson3(raw4, filePath) {
102135
- try {
102136
- return JSON.parse(raw4);
102137
- } catch (error4) {
102138
- throw new CliError({
102139
- code: "INTERNAL",
102140
- message: `Failed to parse JSON file: ${filePath}`,
102141
- exitCode: 1,
102142
- details: { file_path: filePath, error: String(error4?.message || error4) }
102143
- });
102144
- }
102145
- }
102146
- var PluginServerFilesLive = succeed10(PluginServerFiles, {
102147
- defaultPidFile: defaultPidFile3,
102148
- defaultLogFile: defaultLogFile3,
102149
- defaultStateFile: defaultStateFile3,
102150
- readPidFile: (pidFilePath) => tryPromise2({
102151
- try: async () => {
102152
- const resolved = resolveUserFilePath(pidFilePath);
102153
- const raw4 = await fs20.readFile(resolved, "utf8");
102154
- return parseJson3(raw4, resolved);
102155
- },
102156
- catch: (error4) => {
102157
- const code2 = error4?.code;
102158
- if (code2 === "ENOENT")
102159
- return;
102160
- if (isCliError(error4))
102161
- return error4;
102162
- return new CliError({
102163
- code: "INTERNAL",
102164
- message: "Failed to read plugin server pid file",
102165
- exitCode: 1,
102166
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
102167
- });
102168
- }
102169
- }).pipe(catchAll2((error4) => {
102170
- if (error4 === undefined)
102171
- return succeed8(undefined);
102172
- return fail8(error4);
102173
- })),
102174
- writePidFile: (pidFilePath, value8) => tryPromise2({
102175
- try: async () => {
102176
- const resolved = resolveUserFilePath(pidFilePath);
102177
- await ensureDir10(resolved);
102178
- await fs20.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
102179
- `, "utf8");
102180
- },
102181
- catch: (error4) => new CliError({
102182
- code: "INTERNAL",
102183
- message: "Failed to write plugin server pid file",
102184
- exitCode: 1,
102185
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
102186
- })
102187
- }),
102188
- deletePidFile: (pidFilePath) => tryPromise2({
102189
- try: async () => {
102190
- const resolved = resolveUserFilePath(pidFilePath);
102191
- await fs20.rm(resolved, { force: true });
102192
- },
102193
- catch: (error4) => new CliError({
102194
- code: "INTERNAL",
102195
- message: "Failed to delete plugin server pid file",
102196
- exitCode: 1,
102197
- details: { file_path: pidFilePath, error: String(error4?.message || error4) }
102198
- })
102199
- }),
102200
- readStateFile: (stateFilePath) => tryPromise2({
102201
- try: async () => {
102202
- const resolved = resolveUserFilePath(stateFilePath);
102203
- const raw4 = await fs20.readFile(resolved, "utf8");
102204
- return parseJson3(raw4, resolved);
102205
- },
102206
- catch: (error4) => {
102207
- const code2 = error4?.code;
102208
- if (code2 === "ENOENT")
102209
- return;
102210
- if (isCliError(error4))
102211
- return error4;
102212
- return new CliError({
102213
- code: "INTERNAL",
102214
- message: "Failed to read plugin server state file",
102215
- exitCode: 1,
102216
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
102217
- });
102218
- }
102219
- }).pipe(catchAll2((error4) => {
102220
- if (error4 === undefined)
102221
- return succeed8(undefined);
102222
- return fail8(error4);
102223
- })),
102224
- writeStateFile: (stateFilePath, value8) => tryPromise2({
102225
- try: async () => {
102226
- const resolved = resolveUserFilePath(stateFilePath);
102227
- await ensureDir10(resolved);
102228
- await fs20.writeFile(resolved, `${JSON.stringify(value8, null, 2)}
102229
- `, "utf8");
102230
- },
102231
- catch: (error4) => new CliError({
102232
- code: "INTERNAL",
102233
- message: "Failed to write plugin server state file",
102234
- exitCode: 1,
102235
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
102236
- })
102237
- }),
102238
- deleteStateFile: (stateFilePath) => tryPromise2({
102239
- try: async () => {
102240
- const resolved = resolveUserFilePath(stateFilePath);
102241
- await fs20.rm(resolved, { force: true });
102242
- },
102243
- catch: (error4) => new CliError({
102244
- code: "INTERNAL",
102245
- message: "Failed to delete plugin server state file",
102246
- exitCode: 1,
102247
- details: { file_path: stateFilePath, error: String(error4?.message || error4) }
102248
- })
102249
- })
102250
- });
102251
-
102252
103074
  // src/commands/plugin/_shared.ts
102253
103075
  var PLUGIN_SERVER_HEALTH_TIMEOUT_MS = 2000;
102254
103076
  var PLUGIN_SERVER_START_WAIT_DEFAULT_MS = 15000;
@@ -102302,6 +103124,7 @@ function startPluginServer(params3) {
102302
103124
  if (!alive) {
102303
103125
  yield* files.deletePidFile(pidFilePath);
102304
103126
  } else {
103127
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
102305
103128
  return {
102306
103129
  started: false,
102307
103130
  pid: existing.pid,
@@ -102368,6 +103191,7 @@ function ensurePluginServer(params3) {
102368
103191
  if (existing) {
102369
103192
  const alive = yield* proc.isPidRunning(existing.pid);
102370
103193
  if (alive) {
103194
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
102371
103195
  return {
102372
103196
  started: false,
102373
103197
  pid: existing.pid,
@@ -102499,20 +103323,43 @@ var pluginRestartCommand = exports_Command.make("restart", {
102499
103323
  const proc = yield* Process;
102500
103324
  const pidFilePath = resolveUserFilePath(pidFile17 ?? files.defaultPidFile());
102501
103325
  const existing = yield* files.readPidFile(pidFilePath);
103326
+ const stateFilePath = resolveManagedStateFile({
103327
+ pidFilePath,
103328
+ defaultStateFilePath: files.defaultStateFile(),
103329
+ explicitStateFilePath: stateFile20,
103330
+ candidate: existing?.state_file
103331
+ });
102502
103332
  let stoppedPid;
102503
103333
  if (existing) {
102504
103334
  const alive = yield* proc.isPidRunning(existing.pid);
102505
103335
  if (alive) {
103336
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath });
102506
103337
  yield* proc.kill(existing.pid, "SIGTERM");
102507
103338
  const exited = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS });
102508
- if (!exited && force) {
103339
+ if (!exited) {
103340
+ if (!force) {
103341
+ return yield* fail8(new CliError({
103342
+ code: "INTERNAL",
103343
+ message: `Plugin server did not exit within ${PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS}ms; use --force`,
103344
+ exitCode: 1,
103345
+ details: { pid: existing.pid, pid_file: pidFilePath }
103346
+ }));
103347
+ }
102509
103348
  yield* proc.kill(existing.pid, "SIGKILL");
102510
- yield* proc.waitForExit({ pid: existing.pid, timeoutMs: PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS });
103349
+ const killed = yield* proc.waitForExit({ pid: existing.pid, timeoutMs: PLUGIN_SERVER_STOP_WAIT_DEFAULT_MS });
103350
+ if (!killed) {
103351
+ return yield* fail8(new CliError({
103352
+ code: "INTERNAL",
103353
+ message: "Force stop failed (process is still alive)",
103354
+ exitCode: 1,
103355
+ details: { pid: existing.pid, pid_file: pidFilePath }
103356
+ }));
103357
+ }
102511
103358
  }
102512
103359
  stoppedPid = existing.pid;
102513
103360
  }
102514
103361
  yield* files.deletePidFile(pidFilePath).pipe(catchAll2(() => _void));
102515
- yield* files.deleteStateFile(existing.state_file ?? resolveUserFilePath(stateFile20 ?? files.defaultStateFile())).pipe(catchAll2(() => _void));
103362
+ yield* files.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
102516
103363
  }
102517
103364
  const started = yield* startPluginServer({ host: host7, port: port9, waitMs: wait3, pidFile: pidFile17, logFile: logFile10, stateFile: stateFile20 });
102518
103365
  yield* writeSuccess({
@@ -102570,7 +103417,7 @@ import { createServer as createServer2 } from "node:http";
102570
103417
 
102571
103418
  // src/lib/pluginStaticFiles.ts
102572
103419
  import { promises as fs21 } from "node:fs";
102573
- import path31 from "node:path";
103420
+ import path35 from "node:path";
102574
103421
  var CONTENT_TYPES = new Map([
102575
103422
  [".css", "text/css; charset=utf-8"],
102576
103423
  [".html", "text/html; charset=utf-8"],
@@ -102580,7 +103427,7 @@ var CONTENT_TYPES = new Map([
102580
103427
  [".txt", "text/plain; charset=utf-8"]
102581
103428
  ]);
102582
103429
  function contentTypeFor(filePath) {
102583
- return CONTENT_TYPES.get(path31.extname(filePath).toLowerCase()) ?? "application/octet-stream";
103430
+ return CONTENT_TYPES.get(path35.extname(filePath).toLowerCase()) ?? "application/octet-stream";
102584
103431
  }
102585
103432
  function normalizeAssetPath(pathname) {
102586
103433
  let decoded;
@@ -102590,7 +103437,7 @@ function normalizeAssetPath(pathname) {
102590
103437
  return null;
102591
103438
  }
102592
103439
  const raw4 = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
102593
- const normalized = path31.posix.normalize(raw4);
103440
+ const normalized = path35.posix.normalize(raw4);
102594
103441
  if (!normalized || normalized === "." || normalized.startsWith("../") || normalized === "..")
102595
103442
  return null;
102596
103443
  return normalized;
@@ -102604,8 +103451,8 @@ async function readPluginStaticAsset(params3) {
102604
103451
  if (!relativePath) {
102605
103452
  return { ok: false, statusCode: 404, message: "Not found" };
102606
103453
  }
102607
- const filePath = path31.resolve(params3.distPath, relativePath);
102608
- if (filePath !== params3.distPath && !filePath.startsWith(`${params3.distPath}${path31.sep}`)) {
103454
+ const filePath = path35.resolve(params3.distPath, relativePath);
103455
+ if (filePath !== params3.distPath && !filePath.startsWith(`${params3.distPath}${path35.sep}`)) {
102609
103456
  return { ok: false, statusCode: 404, message: "Not found" };
102610
103457
  }
102611
103458
  try {
@@ -102922,8 +103769,14 @@ function stopPluginServer(params3) {
102922
103769
  const files = yield* PluginServerFiles;
102923
103770
  const proc = yield* Process;
102924
103771
  const existing = yield* files.readPidFile(params3.pidFilePath);
103772
+ const stateFilePath = resolveManagedStateFile({
103773
+ pidFilePath: params3.pidFilePath,
103774
+ defaultStateFilePath: files.defaultStateFile(),
103775
+ explicitStateFilePath: params3.stateFilePath,
103776
+ candidate: existing?.state_file
103777
+ });
102925
103778
  if (!existing) {
102926
- yield* files.deleteStateFile(params3.stateFilePath).pipe(catchAll2(() => _void));
103779
+ yield* files.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
102927
103780
  return {
102928
103781
  stopped: true,
102929
103782
  pid_file: params3.pidFilePath
@@ -102931,7 +103784,7 @@ function stopPluginServer(params3) {
102931
103784
  }
102932
103785
  const cleanupStale = () => gen2(function* () {
102933
103786
  yield* files.deletePidFile(params3.pidFilePath);
102934
- yield* files.deleteStateFile(existing.state_file ?? params3.stateFilePath).pipe(catchAll2(() => _void));
103787
+ yield* files.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
102935
103788
  return {
102936
103789
  stopped: true,
102937
103790
  stale: true,
@@ -102943,6 +103796,7 @@ function stopPluginServer(params3) {
102943
103796
  if (!alive) {
102944
103797
  return yield* cleanupStale();
102945
103798
  }
103799
+ yield* requireTrustedPidRecord({ record: existing, pidFilePath: params3.pidFilePath });
102946
103800
  const termResult = yield* proc.kill(existing.pid, "SIGTERM").pipe(either3);
102947
103801
  if (termResult._tag === "Left") {
102948
103802
  if (isEsrch(termResult.left)) {
@@ -102972,7 +103826,7 @@ function stopPluginServer(params3) {
102972
103826
  }
102973
103827
  }
102974
103828
  yield* files.deletePidFile(params3.pidFilePath);
102975
- yield* files.deleteStateFile(existing.state_file ?? params3.stateFilePath).pipe(catchAll2(() => _void));
103829
+ yield* files.deleteStateFile(stateFilePath).pipe(catchAll2(() => _void));
102976
103830
  return {
102977
103831
  stopped: true,
102978
103832
  pid: existing.pid,
@@ -108621,53 +109475,9 @@ var writeReplaceCommand = exports_Command.make("replace", {}).pipe(exports_Comma
108621
109475
  var replaceCommand = writeReplaceCommand;
108622
109476
 
108623
109477
  // src/lib/scenario-store/index.ts
108624
- import path33 from "node:path";
109478
+ import path36 from "node:path";
108625
109479
  import { promises as fs22 } from "node:fs";
108626
109480
 
108627
- // src/lib/builtin-scenarios/index.ts
108628
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
108629
- import path32 from "node:path";
108630
- import { fileURLToPath as fileURLToPath4 } from "node:url";
108631
- var MODULE_DIR = path32.dirname(fileURLToPath4(import.meta.url));
108632
- function locateBuiltinSourceRoot() {
108633
- const packageRootCandidates = [
108634
- path32.resolve(MODULE_DIR, "../../../"),
108635
- path32.resolve(MODULE_DIR, "..")
108636
- ];
108637
- for (const packageRoot of packageRootCandidates) {
108638
- const builtinSourceRoot = path32.join(packageRoot, "builtin-scenarios");
108639
- if (existsSync4(path32.join(builtinSourceRoot, "catalog.json"))) {
108640
- return { packageRoot, builtinSourceRoot };
108641
- }
108642
- }
108643
- throw new Error(`Unable to locate builtin-scenarios catalog from ${MODULE_DIR}`);
108644
- }
108645
- var { packageRoot: PACKAGE_ROOT, builtinSourceRoot: BUILTIN_SOURCE_ROOT } = locateBuiltinSourceRoot();
108646
- var REPO_ROOT = path32.resolve(PACKAGE_ROOT, "../..");
108647
- var BUILTIN_CATALOG_PATH = path32.join(BUILTIN_SOURCE_ROOT, "catalog.json");
108648
- function readJsonFile(filePath) {
108649
- return JSON.parse(readFileSync4(filePath, "utf8"));
108650
- }
108651
- function resolveRepoPath(relPath) {
108652
- return path32.resolve(REPO_ROOT, relPath);
108653
- }
108654
- var builtinScenarioCatalog = readJsonFile(BUILTIN_CATALOG_PATH);
108655
- var builtinScenarioPackages = Object.freeze(Object.fromEntries(builtinScenarioCatalog.map((entry) => [entry.package_id, readJsonFile(resolveRepoPath(entry.package_path))])));
108656
- function getBuiltinScenarioPackage(id5) {
108657
- const pkg = builtinScenarioPackages[id5];
108658
- if (!pkg) {
108659
- throw new Error(`Unknown builtin scenario package: ${id5}`);
108660
- }
108661
- return pkg;
108662
- }
108663
- function getBuiltinScenarioPackageSourcePath(id5) {
108664
- const entry = builtinScenarioCatalog.find((item) => item.package_id === id5);
108665
- if (!entry) {
108666
- throw new Error(`Unknown builtin scenario source: ${id5}`);
108667
- }
108668
- return resolveRepoPath(entry.package_path);
108669
- }
108670
-
108671
109481
  // src/lib/scenario-shared/index.ts
108672
109482
  class ScenarioSharedError extends Error {
108673
109483
  code;
@@ -108680,34 +109490,34 @@ class ScenarioSharedError extends Error {
108680
109490
  function isRecord4(value8) {
108681
109491
  return value8 !== null && typeof value8 === "object" && !Array.isArray(value8);
108682
109492
  }
108683
- function addDiagnostic(diagnostics, errors3, level, path33, message2) {
108684
- diagnostics.push({ level, path: path33, message: message2 });
109493
+ function addDiagnostic(diagnostics, errors3, level, path36, message2) {
109494
+ diagnostics.push({ level, path: path36, message: message2 });
108685
109495
  if (level === "error")
108686
109496
  errors3.push(message2);
108687
109497
  }
108688
- function addChange(changes2, path33, kind2, before2, after3) {
108689
- changes2.push({ path: path33, kind: kind2, before: before2, after: after3 });
109498
+ function addChange(changes2, path36, kind2, before2, after3) {
109499
+ changes2.push({ path: path36, kind: kind2, before: before2, after: after3 });
108690
109500
  }
108691
- function expectRecord(value8, path33, diagnostics, errors3) {
109501
+ function expectRecord(value8, path36, diagnostics, errors3) {
108692
109502
  if (isRecord4(value8))
108693
109503
  return value8;
108694
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must be an object`);
109504
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must be an object`);
108695
109505
  return {};
108696
109506
  }
108697
- function expectString(value8, path33, diagnostics, errors3, fallback = "") {
109507
+ function expectString(value8, path36, diagnostics, errors3, fallback = "") {
108698
109508
  if (typeof value8 === "string" && value8.trim())
108699
109509
  return value8.trim();
108700
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must be a non-empty string`);
109510
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must be a non-empty string`);
108701
109511
  return fallback;
108702
109512
  }
108703
- function expectStringArray(value8, path33, diagnostics, errors3) {
109513
+ function expectStringArray(value8, path36, diagnostics, errors3) {
108704
109514
  if (!Array.isArray(value8)) {
108705
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must be an array`);
109515
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must be an array`);
108706
109516
  return [];
108707
109517
  }
108708
109518
  const out = value8.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => item.length > 0);
108709
109519
  if (out.length !== value8.length) {
108710
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must contain only non-empty strings`);
109520
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must contain only non-empty strings`);
108711
109521
  }
108712
109522
  return out;
108713
109523
  }
@@ -108730,21 +109540,21 @@ function normalizePowerupValue2(value8) {
108730
109540
  return "t";
108731
109541
  return trimmed2;
108732
109542
  }
108733
- function normalizeQuerySelector(raw4, path33, diagnostics, errors3, changes2) {
108734
- const input = expectRecord(raw4, path33, diagnostics, errors3);
109543
+ function normalizeQuerySelector(raw4, path36, diagnostics, errors3, changes2) {
109544
+ const input = expectRecord(raw4, path36, diagnostics, errors3);
108735
109545
  let query2 = input;
108736
109546
  if (isRecord4(input.query)) {
108737
109547
  query2 = input.query;
108738
109548
  } else if (isRecord4(input.queryObj)) {
108739
109549
  query2 = input.queryObj;
108740
- addChange(changes2, `${path33}.queryObj`, "normalized", input.queryObj, query2);
109550
+ addChange(changes2, `${path36}.queryObj`, "normalized", input.queryObj, query2);
108741
109551
  }
108742
109552
  if (isRecord4(query2.query)) {
108743
109553
  query2 = query2.query;
108744
109554
  }
108745
109555
  const version = 2;
108746
- const root = expectRecord(query2.root ?? query2, `${path33}.root`, diagnostics, errors3);
108747
- const normalizedRoot = normalizeQueryRoot2(root, `${path33}.root`, diagnostics, errors3, changes2);
109556
+ const root = expectRecord(query2.root ?? query2, `${path36}.root`, diagnostics, errors3);
109557
+ const normalizedRoot = normalizeQueryRoot2(root, `${path36}.root`, diagnostics, errors3, changes2);
108748
109558
  return {
108749
109559
  version,
108750
109560
  root: normalizedRoot,
@@ -108753,31 +109563,31 @@ function normalizeQuerySelector(raw4, path33, diagnostics, errors3, changes2) {
108753
109563
  ...isRecord4(query2.sort) ? { sort: query2.sort } : {}
108754
109564
  };
108755
109565
  }
108756
- function normalizeQueryRoot2(raw4, path33, diagnostics, errors3, changes2) {
108757
- const type2 = expectString(raw4.type, `${path33}.type`, diagnostics, errors3);
109566
+ function normalizeQueryRoot2(raw4, path36, diagnostics, errors3, changes2) {
109567
+ const type2 = expectString(raw4.type, `${path36}.type`, diagnostics, errors3);
108758
109568
  if (type2 === "text") {
108759
- const value8 = expectString(raw4.value, `${path33}.value`, diagnostics, errors3);
109569
+ const value8 = expectString(raw4.value, `${path36}.value`, diagnostics, errors3);
108760
109570
  const mode = typeof raw4.mode === "string" && raw4.mode.trim() ? raw4.mode.trim() : "contains";
108761
109571
  if (raw4.mode === undefined) {
108762
- addChange(changes2, `${path33}.mode`, "defaulted", undefined, mode);
109572
+ addChange(changes2, `${path36}.mode`, "defaulted", undefined, mode);
108763
109573
  }
108764
109574
  return { type: type2, value: value8, mode };
108765
109575
  }
108766
109576
  if (type2 === "powerup") {
108767
- const powerup3 = expectRecord(raw4.powerup, `${path33}.powerup`, diagnostics, errors3);
109577
+ const powerup3 = expectRecord(raw4.powerup, `${path36}.powerup`, diagnostics, errors3);
108768
109578
  let by = typeof powerup3.by === "string" ? powerup3.by.trim().toLowerCase() : "";
108769
109579
  if (by !== "id" && by !== "rcrt") {
108770
109580
  if (!by) {
108771
109581
  by = "rcrt";
108772
- addChange(changes2, `${path33}.powerup.by`, "defaulted", powerup3.by, by);
109582
+ addChange(changes2, `${path36}.powerup.by`, "defaulted", powerup3.by, by);
108773
109583
  } else {
108774
- addDiagnostic(diagnostics, errors3, "error", `${path33}.powerup.by`, "powerup.by must be one of: id, rcrt");
109584
+ addDiagnostic(diagnostics, errors3, "error", `${path36}.powerup.by`, "powerup.by must be one of: id, rcrt");
108775
109585
  }
108776
109586
  }
108777
- const rawValue = expectString(powerup3.value, `${path33}.powerup.value`, diagnostics, errors3);
109587
+ const rawValue = expectString(powerup3.value, `${path36}.powerup.value`, diagnostics, errors3);
108778
109588
  const value8 = by === "rcrt" ? normalizePowerupValue2(rawValue) : rawValue;
108779
109589
  if (value8 !== rawValue) {
108780
- addChange(changes2, `${path33}.powerup.value`, "normalized", rawValue, value8);
109590
+ addChange(changes2, `${path36}.powerup.value`, "normalized", rawValue, value8);
108781
109591
  }
108782
109592
  return {
108783
109593
  type: type2,
@@ -108790,45 +109600,45 @@ function normalizeQueryRoot2(raw4, path33, diagnostics, errors3, changes2) {
108790
109600
  if ((type2 === "and" || type2 === "or") && Array.isArray(raw4.nodes)) {
108791
109601
  return {
108792
109602
  type: type2,
108793
- nodes: raw4.nodes.map((child, index) => normalizeQueryRoot2(expectRecord(child, `${path33}.nodes[${index}]`, diagnostics, errors3), `${path33}.nodes[${index}]`, diagnostics, errors3, changes2))
109603
+ nodes: raw4.nodes.map((child, index) => normalizeQueryRoot2(expectRecord(child, `${path36}.nodes[${index}]`, diagnostics, errors3), `${path36}.nodes[${index}]`, diagnostics, errors3, changes2))
108794
109604
  };
108795
109605
  }
108796
109606
  if (type2 === "not") {
108797
109607
  return {
108798
109608
  type: type2,
108799
- node: normalizeQueryRoot2(expectRecord(raw4.node, `${path33}.node`, diagnostics, errors3), `${path33}.node`, diagnostics, errors3, changes2)
109609
+ node: normalizeQueryRoot2(expectRecord(raw4.node, `${path36}.node`, diagnostics, errors3), `${path36}.node`, diagnostics, errors3, changes2)
108800
109610
  };
108801
109611
  }
108802
109612
  return raw4;
108803
109613
  }
108804
- function normalizeVars(raw4, path33, diagnostics, errors3, changes2) {
109614
+ function normalizeVars(raw4, path36, diagnostics, errors3, changes2) {
108805
109615
  if (Array.isArray(raw4)) {
108806
- return raw4.map((item, index) => normalizeVar(item, `${path33}[${index}]`, diagnostics, errors3));
109616
+ return raw4.map((item, index) => normalizeVar(item, `${path36}[${index}]`, diagnostics, errors3));
108807
109617
  }
108808
109618
  if (isRecord4(raw4)) {
108809
- const vars = Object.entries(raw4).map(([name, value8]) => normalizeVar({ ...isRecord4(value8) ? value8 : {}, name }, `${path33}.${name}`, diagnostics, errors3));
108810
- addChange(changes2, path33, "normalized", raw4, vars);
109619
+ const vars = Object.entries(raw4).map(([name, value8]) => normalizeVar({ ...isRecord4(value8) ? value8 : {}, name }, `${path36}.${name}`, diagnostics, errors3));
109620
+ addChange(changes2, path36, "normalized", raw4, vars);
108811
109621
  return vars;
108812
109622
  }
108813
109623
  if (raw4 === undefined)
108814
109624
  return [];
108815
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must be an array or object`);
109625
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must be an array or object`);
108816
109626
  return [];
108817
109627
  }
108818
- function normalizeVar(raw4, path33, diagnostics, errors3) {
108819
- const value8 = expectRecord(raw4, path33, diagnostics, errors3);
109628
+ function normalizeVar(raw4, path36, diagnostics, errors3) {
109629
+ const value8 = expectRecord(raw4, path36, diagnostics, errors3);
108820
109630
  return {
108821
- name: expectString(value8.name, `${path33}.name`, diagnostics, errors3),
108822
- type: expectString(value8.type, `${path33}.type`, diagnostics, errors3, "string"),
109631
+ name: expectString(value8.name, `${path36}.name`, diagnostics, errors3),
109632
+ type: expectString(value8.type, `${path36}.type`, diagnostics, errors3, "string"),
108823
109633
  required: asBoolean(value8.required, false),
108824
109634
  ...value8.default !== undefined ? { default: value8.default } : {},
108825
109635
  ...typeof value8.description === "string" && value8.description.trim() ? { description: value8.description.trim() } : {}
108826
109636
  };
108827
109637
  }
108828
- function normalizeCapabilities(raw4, path33, changes2) {
109638
+ function normalizeCapabilities(raw4, path36, changes2) {
108829
109639
  if (Array.isArray(raw4)) {
108830
109640
  const out = Object.fromEntries(raw4.map((item) => String(item ?? "").trim()).filter(Boolean).map((item) => [item.replace(/^requires\./, ""), true]));
108831
- addChange(changes2, path33, "normalized", raw4, { requires: out });
109641
+ addChange(changes2, path36, "normalized", raw4, { requires: out });
108832
109642
  return Object.keys(out).length > 0 ? { requires: out } : {};
108833
109643
  }
108834
109644
  if (!isRecord4(raw4))
@@ -108836,7 +109646,7 @@ function normalizeCapabilities(raw4, path33, changes2) {
108836
109646
  const requires = raw4.requires;
108837
109647
  if (Array.isArray(requires)) {
108838
109648
  const out = Object.fromEntries(requires.map((item) => String(item ?? "").trim()).filter(Boolean).map((item) => [item.replace(/^requires\./, ""), true]));
108839
- addChange(changes2, `${path33}.requires`, "normalized", requires, out);
109649
+ addChange(changes2, `${path36}.requires`, "normalized", requires, out);
108840
109650
  return Object.keys(out).length > 0 ? { requires: out } : {};
108841
109651
  }
108842
109652
  if (isRecord4(requires)) {
@@ -108845,8 +109655,8 @@ function normalizeCapabilities(raw4, path33, changes2) {
108845
109655
  }
108846
109656
  return {};
108847
109657
  }
108848
- function normalizePolicy(raw4, path33, diagnostics, errors3) {
108849
- const value8 = expectRecord(raw4, path33, diagnostics, errors3);
109658
+ function normalizePolicy(raw4, path36, diagnostics, errors3) {
109659
+ const value8 = expectRecord(raw4, path36, diagnostics, errors3);
108850
109660
  const fallbackStrategy = value8.fallback_strategy === "allow_empty_selection" || value8.fallback_strategy === "skip_optional_outputs" || value8.fallback_strategy === "fail" ? value8.fallback_strategy : undefined;
108851
109661
  return {
108852
109662
  wait: asBoolean(value8.wait, false),
@@ -108856,10 +109666,10 @@ function normalizePolicy(raw4, path33, diagnostics, errors3) {
108856
109666
  ...fallbackStrategy ? { fallback_strategy: fallbackStrategy } : {}
108857
109667
  };
108858
109668
  }
108859
- function normalizeScheduling(raw4, path33, diagnostics, errors3) {
109669
+ function normalizeScheduling(raw4, path36, diagnostics, errors3) {
108860
109670
  if (raw4 === undefined)
108861
109671
  return;
108862
- const value8 = expectRecord(raw4, path33, diagnostics, errors3);
109672
+ const value8 = expectRecord(raw4, path36, diagnostics, errors3);
108863
109673
  const batching = value8.batching === "off" || value8.batching === "auto" ? value8.batching : undefined;
108864
109674
  const mergeStrategy = value8.merge_strategy === "off" || value8.merge_strategy === "safe_dedupe" ? value8.merge_strategy : undefined;
108865
109675
  const parallelism = value8.parallelism === "serial" || value8.parallelism === "auto" ? value8.parallelism : undefined;
@@ -108873,13 +109683,13 @@ function normalizeScheduling(raw4, path33, diagnostics, errors3) {
108873
109683
  ...dispatchMode7 ? { dispatch_mode: dispatchMode7 } : {}
108874
109684
  };
108875
109685
  }
108876
- function normalizeNodes(raw4, path33, diagnostics, errors3, changes2) {
109686
+ function normalizeNodes(raw4, path36, diagnostics, errors3, changes2) {
108877
109687
  if (!Array.isArray(raw4)) {
108878
- addDiagnostic(diagnostics, errors3, "error", path33, `${path33} must be an array`);
109688
+ addDiagnostic(diagnostics, errors3, "error", path36, `${path36} must be an array`);
108879
109689
  return [];
108880
109690
  }
108881
109691
  return raw4.map((item, index) => {
108882
- const nodePath = `${path33}[${index}]`;
109692
+ const nodePath = `${path36}[${index}]`;
108883
109693
  const value8 = expectRecord(item, nodePath, diagnostics, errors3);
108884
109694
  const id5 = expectString(value8.id, `${nodePath}.id`, diagnostics, errors3);
108885
109695
  const kind2 = expectString(value8.kind, `${nodePath}.kind`, diagnostics, errors3);
@@ -108931,18 +109741,18 @@ function normalizeNodes(raw4, path33, diagnostics, errors3, changes2) {
108931
109741
  };
108932
109742
  });
108933
109743
  }
108934
- function scanStructuredReferences(value8, onRef, path33) {
109744
+ function scanStructuredReferences(value8, onRef, path36) {
108935
109745
  if (Array.isArray(value8)) {
108936
- value8.forEach((item, index) => scanStructuredReferences(item, onRef, `${path33}[${index}]`));
109746
+ value8.forEach((item, index) => scanStructuredReferences(item, onRef, `${path36}[${index}]`));
108937
109747
  return;
108938
109748
  }
108939
109749
  if (!isRecord4(value8))
108940
109750
  return;
108941
109751
  if (value8.kind === "node_output" && typeof value8.node === "string" && typeof value8.output === "string") {
108942
- onRef(value8, path33);
109752
+ onRef(value8, path36);
108943
109753
  }
108944
109754
  for (const [key, child] of Object.entries(value8)) {
108945
- scanStructuredReferences(child, onRef, `${path33}.${key}`);
109755
+ scanStructuredReferences(child, onRef, `${path36}.${key}`);
108946
109756
  }
108947
109757
  }
108948
109758
  function validateGraph(pkg, diagnostics, errors3) {
@@ -109250,7 +110060,7 @@ function generateScenarioPackage(input) {
109250
110060
 
109251
110061
  // src/lib/scenario-store/index.ts
109252
110062
  function defaultUserScenarioDir() {
109253
- return path33.join(homeDir(), ".agent-remnote", "scenarios");
110063
+ return path36.join(homeDir(), ".agent-remnote", "scenarios");
109254
110064
  }
109255
110065
  function resolveUserScenarioDir(input) {
109256
110066
  if (typeof input === "string" && input.trim()) {
@@ -109281,7 +110091,7 @@ function canonicalScenarioId(input, fieldName) {
109281
110091
  return id5;
109282
110092
  }
109283
110093
  function userScenarioFilePath(id5, installDir) {
109284
- return path33.join(resolveUserScenarioDir(installDir), `${canonicalScenarioId(id5, "Scenario id")}.json`);
110094
+ return path36.join(resolveUserScenarioDir(installDir), `${canonicalScenarioId(id5, "Scenario id")}.json`);
109285
110095
  }
109286
110096
  async function readUserScenarioPackageById(id5, installDir) {
109287
110097
  const canonicalId = canonicalScenarioId(id5, "Scenario id");
@@ -111446,7 +112256,7 @@ var stackEnsureCommand = exports_Command.make("ensure", {
111446
112256
  }).pipe(catchAll2(writeFailure)));
111447
112257
 
111448
112258
  // src/commands/stack/status.ts
111449
- import path34 from "node:path";
112259
+ import path37 from "node:path";
111450
112260
  var stackStatusCommand = exports_Command.make("status", {}, () => gen2(function* () {
111451
112261
  const cfg = yield* AppConfig;
111452
112262
  const daemonFiles = yield* DaemonFiles;
@@ -111467,7 +112277,7 @@ var stackStatusCommand = exports_Command.make("status", {}, () => gen2(function*
111467
112277
  const apiBaseUrl = apiLocalBaseUrl(apiPidInfo?.port ?? cfg.apiPort ?? 3000, apiBasePath);
111468
112278
  const apiStatus = yield* api.status({ baseUrl: apiBaseUrl, timeoutMs: 2000 }).pipe(either3);
111469
112279
  const queueStats2 = yield* queue.stats({ dbPath: cfg.storeDb }).pipe(either3);
111470
- const stateFilePath = resolveUserFilePath(daemonPidInfo?.state_file ?? path34.join(path34.dirname(daemonPidFile), "ws.state.json"));
112280
+ const stateFilePath = resolveUserFilePath(daemonPidInfo?.state_file ?? path37.join(path37.dirname(daemonPidFile), "ws.state.json"));
111471
112281
  const activeWorkerRuntime = clients._tag === "Right" && typeof clients.right.activeWorkerConnId === "string" ? clients.right.clients.find((client) => client.connId === clients.right.activeWorkerConnId)?.runtime ?? null : null;
111472
112282
  const data = {
111473
112283
  runtime: currentRuntimeBuildInfo(),
@@ -111529,7 +112339,7 @@ var stackStatusCommand = exports_Command.make("status", {}, () => gen2(function*
111529
112339
  }).pipe(catchAll2(writeFailure)));
111530
112340
 
111531
112341
  // src/commands/stack/stop.ts
111532
- import path35 from "node:path";
112342
+ import path38 from "node:path";
111533
112343
  var stackStopCommand = exports_Command.make("stop", {}, () => gen2(function* () {
111534
112344
  const apiFiles = yield* ApiDaemonFiles;
111535
112345
  const daemonFiles = yield* DaemonFiles;
@@ -111537,8 +112347,14 @@ var stackStopCommand = exports_Command.make("stop", {}, () => gen2(function* ()
111537
112347
  const supervisorState = yield* SupervisorState;
111538
112348
  const apiPidFile = resolveUserFilePath(apiFiles.defaultPidFile());
111539
112349
  const apiPidInfo = yield* apiFiles.readPidFile(apiPidFile);
111540
- let apiStopped = true;
112350
+ const apiStateFile = resolveManagedStateFile({
112351
+ pidFilePath: apiPidFile,
112352
+ defaultStateFilePath: apiFiles.defaultStateFile(),
112353
+ candidate: apiPidInfo?.state_file
112354
+ });
112355
+ let apiStopped = false;
111541
112356
  if (apiPidInfo && (yield* proc.isPidRunning(apiPidInfo.pid))) {
112357
+ yield* requireTrustedPidRecord({ record: apiPidInfo, pidFilePath: apiPidFile });
111542
112358
  yield* proc.kill(apiPidInfo.pid, "SIGTERM");
111543
112359
  const exited = yield* proc.waitForExit({ pid: apiPidInfo.pid, timeoutMs: API_STOP_WAIT_DEFAULT_MS });
111544
112360
  if (!exited) {
@@ -111548,13 +112364,22 @@ var stackStopCommand = exports_Command.make("stop", {}, () => gen2(function* ()
111548
112364
  return yield* fail8(new CliError({ code: "INTERNAL", message: "Failed to stop api process", exitCode: 1 }));
111549
112365
  }
111550
112366
  }
112367
+ apiStopped = true;
112368
+ } else {
112369
+ apiStopped = true;
111551
112370
  }
111552
112371
  yield* apiFiles.deletePidFile(apiPidFile).pipe(catchAll2(() => _void));
111553
- yield* apiFiles.deleteStateFile(apiPidInfo?.state_file ?? apiFiles.defaultStateFile()).pipe(catchAll2(() => _void));
112372
+ yield* apiFiles.deleteStateFile(apiStateFile).pipe(catchAll2(() => _void));
111554
112373
  const daemonPidFile = resolveUserFilePath(daemonFiles.defaultPidFile());
111555
112374
  const daemonPidInfo = yield* daemonFiles.readPidFile(daemonPidFile);
111556
- let daemonStopped = true;
112375
+ const daemonStateFile = resolveManagedStateFile({
112376
+ pidFilePath: daemonPidFile,
112377
+ defaultStateFilePath: resolveUserFilePath(path38.join(path38.dirname(daemonPidFile), "ws.state.json")),
112378
+ candidate: daemonPidInfo?.state_file
112379
+ });
112380
+ let daemonStopped = false;
111557
112381
  if (daemonPidInfo && (yield* proc.isPidRunning(daemonPidInfo.pid))) {
112382
+ yield* requireTrustedPidRecord({ record: daemonPidInfo, pidFilePath: daemonPidFile });
111558
112383
  yield* proc.kill(daemonPidInfo.pid, "SIGTERM");
111559
112384
  const exited = yield* proc.waitForExit({ pid: daemonPidInfo.pid, timeoutMs: WS_STOP_WAIT_DEFAULT_MS });
111560
112385
  if (!exited) {
@@ -111564,9 +112389,12 @@ var stackStopCommand = exports_Command.make("stop", {}, () => gen2(function* ()
111564
112389
  return yield* fail8(new CliError({ code: "INTERNAL", message: "Failed to stop daemon process", exitCode: 1 }));
111565
112390
  }
111566
112391
  }
112392
+ daemonStopped = true;
112393
+ } else {
112394
+ daemonStopped = true;
111567
112395
  }
111568
112396
  yield* daemonFiles.deletePidFile(daemonPidFile).pipe(catchAll2(() => _void));
111569
- yield* supervisorState.deleteStateFile(resolveUserFilePath(daemonPidInfo?.state_file ?? path35.join(path35.dirname(daemonPidFile), "ws.state.json"))).pipe(catchAll2(() => _void));
112397
+ yield* supervisorState.deleteStateFile(daemonStateFile).pipe(catchAll2(() => _void));
111570
112398
  yield* writeSuccess({
111571
112399
  data: { stopped: true, api_stopped: apiStopped, daemon_stopped: daemonStopped },
111572
112400
  md: `- stopped: true
@@ -111769,8 +112597,6 @@ var cli = exports_Command.run(rootCommand, {
111769
112597
  name: "agent-remnote",
111770
112598
  version
111771
112599
  });
111772
- var jsonRequested = process.argv.includes("--json");
111773
- var debugRequested = process.argv.includes("--debug");
111774
112600
  function canonicalizeArgv(argv) {
111775
112601
  const out = [...argv];
111776
112602
  for (let i = 0;i < out.length; i += 1) {
@@ -111840,6 +112666,9 @@ var ROOT_VALUE_FLAGS2 = new Set([
111840
112666
  "--config-file",
111841
112667
  ...BUILTIN_VALUE_FLAGS2
111842
112668
  ]);
112669
+ var rootCliConfig = parseRootConfigFromArgv(argv);
112670
+ var jsonRequested = rootCliConfig.get("json") === "true";
112671
+ var debugRequested = rootCliConfig.get("debug") === "true";
111843
112672
  function parseRootConfigFromArgv(argv2) {
111844
112673
  const tokens = argv2.slice(2);
111845
112674
  const out = new Map;
@@ -112292,72 +113121,77 @@ function strictCommandPreflightOrExit(argv2) {
112292
113121
  process.exit(error4.exitCode);
112293
113122
  }
112294
113123
  strictCommandPreflightOrExit(argv);
112295
- var configProvider = buildCliEnvConfigProvider({ cli: parseRootConfigFromArgv(argv), env: process.env });
112296
- cli(argv).pipe(withConfigProvider2(configProvider), provide2(mergeAll5(exports_NodeContext.layer, setConsole2(makeCliConsole()))), scoped2, exit2, flatMap9((exit3) => sync3(() => {
112297
- process.exitCode = exitCodeFromExit(exit3);
112298
- if (isSuccess(exit3))
112299
- return;
112300
- const failure = failureOption2(exit3.cause);
112301
- if (isNone2(failure)) {
112302
- if (jsonRequested) {
112303
- process.stdout.write(`${JSON.stringify(fail21({
112304
- code: "INTERNAL",
112305
- message: "Unknown runtime error (defect)",
112306
- details: debugRequested ? { cause: pretty2(exit3.cause) } : undefined
112307
- }))}
113124
+ var isSearchWorkerThread = !isMainThread2 && workerData2?.kind === "search_rem_overview";
113125
+ if (isSearchWorkerThread && parentPort3) {
113126
+ runSearchRemOverviewWorkerJob(workerData2, parentPort3);
113127
+ } else {
113128
+ const configProvider = buildCliEnvConfigProvider({ cli: parseRootConfigFromArgv(argv), env: process.env });
113129
+ cli(argv).pipe(withConfigProvider2(configProvider), provide2(mergeAll5(exports_NodeContext.layer, setConsole2(makeCliConsole()))), scoped2, exit2, flatMap9((exit3) => sync3(() => {
113130
+ process.exitCode = exitCodeFromExit(exit3);
113131
+ if (isSuccess(exit3))
113132
+ return;
113133
+ const failure = failureOption2(exit3.cause);
113134
+ if (isNone2(failure)) {
113135
+ if (jsonRequested) {
113136
+ process.stdout.write(`${JSON.stringify(fail21({
113137
+ code: "INTERNAL",
113138
+ message: "Unknown runtime error (defect)",
113139
+ details: debugRequested ? { cause: pretty2(exit3.cause) } : undefined
113140
+ }))}
112308
113141
  `);
112309
- } else if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
112310
- globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
112311
- process.stderr.write(`${formatHumanErrorLine2("Unknown runtime error (defect)")}
113142
+ } else if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
113143
+ globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
113144
+ process.stderr.write(`${formatHumanErrorLine2("Unknown runtime error (defect)")}
112312
113145
  `);
112313
- if (debugRequested)
112314
- process.stderr.write(pretty2(exit3.cause) + `
113146
+ if (debugRequested)
113147
+ process.stderr.write(pretty2(exit3.cause) + `
112315
113148
  `);
112316
- }
112317
- return;
112318
- }
112319
- const error4 = failure.value;
112320
- if (isValidationError2(error4)) {
112321
- if (!jsonRequested)
113149
+ }
112322
113150
  return;
112323
- const cliError = cliErrorFromValidationError(error4);
112324
- process.stdout.write(`${JSON.stringify(fail21(toJsonError(cliError), cliError.hint))}
112325
- `);
112326
- return;
112327
- }
112328
- if (isCliError(error4)) {
112329
- if (jsonRequested) {
112330
- process.stdout.write(`${JSON.stringify(fail21(toJsonError(error4), error4.hint))}
113151
+ }
113152
+ const error4 = failure.value;
113153
+ if (isValidationError2(error4)) {
113154
+ if (!jsonRequested)
113155
+ return;
113156
+ const cliError = cliErrorFromValidationError(error4);
113157
+ process.stdout.write(`${JSON.stringify(fail21(toJsonError(cliError), cliError.hint))}
112331
113158
  `);
112332
113159
  return;
112333
113160
  }
112334
- if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
112335
- globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
112336
- process.stderr.write(`${formatHumanErrorLine2(error4.message)}
112337
- `);
112338
- if (debugRequested && error4.details !== undefined) {
112339
- process.stderr.write(`${JSON.stringify(error4.details, null, 2)}
113161
+ if (isCliError(error4)) {
113162
+ if (jsonRequested) {
113163
+ process.stdout.write(`${JSON.stringify(fail21(toJsonError(error4), error4.hint))}
112340
113164
  `);
113165
+ return;
112341
113166
  }
112342
- if (error4.hint && error4.hint.length > 0) {
112343
- process.stderr.write(`Hint:
113167
+ if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
113168
+ globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
113169
+ process.stderr.write(`${formatHumanErrorLine2(error4.message)}
113170
+ `);
113171
+ if (debugRequested && error4.details !== undefined) {
113172
+ process.stderr.write(`${JSON.stringify(error4.details, null, 2)}
112344
113173
  `);
112345
- for (const h of error4.hint)
112346
- process.stderr.write(`- ${h}
113174
+ }
113175
+ if (error4.hint && error4.hint.length > 0) {
113176
+ process.stderr.write(`Hint:
113177
+ `);
113178
+ for (const h of error4.hint)
113179
+ process.stderr.write(`- ${h}
112347
113180
  `);
113181
+ }
112348
113182
  }
113183
+ return;
112349
113184
  }
112350
- return;
112351
- }
112352
- if (jsonRequested) {
112353
- process.stdout.write(`${JSON.stringify(fail21({
112354
- code: "INTERNAL",
112355
- message: String(error4?.message || error4 || "Unknown error")
112356
- }))}
113185
+ if (jsonRequested) {
113186
+ process.stdout.write(`${JSON.stringify(fail21({
113187
+ code: "INTERNAL",
113188
+ message: String(error4?.message || error4 || "Unknown error")
113189
+ }))}
112357
113190
  `);
112358
- } else if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
112359
- globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
112360
- process.stderr.write(`${formatHumanErrorLine2(String(error4?.message || error4 || "Unknown error"))}
113191
+ } else if (!globalThis.__REMNOTE_CLI_ERROR_REPORTED__) {
113192
+ globalThis.__REMNOTE_CLI_ERROR_REPORTED__ = true;
113193
+ process.stderr.write(`${formatHumanErrorLine2(String(error4?.message || error4 || "Unknown error"))}
112361
113194
  `);
112362
- }
112363
- }))).pipe(exports_NodeRuntime.runMain);
113195
+ }
113196
+ }))).pipe(exports_NodeRuntime.runMain);
113197
+ }