negotium 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/agent-helpers.js +13 -12
  2. package/dist/agent-helpers.js.map +6 -6
  3. package/dist/background-bash.js +11 -11
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/browser-runtime.js +2 -3
  6. package/dist/browser-runtime.js.map +3 -3
  7. package/dist/{chunk-rnsnhcye.js → chunk-5g5a63vv.js} +11 -11
  8. package/dist/{chunk-rnsnhcye.js.map → chunk-5g5a63vv.js.map} +4 -4
  9. package/dist/hosted-agent.js +12 -12
  10. package/dist/hosted-agent.js.map +5 -5
  11. package/dist/main.js +582 -357
  12. package/dist/main.js.map +12 -11
  13. package/dist/mcp-factories.js +13 -12
  14. package/dist/mcp-factories.js.map +6 -6
  15. package/dist/mcp-servers.js +3 -2
  16. package/dist/mcp-servers.js.map +3 -3
  17. package/dist/prompts.js +2 -3
  18. package/dist/prompts.js.map +3 -3
  19. package/dist/query-runtime.js +2 -3
  20. package/dist/query-runtime.js.map +3 -3
  21. package/dist/registry.js +3 -3
  22. package/dist/registry.js.map +2 -2
  23. package/dist/rollout.js +1 -1
  24. package/dist/runtime/src/index.ts +9 -0
  25. package/dist/runtime/src/node-host.ts +9 -0
  26. package/dist/runtime/src/platform/background-bash/manager.ts +30 -22
  27. package/dist/runtime/src/platform/config.ts +8 -7
  28. package/dist/runtime/src/runtime/bashrs-completions.ts +74 -14
  29. package/dist/runtime/src/runtime/public-helpers.ts +8 -0
  30. package/dist/runtime/src/runtime/turn-runner.ts +6 -0
  31. package/dist/runtime/src/version.ts +1 -1
  32. package/dist/runtime-helpers.js +444 -32
  33. package/dist/runtime-helpers.js.map +10 -4
  34. package/dist/types/apps/negotium/src/mcp-servers.d.ts +16 -3
  35. package/dist/types/packages/core/src/platform/background-bash/manager.d.ts +6 -1
  36. package/dist/types/packages/core/src/platform/config.d.ts +8 -6
  37. package/dist/types/packages/core/src/runtime/bashrs-completions.d.ts +50 -0
  38. package/dist/types/packages/core/src/runtime/public-helpers.d.ts +2 -0
  39. package/dist/types/packages/core/src/version.d.ts +1 -1
  40. package/dist/vault.js +2 -3
  41. package/dist/vault.js.map +3 -3
  42. package/install-bash-rs.mjs +5 -5
  43. package/package.json +1 -1
  44. package/dist/runtime/src/mcp/background-bash-server.ts +0 -756
@@ -354,6 +354,74 @@ var defaultLifecycle = createLifecycleManager({
354
354
  });
355
355
  var onShutdown = defaultLifecycle.onShutdown;
356
356
  var runShutdown = defaultLifecycle.runShutdown;
