negotium 0.2.14 → 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 +2 -2
- package/dist/agent-helpers.js.map +2 -2
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +2 -2
- package/dist/main.js.map +2 -2
- package/dist/mcp-factories.js +2 -2
- package/dist/mcp-factories.js.map +2 -2
- package/dist/registry.js +2 -2
- package/dist/registry.js.map +2 -2
- package/dist/runtime/src/runtime/public-helpers.ts +8 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +443 -30
- package/dist/runtime-helpers.js.map +9 -3
- 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/package.json +1 -1
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";
|
|
@@ -533,7 +601,7 @@ var BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
|
|
|
533
601
|
function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
534
602
|
const envValue = envText(envKey);
|
|
535
603
|
const secretFile = resolve(SECRETS_DIR, filename);
|
|
536
|
-
|
|
604
|
+
mkdirSync3(dirname2(secretFile), { recursive: true });
|
|
537
605
|
if (envValue) {
|
|
538
606
|
if (options.persistEnvValue) {
|
|
539
607
|
writeFileSync2(secretFile, `${envValue}
|
|
@@ -587,27 +655,27 @@ var BASHRS_SPILL_ROOT = resolve(RUN_DIR, "bashrs");
|
|
|
587
655
|
var PLAYWRIGHT_BASE_PORT = parsePortEnv(process.env.PLAYWRIGHT_BASE_PORT, 9100);
|
|
588
656
|
var PLAYWRIGHT_MAX_PORT = parsePortEnv(process.env.PLAYWRIGHT_MAX_PORT, 9499);
|
|
589
657
|
var PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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 });
|
|
611
679
|
var ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
|
|
612
680
|
var AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
|
|
613
681
|
var RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
|
|
@@ -630,10 +698,19 @@ var PDFTOTEXT_BIN = envText("PDFTOTEXT_BIN") ?? "pdftotext";
|
|
|
630
698
|
var _envMaxTellDepth = Number.parseInt(process.env.MAX_TELL_DEPTH ?? "", 10);
|
|
631
699
|
var MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
|
|
632
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
|
+
|
|
633
710
|
// ../../packages/core/src/storage/storage-host.ts
|
|
634
|
-
import { mkdirSync as
|
|
711
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
635
712
|
import { homedir as homedir2 } from "os";
|
|
636
|
-
import { dirname as dirname3, join as
|
|
713
|
+
import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
637
714
|
|
|
638
715
|
// ../../packages/core/src/storage/sqlite.ts
|
|
639
716
|
var isBun = typeof process.versions.bun === "string";
|
|
@@ -698,13 +775,13 @@ function envPath(name, fallback) {
|
|
|
698
775
|
return resolve2(value || fallback);
|
|
699
776
|
}
|
|
700
777
|
function defaultStateDir() {
|
|
701
|
-
return envPath("NEGOTIUM_STATE_DIR",
|
|
778
|
+
return envPath("NEGOTIUM_STATE_DIR", join3(homedir2(), ".negotium"));
|
|
702
779
|
}
|
|
703
780
|
function defaultDataDir() {
|
|
704
|
-
return envPath("NEGOTIUM_DATA_DIR",
|
|
781
|
+
return envPath("NEGOTIUM_DATA_DIR", join3(defaultStateDir(), "data"));
|
|
705
782
|
}
|
|
706
783
|
function defaultSessionsDatabasePath() {
|
|
707
|
-
return envPath("SESSIONS_DB_PATH",
|
|
784
|
+
return envPath("SESSIONS_DB_PATH", join3(resolveStorageDataDir(), "sessions.db"));
|
|
708
785
|
}
|
|
709
786
|
var SQLITE_INIT_RETRY_MS = 25;
|
|
710
787
|
var SQLITE_INIT_TIMEOUT_MS = 5000;
|
|
@@ -741,7 +818,7 @@ function defaultDatabase() {
|
|
|
741
818
|
return fallbackDatabase;
|
|
742
819
|
if (fallbackDatabase)
|
|
743
820
|
fallbackDatabase.close();
|
|
744
|
-
|
|
821
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
745
822
|
fallbackDatabase = new Database(path, { create: true });
|
|
746
823
|
fallbackDatabasePath = path;
|
|
747
824
|
initializeDatabase(fallbackDatabase);
|
|
@@ -799,6 +876,340 @@ var internalStorageDatabase = new Proxy({}, {
|
|
|
799
876
|
// ../../packages/core/src/storage/forum-db.ts
|
|
800
877
|
var db = internalStorageDatabase;
|
|
801
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
|
+
}
|
|
802
1213
|
// ../../packages/core/src/storage/api-messages.ts
|
|
803
1214
|
function initializeApiMessagesSchema() {
|
|
804
1215
|
db.exec(`
|
|
@@ -1099,7 +1510,9 @@ export {
|
|
|
1099
1510
|
topicAppLink,
|
|
1100
1511
|
textResult,
|
|
1101
1512
|
taskPanelMessageId,
|
|
1513
|
+
startBashrsCompletionsWorker,
|
|
1102
1514
|
shouldWarnForContext,
|
|
1515
|
+
setBashrsCompletionSink,
|
|
1103
1516
|
sanitizeTopicName,
|
|
1104
1517
|
sanitizeId,
|
|
1105
1518
|
sanitizeFileName,
|
|
@@ -1145,4 +1558,4 @@ export {
|
|
|
1145
1558
|
CLAUDE_EFFORT_VALUES
|
|
1146
1559
|
};
|
|
1147
1560
|
|
|
1148
|
-
//# debugId=
|
|
1561
|
+
//# debugId=A7EE5EAA6DA08CC064756E2164756E21
|