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.
- package/dist/agent-helpers.js +13 -12
- package/dist/agent-helpers.js.map +6 -6
- package/dist/background-bash.js +11 -11
- package/dist/background-bash.js.map +4 -4
- package/dist/browser-runtime.js +2 -3
- package/dist/browser-runtime.js.map +3 -3
- package/dist/{chunk-rnsnhcye.js → chunk-5g5a63vv.js} +11 -11
- package/dist/{chunk-rnsnhcye.js.map → chunk-5g5a63vv.js.map} +4 -4
- package/dist/hosted-agent.js +12 -12
- package/dist/hosted-agent.js.map +5 -5
- package/dist/main.js +582 -357
- package/dist/main.js.map +12 -11
- package/dist/mcp-factories.js +13 -12
- package/dist/mcp-factories.js.map +6 -6
- package/dist/mcp-servers.js +3 -2
- package/dist/mcp-servers.js.map +3 -3
- package/dist/prompts.js +2 -3
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +2 -3
- package/dist/query-runtime.js.map +3 -3
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/index.ts +9 -0
- package/dist/runtime/src/node-host.ts +9 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +30 -22
- package/dist/runtime/src/platform/config.ts +8 -7
- package/dist/runtime/src/runtime/bashrs-completions.ts +74 -14
- package/dist/runtime/src/runtime/public-helpers.ts +8 -0
- package/dist/runtime/src/runtime/turn-runner.ts +6 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +444 -32
- package/dist/runtime-helpers.js.map +10 -4
- package/dist/types/apps/negotium/src/mcp-servers.d.ts +16 -3
- package/dist/types/packages/core/src/platform/background-bash/manager.d.ts +6 -1
- package/dist/types/packages/core/src/platform/config.d.ts +8 -6
- package/dist/types/packages/core/src/runtime/bashrs-completions.d.ts +50 -0
- package/dist/types/packages/core/src/runtime/public-helpers.d.ts +2 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +2 -3
- package/dist/vault.js.map +3 -3
- package/install-bash-rs.mjs +5 -5
- package/package.json +1 -1
- package/dist/runtime/src/mcp/background-bash-server.ts +0 -756
package/dist/runtime-helpers.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
|
711
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
636
712
|
import { homedir as homedir2 } from "os";
|
|
637
|
-
import { dirname as dirname3, join as
|
|
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",
|
|
778
|
+
return envPath("NEGOTIUM_STATE_DIR", join3(homedir2(), ".negotium"));
|
|
703
779
|
}
|
|
704
780
|
function defaultDataDir() {
|
|
705
|
-
return envPath("NEGOTIUM_DATA_DIR",
|
|
781
|
+
return envPath("NEGOTIUM_DATA_DIR", join3(defaultStateDir(), "data"));
|
|
706
782
|
}
|
|
707
783
|
function defaultSessionsDatabasePath() {
|
|
708
|
-
return envPath("SESSIONS_DB_PATH",
|
|
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
|
-
|
|
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=
|
|
1561
|
+
//# debugId=A7EE5EAA6DA08CC064756E2164756E21
|