357
+ // ../../packages/core/src/runtime/bashrs-completions.ts
358
+ import {
359
+ closeSync as closeSync2,
360
+ openSync as openSync2,
361
+ readdirSync,
362
+ readFileSync as readFileSync3,
363
+ readSync,
364
+ renameSync as renameSync2,
365
+ rmSync,
366
+ statSync as statSync2
367
+ } from "fs";
368
+ import { join as join4 } from "path";
369
+
370
+ // ../../packages/core/src/outbox/utils.ts
371
+ import { mkdirSync as mkdirSync2, watch } from "fs";
372
+ var FALLBACK_INTERVAL_MS = 5000;
373
+ function createOutboxWatchOps(host) {
374
+ function debouncedFlush(fn, label, delayMs) {
375
+ let timer = null;
376
+ let running = false;
377
+ let pending = false;
378
+ async function run() {
379
+ if (running) {
380
+ pending = true;
381
+ return;
382
+ }
383
+ while (true) {
384
+ running = true;
385
+ pending = false;
386
+ try {
387
+ await fn();
388
+ } catch (e) {
389
+ host.logger.error({ err: e }, `${label}: Unhandled error`);
390
+ } finally {
391
+ running = false;
392
+ }
393
+ if (!pending)
394
+ break;
395
+ }
396
+ }
397
+ return () => {
398
+ if (timer)
399
+ clearTimeout(timer);
400
+ timer = setTimeout(() => {
401
+ timer = null;
402
+ run();
403
+ }, delayMs);
404
+ };
405
+ }
406
+ function watchDir(dir, onChange) {
407
+ try {
408
+ mkdirSync2(dir, { recursive: true });
409
+ const watcher = watch(dir, { recursive: true }, () => onChange());
410
+ watcher.on("error", (e) => {
411
+ host.logger.warn({ err: e, dir }, "outbox: fs.watch error");
412
+ });
413
+ return watcher;
414
+ } catch (e) {
415
+ host.logger.warn({ err: e, dir }, "outbox: Failed to watch dir");
416
+ return null;
417
+ }
418
+ }
419
+ return { debouncedFlush, watchDir };
420
+ }
421
+ var defaultWatchOps = createOutboxWatchOps({ logger });
422
+ var debouncedFlush = defaultWatchOps.debouncedFlush;
423
+ var watchDir = defaultWatchOps.watchDir;
424
+
357
425
  // ../../packages/core/src/platform/config.ts
358
426
  import { execFileSync } from "child_process";
359
427
  import { randomBytes } from "crypto";
@@ -362,7 +430,7 @@ import {
362
430
  chmodSync,
363
431
  constants,
364
432
  existsSync,
365
- mkdirSync as mkdirSync2,
433
+ mkdirSync as mkdirSync3,
366
434
  readFileSync as readFileSync2,
367
435
  writeFileSync as writeFileSync2
368
436
  } from "fs";
@@ -496,7 +564,7 @@ function resolveBrowserRsBin(envValue) {
496
564
  }
497
565
  var SNIPPETS_API_URL = (envText("NEGOTIUM_SNIPPETS_API_URL") ?? envText("SNIPPETS_API_URL") ?? "").replace(/\/+$/, "");
498
566
  var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
499
- var BASH_RS_VERSION = "v0.1.2";
567
+ var BASH_RS_VERSION = "v0.1.5";
500
568
  function resolveBashRsBin(envValue) {
501
569
  const override = envValue?.trim();
502
570
  const candidate = override ? resolve(override) : resolve(BINARIES_DIR, "bash-rs", BASH_RS_VERSION, "bash-rs");
@@ -527,14 +595,13 @@ var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
527
595
  var COMPACTION_LOG_SERVER = resolve(PROJECT_ROOT, "src/mcp/compaction-log-server.ts");
528
596
  var SYSTEM_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/system-health-server.ts");
529
597
  var AGENT_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/agent-health-server.ts");
530
- var BACKGROUND_BASH_SERVER = resolve(PROJECT_ROOT, "src/mcp/background-bash-server.ts");
531
598
  var VAULT_SERVER = resolve(PROJECT_ROOT, "src/mcp/vault-server.ts");
532
599
  var BG_BASH_BASE_PORT = parsePortEnv(process.env.BG_BASH_BASE_PORT, 9700);
533
600
  var BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
534
601
  function loadOrCreateLocalSecret(envKey, filename, options = {}) {
535
602
  const envValue = envText(envKey);
536
603
  const secretFile = resolve(SECRETS_DIR, filename);
537
- mkdirSync2(dirname2(secretFile), { recursive: true });
604
+ mkdirSync3(dirname2(secretFile), { recursive: true });
538
605
  if (envValue) {
539
606
  if (options.persistEnvValue) {
540
607
  writeFileSync2(secretFile, `${envValue}
@@ -588,27 +655,27 @@ var BASHRS_SPILL_ROOT = resolve(RUN_DIR, "bashrs");
588
655
  var PLAYWRIGHT_BASE_PORT = parsePortEnv(process.env.PLAYWRIGHT_BASE_PORT, 9100);
589
656
  var PLAYWRIGHT_MAX_PORT = parsePortEnv(process.env.PLAYWRIGHT_MAX_PORT, 9499);
590
657
  var PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
591
- mkdirSync2(STATE_DIR, { recursive: true });
592
- mkdirSync2(DATA_DIR, { recursive: true });
593
- mkdirSync2(UPLOADS_DIR, { recursive: true });
594
- mkdirSync2(VAULT_DIR, { recursive: true, mode: 448 });
595
- mkdirSync2(LOG_DIR, { recursive: true });
596
- mkdirSync2(PROGRESS_DIR, { recursive: true });
597
- mkdirSync2(DM_CMD_DIR, { recursive: true });
598
- mkdirSync2(DM_RESP_DIR, { recursive: true });
599
- mkdirSync2(SESSION_INBOX_DIR, { recursive: true });
600
- mkdirSync2(SESSION_ASKS_DIR, { recursive: true });
601
- mkdirSync2(PLAYWRIGHT_PORTS_DIR, { recursive: true });
602
- mkdirSync2(WORKSPACE_DIR, { recursive: true });
603
- mkdirSync2(TOPIC_WORKSPACE_DIR, { recursive: true });
604
- mkdirSync2(SHARED_WIKI_DIR, { recursive: true });
605
- mkdirSync2(CRON_WORKSPACE_DIR, { recursive: true });
606
- mkdirSync2(DM_WORKSPACE_DIR, { recursive: true });
607
- mkdirSync2(SESSION_WORKSPACE_DIR, { recursive: true });
608
- mkdirSync2(BROWSER_DIR, { recursive: true });
609
- mkdirSync2(BROWSER_PROFILES_DIR, { recursive: true });
610
- mkdirSync2(BINARIES_DIR, { recursive: true });
611
- mkdirSync2(SECRETS_DIR, { recursive: true, mode: 448 });
658
+ mkdirSync3(STATE_DIR, { recursive: true });
659
+ mkdirSync3(DATA_DIR, { recursive: true });
660
+ mkdirSync3(UPLOADS_DIR, { recursive: true });
661
+ mkdirSync3(VAULT_DIR, { recursive: true, mode: 448 });
662
+ mkdirSync3(LOG_DIR, { recursive: true });
663
+ mkdirSync3(PROGRESS_DIR, { recursive: true });
664
+ mkdirSync3(DM_CMD_DIR, { recursive: true });
665
+ mkdirSync3(DM_RESP_DIR, { recursive: true });
666
+ mkdirSync3(SESSION_INBOX_DIR, { recursive: true });
667
+ mkdirSync3(SESSION_ASKS_DIR, { recursive: true });
668
+ mkdirSync3(PLAYWRIGHT_PORTS_DIR, { recursive: true });
669
+ mkdirSync3(WORKSPACE_DIR, { recursive: true });
670
+ mkdirSync3(TOPIC_WORKSPACE_DIR, { recursive: true });
671
+ mkdirSync3(SHARED_WIKI_DIR, { recursive: true });
672
+ mkdirSync3(CRON_WORKSPACE_DIR, { recursive: true });
673
+ mkdirSync3(DM_WORKSPACE_DIR, { recursive: true });
674
+ mkdirSync3(SESSION_WORKSPACE_DIR, { recursive: true });
675
+ mkdirSync3(BROWSER_DIR, { recursive: true });
676
+ mkdirSync3(BROWSER_PROFILES_DIR, { recursive: true });
677
+ mkdirSync3(BINARIES_DIR, { recursive: true });
678
+ mkdirSync3(SECRETS_DIR, { recursive: true, mode: 448 });
612
679
  var ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
613
680
  var AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
614
681
  var RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
@@ -631,10 +698,19 @@ var PDFTOTEXT_BIN = envText("PDFTOTEXT_BIN") ?? "pdftotext";
631
698
  var _envMaxTellDepth = Number.parseInt(process.env.MAX_TELL_DEPTH ?? "", 10);
632
699
  var MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
633
700
 
701
+ // ../../packages/core/src/query/session-inbox-path.ts
702
+ import { join as join2 } from "path";
703
+ var TOPIC_ID_FILE_PREFIX = "topic-id-";
704
+ var JSONL_SUFFIX = ".jsonl";
705
+ function sessionInboxPath(userId, topicId) {
706
+ const key = Buffer.from(topicId, "utf8").toString("base64url");
707
+ return join2(SESSION_INBOX_DIR, userId, `${TOPIC_ID_FILE_PREFIX}${key}${JSONL_SUFFIX}`);
708
+ }
709
+
634
710
  // ../../packages/core/src/storage/storage-host.ts
635
- import { mkdirSync as mkdirSync3 } from "fs";
711
+ import { mkdirSync as mkdirSync4 } from "fs";
636
712
  import { homedir as homedir2 } from "os";
637
- import { dirname as dirname3, join as join2, resolve as resolve2 } from "path";
713
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
638
714
 
639
715
  // ../../packages/core/src/storage/sqlite.ts
640
716
  var isBun = typeof process.versions.bun === "string";
@@ -699,13 +775,13 @@ function envPath(name, fallback) {
699
775
  return resolve2(value || fallback);
700
776
  }
701
777
  function defaultStateDir() {
702
- return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
778
+ return envPath("NEGOTIUM_STATE_DIR", join3(homedir2(), ".negotium"));
703
779
  }
704
780
  function defaultDataDir() {
705
- return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
781
+ return envPath("NEGOTIUM_DATA_DIR", join3(defaultStateDir(), "data"));
706
782
  }
707
783
  function defaultSessionsDatabasePath() {
708
- return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
784
+ return envPath("SESSIONS_DB_PATH", join3(resolveStorageDataDir(), "sessions.db"));
709
785
  }
710
786
  var SQLITE_INIT_RETRY_MS = 25;
711
787
  var SQLITE_INIT_TIMEOUT_MS = 5000;
@@ -742,7 +818,7 @@ function defaultDatabase() {
742
818
  return fallbackDatabase;
743
819
  if (fallbackDatabase)
744
820
  fallbackDatabase.close();
745
- mkdirSync3(dirname3(path), { recursive: true });
821
+ mkdirSync4(dirname3(path), { recursive: true });
746
822
  fallbackDatabase = new Database(path, { create: true });
747
823
  fallbackDatabasePath = path;
748
824
  initializeDatabase(fallbackDatabase);
@@ -800,6 +876,340 @@ var internalStorageDatabase = new Proxy({}, {
800
876
  // ../../packages/core/src/storage/forum-db.ts
801
877
  var db = internalStorageDatabase;
802
878
 
879
+ // ../../packages/core/src/storage/runtime-leases.ts
880
+ import { randomUUID } from "crypto";
881
+
882
+ // ../../packages/core/src/storage/runtime-topic-state.ts
883
+ registerStorageSchemaInitializer((database) => {
884
+ database.exec(`
885
+ CREATE TABLE IF NOT EXISTS runtime_topic_state (
886
+ topic_id TEXT PRIMARY KEY,
887
+ epoch INTEGER NOT NULL DEFAULT 0,
888
+ maintenance INTEGER NOT NULL DEFAULT 0 CHECK (maintenance IN (0, 1)),
889
+ maintenance_owner TEXT,
890
+ heartbeat_at INTEGER
891
+ )
892
+ `);
893
+ }, 32);
894
+
895
+ // ../../packages/core/src/storage/runtime-leases.ts
896
+ var RUNTIME_INSTANCE_ID = `${process.pid}-${randomUUID()}`;
897
+ registerStorageSchemaInitializer((database) => {
898
+ database.exec(`
899
+ CREATE TABLE IF NOT EXISTS runtime_turn_leases (
900
+ topic_id TEXT PRIMARY KEY,
901
+ query_id TEXT NOT NULL UNIQUE,
902
+ owner_id TEXT NOT NULL,
903
+ origin TEXT NOT NULL,
904
+ started_at INTEGER NOT NULL,
905
+ heartbeat_at INTEGER NOT NULL,
906
+ abort_requested INTEGER NOT NULL DEFAULT 0 CHECK (abort_requested IN (0, 1)),
907
+ abort_reason TEXT CHECK (abort_reason IS NULL OR abort_reason IN ('internal', 'external'))
908
+ );
909
+ CREATE INDEX IF NOT EXISTS idx_runtime_turn_leases_heartbeat ON runtime_turn_leases(heartbeat_at);
910
+ `);
911
+ }, 33);
912
+
913
+ // ../../packages/core/src/storage/runtime-process-leases.ts
914
+ var PROCESS_LEASE_STALE_MS = 5000;
915
+ var PROCESS_LEASE_HEARTBEAT_MS = 1000;
916
+ registerStorageSchemaInitializer((database) => {
917
+ database.exec(`
918
+ CREATE TABLE IF NOT EXISTS runtime_process_leases (
919
+ role TEXT PRIMARY KEY,
920
+ owner_id TEXT NOT NULL UNIQUE,
921
+ pid INTEGER NOT NULL,
922
+ started_at INTEGER NOT NULL,
923
+ heartbeat_at INTEGER NOT NULL
924
+ );
925
+ CREATE INDEX IF NOT EXISTS idx_runtime_process_leases_heartbeat ON runtime_process_leases(heartbeat_at);
926
+ `);
927
+ }, 34);
928
+ function rowToLease(row) {
929
+ return {
930
+ role: row.role,
931
+ ownerId: row.owner_id,
932
+ pid: Number(row.pid),
933
+ startedAt: Number(row.started_at),
934
+ heartbeatAt: Number(row.heartbeat_at)
935
+ };
936
+ }
937
+ function isProcessAlive(pid) {
938
+ if (!Number.isSafeInteger(pid) || pid <= 0)
939
+ return false;
940
+ try {
941
+ process.kill(pid, 0);
942
+ return true;
943
+ } catch (error) {
944
+ return error.code !== "ESRCH";
945
+ }
946
+ }
947
+ function removeDeadRuntimeProcessLease(role) {
948
+ const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
949
+ if (!row)
950
+ return;
951
+ const lease = rowToLease(row);
952
+ if (isProcessAlive(lease.pid))
953
+ return;
954
+ db.query(`DELETE FROM runtime_process_leases
955
+ WHERE role = ? AND owner_id = ? AND pid = ? AND heartbeat_at = ?`).run(lease.role, lease.ownerId, lease.pid, lease.heartbeatAt);
956
+ }
957
+ function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
958
+ const result = db.query(`UPDATE runtime_process_leases
959
+ SET heartbeat_at = ?
960
+ WHERE role = ? AND owner_id = ?`).run(now, role, ownerId);
961
+ return Number(result.changes ?? 0) > 0;
962
+ }
963
+ function releaseRuntimeProcessLease(role, ownerId) {
964
+ const result = db.query("DELETE FROM runtime_process_leases WHERE role = ? AND owner_id = ?").run(role, ownerId);
965
+ return Number(result.changes ?? 0) > 0;
966
+ }
967
+ function acquireRuntimeProcessLease(role, options = {}) {
968
+ const normalizedRole = role.trim();
969
+ if (!normalizedRole)
970
+ throw new Error("runtime process lease role must not be empty");
971
+ removeDeadRuntimeProcessLease(normalizedRole);
972
+ const ownerId = options.ownerId ?? `${RUNTIME_INSTANCE_ID}:${normalizedRole}`;
973
+ const pid = options.pid ?? process.pid;
974
+ const now = options.now ?? Date.now();
975
+ const staleMs = options.staleMs ?? PROCESS_LEASE_STALE_MS;
976
+ const result = db.query(`INSERT INTO runtime_process_leases (role, owner_id, pid, started_at, heartbeat_at)
977
+ VALUES (?, ?, ?, ?, ?)
978
+ ON CONFLICT(role) DO UPDATE SET
979
+ owner_id = excluded.owner_id,
980
+ pid = excluded.pid,
981
+ started_at = excluded.started_at,
982
+ heartbeat_at = excluded.heartbeat_at
983
+ WHERE runtime_process_leases.heartbeat_at < ?`).run(normalizedRole, ownerId, pid, now, now, now - staleMs);
984
+ if (Number(result.changes ?? 0) === 0)
985
+ return null;
986
+ let stopped = false;
987
+ const heartbeatMs = options.heartbeatMs ?? PROCESS_LEASE_HEARTBEAT_MS;
988
+ const timer = setInterval(() => {
989
+ if (stopped)
990
+ return;
991
+ if (heartbeatRuntimeProcessLease(normalizedRole, ownerId))
992
+ return;
993
+ stopped = true;
994
+ clearInterval(timer);
995
+ options.onLost?.();
996
+ }, heartbeatMs);
997
+ timer.unref?.();
998
+ return {
999
+ role: normalizedRole,
1000
+ ownerId,
1001
+ pid,
1002
+ startedAt: now,
1003
+ heartbeatAt: now,
1004
+ stop() {
1005
+ if (stopped)
1006
+ return;
1007
+ stopped = true;
1008
+ clearInterval(timer);
1009
+ releaseRuntimeProcessLease(normalizedRole, ownerId);
1010
+ }
1011
+ };
1012
+ }
1013
+
1014
+ // ../../packages/core/src/runtime/bashrs-completions.ts
1015
+ var defaultSink = ({ userId, topicId, bashId, message }) => {
1016
+ appendJsonlEntry(sessionInboxPath(userId, topicId), {
1017
+ type: "tell",
1018
+ from: "__bg_bash__",
1019
+ message,
1020
+ depth: 0,
1021
+ requestId: bashId,
1022
+ timestamp: new Date().toISOString()
1023
+ });
1024
+ };
1025
+ var completionSink = defaultSink;
1026
+ function setBashrsCompletionSink(sink) {
1027
+ completionSink = sink ?? defaultSink;
1028
+ }
1029
+ var PROCESS_ROLE = "worker:bashrs-completions";
1030
+ var RETENTION_MS = 15 * 60000;
1031
+ var TAIL_BYTES = 8 * 1024;
1032
+ function isJobResult(value) {
1033
+ if (!value || typeof value !== "object")
1034
+ return false;
1035
+ const v = value;
1036
+ return typeof v.bash_id === "string" && typeof v.owner === "string";
1037
+ }
1038
+ function readTail(filePath) {
1039
+ try {
1040
+ const { size } = statSync2(filePath);
1041
+ const fd = openSync2(filePath, "r");
1042
+ try {
1043
+ const start = Math.max(0, size - TAIL_BYTES);
1044
+ const buf = Buffer.alloc(size - start);
1045
+ readSync(fd, buf, 0, buf.length, start);
1046
+ return { text: buf.toString("utf8"), truncated: start > 0 };
1047
+ } finally {
1048
+ closeSync2(fd);
1049
+ }
1050
+ } catch {
1051
+ return { text: "", truncated: false };
1052
+ }
1053
+ }
1054
+ function buildMessage(dir, result) {
1055
+ const stdout = readTail(join4(dir, "stdout.log"));
1056
+ const stderr = readTail(join4(dir, "stderr.log"));
1057
+ const parts = [];
1058
+ if (stdout.text.trim() || stdout.truncated) {
1059
+ parts.push(`stdout${stdout.truncated ? ` (truncated, full output: ${join4(dir, "stdout.log")})` : ""}:
1060
+ ${stdout.text.trim()}`);
1061
+ }
1062
+ if (stderr.text.trim() || stderr.truncated) {
1063
+ parts.push(`stderr${stderr.truncated ? ` (truncated, full output: ${join4(dir, "stderr.log")})` : ""}:
1064
+ ${stderr.text.trim()}`);
1065
+ }
1066
+ const header = watchHeader(result) ?? `[background_bash ${result.bash_id} finished]`;
1067
+ const exitLine = result.unknown ? "exit code: unknown (bash-rs restarted while this job was running)" : `exit code: ${result.exit_code ?? "unknown"}`;
1068
+ return `${header}
1069
+ ${exitLine}
1070
+ ${parts.join(`
1071
+ `) || "(no output)"}`;
1072
+ }
1073
+ function watchHeader(result) {
1074
+ if (result.matched_line) {
1075
+ return `[background_bash_watch ${result.bash_id} matched]
1076
+ matched line: ${result.matched_line.slice(0, 500)}`;
1077
+ }
1078
+ if (result.watch_outcome === "timeout") {
1079
+ return `[background_bash_watch ${result.bash_id} timed out without a match]`;
1080
+ }
1081
+ if (result.watch_outcome === "exited") {
1082
+ return `[background_bash_watch ${result.bash_id} exited before matching]`;
1083
+ }
1084
+ return null;
1085
+ }
1086
+ function parseOwner(owner) {
1087
+ const nul = owner.indexOf("\x00");
1088
+ if (nul < 0)
1089
+ return null;
1090
+ const userId = owner.slice(0, nul);
1091
+ const topicId = owner.slice(nul + 1);
1092
+ if (!userId || !topicId)
1093
+ return null;
1094
+ return { userId, topicId };
1095
+ }
1096
+ function injectedMarker(dir) {
1097
+ return join4(dir, "result.json.injected");
1098
+ }
1099
+ async function flushBashrsCompletions() {
1100
+ let entries;
1101
+ try {
1102
+ entries = readdirSync(BASHRS_SPILL_ROOT);
1103
+ } catch {
1104
+ return;
1105
+ }
1106
+ const now = Date.now();
1107
+ for (const bashId of entries) {
1108
+ const dir = join4(BASHRS_SPILL_ROOT, bashId);
1109
+ const resultPath = join4(dir, "result.json");
1110
+ const marker = injectedMarker(dir);
1111
+ try {
1112
+ const markerStat = statSync2(marker);
1113
+ if (now - markerStat.mtimeMs > RETENTION_MS) {
1114
+ rmSync(dir, { recursive: true, force: true });
1115
+ }
1116
+ continue;
1117
+ } catch {}
1118
+ let raw;
1119
+ try {
1120
+ raw = readFileSync3(resultPath, "utf8");
1121
+ } catch {
1122
+ continue;
1123
+ }
1124
+ let result;
1125
+ try {
1126
+ result = JSON.parse(raw);
1127
+ } catch (err) {
1128
+ logger.warn({ err, dir }, "bashrs-completions: corrupt result.json, skipping");
1129
+ continue;
1130
+ }
1131
+ if (!isJobResult(result)) {
1132
+ logger.warn({ dir }, "bashrs-completions: result.json missing bash_id/owner, skipping");
1133
+ continue;
1134
+ }
1135
+ const parsed = parseOwner(result.owner);
1136
+ if (!parsed) {
1137
+ continue;
1138
+ }
1139
+ try {
1140
+ completionSink({
1141
+ userId: parsed.userId,
1142
+ topicId: parsed.topicId,
1143
+ bashId: result.bash_id,
1144
+ message: buildMessage(dir, result)
1145
+ });
1146
+ renameSync2(resultPath, marker);
1147
+ logger.info({ bashId: result.bash_id, userId: parsed.userId, topicId: parsed.topicId }, "bashrs-completions: delivered");
1148
+ } catch (err) {
1149
+ logger.warn({ err, dir }, "bashrs-completions: delivery failed, will retry");
1150
+ }
1151
+ }
1152
+ }
1153
+ var watcher = null;
1154
+ var fallbackTimer = null;
1155
+ var debouncedTrigger = null;
1156
+ var leadershipTimer = null;
1157
+ var workerLease = null;
1158
+ var workerStarted = false;
1159
+ function stopLeaderResources() {
1160
+ if (watcher) {
1161
+ watcher.close();
1162
+ watcher = null;
1163
+ }
1164
+ if (fallbackTimer) {
1165
+ clearInterval(fallbackTimer);
1166
+ fallbackTimer = null;
1167
+ }
1168
+ debouncedTrigger = null;
1169
+ }
1170
+ function tryBecomeLeader() {
1171
+ if (!workerStarted || workerLease)
1172
+ return;
1173
+ const acquired = acquireRuntimeProcessLease(PROCESS_ROLE, {
1174
+ onLost: () => {
1175
+ if (workerLease !== acquired)
1176
+ return;
1177
+ workerLease = null;
1178
+ stopLeaderResources();
1179
+ logger.warn("bashrs-completions: worker leadership lost");
1180
+ }
1181
+ });
1182
+ if (!acquired)
1183
+ return;
1184
+ workerLease = acquired;
1185
+ debouncedTrigger = debouncedFlush(flushBashrsCompletions, "bashrs-completions", 200);
1186
+ watcher = watchDir(BASHRS_SPILL_ROOT, () => debouncedTrigger?.());
1187
+ fallbackTimer = setInterval(() => debouncedTrigger?.(), FALLBACK_INTERVAL_MS);
1188
+ fallbackTimer.unref?.();
1189
+ logger.info({ dir: BASHRS_SPILL_ROOT, role: PROCESS_ROLE }, "bashrs-completions: worker leadership acquired");
1190
+ flushBashrsCompletions();
1191
+ }
1192
+ function startBashrsCompletionsWorker() {
1193
+ if (workerStarted)
1194
+ return () => {};
1195
+ workerStarted = true;
1196
+ tryBecomeLeader();
1197
+ leadershipTimer = setInterval(tryBecomeLeader, PROCESS_LEASE_HEARTBEAT_MS);
1198
+ leadershipTimer.unref?.();
1199
+ return () => {
1200
+ if (!workerStarted)
1201
+ return;
1202
+ workerStarted = false;
1203
+ if (leadershipTimer) {
1204
+ clearInterval(leadershipTimer);
1205
+ leadershipTimer = null;
1206
+ }
1207
+ stopLeaderResources();
1208
+ workerLease?.stop();
1209
+ workerLease = null;
1210
+ logger.info("bashrs-completions: worker stopped");
1211
+ };
1212
+ }
803
1213
  // ../../packages/core/src/storage/api-messages.ts
804
1214
  function initializeApiMessagesSchema() {
805
1215
  db.exec(`
@@ -1100,7 +1510,9 @@ export {
1100
1510
  topicAppLink,
1101
1511
  textResult,
1102
1512
  taskPanelMessageId,
1513
+ startBashrsCompletionsWorker,
1103
1514
  shouldWarnForContext,
1515
+ setBashrsCompletionSink,
1104
1516
  sanitizeTopicName,
1105
1517
  sanitizeId,
1106
1518
  sanitizeFileName,
@@ -1146,4 +1558,4 @@ export {
1146
1558
  CLAUDE_EFFORT_VALUES
1147
1559
  };
1148
1560
 
1149
- //# debugId=3113220EF57A6BCB64756E2164756E21
1561
+ //# debugId=A7EE5EAA6DA08CC064756E2164756E21