@integrity-labs/agt-cli 0.28.565 → 0.28.567
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/bin/agt.js +4 -4
- package/dist/{chunk-TOGADVFR.js → chunk-FSWAPB5J.js} +40 -2
- package/dist/chunk-FSWAPB5J.js.map +1 -0
- package/dist/{chunk-I6EWKBSV.js → chunk-IALXJ6H3.js} +3 -3
- package/dist/{claude-pair-runtime-63Y4MIMU.js → claude-pair-runtime-4C5NQQHR.js} +2 -2
- package/dist/lib/manager-worker.js +1241 -424
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +29 -0
- package/dist/mcp/origami.js +29 -0
- package/dist/mcp/slack-channel.js +29 -0
- package/dist/mcp/telegram-channel.js +29 -0
- package/dist/{persistent-session-MLLLDI5U.js → persistent-session-DVTSZAPE.js} +2 -2
- package/dist/{responsiveness-probe-QMAIFW4P.js → responsiveness-probe-KEFPJXQ6.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-TOGADVFR.js.map +0 -1
- /package/dist/{chunk-I6EWKBSV.js.map → chunk-IALXJ6H3.js.map} +0 -0
- /package/dist/{claude-pair-runtime-63Y4MIMU.js.map → claude-pair-runtime-4C5NQQHR.js.map} +0 -0
- /package/dist/{persistent-session-MLLLDI5U.js.map → persistent-session-DVTSZAPE.js.map} +0 -0
- /package/dist/{responsiveness-probe-QMAIFW4P.js.map → responsiveness-probe-KEFPJXQ6.js.map} +0 -0
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
safeWriteJsonAtomic,
|
|
54
54
|
setConfigHash,
|
|
55
55
|
tripClass
|
|
56
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-IALXJ6H3.js";
|
|
57
57
|
import {
|
|
58
58
|
getProjectDir as getProjectDir2,
|
|
59
59
|
getReadyTasks,
|
|
@@ -182,17 +182,17 @@ import {
|
|
|
182
182
|
toOpencodeModel,
|
|
183
183
|
transcriptActivityAgeSeconds,
|
|
184
184
|
writeEgressAllowlist
|
|
185
|
-
} from "../chunk-
|
|
185
|
+
} from "../chunk-FSWAPB5J.js";
|
|
186
186
|
import {
|
|
187
187
|
reapOrphanChannelMcps
|
|
188
188
|
} from "../chunk-XWVM4KPK.js";
|
|
189
189
|
|
|
190
190
|
// src/lib/manager-worker.ts
|
|
191
|
-
import { createHash as
|
|
192
|
-
import { readFileSync as
|
|
191
|
+
import { createHash as createHash17 } from "crypto";
|
|
192
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync14, rmSync as rmSync5, readdirSync as readdirSync9, statSync as statSync8, copyFileSync } from "fs";
|
|
193
193
|
import { execFileSync as syncExecFile } from "child_process";
|
|
194
|
-
import { join as
|
|
195
|
-
import { homedir as
|
|
194
|
+
import { join as join33, dirname as dirname9, delimiter as pathDelimiter } from "path";
|
|
195
|
+
import { homedir as homedir15 } from "os";
|
|
196
196
|
import { fileURLToPath } from "url";
|
|
197
197
|
|
|
198
198
|
// src/lib/claude-code-upgrade-throttle.ts
|
|
@@ -3503,14 +3503,804 @@ async function reportSkip2(api2, agentId, conversationId, log2, codeName) {
|
|
|
3503
3503
|
}
|
|
3504
3504
|
}
|
|
3505
3505
|
|
|
3506
|
-
// src/lib/
|
|
3507
|
-
import {
|
|
3506
|
+
// src/lib/tool-call-audit.ts
|
|
3507
|
+
import { homedir as homedir9 } from "os";
|
|
3508
|
+
import { join as join17 } from "path";
|
|
3509
|
+
|
|
3510
|
+
// src/lib/tool-call-path-salt.ts
|
|
3511
|
+
import { randomBytes } from "crypto";
|
|
3512
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
3508
3513
|
import { homedir as homedir7 } from "os";
|
|
3509
|
-
import { join as join14 } from "path";
|
|
3510
|
-
var
|
|
3511
|
-
var
|
|
3514
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
3515
|
+
var SALT_BYTES = 32;
|
|
3516
|
+
var SALT_RE = /^[0-9a-f]{64}$/;
|
|
3517
|
+
function pathSaltPath(codeName, homeDir) {
|
|
3518
|
+
const home = homeDir ?? (process.env["HOME"]?.trim() || homedir7());
|
|
3519
|
+
const key = agentRuntimeKey(codeName, homeDir);
|
|
3520
|
+
return join14(home, ".augmented", key, "tool-call-path-salt");
|
|
3521
|
+
}
|
|
3522
|
+
function readToolCallPathSalt(codeName, homeDir) {
|
|
3523
|
+
let file;
|
|
3524
|
+
try {
|
|
3525
|
+
file = pathSaltPath(codeName, homeDir);
|
|
3526
|
+
} catch {
|
|
3527
|
+
return null;
|
|
3528
|
+
}
|
|
3529
|
+
try {
|
|
3530
|
+
if (existsSync3(file)) {
|
|
3531
|
+
const existing = readFileSync11(file, "utf-8").trim();
|
|
3532
|
+
if (SALT_RE.test(existing)) return existing;
|
|
3533
|
+
}
|
|
3534
|
+
} catch {
|
|
3535
|
+
}
|
|
3536
|
+
const salt = randomBytes(SALT_BYTES).toString("hex");
|
|
3537
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
3538
|
+
try {
|
|
3539
|
+
mkdirSync5(dirname5(file), { recursive: true, mode: 448 });
|
|
3540
|
+
writeFileSync6(tmp, `${salt}
|
|
3541
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
3542
|
+
renameSync3(tmp, file);
|
|
3543
|
+
} catch {
|
|
3544
|
+
try {
|
|
3545
|
+
unlinkSync(tmp);
|
|
3546
|
+
} catch {
|
|
3547
|
+
}
|
|
3548
|
+
return null;
|
|
3549
|
+
}
|
|
3550
|
+
return salt;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
// src/lib/tool-call-scan.ts
|
|
3554
|
+
import { statSync as statSync4 } from "fs";
|
|
3555
|
+
|
|
3556
|
+
// src/lib/host-archive-address.ts
|
|
3557
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
3558
|
+
var DEFAULT_ARCHIVE_ADDRESS_PATH = "/var/lib/augmented/session-archive-address.json";
|
|
3559
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3560
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3561
|
+
function str(value) {
|
|
3562
|
+
if (typeof value !== "string") return null;
|
|
3563
|
+
const trimmed = value.trim();
|
|
3564
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3565
|
+
}
|
|
3566
|
+
function readHostArchiveAddress(path) {
|
|
3567
|
+
const file = path ?? process.env["ARCHIVE_ADDRESS_FILE"]?.trim() ?? process.env["AGT_SESSION_ARCHIVE_ADDRESS_FILE"]?.trim() ?? DEFAULT_ARCHIVE_ADDRESS_PATH;
|
|
3568
|
+
const now = Date.now();
|
|
3569
|
+
const hit = cache.get(file);
|
|
3570
|
+
if (hit && now - hit.at < CACHE_TTL_MS) return hit.value;
|
|
3571
|
+
let value = null;
|
|
3572
|
+
try {
|
|
3573
|
+
const raw = readFileSync12(file, "utf-8");
|
|
3574
|
+
const parsed = JSON.parse(raw);
|
|
3575
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3576
|
+
const obj = parsed;
|
|
3577
|
+
const bucket = str(obj["bucket"]);
|
|
3578
|
+
const keyPrefix = str(obj["key_prefix"]);
|
|
3579
|
+
if (bucket && keyPrefix) {
|
|
3580
|
+
value = {
|
|
3581
|
+
bucket,
|
|
3582
|
+
region: str(obj["region"]),
|
|
3583
|
+
instanceId: str(obj["instance_id"]),
|
|
3584
|
+
keyPrefix
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
} catch {
|
|
3589
|
+
value = null;
|
|
3590
|
+
}
|
|
3591
|
+
cache.set(file, { at: now, value });
|
|
3592
|
+
return value;
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// src/lib/tool-call-extractor.ts
|
|
3596
|
+
import { closeSync, fstatSync, openSync, readFileSync as readFileSync13, readSync, readdirSync as readdirSync4 } from "fs";
|
|
3597
|
+
import { basename, join as join15, relative } from "path";
|
|
3598
|
+
import { StringDecoder } from "string_decoder";
|
|
3599
|
+
|
|
3600
|
+
// src/lib/tool-call-redaction.ts
|
|
3601
|
+
import { createHmac } from "crypto";
|
|
3602
|
+
|
|
3603
|
+
// ../../packages/core/dist/tool-calls/input-hash.js
|
|
3604
|
+
import { createHash as createHash7 } from "crypto";
|
|
3605
|
+
function hashToolInput(input) {
|
|
3606
|
+
if (input === void 0)
|
|
3607
|
+
return null;
|
|
3608
|
+
try {
|
|
3609
|
+
return createHash7("sha256").update(canonicalJson2(input)).digest("hex");
|
|
3610
|
+
} catch {
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
function canonicalJson2(value) {
|
|
3615
|
+
if (value === null || typeof value !== "object")
|
|
3616
|
+
return JSON.stringify(value) ?? "null";
|
|
3617
|
+
if (Array.isArray(value))
|
|
3618
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
3619
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3620
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson2(v)}`).join(",")}}`;
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
// src/lib/tool-call-redaction.ts
|
|
3624
|
+
var REDACTION_RULE_VERSION = "r2";
|
|
3625
|
+
var REDACTED_TARGET_SHAPES = {
|
|
3626
|
+
argv0: /^[A-Za-z0-9._+-]{1,64}$/,
|
|
3627
|
+
mcp_tool: /^[A-Za-z0-9._+-]{1,64}$/,
|
|
3628
|
+
path_hash: /^[0-9a-f]{32}$/,
|
|
3629
|
+
url_host: /^[A-Za-z0-9.-]{1,253}$/
|
|
3630
|
+
};
|
|
3631
|
+
var NONE = { target: null, kind: "none" };
|
|
3632
|
+
var SAFE_IDENTIFIER_RE = /^[A-Za-z0-9._+-]{1,64}$/;
|
|
3633
|
+
var ENV_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
3634
|
+
function hashPath(path, salt) {
|
|
3635
|
+
if (!salt) return null;
|
|
3636
|
+
return createHmac("sha256", salt).update(path).digest("hex").slice(0, 32);
|
|
3637
|
+
}
|
|
3638
|
+
function parseArgv0(command) {
|
|
3639
|
+
const firstLine = command.split("\n", 1)[0] ?? "";
|
|
3640
|
+
const tokens = firstLine.trim().split(/\s+/).filter(Boolean);
|
|
3641
|
+
let i = 0;
|
|
3642
|
+
while (i < tokens.length && ENV_ASSIGNMENT_RE.test(tokens[i])) i++;
|
|
3643
|
+
const token = tokens[i];
|
|
3644
|
+
if (!token) return { name: null, isPath: false };
|
|
3645
|
+
if (token.includes("/")) return { name: token, isPath: true };
|
|
3646
|
+
return { name: SAFE_IDENTIFIER_RE.test(token) ? token : null, isPath: false };
|
|
3647
|
+
}
|
|
3648
|
+
function stringField(input, field) {
|
|
3649
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
3650
|
+
const v = input[field];
|
|
3651
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
3652
|
+
}
|
|
3653
|
+
function parseMcpTool(toolName) {
|
|
3654
|
+
const m = /^mcp__[^_]+(?:_[^_]+)*?__(.+)$/.exec(toolName);
|
|
3655
|
+
const tool = m?.[1];
|
|
3656
|
+
return tool && SAFE_IDENTIFIER_RE.test(tool) ? tool : null;
|
|
3657
|
+
}
|
|
3658
|
+
function redactToolTarget(toolName, input, ctx) {
|
|
3659
|
+
const out = redactToolTargetInner(toolName, input, ctx);
|
|
3660
|
+
if (out.kind === "none") return out.target === null ? out : NONE;
|
|
3661
|
+
return out.target !== null && REDACTED_TARGET_SHAPES[out.kind].test(out.target) ? out : NONE;
|
|
3662
|
+
}
|
|
3663
|
+
function redactToolTargetInner(toolName, input, ctx) {
|
|
3664
|
+
if (ctx.hashOnly) return NONE;
|
|
3665
|
+
if (toolName.startsWith("mcp__")) {
|
|
3666
|
+
const tool = parseMcpTool(toolName);
|
|
3667
|
+
return tool ? { target: tool, kind: "mcp_tool" } : NONE;
|
|
3668
|
+
}
|
|
3669
|
+
switch (toolName) {
|
|
3670
|
+
case "Bash": {
|
|
3671
|
+
const command = stringField(input, "command");
|
|
3672
|
+
if (!command) return NONE;
|
|
3673
|
+
const { name, isPath } = parseArgv0(command);
|
|
3674
|
+
if (!name) return NONE;
|
|
3675
|
+
if (isPath) {
|
|
3676
|
+
const hashed = hashPath(name, ctx.pathHashSalt);
|
|
3677
|
+
return hashed ? { target: hashed, kind: "path_hash" } : NONE;
|
|
3678
|
+
}
|
|
3679
|
+
const scrubbed = scrubSensitive(name);
|
|
3680
|
+
return SAFE_IDENTIFIER_RE.test(scrubbed) ? { target: scrubbed, kind: "argv0" } : NONE;
|
|
3681
|
+
}
|
|
3682
|
+
// `MultiEdit`, `NotebookEdit` and `NotebookRead` are allowlisted WITHOUT
|
|
3683
|
+
// having been observed — 0 occurrences each across 81 real transcripts,
|
|
3684
|
+
// where only `Edit` appears. Normally that is exactly the speculative
|
|
3685
|
+
// widening this module refuses to do, so the exception is worth stating: the
|
|
3686
|
+
// sole disclosure on this arm is a keyed hash. If one of these names turns
|
|
3687
|
+
// out to belong to some unrelated future tool, the row carries an unreadable
|
|
3688
|
+
// digest of whatever its path field held — no content escapes either way.
|
|
3689
|
+
// Against that, omitting a real file tool is a SILENT audit gap, which is
|
|
3690
|
+
// the costlier error. (CodeRabbit.)
|
|
3691
|
+
case "Read":
|
|
3692
|
+
case "Write":
|
|
3693
|
+
case "Edit":
|
|
3694
|
+
case "MultiEdit": {
|
|
3695
|
+
const path = stringField(input, "file_path");
|
|
3696
|
+
if (!path) return NONE;
|
|
3697
|
+
const hashed = hashPath(path, ctx.pathHashSalt);
|
|
3698
|
+
return hashed ? { target: hashed, kind: "path_hash" } : NONE;
|
|
3699
|
+
}
|
|
3700
|
+
case "NotebookEdit":
|
|
3701
|
+
case "NotebookRead": {
|
|
3702
|
+
const path = stringField(input, "notebook_path");
|
|
3703
|
+
if (!path) return NONE;
|
|
3704
|
+
const hashed = hashPath(path, ctx.pathHashSalt);
|
|
3705
|
+
return hashed ? { target: hashed, kind: "path_hash" } : NONE;
|
|
3706
|
+
}
|
|
3707
|
+
case "WebFetch": {
|
|
3708
|
+
const url = stringField(input, "url");
|
|
3709
|
+
if (!url) return NONE;
|
|
3710
|
+
try {
|
|
3711
|
+
const host = new URL(url).hostname;
|
|
3712
|
+
return host ? { target: host, kind: "url_host" } : NONE;
|
|
3713
|
+
} catch {
|
|
3714
|
+
return NONE;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
default:
|
|
3718
|
+
return NONE;
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
// src/lib/tool-call-extractor.ts
|
|
3723
|
+
var EXTRACTOR_VERSION = "e1";
|
|
3724
|
+
function enumerateSessionTranscripts(transcriptDir, sessionId, projectsRoot) {
|
|
3725
|
+
const files = [];
|
|
3726
|
+
const mainAbs = join15(transcriptDir, `${sessionId}.jsonl`);
|
|
3727
|
+
files.push({
|
|
3728
|
+
absPath: mainAbs,
|
|
3729
|
+
relPath: relative(projectsRoot, mainAbs),
|
|
3730
|
+
ref: "main",
|
|
3731
|
+
isSubagent: false,
|
|
3732
|
+
subagentId: null
|
|
3733
|
+
});
|
|
3734
|
+
const subDir = join15(transcriptDir, sessionId, "subagents");
|
|
3735
|
+
let entries;
|
|
3736
|
+
try {
|
|
3737
|
+
entries = readdirSync4(subDir);
|
|
3738
|
+
} catch {
|
|
3739
|
+
return files;
|
|
3740
|
+
}
|
|
3741
|
+
for (const name of entries) {
|
|
3742
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
3743
|
+
const abs = join15(subDir, name);
|
|
3744
|
+
const stem = basename(name, ".jsonl");
|
|
3745
|
+
files.push({
|
|
3746
|
+
absPath: abs,
|
|
3747
|
+
relPath: relative(projectsRoot, abs),
|
|
3748
|
+
ref: `subagent:${stem}`,
|
|
3749
|
+
isSubagent: true,
|
|
3750
|
+
subagentId: stem.startsWith("agent-") ? stem.slice("agent-".length) : stem
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
3753
|
+
return files;
|
|
3754
|
+
}
|
|
3755
|
+
function scanLines(lines, file, opts) {
|
|
3756
|
+
const pending2 = /* @__PURE__ */ new Map();
|
|
3757
|
+
const ordered = [];
|
|
3758
|
+
for (const [lineOffset, rawLine, byteStart] of lines) {
|
|
3759
|
+
const trimmed = rawLine.trim();
|
|
3760
|
+
if (trimmed.length === 0) continue;
|
|
3761
|
+
let record;
|
|
3762
|
+
try {
|
|
3763
|
+
record = JSON.parse(trimmed);
|
|
3764
|
+
} catch {
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
if (typeof record !== "object" || record === null) continue;
|
|
3768
|
+
const message = record.message;
|
|
3769
|
+
const blocks = message && typeof message === "object" && Array.isArray(message.content) ? message.content : null;
|
|
3770
|
+
if (!blocks) continue;
|
|
3771
|
+
for (const raw of blocks) {
|
|
3772
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
3773
|
+
const block = raw;
|
|
3774
|
+
if (block.type === "tool_use") {
|
|
3775
|
+
const id = typeof block.id === "string" ? block.id : null;
|
|
3776
|
+
const name = typeof block.name === "string" ? block.name : null;
|
|
3777
|
+
if (!id || !name) continue;
|
|
3778
|
+
const { target, kind } = redactToolTarget(name, block.input, opts.redaction);
|
|
3779
|
+
const occurredAt = typeof record.timestamp === "string" ? record.timestamp : null;
|
|
3780
|
+
const built = {
|
|
3781
|
+
tool_use_id: id,
|
|
3782
|
+
tool_name: name,
|
|
3783
|
+
occurred_at: occurredAt,
|
|
3784
|
+
session_id: typeof record.sessionId === "string" ? record.sessionId : null,
|
|
3785
|
+
transcript_rel_path: file.relPath,
|
|
3786
|
+
transcript_ref: file.ref,
|
|
3787
|
+
line_offset: lineOffset,
|
|
3788
|
+
// The FILE is the sole authority, deliberately — not `record.isSidechain`.
|
|
3789
|
+
//
|
|
3790
|
+
// These three fields are one fact stated three ways and must not be
|
|
3791
|
+
// able to contradict each other: `is_subagent` true iff
|
|
3792
|
+
// `transcript_ref` starts with `subagent:` iff `subagent_id` is set.
|
|
3793
|
+
// Letting `record.isSidechain` raise `is_subagent` on its own could
|
|
3794
|
+
// produce is_subagent=true with transcript_ref='main' and a null
|
|
3795
|
+
// subagent_id — a row claiming a delegated worker made the call while
|
|
3796
|
+
// addressing the parent's transcript. `transcript_ref` is an EVIDENCE
|
|
3797
|
+
// ADDRESS: it must name the file the line actually lives in, so the
|
|
3798
|
+
// file has to win and the other two follow it. (CodeRabbit.)
|
|
3799
|
+
//
|
|
3800
|
+
// Nothing is lost today: no main-transcript record in the measured
|
|
3801
|
+
// corpus carried isSidechain on a tool_use, and every sidecar record
|
|
3802
|
+
// did. If Claude Code ever inlines sidechain turns into the main file,
|
|
3803
|
+
// record-level sidechain-ness needs its own field rather than
|
|
3804
|
+
// overloading this one.
|
|
3805
|
+
is_subagent: file.isSubagent,
|
|
3806
|
+
subagent_id: file.isSubagent ? typeof record.agentId === "string" && record.agentId ? record.agentId : file.subagentId : null,
|
|
3807
|
+
is_error: null,
|
|
3808
|
+
// resolved on pairing below
|
|
3809
|
+
input_sha256: hashToolInput(block.input),
|
|
3810
|
+
redacted_target: target,
|
|
3811
|
+
redacted_target_kind: kind,
|
|
3812
|
+
extractor_version: EXTRACTOR_VERSION,
|
|
3813
|
+
redaction_rule_version: REDACTION_RULE_VERSION,
|
|
3814
|
+
archive_bucket: opts.archive?.bucket ?? null,
|
|
3815
|
+
archive_region: opts.archive?.region ?? null,
|
|
3816
|
+
host_instance_id: opts.archive?.instanceId ?? null,
|
|
3817
|
+
archive_key_prefix: opts.archive?.keyPrefix ?? null
|
|
3818
|
+
};
|
|
3819
|
+
ordered.push(built);
|
|
3820
|
+
pending2.set(id, { record: built, byteStart });
|
|
3821
|
+
continue;
|
|
3822
|
+
}
|
|
3823
|
+
if (block.type === "tool_result") {
|
|
3824
|
+
const id = typeof block.tool_use_id === "string" ? block.tool_use_id : null;
|
|
3825
|
+
if (!id) continue;
|
|
3826
|
+
const call = pending2.get(id);
|
|
3827
|
+
if (!call) continue;
|
|
3828
|
+
call.record.is_error = "is_error" in block && block.is_error === null ? null : block.is_error === true;
|
|
3829
|
+
pending2.delete(id);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
return { records: ordered, pending: pending2 };
|
|
3834
|
+
}
|
|
3835
|
+
var WINDOW_CHUNK_BYTES = 64 * 1024;
|
|
3836
|
+
function extractTranscriptWindow(file, opts, from) {
|
|
3837
|
+
let fd;
|
|
3838
|
+
try {
|
|
3839
|
+
fd = openSync(file.absPath, "r");
|
|
3840
|
+
} catch {
|
|
3841
|
+
return {
|
|
3842
|
+
records: [],
|
|
3843
|
+
endByte: from.byteOffset,
|
|
3844
|
+
endLine: from.lineOffset,
|
|
3845
|
+
unpaired: [],
|
|
3846
|
+
missing: true,
|
|
3847
|
+
rewound: false
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
let startByte = from.byteOffset;
|
|
3851
|
+
let startLine = from.lineOffset;
|
|
3852
|
+
let rewound = false;
|
|
3853
|
+
try {
|
|
3854
|
+
const { size } = fstatSync(fd);
|
|
3855
|
+
if (startByte > size) {
|
|
3856
|
+
startByte = 0;
|
|
3857
|
+
startLine = 0;
|
|
3858
|
+
rewound = true;
|
|
3859
|
+
}
|
|
3860
|
+
} catch {
|
|
3861
|
+
}
|
|
3862
|
+
const decoder = new StringDecoder("utf8");
|
|
3863
|
+
const buf = Buffer.allocUnsafe(WINDOW_CHUNK_BYTES);
|
|
3864
|
+
let position = startByte;
|
|
3865
|
+
let runningByte = startByte;
|
|
3866
|
+
let lineOffset = startLine;
|
|
3867
|
+
let carry = "";
|
|
3868
|
+
const pairs = [];
|
|
3869
|
+
try {
|
|
3870
|
+
for (; ; ) {
|
|
3871
|
+
const bytesRead = readSync(fd, buf, 0, WINDOW_CHUNK_BYTES, position);
|
|
3872
|
+
if (bytesRead <= 0) break;
|
|
3873
|
+
position += bytesRead;
|
|
3874
|
+
carry += decoder.write(buf.subarray(0, bytesRead));
|
|
3875
|
+
let nl;
|
|
3876
|
+
while ((nl = carry.indexOf("\n")) !== -1) {
|
|
3877
|
+
const line = carry.slice(0, nl);
|
|
3878
|
+
pairs.push([lineOffset, line, runningByte]);
|
|
3879
|
+
runningByte += Buffer.byteLength(line, "utf8") + 1;
|
|
3880
|
+
lineOffset += 1;
|
|
3881
|
+
carry = carry.slice(nl + 1);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
} catch {
|
|
3885
|
+
} finally {
|
|
3886
|
+
try {
|
|
3887
|
+
closeSync(fd);
|
|
3888
|
+
} catch {
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
const { records, pending: pending2 } = scanLines(pairs, file, opts);
|
|
3892
|
+
const unpaired = [...pending2.values()].map((p) => ({
|
|
3893
|
+
toolUseId: p.record.tool_use_id,
|
|
3894
|
+
byteStart: p.byteStart,
|
|
3895
|
+
lineOffset: p.record.line_offset
|
|
3896
|
+
})).sort((a, b) => a.byteStart - b.byteStart);
|
|
3897
|
+
return { records, endByte: runningByte, endLine: lineOffset, unpaired, missing: false, rewound };
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// src/lib/tool-call-cursor.ts
|
|
3901
|
+
import { existsSync as existsSync4, readFileSync as readFileSync14 } from "fs";
|
|
3902
|
+
import { homedir as homedir8 } from "os";
|
|
3903
|
+
import { join as join16 } from "path";
|
|
3904
|
+
var COVERAGE_DISPOSITIONS = [
|
|
3905
|
+
"ok",
|
|
3906
|
+
"not_entitled",
|
|
3907
|
+
"ingest_failed",
|
|
3908
|
+
"batch_rejected",
|
|
3909
|
+
"agent_unbound",
|
|
3910
|
+
"transcript_missing"
|
|
3911
|
+
];
|
|
3912
|
+
function mayAdvance(outcome) {
|
|
3913
|
+
switch (outcome.kind) {
|
|
3914
|
+
case "ingested":
|
|
3915
|
+
return true;
|
|
3916
|
+
// Definitive: nothing will EVER be collected for this org. Holding would
|
|
3917
|
+
// re-offer the same lines forever against an answer that cannot change.
|
|
3918
|
+
case "not_entitled":
|
|
3919
|
+
return true;
|
|
3920
|
+
// A producer bug. Retrying is futile and holding wedges every later call
|
|
3921
|
+
// behind it, so step over — loudly, and counted.
|
|
3922
|
+
case "batch_rejected":
|
|
3923
|
+
return true;
|
|
3924
|
+
case "agent_unbound":
|
|
3925
|
+
return false;
|
|
3926
|
+
case "ingest_failed":
|
|
3927
|
+
return false;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
function dispositionFor(outcome) {
|
|
3931
|
+
switch (outcome.kind) {
|
|
3932
|
+
case "ingested":
|
|
3933
|
+
return "ok";
|
|
3934
|
+
case "not_entitled":
|
|
3935
|
+
return "not_entitled";
|
|
3936
|
+
case "batch_rejected":
|
|
3937
|
+
return "batch_rejected";
|
|
3938
|
+
case "agent_unbound":
|
|
3939
|
+
return "agent_unbound";
|
|
3940
|
+
case "ingest_failed":
|
|
3941
|
+
return "ingest_failed";
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
var UNPAIRED_GRACE_MS = 30 * 60 * 1e3;
|
|
3945
|
+
var KEY_SEP = "\0";
|
|
3946
|
+
function cursorKey(sessionId, transcriptRef) {
|
|
3947
|
+
return `${sessionId ?? ""}${KEY_SEP}${transcriptRef}`;
|
|
3948
|
+
}
|
|
3949
|
+
function parseCursorKey(key) {
|
|
3950
|
+
const i = key.indexOf(KEY_SEP);
|
|
3951
|
+
if (i < 0) return { sessionId: null, transcriptRef: key };
|
|
3952
|
+
const sessionId = key.slice(0, i);
|
|
3953
|
+
return { sessionId: sessionId.length > 0 ? sessionId : null, transcriptRef: key.slice(i + 1) };
|
|
3954
|
+
}
|
|
3955
|
+
function cursorStatePath(codeName, homeDir) {
|
|
3956
|
+
const home = homeDir ?? (process.env["HOME"]?.trim() || homedir8());
|
|
3957
|
+
const key = agentRuntimeKey(codeName, homeDir);
|
|
3958
|
+
return join16(home, ".augmented", key, "tool-call-cursors.json");
|
|
3959
|
+
}
|
|
3960
|
+
function loadCursors(path) {
|
|
3961
|
+
const out = /* @__PURE__ */ new Map();
|
|
3962
|
+
if (!existsSync4(path)) return out;
|
|
3963
|
+
try {
|
|
3964
|
+
const parsed = JSON.parse(readFileSync14(path, "utf-8"));
|
|
3965
|
+
if (!parsed || parsed.version !== 1 || typeof parsed.files !== "object") return out;
|
|
3966
|
+
for (const [k, v] of Object.entries(parsed.files)) {
|
|
3967
|
+
if (!v || typeof v !== "object") continue;
|
|
3968
|
+
const offsetsOk = ["byteOffset", "lineOffset", "startedFromLine", "callsExtracted"].every((f) => {
|
|
3969
|
+
const n = v[f];
|
|
3970
|
+
return n === void 0 || typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
|
|
3971
|
+
});
|
|
3972
|
+
if (typeof v.byteOffset !== "number" || typeof v.lineOffset !== "number" || !offsetsOk) continue;
|
|
3973
|
+
out.set(k, {
|
|
3974
|
+
byteOffset: v.byteOffset,
|
|
3975
|
+
lineOffset: v.lineOffset,
|
|
3976
|
+
startedFromLine: typeof v.startedFromLine === "number" ? v.startedFromLine : 0,
|
|
3977
|
+
callsExtracted: typeof v.callsExtracted === "number" ? v.callsExtracted : 0,
|
|
3978
|
+
unpairedSince: typeof v.unpairedSince === "string" ? v.unpairedSince : null,
|
|
3979
|
+
lastDisposition: COVERAGE_DISPOSITIONS.includes(v.lastDisposition) ? v.lastDisposition : "ok",
|
|
3980
|
+
lastScanAt: typeof v.lastScanAt === "string" ? v.lastScanAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
3981
|
+
firstScanAt: typeof v.firstScanAt === "string" ? v.firstScanAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
3982
|
+
transcriptRelPath: typeof v.transcriptRelPath === "string" ? v.transcriptRelPath : null
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
} catch {
|
|
3986
|
+
}
|
|
3987
|
+
return out;
|
|
3988
|
+
}
|
|
3989
|
+
function saveCursors(path, cursors) {
|
|
3990
|
+
const files = {};
|
|
3991
|
+
for (const [k, v] of cursors) files[k] = v;
|
|
3992
|
+
atomicWriteFileSync(path, JSON.stringify({ version: 1, files }, null, 2));
|
|
3993
|
+
}
|
|
3994
|
+
function nextCursor(args) {
|
|
3995
|
+
const { previous, endByte, endLine, earliestUnpaired, outcome, nowMs } = args;
|
|
3996
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
3997
|
+
const disposition = dispositionFor(outcome);
|
|
3998
|
+
if (!mayAdvance(outcome)) {
|
|
3999
|
+
return { ...previous, lastDisposition: disposition, lastScanAt: nowIso };
|
|
4000
|
+
}
|
|
4001
|
+
const callsExtracted = previous.callsExtracted + args.callsExtracted;
|
|
4002
|
+
if (!earliestUnpaired) {
|
|
4003
|
+
return {
|
|
4004
|
+
...previous,
|
|
4005
|
+
byteOffset: endByte,
|
|
4006
|
+
lineOffset: endLine,
|
|
4007
|
+
callsExtracted,
|
|
4008
|
+
unpairedSince: null,
|
|
4009
|
+
lastDisposition: disposition,
|
|
4010
|
+
lastScanAt: nowIso
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
4013
|
+
const since = previous.unpairedSince ?? nowIso;
|
|
4014
|
+
const waited = nowMs - Date.parse(since);
|
|
4015
|
+
if (!Number.isFinite(waited) || waited >= UNPAIRED_GRACE_MS) {
|
|
4016
|
+
return {
|
|
4017
|
+
...previous,
|
|
4018
|
+
byteOffset: endByte,
|
|
4019
|
+
lineOffset: endLine,
|
|
4020
|
+
callsExtracted,
|
|
4021
|
+
unpairedSince: null,
|
|
4022
|
+
lastDisposition: disposition,
|
|
4023
|
+
lastScanAt: nowIso
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
return {
|
|
4027
|
+
...previous,
|
|
4028
|
+
byteOffset: Math.max(previous.byteOffset, Math.min(endByte, earliestUnpaired.byteStart)),
|
|
4029
|
+
lineOffset: Math.max(previous.lineOffset, Math.min(endLine, earliestUnpaired.lineOffset)),
|
|
4030
|
+
callsExtracted,
|
|
4031
|
+
unpairedSince: since,
|
|
4032
|
+
lastDisposition: disposition,
|
|
4033
|
+
lastScanAt: nowIso
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function initialCursor(args) {
|
|
4037
|
+
const iso = new Date(args.nowMs).toISOString();
|
|
4038
|
+
return {
|
|
4039
|
+
byteOffset: args.startByte,
|
|
4040
|
+
lineOffset: args.startLine,
|
|
4041
|
+
startedFromLine: args.startLine,
|
|
4042
|
+
callsExtracted: 0,
|
|
4043
|
+
unpairedSince: null,
|
|
4044
|
+
lastDisposition: "ok",
|
|
4045
|
+
lastScanAt: iso,
|
|
4046
|
+
firstScanAt: iso,
|
|
4047
|
+
transcriptRelPath: args.transcriptRelPath
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4050
|
+
function coverageRowFor(key, cursor, versions) {
|
|
4051
|
+
const { sessionId, transcriptRef } = parseCursorKey(key);
|
|
4052
|
+
return {
|
|
4053
|
+
session_id: sessionId,
|
|
4054
|
+
transcript_ref: transcriptRef,
|
|
4055
|
+
transcript_rel_path: cursor.transcriptRelPath,
|
|
4056
|
+
through_line: cursor.lineOffset,
|
|
4057
|
+
through_byte: cursor.byteOffset,
|
|
4058
|
+
started_from_line: cursor.startedFromLine,
|
|
4059
|
+
calls_extracted: cursor.callsExtracted,
|
|
4060
|
+
last_disposition: cursor.lastDisposition,
|
|
4061
|
+
extractor_version: versions.extractor,
|
|
4062
|
+
redaction_rule_version: versions.redaction
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// src/lib/tool-call-scan.ts
|
|
4067
|
+
var MAX_CALLS_PER_POST = 500;
|
|
4068
|
+
var MAX_COVERAGE_ROWS_PER_POST = 200;
|
|
4069
|
+
function classifyIngestResult(result, error) {
|
|
4070
|
+
if (error) {
|
|
4071
|
+
if (error instanceof ApiError) {
|
|
4072
|
+
if (error.status === 404) return { kind: "agent_unbound" };
|
|
4073
|
+
if (error.status === 400 && error.body?.["code"] === "batch_too_large") {
|
|
4074
|
+
return { kind: "ingest_failed", reason: `oversize batch (HTTP 400): ${error.message}` };
|
|
4075
|
+
}
|
|
4076
|
+
if (error.status >= 400 && error.status < 500) {
|
|
4077
|
+
return { kind: "batch_rejected", reason: `HTTP ${error.status}: ${error.message}` };
|
|
4078
|
+
}
|
|
4079
|
+
return { kind: "ingest_failed", reason: `HTTP ${error.status}: ${error.message}` };
|
|
4080
|
+
}
|
|
4081
|
+
return { kind: "ingest_failed", reason: String(error?.message ?? error) };
|
|
4082
|
+
}
|
|
4083
|
+
const body = result ?? {};
|
|
4084
|
+
if (body["entitled"] === false) return { kind: "not_entitled" };
|
|
4085
|
+
if (body["ok"] === false) {
|
|
4086
|
+
return { kind: "ingest_failed", reason: String(body["message"] ?? "ok:false") };
|
|
4087
|
+
}
|
|
4088
|
+
return { kind: "ingested" };
|
|
4089
|
+
}
|
|
4090
|
+
function chunk(items, size) {
|
|
4091
|
+
const out = [];
|
|
4092
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
4093
|
+
return out;
|
|
4094
|
+
}
|
|
4095
|
+
function endOfFile(absPath) {
|
|
4096
|
+
try {
|
|
4097
|
+
return { byteOffset: statSync4(absPath).size, lineOffset: 0 };
|
|
4098
|
+
} catch {
|
|
4099
|
+
return null;
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
async function scanAgentToolCalls(args) {
|
|
4103
|
+
const now = args.now ?? Date.now;
|
|
4104
|
+
const log2 = args.log;
|
|
4105
|
+
const summary = {
|
|
4106
|
+
filesScanned: 0,
|
|
4107
|
+
callsExtracted: 0,
|
|
4108
|
+
callsIngested: 0,
|
|
4109
|
+
filesHeld: 0,
|
|
4110
|
+
coverageReported: 0,
|
|
4111
|
+
notEntitled: false
|
|
4112
|
+
};
|
|
4113
|
+
const path = args.cursorPath ?? cursorStatePath(args.codeName, args.homeDir);
|
|
4114
|
+
const cursors = loadCursors(path);
|
|
4115
|
+
const extractOpts = {
|
|
4116
|
+
transcriptDir: args.transcriptDir,
|
|
4117
|
+
archive: readHostArchiveAddress(),
|
|
4118
|
+
redaction: { pathHashSalt: args.pathHashSalt, hashOnly: args.hashOnly }
|
|
4119
|
+
};
|
|
4120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4121
|
+
const files = [];
|
|
4122
|
+
for (const sessionId of args.sessionIds) {
|
|
4123
|
+
for (const f of enumerateSessionTranscripts(args.transcriptDir, sessionId, args.projectsRoot)) {
|
|
4124
|
+
const key = cursorKey(sessionId, f.ref);
|
|
4125
|
+
if (seen.has(key)) continue;
|
|
4126
|
+
seen.add(key);
|
|
4127
|
+
files.push({ key, file: f });
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
const touched = [];
|
|
4131
|
+
for (const { key, file } of files) {
|
|
4132
|
+
let cursor = cursors.get(key);
|
|
4133
|
+
if (!cursor) {
|
|
4134
|
+
const end = endOfFile(file.absPath);
|
|
4135
|
+
if (!end) continue;
|
|
4136
|
+
cursor = initialCursor({
|
|
4137
|
+
startByte: args.backfill ? 0 : end.byteOffset,
|
|
4138
|
+
startLine: 0,
|
|
4139
|
+
transcriptRelPath: file.relPath,
|
|
4140
|
+
nowMs: now()
|
|
4141
|
+
});
|
|
4142
|
+
cursors.set(key, cursor);
|
|
4143
|
+
touched.push(key);
|
|
4144
|
+
if (!args.backfill && end.byteOffset > 0) {
|
|
4145
|
+
log2(
|
|
4146
|
+
`[tool-call-scan] first-seen ${file.relPath} ref=${file.ref} \u2014 starting at byte ${end.byteOffset} (scan-from-now; set backfill to include the existing ${end.byteOffset}B)`
|
|
4147
|
+
);
|
|
4148
|
+
}
|
|
4149
|
+
if (!args.backfill) continue;
|
|
4150
|
+
}
|
|
4151
|
+
const window = extractTranscriptWindow(file, extractOpts, {
|
|
4152
|
+
byteOffset: cursor.byteOffset,
|
|
4153
|
+
lineOffset: cursor.lineOffset
|
|
4154
|
+
});
|
|
4155
|
+
summary.filesScanned += 1;
|
|
4156
|
+
if (window.missing) {
|
|
4157
|
+
cursors.set(key, { ...cursor, lastDisposition: "transcript_missing", lastScanAt: new Date(now()).toISOString() });
|
|
4158
|
+
touched.push(key);
|
|
4159
|
+
continue;
|
|
4160
|
+
}
|
|
4161
|
+
if (window.rewound) {
|
|
4162
|
+
log2(`[tool-call-scan] ${file.relPath} ref=${file.ref} shrank below the cursor \u2014 re-reading from 0`);
|
|
4163
|
+
}
|
|
4164
|
+
summary.callsExtracted += window.records.length;
|
|
4165
|
+
const unpairedIds = new Set(window.unpaired.map((u) => u.toolUseId));
|
|
4166
|
+
const sendable = window.records.filter((r) => !unpairedIds.has(r.tool_use_id));
|
|
4167
|
+
let outcome = { kind: "ingested" };
|
|
4168
|
+
let attempted = false;
|
|
4169
|
+
let ingested = 0;
|
|
4170
|
+
for (const batch of chunk(sendable, MAX_CALLS_PER_POST)) {
|
|
4171
|
+
attempted = true;
|
|
4172
|
+
const res = await postCalls(args.api, args.agentId, batch);
|
|
4173
|
+
outcome = res.outcome;
|
|
4174
|
+
ingested += res.ingested;
|
|
4175
|
+
if (outcome.kind === "not_entitled") summary.notEntitled = true;
|
|
4176
|
+
if (outcome.kind !== "ingested" && outcome.kind !== "not_entitled") break;
|
|
4177
|
+
}
|
|
4178
|
+
summary.callsIngested += ingested;
|
|
4179
|
+
const computed = nextCursor({
|
|
4180
|
+
previous: cursor,
|
|
4181
|
+
endByte: window.endByte,
|
|
4182
|
+
endLine: window.endLine,
|
|
4183
|
+
earliestUnpaired: window.unpaired[0] ?? null,
|
|
4184
|
+
outcome,
|
|
4185
|
+
callsExtracted: sendable.length,
|
|
4186
|
+
nowMs: now()
|
|
4187
|
+
});
|
|
4188
|
+
const advanced = attempted ? computed : { ...computed, lastDisposition: cursor.lastDisposition };
|
|
4189
|
+
if (advanced.byteOffset === cursor.byteOffset && outcome.kind === "ingest_failed") {
|
|
4190
|
+
summary.filesHeld += 1;
|
|
4191
|
+
log2(
|
|
4192
|
+
`[tool-call-scan] HOLDING cursor for ${file.relPath} ref=${file.ref} at byte ${cursor.byteOffset}: ${outcome.reason}`
|
|
4193
|
+
);
|
|
4194
|
+
}
|
|
4195
|
+
cursors.set(key, advanced);
|
|
4196
|
+
touched.push(key);
|
|
4197
|
+
if (outcome.kind === "agent_unbound") {
|
|
4198
|
+
log2(`[tool-call-scan] host is not bound to agent ${args.agentId} \u2014 stopping this tick`);
|
|
4199
|
+
break;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
saveCursors(path, cursors);
|
|
4203
|
+
summary.coverageReported = await reportCoverage(args, cursors, touched, log2);
|
|
4204
|
+
return summary;
|
|
4205
|
+
}
|
|
4206
|
+
async function postCalls(api2, agentId, calls) {
|
|
4207
|
+
try {
|
|
4208
|
+
const res = await api2.post(
|
|
4209
|
+
"/host/tool-calls",
|
|
4210
|
+
{ agent_id: agentId, calls }
|
|
4211
|
+
);
|
|
4212
|
+
const outcome = classifyIngestResult(res);
|
|
4213
|
+
return { outcome, ingested: typeof res?.ingested === "number" ? res.ingested : 0 };
|
|
4214
|
+
} catch (err) {
|
|
4215
|
+
return { outcome: classifyIngestResult(void 0, err), ingested: 0 };
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
async function reportCoverage(args, cursors, touched, log2) {
|
|
4219
|
+
const rows = [];
|
|
4220
|
+
for (const key of new Set(touched)) {
|
|
4221
|
+
const c = cursors.get(key);
|
|
4222
|
+
if (c) rows.push(coverageRowFor(key, c, { extractor: EXTRACTOR_VERSION, redaction: REDACTION_RULE_VERSION }));
|
|
4223
|
+
}
|
|
4224
|
+
if (rows.length === 0) return 0;
|
|
4225
|
+
let reported = 0;
|
|
4226
|
+
for (const batch of chunk(rows, MAX_COVERAGE_ROWS_PER_POST)) {
|
|
4227
|
+
try {
|
|
4228
|
+
await args.api.post("/host/tool-calls/coverage", { agent_id: args.agentId, coverage: batch });
|
|
4229
|
+
reported += batch.length;
|
|
4230
|
+
} catch (err) {
|
|
4231
|
+
log2(`[tool-call-scan] coverage report failed (${batch.length} rows): ${String(err)}`);
|
|
4232
|
+
break;
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
return reported;
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/lib/tool-call-audit.ts
|
|
4239
|
+
var MIN_CHECK_INTERVAL_MS6 = 10 * 6e4;
|
|
4240
|
+
var ENTITLEMENT_RECHECK_MS = 30 * 6e4;
|
|
4241
|
+
var state5 = /* @__PURE__ */ new Map();
|
|
4242
|
+
function entitlementLatch(entry, nowMs) {
|
|
4243
|
+
if (!entry?.notEntitledAt) return false;
|
|
4244
|
+
return !hasElapsed(entry.notEntitledAt, nowMs, ENTITLEMENT_RECHECK_MS);
|
|
4245
|
+
}
|
|
4246
|
+
function hasElapsed(sinceMs, nowMs, windowMs) {
|
|
4247
|
+
const delta = nowMs - sinceMs;
|
|
4248
|
+
if (!Number.isFinite(delta) || delta < 0) return true;
|
|
4249
|
+
return delta >= windowMs;
|
|
4250
|
+
}
|
|
4251
|
+
async function maybeScanToolCalls(args) {
|
|
4252
|
+
const { api: api2, codeName, agentId, log: log2 } = args;
|
|
4253
|
+
const nowFn = args.now ?? Date.now;
|
|
4254
|
+
const nowMs = nowFn();
|
|
4255
|
+
try {
|
|
4256
|
+
const existing = state5.get(codeName);
|
|
4257
|
+
if (existing && !hasElapsed(existing.lastCheckedAt, nowMs, MIN_CHECK_INTERVAL_MS6)) return;
|
|
4258
|
+
if (entitlementLatch(existing, nowMs)) {
|
|
4259
|
+
state5.set(codeName, { lastCheckedAt: nowMs, notEntitledAt: existing?.notEntitledAt ?? null });
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
state5.set(codeName, { lastCheckedAt: nowMs, notEntitledAt: existing?.notEntitledAt ?? null });
|
|
4263
|
+
const salt = readToolCallPathSalt(codeName, args.homeDir);
|
|
4264
|
+
if (!salt) {
|
|
4265
|
+
log2(`[tool-call-audit] ${codeName}: no path-hash salt available \u2014 file targets withheld`);
|
|
4266
|
+
}
|
|
4267
|
+
const home = args.homeDir ?? (process.env["HOME"]?.trim() || homedir9());
|
|
4268
|
+
const projectsRoot = args.projectsRoot ?? join17(home, ".claude", "projects");
|
|
4269
|
+
const transcriptDir = args.transcriptDir ?? sessionTranscriptDir(getProjectDir(codeName));
|
|
4270
|
+
const current = peekCurrentSession(codeName);
|
|
4271
|
+
const sessionIds = current ? [current.sessionId] : [];
|
|
4272
|
+
const summary = await scanAgentToolCalls({
|
|
4273
|
+
api: api2,
|
|
4274
|
+
agentId,
|
|
4275
|
+
codeName,
|
|
4276
|
+
transcriptDir,
|
|
4277
|
+
projectsRoot,
|
|
4278
|
+
sessionIds,
|
|
4279
|
+
pathHashSalt: salt ?? "",
|
|
4280
|
+
...args.hashOnly === void 0 ? {} : { hashOnly: args.hashOnly },
|
|
4281
|
+
log: log2,
|
|
4282
|
+
now: nowFn,
|
|
4283
|
+
...args.homeDir === void 0 ? {} : { homeDir: args.homeDir },
|
|
4284
|
+
...args.cursorPath === void 0 ? {} : { cursorPath: args.cursorPath }
|
|
4285
|
+
});
|
|
4286
|
+
if (summary.notEntitled) {
|
|
4287
|
+
state5.set(codeName, { lastCheckedAt: nowMs, notEntitledAt: nowMs });
|
|
4288
|
+
} else if (summary.callsIngested > 0) {
|
|
4289
|
+
state5.set(codeName, { lastCheckedAt: nowMs, notEntitledAt: null });
|
|
4290
|
+
}
|
|
4291
|
+
} catch (err) {
|
|
4292
|
+
log2(`[tool-call-audit] ${codeName}: scan failed: ${err.message}`);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
// src/lib/activity-cache-monitor.ts
|
|
4297
|
+
import { existsSync as existsSync5, readFileSync as readFileSync15 } from "fs";
|
|
4298
|
+
import { homedir as homedir10 } from "os";
|
|
4299
|
+
import { join as join18 } from "path";
|
|
4300
|
+
var MIN_CHECK_INTERVAL_MS7 = 6e4;
|
|
4301
|
+
var STATS_CACHE_PATH = join18(homedir10(), ".claude", "stats-cache.json");
|
|
3512
4302
|
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3513
|
-
var
|
|
4303
|
+
var state6 = { lastObservedDate: null, lastCheckedAt: 0 };
|
|
3514
4304
|
function selectNewDailyRows(raw, lastObservedDate) {
|
|
3515
4305
|
let parsed;
|
|
3516
4306
|
try {
|
|
@@ -3549,24 +4339,24 @@ async function maybeReportActivityCache(args) {
|
|
|
3549
4339
|
const { api: api2, log: log2 } = args;
|
|
3550
4340
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
3551
4341
|
const nowMs = now.getTime();
|
|
3552
|
-
if (nowMs -
|
|
3553
|
-
|
|
3554
|
-
if (!
|
|
4342
|
+
if (nowMs - state6.lastCheckedAt < MIN_CHECK_INTERVAL_MS7) return;
|
|
4343
|
+
state6.lastCheckedAt = nowMs;
|
|
4344
|
+
if (!existsSync5(STATS_CACHE_PATH)) {
|
|
3555
4345
|
return;
|
|
3556
4346
|
}
|
|
3557
4347
|
let raw;
|
|
3558
4348
|
try {
|
|
3559
|
-
raw =
|
|
4349
|
+
raw = readFileSync15(STATS_CACHE_PATH, "utf-8");
|
|
3560
4350
|
} catch (err) {
|
|
3561
4351
|
log2(`[activity-cache] readFileSync failed: ${err.message}`);
|
|
3562
4352
|
return;
|
|
3563
4353
|
}
|
|
3564
|
-
const rows = selectNewDailyRows(raw,
|
|
4354
|
+
const rows = selectNewDailyRows(raw, state6.lastObservedDate);
|
|
3565
4355
|
if (rows.length === 0) return;
|
|
3566
4356
|
for (const row of rows) {
|
|
3567
4357
|
try {
|
|
3568
4358
|
await api2.post("/host/activity-observations", row);
|
|
3569
|
-
|
|
4359
|
+
state6.lastObservedDate = row.date;
|
|
3570
4360
|
} catch (err) {
|
|
3571
4361
|
log2(
|
|
3572
4362
|
`[activity-cache] POST /host/activity-observations failed for date=${row.date}: ${err.message}`
|
|
@@ -3741,7 +4531,7 @@ function bundleFingerprint(files) {
|
|
|
3741
4531
|
}
|
|
3742
4532
|
|
|
3743
4533
|
// src/lib/channel-config-hash.ts
|
|
3744
|
-
import { createHash as
|
|
4534
|
+
import { createHash as createHash8 } from "crypto";
|
|
3745
4535
|
var DERIVED_PRINCIPAL_ID_LIST_KEYS = [
|
|
3746
4536
|
"allowed_users",
|
|
3747
4537
|
"diagnostic_chat_ids",
|
|
@@ -3762,7 +4552,7 @@ function normalizeChannelConfigForHash(config2) {
|
|
|
3762
4552
|
}
|
|
3763
4553
|
var CHANNEL_WRITE_VERSION = 9;
|
|
3764
4554
|
function computeChannelConfigHash(input) {
|
|
3765
|
-
return
|
|
4555
|
+
return createHash8("sha256").update(
|
|
3766
4556
|
canonicalJson({
|
|
3767
4557
|
writeVersion: CHANNEL_WRITE_VERSION,
|
|
3768
4558
|
cliVersion: input.cliVersion,
|
|
@@ -3781,18 +4571,18 @@ function computeChannelConfigHash(input) {
|
|
|
3781
4571
|
}
|
|
3782
4572
|
|
|
3783
4573
|
// src/lib/channel-hash-cache.ts
|
|
3784
|
-
import { existsSync as
|
|
3785
|
-
import { join as
|
|
4574
|
+
import { existsSync as existsSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
|
|
4575
|
+
import { join as join19 } from "path";
|
|
3786
4576
|
var CACHE_FILENAME = "channel-hash-cache.json";
|
|
3787
4577
|
function getChannelHashCacheFile(configDir) {
|
|
3788
|
-
return
|
|
4578
|
+
return join19(configDir, CACHE_FILENAME);
|
|
3789
4579
|
}
|
|
3790
4580
|
function loadChannelHashCache(target, configDir) {
|
|
3791
4581
|
const path = getChannelHashCacheFile(configDir);
|
|
3792
|
-
if (!
|
|
4582
|
+
if (!existsSync6(path)) return;
|
|
3793
4583
|
let parsed;
|
|
3794
4584
|
try {
|
|
3795
|
-
parsed = JSON.parse(
|
|
4585
|
+
parsed = JSON.parse(readFileSync16(path, "utf-8"));
|
|
3796
4586
|
} catch {
|
|
3797
4587
|
return;
|
|
3798
4588
|
}
|
|
@@ -3806,14 +4596,14 @@ function saveChannelHashCache(source, configDir) {
|
|
|
3806
4596
|
const obj = {};
|
|
3807
4597
|
for (const [key, value] of source) obj[key] = value;
|
|
3808
4598
|
try {
|
|
3809
|
-
|
|
4599
|
+
writeFileSync7(path, JSON.stringify(obj, null, 2));
|
|
3810
4600
|
} catch {
|
|
3811
4601
|
}
|
|
3812
4602
|
}
|
|
3813
4603
|
|
|
3814
4604
|
// src/lib/sender-policy-baseline.ts
|
|
3815
|
-
import { existsSync as
|
|
3816
|
-
import { join as
|
|
4605
|
+
import { existsSync as existsSync7, readFileSync as readFileSync17 } from "fs";
|
|
4606
|
+
import { join as join20 } from "path";
|
|
3817
4607
|
var BASELINE_FILENAME = "sender-policy-baseline.json";
|
|
3818
4608
|
var SENDER_POLICY_BASELINE_VERSION = 1;
|
|
3819
4609
|
var BASELINE_CONCERNS = ["senderPolicy", "slackBehaviour", "msteamsBehaviour"];
|
|
@@ -3825,14 +4615,14 @@ function createDeliveryBaselineMaps() {
|
|
|
3825
4615
|
};
|
|
3826
4616
|
}
|
|
3827
4617
|
function getSenderPolicyBaselineFile(configDir) {
|
|
3828
|
-
return
|
|
4618
|
+
return join20(configDir, BASELINE_FILENAME);
|
|
3829
4619
|
}
|
|
3830
4620
|
function loadSenderPolicyBaseline(target, configDir, log2) {
|
|
3831
4621
|
const path = getSenderPolicyBaselineFile(configDir);
|
|
3832
|
-
if (!
|
|
4622
|
+
if (!existsSync7(path)) return;
|
|
3833
4623
|
let parsed;
|
|
3834
4624
|
try {
|
|
3835
|
-
parsed = JSON.parse(
|
|
4625
|
+
parsed = JSON.parse(readFileSync17(path, "utf-8"));
|
|
3836
4626
|
} catch (err) {
|
|
3837
4627
|
log2?.(
|
|
3838
4628
|
`[sender-policy] discarding corrupt ${BASELINE_FILENAME} (${err.message}) - restrictive-policy agents will take one fail-closed restart`
|
|
@@ -4355,37 +5145,37 @@ function planGlobalSkillSync(globalSkills, prevIds, hashOf, knownHash, options)
|
|
|
4355
5145
|
}
|
|
4356
5146
|
|
|
4357
5147
|
// src/lib/manager/integration-skill-cache.ts
|
|
4358
|
-
import { join as
|
|
5148
|
+
import { join as join21 } from "path";
|
|
4359
5149
|
function integrationSkillHashKey(agentId, skillId) {
|
|
4360
5150
|
return `plugin-skill:${agentId}:${skillId}`;
|
|
4361
5151
|
}
|
|
4362
|
-
function shouldWriteIntegrationSkill(
|
|
4363
|
-
return
|
|
5152
|
+
function shouldWriteIntegrationSkill(cache3, agentId, skillId, contentHash) {
|
|
5153
|
+
return cache3.get(integrationSkillHashKey(agentId, skillId)) !== contentHash;
|
|
4364
5154
|
}
|
|
4365
|
-
function rememberIntegrationSkill(
|
|
4366
|
-
|
|
5155
|
+
function rememberIntegrationSkill(cache3, agentId, skillId, contentHash) {
|
|
5156
|
+
cache3.set(integrationSkillHashKey(agentId, skillId), contentHash);
|
|
4367
5157
|
}
|
|
4368
|
-
function forgetIntegrationSkill(
|
|
4369
|
-
|
|
5158
|
+
function forgetIntegrationSkill(cache3, agentId, skillId) {
|
|
5159
|
+
cache3.delete(integrationSkillHashKey(agentId, skillId));
|
|
4370
5160
|
}
|
|
4371
5161
|
function removeIntegrationSkillFolder(opts) {
|
|
4372
5162
|
forgetIntegrationSkill(opts.cache, opts.agentId, opts.entry);
|
|
4373
5163
|
for (const dir of opts.dirs) {
|
|
4374
|
-
opts.removeDir(
|
|
5164
|
+
opts.removeDir(join21(dir, opts.entry));
|
|
4375
5165
|
}
|
|
4376
5166
|
}
|
|
4377
5167
|
|
|
4378
5168
|
// src/lib/manager/managed-skill-manifest.ts
|
|
4379
|
-
import { existsSync as
|
|
4380
|
-
import { dirname as
|
|
5169
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
5170
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
4381
5171
|
var MANIFEST_VERSION = 1;
|
|
4382
5172
|
function managedSkillManifestPath(agentRootDir) {
|
|
4383
|
-
return
|
|
5173
|
+
return join22(agentRootDir, "managed-skills.json");
|
|
4384
5174
|
}
|
|
4385
5175
|
function readManagedSkillManifest(path) {
|
|
4386
5176
|
try {
|
|
4387
|
-
if (!
|
|
4388
|
-
const parsed = JSON.parse(
|
|
5177
|
+
if (!existsSync8(path)) return /* @__PURE__ */ new Set();
|
|
5178
|
+
const parsed = JSON.parse(readFileSync18(path, "utf-8"));
|
|
4389
5179
|
const ids = Array.isArray(parsed?.globalSkillIds) ? parsed.globalSkillIds : [];
|
|
4390
5180
|
return new Set(ids.filter((id) => typeof id === "string" && id.length > 0));
|
|
4391
5181
|
} catch {
|
|
@@ -4394,12 +5184,12 @@ function readManagedSkillManifest(path) {
|
|
|
4394
5184
|
}
|
|
4395
5185
|
function writeManagedSkillManifest(path, ids) {
|
|
4396
5186
|
try {
|
|
4397
|
-
|
|
5187
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
4398
5188
|
const body = {
|
|
4399
5189
|
version: MANIFEST_VERSION,
|
|
4400
5190
|
globalSkillIds: [...ids].sort()
|
|
4401
5191
|
};
|
|
4402
|
-
|
|
5192
|
+
writeFileSync8(path, JSON.stringify(body, null, 2));
|
|
4403
5193
|
} catch {
|
|
4404
5194
|
}
|
|
4405
5195
|
}
|
|
@@ -4497,9 +5287,9 @@ function resolveModelChain(refreshData) {
|
|
|
4497
5287
|
}
|
|
4498
5288
|
|
|
4499
5289
|
// src/lib/manager/claude-auth.ts
|
|
4500
|
-
import { existsSync as
|
|
4501
|
-
import { join as
|
|
4502
|
-
import { homedir as
|
|
5290
|
+
import { existsSync as existsSync9, rmSync as rmSync3 } from "fs";
|
|
5291
|
+
import { join as join23 } from "path";
|
|
5292
|
+
import { homedir as homedir11 } from "os";
|
|
4503
5293
|
async function applyClaudeAuthToEnv(childEnv, label) {
|
|
4504
5294
|
const apiKey = getApiKey();
|
|
4505
5295
|
if (!apiKey) {
|
|
@@ -4511,10 +5301,10 @@ async function applyClaudeAuthToEnv(childEnv, label) {
|
|
|
4511
5301
|
throw new Error("claude_auth_mode=api_key but /host/exchange returned no decrypted key");
|
|
4512
5302
|
}
|
|
4513
5303
|
childEnv.ANTHROPIC_API_KEY = exchange.anthropicApiKey;
|
|
4514
|
-
const claudeDir =
|
|
5304
|
+
const claudeDir = join23(homedir11(), ".claude");
|
|
4515
5305
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
4516
|
-
const p =
|
|
4517
|
-
if (
|
|
5306
|
+
const p = join23(claudeDir, filename);
|
|
5307
|
+
if (existsSync9(p)) {
|
|
4518
5308
|
try {
|
|
4519
5309
|
rmSync3(p, { force: true });
|
|
4520
5310
|
log(`[${label}] Removed ${p} (api_key mode \u2014 preventing OAuth fallback)`);
|
|
@@ -4595,8 +5385,8 @@ function heartbeatRuntimeAuthFields(probeVerdict) {
|
|
|
4595
5385
|
}
|
|
4596
5386
|
|
|
4597
5387
|
// src/lib/manager/kanban/parsers.ts
|
|
4598
|
-
import { existsSync as
|
|
4599
|
-
import { join as
|
|
5388
|
+
import { existsSync as existsSync10, readFileSync as readFileSync19 } from "fs";
|
|
5389
|
+
import { join as join24 } from "path";
|
|
4600
5390
|
var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-summary"]);
|
|
4601
5391
|
var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
|
|
4602
5392
|
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
@@ -4632,21 +5422,21 @@ function nudgeIntervalForCount(nudgeCount, fullCadenceMs, maxCadenceMs) {
|
|
|
4632
5422
|
const exponent = Math.max(0, Math.min(nudgeCount - 1, 8));
|
|
4633
5423
|
return Math.min(fullCadenceMs * 3 ** exponent, Math.max(maxCadenceMs, fullCadenceMs));
|
|
4634
5424
|
}
|
|
4635
|
-
function shouldNudgeUnchangedBoard(signature,
|
|
4636
|
-
if (!
|
|
4637
|
-
if (
|
|
4638
|
-
return now -
|
|
5425
|
+
function shouldNudgeUnchangedBoard(signature, state8, now, fullCadenceMs, maxCadenceMs = fullCadenceMs) {
|
|
5426
|
+
if (!state8) return true;
|
|
5427
|
+
if (state8.signature !== signature) return true;
|
|
5428
|
+
return now - state8.nudgedAt >= nudgeIntervalForCount(state8.nudgeCount ?? 1, fullCadenceMs, maxCadenceMs);
|
|
4639
5429
|
}
|
|
4640
5430
|
function failureRetryIntervalForCount(failureCount, baseMs, maxMs) {
|
|
4641
5431
|
const exponent = Math.max(0, Math.min(failureCount - 1, 8));
|
|
4642
5432
|
return Math.min(baseMs * 3 ** exponent, Math.max(maxMs, baseMs));
|
|
4643
5433
|
}
|
|
4644
5434
|
var KANBAN_NOTICE_BREAKER_THRESHOLD = 5;
|
|
4645
|
-
function shouldAttemptNoticeEnqueue(
|
|
4646
|
-
const failures =
|
|
5435
|
+
function shouldAttemptNoticeEnqueue(state8, now, baseMs, maxMs, breakerThreshold = KANBAN_NOTICE_BREAKER_THRESHOLD) {
|
|
5436
|
+
const failures = state8?.failureCount ?? 0;
|
|
4647
5437
|
if (failures <= 0) return { attempt: true, breakerOpen: false };
|
|
4648
5438
|
const breakerOpen = failures >= breakerThreshold;
|
|
4649
|
-
const failedAt =
|
|
5439
|
+
const failedAt = state8?.failedAt;
|
|
4650
5440
|
const due = failedAt === void 0 || failedAt > now || now - failedAt >= failureRetryIntervalForCount(failures, baseMs, maxMs);
|
|
4651
5441
|
return { attempt: due, breakerOpen };
|
|
4652
5442
|
}
|
|
@@ -4748,12 +5538,12 @@ function getBuiltInSkillContent(skillId) {
|
|
|
4748
5538
|
if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId);
|
|
4749
5539
|
try {
|
|
4750
5540
|
const candidates = [
|
|
4751
|
-
|
|
4752
|
-
|
|
5541
|
+
join24(process.cwd(), "skills", skillId, "SKILL.md"),
|
|
5542
|
+
join24(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "..", "..", "skills", skillId, "SKILL.md")
|
|
4753
5543
|
];
|
|
4754
5544
|
for (const candidate of candidates) {
|
|
4755
|
-
if (
|
|
4756
|
-
const content =
|
|
5545
|
+
if (existsSync10(candidate)) {
|
|
5546
|
+
const content = readFileSync19(candidate, "utf-8");
|
|
4757
5547
|
const files = [{ relativePath: "SKILL.md", content }];
|
|
4758
5548
|
builtInSkillCache.set(skillId, files);
|
|
4759
5549
|
return files;
|
|
@@ -4894,19 +5684,19 @@ function formatBoardForPrompt(items, template) {
|
|
|
4894
5684
|
}
|
|
4895
5685
|
|
|
4896
5686
|
// src/lib/manager/kanban/nudge-state-cache.ts
|
|
4897
|
-
import { existsSync as
|
|
4898
|
-
import { join as
|
|
5687
|
+
import { existsSync as existsSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
|
|
5688
|
+
import { join as join25 } from "path";
|
|
4899
5689
|
var CACHE_FILENAME2 = "kanban-nudge-state.json";
|
|
4900
5690
|
var KANBAN_NUDGE_STATE_VERSION = 1;
|
|
4901
5691
|
function getKanbanNudgeStateFile(configDir) {
|
|
4902
|
-
return
|
|
5692
|
+
return join25(configDir, CACHE_FILENAME2);
|
|
4903
5693
|
}
|
|
4904
5694
|
function loadKanbanNudgeState(target, configDir) {
|
|
4905
5695
|
const path = getKanbanNudgeStateFile(configDir);
|
|
4906
|
-
if (!
|
|
5696
|
+
if (!existsSync11(path)) return;
|
|
4907
5697
|
let parsed;
|
|
4908
5698
|
try {
|
|
4909
|
-
parsed = JSON.parse(
|
|
5699
|
+
parsed = JSON.parse(readFileSync20(path, "utf-8"));
|
|
4910
5700
|
} catch {
|
|
4911
5701
|
return;
|
|
4912
5702
|
}
|
|
@@ -4936,15 +5726,15 @@ function loadKanbanNudgeState(target, configDir) {
|
|
|
4936
5726
|
function saveKanbanNudgeState(source, configDir) {
|
|
4937
5727
|
const path = getKanbanNudgeStateFile(configDir);
|
|
4938
5728
|
const agents = {};
|
|
4939
|
-
for (const [codeName,
|
|
5729
|
+
for (const [codeName, state8] of source) agents[codeName] = state8;
|
|
4940
5730
|
try {
|
|
4941
|
-
|
|
5731
|
+
writeFileSync9(path, JSON.stringify({ version: KANBAN_NUDGE_STATE_VERSION, agents }, null, 2));
|
|
4942
5732
|
} catch {
|
|
4943
5733
|
}
|
|
4944
5734
|
}
|
|
4945
5735
|
|
|
4946
5736
|
// src/lib/manager/kanban/notify.ts
|
|
4947
|
-
import { createHash as
|
|
5737
|
+
import { createHash as createHash9 } from "crypto";
|
|
4948
5738
|
async function enqueueKanbanNotice(opts) {
|
|
4949
5739
|
try {
|
|
4950
5740
|
const res = await api.post(
|
|
@@ -4959,7 +5749,7 @@ async function enqueueKanbanNotice(opts) {
|
|
|
4959
5749
|
return res.ok === true;
|
|
4960
5750
|
} catch (err) {
|
|
4961
5751
|
const errText = err instanceof Error ? err.message : String(err);
|
|
4962
|
-
const errId =
|
|
5752
|
+
const errId = createHash9("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4963
5753
|
log(`[kanban] notice enqueue failed for agent_id=${opts.agentId} error_id=${errId}`);
|
|
4964
5754
|
return false;
|
|
4965
5755
|
}
|
|
@@ -4974,7 +5764,7 @@ async function cancelKanbanNotice(agentId) {
|
|
|
4974
5764
|
return typeof res.cancelled === "number" ? res.cancelled : 0;
|
|
4975
5765
|
} catch (err) {
|
|
4976
5766
|
const errText = err instanceof Error ? err.message : String(err);
|
|
4977
|
-
const errId =
|
|
5767
|
+
const errId = createHash9("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4978
5768
|
log(`[kanban] notice cancel failed for agent_id=${agentId} error_id=${errId}`);
|
|
4979
5769
|
return null;
|
|
4980
5770
|
}
|
|
@@ -5380,7 +6170,7 @@ function deriveScheduledTaskNotify(task) {
|
|
|
5380
6170
|
}
|
|
5381
6171
|
|
|
5382
6172
|
// src/lib/manager/scheduler/runs.ts
|
|
5383
|
-
import { createHash as
|
|
6173
|
+
import { createHash as createHash10 } from "crypto";
|
|
5384
6174
|
async function startRun(opts) {
|
|
5385
6175
|
try {
|
|
5386
6176
|
const res = await api.post(
|
|
@@ -5395,7 +6185,7 @@ async function startRun(opts) {
|
|
|
5395
6185
|
};
|
|
5396
6186
|
} catch (err) {
|
|
5397
6187
|
const errText = err instanceof Error ? err.message : String(err);
|
|
5398
|
-
const errId =
|
|
6188
|
+
const errId = createHash10("sha256").update(errText).digest("hex").slice(0, 12);
|
|
5399
6189
|
log(`[runs] start failed for agent_id=${opts.agent_id} source_type=${opts.source_type} error_id=${errId}`);
|
|
5400
6190
|
return { run_id: null, kanban_item_id: null };
|
|
5401
6191
|
}
|
|
@@ -5434,7 +6224,7 @@ async function finishRun(runId, outcome, options = {}) {
|
|
|
5434
6224
|
return;
|
|
5435
6225
|
} catch (err) {
|
|
5436
6226
|
const errText = err instanceof Error ? err.message : String(err);
|
|
5437
|
-
const errId =
|
|
6227
|
+
const errId = createHash10("sha256").update(errText).digest("hex").slice(0, 12);
|
|
5438
6228
|
const status = err instanceof ApiError ? err.status : 0;
|
|
5439
6229
|
if (isRetryableFinishError(err) && attempt < maxRetries) {
|
|
5440
6230
|
log(
|
|
@@ -5463,7 +6253,7 @@ async function fetchPriorScheduledRuns(agentId, taskId) {
|
|
|
5463
6253
|
return rows.filter((r) => typeof r.output_text === "string" && r.output_text.length > 0).map((r) => ({ startedAt: r.started_at, output: r.output_text }));
|
|
5464
6254
|
} catch (err) {
|
|
5465
6255
|
const errText = err instanceof Error ? err.message : String(err);
|
|
5466
|
-
const errId =
|
|
6256
|
+
const errId = createHash10("sha256").update(errText).digest("hex").slice(0, 12);
|
|
5467
6257
|
log(`[runs] prior-runs lookup failed for task_id=${taskId} error_id=${errId}`);
|
|
5468
6258
|
return [];
|
|
5469
6259
|
}
|
|
@@ -5512,13 +6302,13 @@ function closeScheduledRunsForCode(codeName, outcome, reason) {
|
|
|
5512
6302
|
}
|
|
5513
6303
|
|
|
5514
6304
|
// src/lib/manager/scheduler/kanban-route.ts
|
|
5515
|
-
import { createHash as
|
|
5516
|
-
import { writeFileSync as
|
|
5517
|
-
import { homedir as
|
|
5518
|
-
import { join as
|
|
6305
|
+
import { createHash as createHash12 } from "crypto";
|
|
6306
|
+
import { writeFileSync as writeFileSync10, renameSync as renameSync4, mkdirSync as mkdirSync7, readFileSync as readFileSync21, unlinkSync as unlinkSync2 } from "fs";
|
|
6307
|
+
import { homedir as homedir12 } from "os";
|
|
6308
|
+
import { join as join26, dirname as dirname7 } from "path";
|
|
5519
6309
|
|
|
5520
6310
|
// src/lib/manager/scheduler/notify.ts
|
|
5521
|
-
import { createHash as
|
|
6311
|
+
import { createHash as createHash11 } from "crypto";
|
|
5522
6312
|
async function enqueueScheduledTaskNotice(opts) {
|
|
5523
6313
|
try {
|
|
5524
6314
|
const res = await api.post(
|
|
@@ -5535,7 +6325,7 @@ async function enqueueScheduledTaskNotice(opts) {
|
|
|
5535
6325
|
return res.ok === true;
|
|
5536
6326
|
} catch (err) {
|
|
5537
6327
|
const errText = err instanceof Error ? err.message : String(err);
|
|
5538
|
-
const errId =
|
|
6328
|
+
const errId = createHash11("sha256").update(errText).digest("hex").slice(0, 12);
|
|
5539
6329
|
log(
|
|
5540
6330
|
`[scheduled-kanban] notice enqueue failed for agent_id=${opts.agentId} task_id=${opts.taskId} error_id=${errId}`
|
|
5541
6331
|
);
|
|
@@ -5765,8 +6555,8 @@ async function runScheduledCardDelivery(codeName, agentId, cardId, completedBy,
|
|
|
5765
6555
|
markScheduledCardDeliveryComplete(cardId);
|
|
5766
6556
|
return "terminal";
|
|
5767
6557
|
}
|
|
5768
|
-
const
|
|
5769
|
-
const task =
|
|
6558
|
+
const state8 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
|
|
6559
|
+
const task = state8.tasks[card.source_ref];
|
|
5770
6560
|
if (!task) {
|
|
5771
6561
|
log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' \u2014 skipping`);
|
|
5772
6562
|
markScheduledCardDeliveryComplete(cardId);
|
|
@@ -5874,21 +6664,21 @@ function resolveScheduledSlackTarget(task) {
|
|
|
5874
6664
|
}
|
|
5875
6665
|
function stampScheduledTurnMarker(codeName, taskId, target) {
|
|
5876
6666
|
try {
|
|
5877
|
-
const file =
|
|
6667
|
+
const file = join26(homedir12(), ".augmented", codeName, SCHEDULED_TURN_MARKER_FILENAME);
|
|
5878
6668
|
const marker = { ts: Date.now(), task_id: taskId, ...target ? { target } : {} };
|
|
5879
6669
|
const tmp = `${file}.tmp`;
|
|
5880
|
-
|
|
5881
|
-
|
|
6670
|
+
writeFileSync10(tmp, JSON.stringify(marker), "utf8");
|
|
6671
|
+
renameSync4(tmp, file);
|
|
5882
6672
|
} catch (err) {
|
|
5883
6673
|
log(`[scheduled-kanban] scheduled-turn marker write failed for '${codeName}': ${err.message}`);
|
|
5884
6674
|
}
|
|
5885
6675
|
}
|
|
5886
6676
|
function clearScheduledTurnMarkerForTask(codeName, taskId) {
|
|
5887
|
-
const file =
|
|
6677
|
+
const file = join26(homedir12(), ".augmented", codeName, SCHEDULED_TURN_MARKER_FILENAME);
|
|
5888
6678
|
try {
|
|
5889
|
-
const raw = JSON.parse(
|
|
6679
|
+
const raw = JSON.parse(readFileSync21(file, "utf8"));
|
|
5890
6680
|
if (typeof raw?.task_id !== "string" || raw.task_id !== taskId) return;
|
|
5891
|
-
|
|
6681
|
+
unlinkSync2(file);
|
|
5892
6682
|
log(`[scheduled-kanban] scheduled-turn marker cleared for '${codeName}' (task ${taskId} complete)`);
|
|
5893
6683
|
} catch {
|
|
5894
6684
|
}
|
|
@@ -5946,9 +6736,9 @@ async function routeScheduledTaskViaKanban(codeName, agentId, task, prompt, dura
|
|
|
5946
6736
|
return false;
|
|
5947
6737
|
}
|
|
5948
6738
|
try {
|
|
5949
|
-
const doorbell = directChatDoorbellPath(agentId,
|
|
5950
|
-
|
|
5951
|
-
|
|
6739
|
+
const doorbell = directChatDoorbellPath(agentId, homedir12());
|
|
6740
|
+
mkdirSync7(dirname7(doorbell), { recursive: true });
|
|
6741
|
+
writeFileSync10(doorbell, String(Date.now()));
|
|
5952
6742
|
} catch (err) {
|
|
5953
6743
|
log(`[scheduled-kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
|
|
5954
6744
|
}
|
|
@@ -6014,24 +6804,24 @@ async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput,
|
|
|
6014
6804
|
const assertion = parseDeliverAssertion(rawOutput);
|
|
6015
6805
|
if (!assertion.deliver) {
|
|
6016
6806
|
const trimmed = (rawOutput ?? "").trim();
|
|
6017
|
-
const outputHash = trimmed.length === 0 ? "empty" :
|
|
6807
|
+
const outputHash = trimmed.length === 0 ? "empty" : createHash12("sha256").update(trimmed).digest("hex").slice(0, 12);
|
|
6018
6808
|
log(`[claude-scheduler] Suppressed by delivery_policy=conditional for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 ${assertion.vacuous ? "deliver marker had a vacuous reason" : "no deliver marker"}; output_len=${trimmed.length} output_hash=${outputHash}`);
|
|
6019
6809
|
if (delivery?.mode === "announce" && delivery.to) {
|
|
6020
6810
|
await reportDeliveryStatus(agentId, delivery.taskId, { status: "skipped", error_code: "SUPPRESSED_BY_POLICY" });
|
|
6021
6811
|
}
|
|
6022
6812
|
return { ok: true };
|
|
6023
6813
|
}
|
|
6024
|
-
const reasonHash =
|
|
6814
|
+
const reasonHash = createHash12("sha256").update(assertion.reason ?? "").digest("hex").slice(0, 12);
|
|
6025
6815
|
log(`[claude-scheduler] Delivering conditional run for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 agent asserted a concrete trigger (reason_len=${(assertion.reason ?? "").length} reason_hash=${reasonHash})`);
|
|
6026
6816
|
rawOutput = assertion.deliverable;
|
|
6027
6817
|
}
|
|
6028
6818
|
const classification = classifyOutput(rawOutput);
|
|
6029
6819
|
if (classification.action === "suppress") {
|
|
6030
6820
|
const trimmed = (rawOutput ?? "").trim();
|
|
6031
|
-
const outputHash = trimmed.length === 0 ? "empty" :
|
|
6821
|
+
const outputHash = trimmed.length === 0 ? "empty" : createHash12("sha256").update(trimmed).digest("hex").slice(0, 12);
|
|
6032
6822
|
log(`[claude-scheduler] Suppressing delivery for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 output_len=${trimmed.length} output_hash=${outputHash}`);
|
|
6033
6823
|
if (classification.suppressedNotes) {
|
|
6034
|
-
const notesHash =
|
|
6824
|
+
const notesHash = createHash12("sha256").update(classification.suppressedNotes).digest("hex").slice(0, 12);
|
|
6035
6825
|
log(`[claude-scheduler] Suppressed notes for '${codeName}' (task=${delivery?.taskId ?? "n/a"}) \u2014 notes_len=${classification.suppressedNotes.length} notes_hash=${notesHash}`);
|
|
6036
6826
|
}
|
|
6037
6827
|
if (delivery?.mode === "announce" && delivery.to) {
|
|
@@ -6097,13 +6887,13 @@ async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput,
|
|
|
6097
6887
|
}
|
|
6098
6888
|
|
|
6099
6889
|
// src/lib/manager/scheduler/execution.ts
|
|
6100
|
-
import { createHash as
|
|
6101
|
-
import { homedir as
|
|
6102
|
-
import { join as
|
|
6890
|
+
import { createHash as createHash13 } from "crypto";
|
|
6891
|
+
import { homedir as homedir13 } from "os";
|
|
6892
|
+
import { join as join28 } from "path";
|
|
6103
6893
|
|
|
6104
6894
|
// src/lib/agent-serving-probe.ts
|
|
6105
|
-
import { readFileSync as
|
|
6106
|
-
import { join as
|
|
6895
|
+
import { readFileSync as readFileSync22, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
6896
|
+
import { join as join27 } from "path";
|
|
6107
6897
|
var RATE_LIMIT_WINDOW_MS = 6 * 60 * 60 * 1e3;
|
|
6108
6898
|
function probeRateLimit(args) {
|
|
6109
6899
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
@@ -6112,23 +6902,23 @@ function probeRateLimit(args) {
|
|
|
6112
6902
|
const dir = args.transcriptDir ?? sessionTranscriptDir(args.projectDir);
|
|
6113
6903
|
let entries;
|
|
6114
6904
|
try {
|
|
6115
|
-
entries =
|
|
6905
|
+
entries = readdirSync5(dir);
|
|
6116
6906
|
} catch {
|
|
6117
6907
|
return UNKNOWN_RATE_LIMIT;
|
|
6118
6908
|
}
|
|
6119
6909
|
let newest = UNKNOWN_RATE_LIMIT;
|
|
6120
6910
|
for (const name of entries) {
|
|
6121
6911
|
if (!name.endsWith(".jsonl")) continue;
|
|
6122
|
-
const path =
|
|
6912
|
+
const path = join27(dir, name);
|
|
6123
6913
|
try {
|
|
6124
|
-
const st =
|
|
6914
|
+
const st = statSync5(path);
|
|
6125
6915
|
if (!st.isFile() || st.mtimeMs < startMs) continue;
|
|
6126
6916
|
} catch {
|
|
6127
6917
|
continue;
|
|
6128
6918
|
}
|
|
6129
6919
|
let content;
|
|
6130
6920
|
try {
|
|
6131
|
-
content =
|
|
6921
|
+
content = readFileSync22(path, "utf-8");
|
|
6132
6922
|
} catch {
|
|
6133
6923
|
continue;
|
|
6134
6924
|
}
|
|
@@ -6190,7 +6980,7 @@ function shouldLogUsageCapDeferral(site, codeName, limitedUntil) {
|
|
|
6190
6980
|
|
|
6191
6981
|
// src/lib/manager/scheduler/execution.ts
|
|
6192
6982
|
function claudePidFilePath() {
|
|
6193
|
-
return
|
|
6983
|
+
return join28(homedir13(), ".augmented", "manager-claude-pids.json");
|
|
6194
6984
|
}
|
|
6195
6985
|
var inFlightClaudePids = /* @__PURE__ */ new Map();
|
|
6196
6986
|
function registerClaudeSpawn(record) {
|
|
@@ -6210,24 +7000,24 @@ function unregisterClaudeSpawn(pid) {
|
|
|
6210
7000
|
}
|
|
6211
7001
|
async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData, durableScheduledNudge = false) {
|
|
6212
7002
|
const codeName = agent.code_name;
|
|
6213
|
-
const stableTasksHash =
|
|
6214
|
-
const boardHash = boardItems.length > 0 ?
|
|
7003
|
+
const stableTasksHash = createHash13("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
7004
|
+
const boardHash = boardItems.length > 0 ? createHash13("sha256").update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable })))).digest("hex").slice(0, 16) : "empty";
|
|
6215
7005
|
const resolvedModels = resolveModelChain(refreshData);
|
|
6216
|
-
const modelsHash =
|
|
7006
|
+
const modelsHash = createHash13("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
|
|
6217
7007
|
const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
|
|
6218
7008
|
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
6219
7009
|
if (combinedHash !== prevHash) {
|
|
6220
7010
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
6221
|
-
const
|
|
6222
|
-
claudeSchedulerStates.set(codeName,
|
|
7011
|
+
const state9 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
7012
|
+
claudeSchedulerStates.set(codeName, state9);
|
|
6223
7013
|
agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
|
|
6224
7014
|
log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
6225
7015
|
}
|
|
6226
7016
|
if (!claudeSchedulerStates.has(codeName)) {
|
|
6227
7017
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
6228
7018
|
}
|
|
6229
|
-
const
|
|
6230
|
-
const ready = getReadyTasks(
|
|
7019
|
+
const state8 = claudeSchedulerStates.get(codeName);
|
|
7020
|
+
const ready = getReadyTasks(state8, inFlightClaudeTasks);
|
|
6231
7021
|
if (ready.length === 0) return;
|
|
6232
7022
|
const limitedUntil = readUsageCapUntil({ codeName, projectDir: getProjectDir(codeName) });
|
|
6233
7023
|
if (limitedUntil) {
|
|
@@ -6260,19 +7050,19 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
6260
7050
|
}
|
|
6261
7051
|
|
|
6262
7052
|
// src/lib/occupancy-gate.ts
|
|
6263
|
-
import { closeSync, openSync, readdirSync as
|
|
6264
|
-
import { join as
|
|
7053
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync6, readSync as readSync2, statSync as statSync6 } from "fs";
|
|
7054
|
+
import { join as join29 } from "path";
|
|
6265
7055
|
var TAIL_BYTES = 256 * 1024;
|
|
6266
7056
|
var MAX_RECORD_ALIGN_BYTES = 8 * 1024 * 1024;
|
|
6267
7057
|
var GATE_MAX_BUCKET_AGE_MS = 30 * 6e4;
|
|
6268
7058
|
var TRANSCRIPT_MTIME_WINDOW_MS4 = GATE_MAX_BUCKET_AGE_MS + DEFAULT_NEIGHBOURHOOD_MS + 6e4;
|
|
6269
|
-
var
|
|
7059
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
6270
7060
|
function readAlignedTail(path, tailBytes, maxAlignBytes) {
|
|
6271
7061
|
let fd = null;
|
|
6272
7062
|
try {
|
|
6273
|
-
const size =
|
|
7063
|
+
const size = statSync6(path).size;
|
|
6274
7064
|
if (size <= 0) return { content: "", complete: true, truncated: false };
|
|
6275
|
-
fd =
|
|
7065
|
+
fd = openSync2(path, "r");
|
|
6276
7066
|
let readStart = Math.max(0, size - tailBytes);
|
|
6277
7067
|
if (readStart > 0) {
|
|
6278
7068
|
const scanFloor = Math.max(0, readStart - maxAlignBytes);
|
|
@@ -6283,7 +7073,7 @@ function readAlignedTail(path, tailBytes, maxAlignBytes) {
|
|
|
6283
7073
|
const from = Math.max(scanFloor, pos - CHUNK);
|
|
6284
7074
|
const len2 = pos - from;
|
|
6285
7075
|
const b = Buffer.allocUnsafe(len2);
|
|
6286
|
-
if (
|
|
7076
|
+
if (readSync2(fd, b, 0, len2, from) !== len2) return null;
|
|
6287
7077
|
const idx = b.lastIndexOf(10);
|
|
6288
7078
|
if (idx !== -1) {
|
|
6289
7079
|
boundary = from + idx + 1;
|
|
@@ -6301,14 +7091,14 @@ function readAlignedTail(path, tailBytes, maxAlignBytes) {
|
|
|
6301
7091
|
}
|
|
6302
7092
|
const len = size - readStart;
|
|
6303
7093
|
const buf = Buffer.allocUnsafe(len);
|
|
6304
|
-
if (
|
|
7094
|
+
if (readSync2(fd, buf, 0, len, readStart) !== len) return null;
|
|
6305
7095
|
return { content: buf.toString("utf-8", 0, len), complete: true, truncated: readStart > 0 };
|
|
6306
7096
|
} catch {
|
|
6307
7097
|
return null;
|
|
6308
7098
|
} finally {
|
|
6309
7099
|
if (fd != null) {
|
|
6310
7100
|
try {
|
|
6311
|
-
|
|
7101
|
+
closeSync2(fd);
|
|
6312
7102
|
} catch {
|
|
6313
7103
|
}
|
|
6314
7104
|
}
|
|
@@ -6340,26 +7130,26 @@ function candidateTranscriptPaths(dir) {
|
|
|
6340
7130
|
const paths = [];
|
|
6341
7131
|
let top;
|
|
6342
7132
|
try {
|
|
6343
|
-
top =
|
|
7133
|
+
top = readdirSync6(dir);
|
|
6344
7134
|
} catch {
|
|
6345
7135
|
return { paths, complete: false };
|
|
6346
7136
|
}
|
|
6347
7137
|
let complete = true;
|
|
6348
7138
|
for (const name of top) {
|
|
6349
7139
|
if (name.endsWith(".jsonl")) {
|
|
6350
|
-
paths.push(
|
|
7140
|
+
paths.push(join29(dir, name));
|
|
6351
7141
|
continue;
|
|
6352
7142
|
}
|
|
6353
|
-
const subDir =
|
|
7143
|
+
const subDir = join29(dir, name, "subagents");
|
|
6354
7144
|
let subs;
|
|
6355
7145
|
try {
|
|
6356
|
-
subs =
|
|
7146
|
+
subs = readdirSync6(subDir);
|
|
6357
7147
|
} catch (err) {
|
|
6358
7148
|
if (!isAbsentDirError(err)) complete = false;
|
|
6359
7149
|
continue;
|
|
6360
7150
|
}
|
|
6361
7151
|
for (const sub of subs) {
|
|
6362
|
-
if (sub.endsWith(".jsonl")) paths.push(
|
|
7152
|
+
if (sub.endsWith(".jsonl")) paths.push(join29(subDir, sub));
|
|
6363
7153
|
}
|
|
6364
7154
|
}
|
|
6365
7155
|
return { paths, complete };
|
|
@@ -6369,7 +7159,7 @@ function collectQualifyingTurns(codeName, nowMs, opts = {}) {
|
|
|
6369
7159
|
const tailBytes = opts.tailBytes ?? TAIL_BYTES;
|
|
6370
7160
|
const maxAlignBytes = opts.maxAlignBytes ?? MAX_RECORD_ALIGN_BYTES;
|
|
6371
7161
|
const sinceMs = nowMs - TRANSCRIPT_MTIME_WINDOW_MS4;
|
|
6372
|
-
const agentCache =
|
|
7162
|
+
const agentCache = cache2.get(codeName) ?? /* @__PURE__ */ new Map();
|
|
6373
7163
|
const nextCache = /* @__PURE__ */ new Map();
|
|
6374
7164
|
const all = /* @__PURE__ */ new Set();
|
|
6375
7165
|
let transcriptsRead = 0;
|
|
@@ -6382,7 +7172,7 @@ function collectQualifyingTurns(codeName, nowMs, opts = {}) {
|
|
|
6382
7172
|
for (const path of paths) {
|
|
6383
7173
|
let st;
|
|
6384
7174
|
try {
|
|
6385
|
-
st =
|
|
7175
|
+
st = statSync6(path);
|
|
6386
7176
|
} catch (err) {
|
|
6387
7177
|
if (!isAbsentDirError(err)) complete = false;
|
|
6388
7178
|
continue;
|
|
@@ -6418,7 +7208,7 @@ function collectQualifyingTurns(codeName, nowMs, opts = {}) {
|
|
|
6418
7208
|
}
|
|
6419
7209
|
for (const t of turns) all.add(t);
|
|
6420
7210
|
}
|
|
6421
|
-
|
|
7211
|
+
cache2.set(codeName, nextCache);
|
|
6422
7212
|
return { turns: [...all].sort((a, b) => a - b), transcriptsRead, complete, coveredFromMs };
|
|
6423
7213
|
}
|
|
6424
7214
|
function qualifyBuckets(buckets, qualifying, mode, nowMs, neighbourhoodMs = DEFAULT_NEIGHBOURHOOD_MS) {
|
|
@@ -6454,14 +7244,14 @@ function qualifyBuckets(buckets, qualifying, mode, nowMs, neighbourhoodMs = DEFA
|
|
|
6454
7244
|
}
|
|
6455
7245
|
|
|
6456
7246
|
// src/lib/pane-occupancy-sampler.ts
|
|
6457
|
-
import { statSync as
|
|
7247
|
+
import { statSync as statSync7 } from "fs";
|
|
6458
7248
|
var SAMPLE_INTERVAL_MS = 1e4;
|
|
6459
7249
|
var IDLE_GAP_MS = 12e4;
|
|
6460
7250
|
var POST_IDLE_CREDIT_MS = 6e4;
|
|
6461
7251
|
var lastMtimeMs = /* @__PURE__ */ new Map();
|
|
6462
7252
|
function paneMtimeMs(codeName) {
|
|
6463
7253
|
try {
|
|
6464
|
-
return
|
|
7254
|
+
return statSync7(paneLogPath(codeName)).mtimeMs;
|
|
6465
7255
|
} catch {
|
|
6466
7256
|
return null;
|
|
6467
7257
|
}
|
|
@@ -6507,7 +7297,7 @@ function stopPaneOccupancySampler() {
|
|
|
6507
7297
|
}
|
|
6508
7298
|
|
|
6509
7299
|
// src/lib/opencode-slack-ingest.ts
|
|
6510
|
-
import { createHash as
|
|
7300
|
+
import { createHash as createHash14 } from "crypto";
|
|
6511
7301
|
|
|
6512
7302
|
// ../../packages/core/dist/channels/slack-rich-text.js
|
|
6513
7303
|
var MAX_DEPTH = 12;
|
|
@@ -7458,7 +8248,7 @@ function startSlackIngest(config2) {
|
|
|
7458
8248
|
// src/lib/opencode-slack-ingest.ts
|
|
7459
8249
|
var ingests = /* @__PURE__ */ new Map();
|
|
7460
8250
|
function fingerprint(params) {
|
|
7461
|
-
return
|
|
8251
|
+
return createHash14("sha256").update(
|
|
7462
8252
|
JSON.stringify({
|
|
7463
8253
|
appToken: params.appToken,
|
|
7464
8254
|
botToken: params.botToken,
|
|
@@ -7539,7 +8329,7 @@ function stopAllOpencodeSlackIngests(log2) {
|
|
|
7539
8329
|
}
|
|
7540
8330
|
|
|
7541
8331
|
// src/lib/manager/opencode-scheduler.ts
|
|
7542
|
-
import { createHash as
|
|
8332
|
+
import { createHash as createHash15 } from "crypto";
|
|
7543
8333
|
var MAX_OPENCODE_SCHED_CONCURRENCY = 2;
|
|
7544
8334
|
var opencodeSchedulerStates = /* @__PURE__ */ new Map();
|
|
7545
8335
|
var inFlightOpencodeTasks = /* @__PURE__ */ new Set();
|
|
@@ -7553,19 +8343,19 @@ function shouldDeliver(task, reply) {
|
|
|
7553
8343
|
}
|
|
7554
8344
|
async function syncAndCheckOpencodeScheduler(agent, tasks, refreshData) {
|
|
7555
8345
|
const codeName = agent.code_name;
|
|
7556
|
-
const tasksHash =
|
|
8346
|
+
const tasksHash = createHash15("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
7557
8347
|
if (knownOpencodeTaskHashes.get(agent.agent_id) !== tasksHash) {
|
|
7558
8348
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
7559
|
-
const
|
|
7560
|
-
opencodeSchedulerStates.set(codeName,
|
|
8349
|
+
const state9 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
8350
|
+
opencodeSchedulerStates.set(codeName, state9);
|
|
7561
8351
|
knownOpencodeTaskHashes.set(agent.agent_id, tasksHash);
|
|
7562
8352
|
log(`[opencode-scheduler] tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
7563
8353
|
}
|
|
7564
8354
|
if (!opencodeSchedulerStates.has(codeName)) {
|
|
7565
8355
|
opencodeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
7566
8356
|
}
|
|
7567
|
-
const
|
|
7568
|
-
const ready = getReadyTasks(
|
|
8357
|
+
const state8 = opencodeSchedulerStates.get(codeName);
|
|
8358
|
+
const ready = getReadyTasks(state8, inFlightOpencodeTasks);
|
|
7569
8359
|
if (ready.length === 0) return;
|
|
7570
8360
|
if (!isOpencodeSessionHealthy(codeName)) {
|
|
7571
8361
|
log(`[opencode-scheduler] '${codeName}' has ${ready.length} due task(s) but no healthy serve yet - deferring`);
|
|
@@ -7631,10 +8421,10 @@ async function fireOpencodeScheduledTask(agent, task) {
|
|
|
7631
8421
|
}
|
|
7632
8422
|
|
|
7633
8423
|
// src/lib/opencode-telegram-ingest.ts
|
|
7634
|
-
import { createHash as
|
|
7635
|
-
import { existsSync as
|
|
8424
|
+
import { createHash as createHash16 } from "crypto";
|
|
8425
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync23, renameSync as renameSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "fs";
|
|
7636
8426
|
import { randomUUID } from "crypto";
|
|
7637
|
-
import { join as
|
|
8427
|
+
import { join as join30 } from "path";
|
|
7638
8428
|
|
|
7639
8429
|
// src/lib/telegram-ingest.ts
|
|
7640
8430
|
import https2 from "https";
|
|
@@ -8167,7 +8957,7 @@ function anySignal(signals) {
|
|
|
8167
8957
|
// src/lib/opencode-telegram-ingest.ts
|
|
8168
8958
|
var ingests2 = /* @__PURE__ */ new Map();
|
|
8169
8959
|
function fingerprint2(params) {
|
|
8170
|
-
return
|
|
8960
|
+
return createHash16("sha256").update(
|
|
8171
8961
|
JSON.stringify({
|
|
8172
8962
|
botToken: params.botToken,
|
|
8173
8963
|
allowedChats: [...params.allowedChats].sort(),
|
|
@@ -8182,7 +8972,7 @@ function createFileOffsetStore(codeName, log2, currentBotId) {
|
|
|
8182
8972
|
let filePath;
|
|
8183
8973
|
try {
|
|
8184
8974
|
dir = getFramework("opencode").getAgentDir(codeName);
|
|
8185
|
-
filePath =
|
|
8975
|
+
filePath = join30(dir, "telegram-getupdates-offset-opencode.json");
|
|
8186
8976
|
} catch {
|
|
8187
8977
|
dir = null;
|
|
8188
8978
|
filePath = null;
|
|
@@ -8191,7 +8981,7 @@ function createFileOffsetStore(codeName, log2, currentBotId) {
|
|
|
8191
8981
|
load() {
|
|
8192
8982
|
if (!filePath) return 0;
|
|
8193
8983
|
try {
|
|
8194
|
-
const parsed = JSON.parse(
|
|
8984
|
+
const parsed = JSON.parse(readFileSync23(filePath, "utf-8"));
|
|
8195
8985
|
if (currentBotId != null && typeof parsed?.bot_id === "number" && parsed.bot_id !== currentBotId) {
|
|
8196
8986
|
log2(`[telegram-ingest:${codeName}] offset cursor belongs to a different bot; ignoring (bot swap)`);
|
|
8197
8987
|
return 0;
|
|
@@ -8209,8 +8999,8 @@ function createFileOffsetStore(codeName, log2, currentBotId) {
|
|
|
8209
8999
|
if (!filePath || !dir) return;
|
|
8210
9000
|
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
8211
9001
|
try {
|
|
8212
|
-
|
|
8213
|
-
|
|
9002
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
9003
|
+
writeFileSync11(
|
|
8214
9004
|
tmpPath,
|
|
8215
9005
|
JSON.stringify({
|
|
8216
9006
|
offset,
|
|
@@ -8220,11 +9010,11 @@ function createFileOffsetStore(codeName, log2, currentBotId) {
|
|
|
8220
9010
|
}),
|
|
8221
9011
|
{ mode: 384 }
|
|
8222
9012
|
);
|
|
8223
|
-
|
|
9013
|
+
renameSync5(tmpPath, filePath);
|
|
8224
9014
|
} catch (err) {
|
|
8225
9015
|
log2(`[telegram-ingest:${codeName}] offset persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8226
9016
|
try {
|
|
8227
|
-
if (
|
|
9017
|
+
if (existsSync12(tmpPath)) unlinkSync3(tmpPath);
|
|
8228
9018
|
} catch {
|
|
8229
9019
|
}
|
|
8230
9020
|
}
|
|
@@ -8440,10 +9230,10 @@ function recordWedgeForCards(states, inProgressCardIds, nowMs, config2) {
|
|
|
8440
9230
|
function pruneCardStates(states, liveInProgressCardIds, nowMs, config2) {
|
|
8441
9231
|
const live = liveInProgressCardIds instanceof Set ? liveInProgressCardIds : new Set(liveInProgressCardIds);
|
|
8442
9232
|
const next = /* @__PURE__ */ new Map();
|
|
8443
|
-
for (const [id,
|
|
9233
|
+
for (const [id, state8] of states) {
|
|
8444
9234
|
if (!live.has(id)) continue;
|
|
8445
|
-
if (nowMs -
|
|
8446
|
-
next.set(id,
|
|
9235
|
+
if (nowMs - state8.lastWedgeAtMs > config2.cooldownMs) continue;
|
|
9236
|
+
next.set(id, state8);
|
|
8447
9237
|
}
|
|
8448
9238
|
return next;
|
|
8449
9239
|
}
|
|
@@ -8461,24 +9251,24 @@ function partitionActionableByPoison(actionable, states, config2) {
|
|
|
8461
9251
|
}
|
|
8462
9252
|
|
|
8463
9253
|
// src/lib/restart-flags.ts
|
|
8464
|
-
import { existsSync as
|
|
8465
|
-
import { homedir as
|
|
8466
|
-
import { join as
|
|
9254
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync24, renameSync as renameSync6, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
9255
|
+
import { homedir as homedir14 } from "os";
|
|
9256
|
+
import { join as join31 } from "path";
|
|
8467
9257
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8468
9258
|
function restartFlagsDir() {
|
|
8469
|
-
return
|
|
9259
|
+
return join31(homedir14(), ".augmented", "restart-flags");
|
|
8470
9260
|
}
|
|
8471
9261
|
function flagPath(codeName) {
|
|
8472
|
-
return
|
|
9262
|
+
return join31(restartFlagsDir(), `${codeName}.flag`);
|
|
8473
9263
|
}
|
|
8474
9264
|
function readRestartFlags() {
|
|
8475
9265
|
const dir = restartFlagsDir();
|
|
8476
|
-
if (!
|
|
9266
|
+
if (!existsSync13(dir)) return [];
|
|
8477
9267
|
const out = [];
|
|
8478
|
-
for (const entry of
|
|
9268
|
+
for (const entry of readdirSync7(dir)) {
|
|
8479
9269
|
if (!entry.endsWith(".flag")) continue;
|
|
8480
9270
|
try {
|
|
8481
|
-
const raw =
|
|
9271
|
+
const raw = readFileSync24(join31(dir, entry), "utf8");
|
|
8482
9272
|
const parsed = JSON.parse(raw);
|
|
8483
9273
|
if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
|
|
8484
9274
|
parsed.codeName = entry.replace(/\.flag$/, "");
|
|
@@ -8496,7 +9286,7 @@ function readRestartFlags() {
|
|
|
8496
9286
|
}
|
|
8497
9287
|
function deleteRestartFlag(codeName) {
|
|
8498
9288
|
const path = flagPath(codeName);
|
|
8499
|
-
if (
|
|
9289
|
+
if (existsSync13(path)) {
|
|
8500
9290
|
rmSync4(path, { force: true });
|
|
8501
9291
|
}
|
|
8502
9292
|
}
|
|
@@ -8596,8 +9386,8 @@ async function sendError(flag, opts, text) {
|
|
|
8596
9386
|
}
|
|
8597
9387
|
|
|
8598
9388
|
// src/lib/restart-context.ts
|
|
8599
|
-
import { readdirSync as
|
|
8600
|
-
import { dirname as
|
|
9389
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync25, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
9390
|
+
import { dirname as dirname8, join as join32 } from "path";
|
|
8601
9391
|
var SLACK_PENDING_INBOUND_DIRNAME = "slack-pending-inbound";
|
|
8602
9392
|
var SLACK_RESTART_CONTEXT_DIRNAME = "slack-restart-context";
|
|
8603
9393
|
var MAX_TOPIC_CHARS = 140;
|
|
@@ -8606,13 +9396,13 @@ var RECONSTRUCT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
8606
9396
|
var WINDOW_PAD_MS3 = 5 * 6e4;
|
|
8607
9397
|
var DEFAULT_MAX_MARKERS_PER_AGENT = 25;
|
|
8608
9398
|
function augmentedAgentDir(codeName) {
|
|
8609
|
-
return
|
|
9399
|
+
return dirname8(getProjectDir(codeName));
|
|
8610
9400
|
}
|
|
8611
9401
|
function slackPendingInboundDir(codeName) {
|
|
8612
|
-
return
|
|
9402
|
+
return join32(augmentedAgentDir(codeName), SLACK_PENDING_INBOUND_DIRNAME);
|
|
8613
9403
|
}
|
|
8614
9404
|
function slackRestartContextDir(codeName) {
|
|
8615
|
-
return
|
|
9405
|
+
return join32(augmentedAgentDir(codeName), SLACK_RESTART_CONTEXT_DIRNAME);
|
|
8616
9406
|
}
|
|
8617
9407
|
function sanitizeTopic(raw) {
|
|
8618
9408
|
const cleaned = raw.replace(/\s+/g, " ").trim().replace(/[<>]/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -8647,14 +9437,14 @@ function computeRestartContextHints(markers, allTurns, nowMs, reconstruct = reco
|
|
|
8647
9437
|
}
|
|
8648
9438
|
function safeReaddir(dir) {
|
|
8649
9439
|
try {
|
|
8650
|
-
return
|
|
9440
|
+
return readdirSync8(dir);
|
|
8651
9441
|
} catch {
|
|
8652
9442
|
return [];
|
|
8653
9443
|
}
|
|
8654
9444
|
}
|
|
8655
9445
|
function readStrandedMarker(path) {
|
|
8656
9446
|
try {
|
|
8657
|
-
const parsed = JSON.parse(
|
|
9447
|
+
const parsed = JSON.parse(readFileSync25(path, "utf-8"));
|
|
8658
9448
|
if (typeof parsed.channel === "string" && typeof parsed.thread_ts === "string") {
|
|
8659
9449
|
return { channel: parsed.channel, thread_ts: parsed.thread_ts };
|
|
8660
9450
|
}
|
|
@@ -8663,8 +9453,8 @@ function readStrandedMarker(path) {
|
|
|
8663
9453
|
return null;
|
|
8664
9454
|
}
|
|
8665
9455
|
function writeHintFile(path, dir, hint) {
|
|
8666
|
-
|
|
8667
|
-
|
|
9456
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
9457
|
+
writeFileSync13(path, JSON.stringify(hint), { mode: 384 });
|
|
8668
9458
|
}
|
|
8669
9459
|
function pruneHintsExcept(codeName, freshFilenames) {
|
|
8670
9460
|
const ctxDir = slackRestartContextDir(codeName);
|
|
@@ -8672,7 +9462,7 @@ function pruneHintsExcept(codeName, freshFilenames) {
|
|
|
8672
9462
|
if (!filename.endsWith(".json")) continue;
|
|
8673
9463
|
if (freshFilenames.has(filename)) continue;
|
|
8674
9464
|
try {
|
|
8675
|
-
|
|
9465
|
+
unlinkSync4(join32(ctxDir, filename));
|
|
8676
9466
|
} catch {
|
|
8677
9467
|
}
|
|
8678
9468
|
}
|
|
@@ -8693,7 +9483,7 @@ function refreshSlackRestartContextHints(codeNames, opts = {}) {
|
|
|
8693
9483
|
}
|
|
8694
9484
|
const markers = [];
|
|
8695
9485
|
for (const filename of markerFilenames.slice(0, cap)) {
|
|
8696
|
-
const parsed = readStrandedMarker(
|
|
9486
|
+
const parsed = readStrandedMarker(join32(markerDir, filename));
|
|
8697
9487
|
if (parsed) markers.push({ filename, channel: parsed.channel, thread_ts: parsed.thread_ts });
|
|
8698
9488
|
}
|
|
8699
9489
|
if (markers.length === 0) {
|
|
@@ -8707,7 +9497,7 @@ function refreshSlackRestartContextHints(codeNames, opts = {}) {
|
|
|
8707
9497
|
const freshFilenames = /* @__PURE__ */ new Set();
|
|
8708
9498
|
for (const { filename, hint } of hints) {
|
|
8709
9499
|
try {
|
|
8710
|
-
writeHintFile(
|
|
9500
|
+
writeHintFile(join32(ctxDir, filename), ctxDir, hint);
|
|
8711
9501
|
freshFilenames.add(filename);
|
|
8712
9502
|
} catch (err) {
|
|
8713
9503
|
log2(`[restart-context] ${codeName}: hint write failed for ${filename}: ${err.message}`);
|
|
@@ -9329,8 +10119,8 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
|
|
|
9329
10119
|
function shouldSkipRevokedCleanup(previousKnownStatus) {
|
|
9330
10120
|
return previousKnownStatus === "revoked";
|
|
9331
10121
|
}
|
|
9332
|
-
function hasRevokedResiduals(
|
|
9333
|
-
return
|
|
10122
|
+
function hasRevokedResiduals(state8) {
|
|
10123
|
+
return state8.gatewayRunning || state8.portAllocated || state8.provisionDirExists;
|
|
9334
10124
|
}
|
|
9335
10125
|
var pendingSessionRestarts = /* @__PURE__ */ new Map();
|
|
9336
10126
|
var lastRestartWasExternal = /* @__PURE__ */ new Map();
|
|
@@ -9508,12 +10298,12 @@ function maybeClearStaleTrip(agent) {
|
|
|
9508
10298
|
const marker = getAutoResumeMarker(codeName);
|
|
9509
10299
|
const isSelfResume = marker !== void 0 && Date.now() - marker.autoResumedAt < AUTO_RESUME_SELF_WINDOW_MS;
|
|
9510
10300
|
if (!isSelfResume) deleteAutoResumeMarker(codeName);
|
|
9511
|
-
|
|
9512
|
-
...
|
|
10301
|
+
state7 = {
|
|
10302
|
+
...state7,
|
|
9513
10303
|
circuitBreakerTrips: restartBreaker.serialize(),
|
|
9514
10304
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
9515
10305
|
};
|
|
9516
|
-
send({ type: "state-update", state:
|
|
10306
|
+
send({ type: "state-update", state: state7 });
|
|
9517
10307
|
}
|
|
9518
10308
|
var autoResumeMarkers = /* @__PURE__ */ new Map();
|
|
9519
10309
|
function autoResumeMarkerKey(codeName) {
|
|
@@ -9605,12 +10395,12 @@ function maybeAutoResume(agent) {
|
|
|
9605
10395
|
restartBreaker.clear(codeName);
|
|
9606
10396
|
reportedTrips.delete(codeName);
|
|
9607
10397
|
dependencyRecoveryLedger.clear(codeName);
|
|
9608
|
-
|
|
9609
|
-
...
|
|
10398
|
+
state7 = {
|
|
10399
|
+
...state7,
|
|
9610
10400
|
circuitBreakerTrips: restartBreaker.serialize(),
|
|
9611
10401
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
9612
10402
|
};
|
|
9613
|
-
send({ type: "state-update", state:
|
|
10403
|
+
send({ type: "state-update", state: state7 });
|
|
9614
10404
|
log(`[auto-resume] agent=${codeName} resumed \u2014 re-trip within backoff window will stay paused (ENG-6088)`);
|
|
9615
10405
|
} else {
|
|
9616
10406
|
autoResumeStandDowns.add(`${codeName}:${trippedAt}`);
|
|
@@ -9719,12 +10509,12 @@ async function maybeResumeReconcile(agent) {
|
|
|
9719
10509
|
restartBreaker.clear(codeName);
|
|
9720
10510
|
reportedTrips.delete(codeName);
|
|
9721
10511
|
dependencyRecoveryLedger.clear(codeName);
|
|
9722
|
-
|
|
9723
|
-
...
|
|
10512
|
+
state7 = {
|
|
10513
|
+
...state7,
|
|
9724
10514
|
circuitBreakerTrips: restartBreaker.serialize(),
|
|
9725
10515
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
9726
10516
|
};
|
|
9727
|
-
send({ type: "state-update", state:
|
|
10517
|
+
send({ type: "state-update", state: state7 });
|
|
9728
10518
|
log(`[resume-reconciler] agent=${codeName} resumed \u2014 a re-trip within the backoff window will latch unstable (ENG-6383)`);
|
|
9729
10519
|
} else {
|
|
9730
10520
|
autoResumeStandDowns.add(standDownKey);
|
|
@@ -9888,6 +10678,23 @@ function cancelPendingSessionRestart(codeName) {
|
|
|
9888
10678
|
log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);
|
|
9889
10679
|
}
|
|
9890
10680
|
var lastSpawnOutcomeByAgent = /* @__PURE__ */ new Map();
|
|
10681
|
+
var lastSpawnDecisionByAgent = /* @__PURE__ */ new Map();
|
|
10682
|
+
function recordSpawnOutcome(codeName, result) {
|
|
10683
|
+
lastSpawnOutcomeByAgent.set(codeName, {
|
|
10684
|
+
spawnAttempted: result.spawnAttempted,
|
|
10685
|
+
sessionHealthyAfter: result.sessionHealthyAfter
|
|
10686
|
+
});
|
|
10687
|
+
if (result.decision) lastSpawnDecisionByAgent.set(codeName, result.decision);
|
|
10688
|
+
}
|
|
10689
|
+
function spawnOutcomeForDiagnostics(codeName) {
|
|
10690
|
+
const last = lastSpawnOutcomeByAgent.get(codeName);
|
|
10691
|
+
if (!last) return void 0;
|
|
10692
|
+
return {
|
|
10693
|
+
spawnAttempted: last.spawnAttempted,
|
|
10694
|
+
sessionHealthyAfter: last.sessionHealthyAfter,
|
|
10695
|
+
lastDecision: lastSpawnDecisionByAgent.get(codeName) ?? null
|
|
10696
|
+
};
|
|
10697
|
+
}
|
|
9891
10698
|
function spawnWasRefusedWithNoSession(codeName) {
|
|
9892
10699
|
const last = lastSpawnOutcomeByAgent.get(codeName);
|
|
9893
10700
|
if (!last) return false;
|
|
@@ -10057,7 +10864,7 @@ function performLazyDayRolloverReset(codeName, agentTimezone) {
|
|
|
10057
10864
|
}
|
|
10058
10865
|
function paneLogAgeSecondsFor(codeName) {
|
|
10059
10866
|
try {
|
|
10060
|
-
const mtimeMs =
|
|
10867
|
+
const mtimeMs = statSync8(paneLogPath(codeName)).mtimeMs;
|
|
10061
10868
|
return Math.max(0, Math.floor((Date.now() - mtimeMs) / 1e3));
|
|
10062
10869
|
} catch (err) {
|
|
10063
10870
|
if (err?.code === "ENOENT") return null;
|
|
@@ -10106,7 +10913,7 @@ function restartGateFor(codeName, reason) {
|
|
|
10106
10913
|
}
|
|
10107
10914
|
function isHostBusyForForcedUpdate(opts) {
|
|
10108
10915
|
const now = /* @__PURE__ */ new Date();
|
|
10109
|
-
for (const agent of
|
|
10916
|
+
for (const agent of state7.agents) {
|
|
10110
10917
|
const decision = decideRestartGate({
|
|
10111
10918
|
window: null,
|
|
10112
10919
|
paneLogAgeSeconds: paneLogAgeSecondsFor(agent.codeName),
|
|
@@ -10139,7 +10946,7 @@ function runPendingForcedUpdate() {
|
|
|
10139
10946
|
relaxed
|
|
10140
10947
|
} = decidePendingForcedUpdate({
|
|
10141
10948
|
requestedAt: requestedUpdateAt,
|
|
10142
|
-
lastProcessedAt:
|
|
10949
|
+
lastProcessedAt: state7.lastUpdateRequestProcessedAt ?? null,
|
|
10143
10950
|
deferStreak: forcedUpdateDeferStreak,
|
|
10144
10951
|
nowMs: Date.now(),
|
|
10145
10952
|
isHostBusy: (isRelaxed) => isHostBusyForForcedUpdate({ relaxed: isRelaxed })
|
|
@@ -10173,7 +10980,7 @@ function runPendingForcedUpdate() {
|
|
|
10173
10980
|
log(
|
|
10174
10981
|
forcedUpdate === "consume-deadline" ? `[self-update] WARN "Update CLI now" requested at ${requestedUpdateAt} has been pending ${describePendingFor(pendingForMs)} and the host still reads busy \u2014 running the window-bypassing self-update anyway (ENG-8449 hard deadline). A live turn may be interrupted; the operator asked for this update explicitly.` : `[self-update] "Update CLI now" requested at ${requestedUpdateAt} \u2014 running window-bypassing self-update now (host idle${relaxed ? ", relaxed gate" : ""}).`
|
|
10175
10982
|
);
|
|
10176
|
-
const prevProcessedAt =
|
|
10983
|
+
const prevProcessedAt = state7.lastUpdateRequestProcessedAt ?? null;
|
|
10177
10984
|
void checkAndUpdateCli({ force: true }).then((outcome) => {
|
|
10178
10985
|
if (!shouldConsumeForcedUpdate(outcome)) {
|
|
10179
10986
|
log(
|
|
@@ -10184,11 +10991,11 @@ function runPendingForcedUpdate() {
|
|
|
10184
10991
|
log(
|
|
10185
10992
|
outcome === "updated" ? `[self-update] "Update CLI now" completed \u2014 upgrade installed; manager restart scheduled.` : `[self-update] "Update CLI now" completed \u2014 host was already on the latest in-channel build; nothing to install.`
|
|
10186
10993
|
);
|
|
10187
|
-
|
|
10994
|
+
state7.lastUpdateRequestProcessedAt = requestedUpdateAt;
|
|
10188
10995
|
try {
|
|
10189
|
-
atomicWriteFileSync(getStateFile(), JSON.stringify(
|
|
10996
|
+
atomicWriteFileSync(getStateFile(), JSON.stringify(state7, null, 2));
|
|
10190
10997
|
} catch (err) {
|
|
10191
|
-
|
|
10998
|
+
state7.lastUpdateRequestProcessedAt = prevProcessedAt;
|
|
10192
10999
|
log(
|
|
10193
11000
|
`[self-update] failed to persist update-request ack; retrying next poll: ${err.message}`
|
|
10194
11001
|
);
|
|
@@ -10207,15 +11014,15 @@ var runningChannelSecretHashes = /* @__PURE__ */ new Map();
|
|
|
10207
11014
|
var sessionLaunchManagedStructure = /* @__PURE__ */ new Map();
|
|
10208
11015
|
function projectMcpHash(_codeName, projectDir) {
|
|
10209
11016
|
try {
|
|
10210
|
-
const raw =
|
|
10211
|
-
return
|
|
11017
|
+
const raw = readFileSync26(join33(projectDir, ".mcp.json"), "utf-8");
|
|
11018
|
+
return createHash17("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
10212
11019
|
} catch {
|
|
10213
11020
|
return null;
|
|
10214
11021
|
}
|
|
10215
11022
|
}
|
|
10216
11023
|
function projectMcpKeys(_codeName, projectDir) {
|
|
10217
11024
|
try {
|
|
10218
|
-
const raw =
|
|
11025
|
+
const raw = readFileSync26(join33(projectDir, ".mcp.json"), "utf-8");
|
|
10219
11026
|
const parsed = JSON.parse(raw);
|
|
10220
11027
|
const servers = parsed.mcpServers;
|
|
10221
11028
|
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
@@ -10233,7 +11040,7 @@ function seedSessionLaunchBaselines(codeName, projectDir) {
|
|
|
10233
11040
|
else runningMcpServerKeys.delete(codeName);
|
|
10234
11041
|
let launchStructure = null;
|
|
10235
11042
|
try {
|
|
10236
|
-
const raw =
|
|
11043
|
+
const raw = readFileSync26(join33(projectDir, ".mcp.json"), "utf-8");
|
|
10237
11044
|
launchStructure = managedMcpStructureHashFromFile(
|
|
10238
11045
|
JSON.parse(raw),
|
|
10239
11046
|
isManagedMcpServerKey
|
|
@@ -10343,7 +11150,7 @@ async function runAgentSessionToolBindProbes(agent, integrations, projectDir, op
|
|
|
10343
11150
|
if (result && result.rebindCandidates.length > 0 && hostFlagStore().getBoolean("session-tool-rebind")) {
|
|
10344
11151
|
let mcpJsonForRebind = null;
|
|
10345
11152
|
try {
|
|
10346
|
-
mcpJsonForRebind = JSON.parse(
|
|
11153
|
+
mcpJsonForRebind = JSON.parse(readFileSync26(join33(projectDir, ".mcp.json"), "utf-8"));
|
|
10347
11154
|
} catch {
|
|
10348
11155
|
mcpJsonForRebind = null;
|
|
10349
11156
|
}
|
|
@@ -10487,7 +11294,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
|
10487
11294
|
function projectChannelSecretHash(projectDir) {
|
|
10488
11295
|
try {
|
|
10489
11296
|
const entries = parseEnvIntegrations(
|
|
10490
|
-
|
|
11297
|
+
readFileSync26(join33(projectDir, ".env.integrations"), "utf-8")
|
|
10491
11298
|
);
|
|
10492
11299
|
return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
|
|
10493
11300
|
} catch {
|
|
@@ -10528,7 +11335,7 @@ var STALE_TASK_THRESHOLD_MS = (() => {
|
|
|
10528
11335
|
})();
|
|
10529
11336
|
var taskDisplayInfo = /* @__PURE__ */ new Map();
|
|
10530
11337
|
var activeChannels = /* @__PURE__ */ new Map();
|
|
10531
|
-
var
|
|
11338
|
+
var state7 = {
|
|
10532
11339
|
pid: process.pid,
|
|
10533
11340
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10534
11341
|
lastPollAt: null,
|
|
@@ -10583,7 +11390,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
10583
11390
|
var lastVersionCheckAt = 0;
|
|
10584
11391
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
10585
11392
|
var lastResponsivenessProbeAt = 0;
|
|
10586
|
-
var agtCliVersion = true ? "0.28.
|
|
11393
|
+
var agtCliVersion = true ? "0.28.567" : "dev";
|
|
10587
11394
|
function resolveBrewPath(execFileSync2) {
|
|
10588
11395
|
try {
|
|
10589
11396
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -10596,7 +11403,7 @@ function resolveBrewPath(execFileSync2) {
|
|
|
10596
11403
|
"/usr/local/bin/brew"
|
|
10597
11404
|
];
|
|
10598
11405
|
for (const path of fallbacks) {
|
|
10599
|
-
if (
|
|
11406
|
+
if (existsSync14(path)) return path;
|
|
10600
11407
|
}
|
|
10601
11408
|
return null;
|
|
10602
11409
|
}
|
|
@@ -10606,7 +11413,7 @@ function claudeBinaryInstalled(execFileSync2) {
|
|
|
10606
11413
|
"/opt/homebrew/bin/claude",
|
|
10607
11414
|
"/usr/local/bin/claude"
|
|
10608
11415
|
];
|
|
10609
|
-
if (canonical.some((path) =>
|
|
11416
|
+
if (canonical.some((path) => existsSync14(path))) return true;
|
|
10610
11417
|
try {
|
|
10611
11418
|
execFileSync2("which", ["claude"], { timeout: 5e3 });
|
|
10612
11419
|
return true;
|
|
@@ -10678,7 +11485,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
10678
11485
|
toolkitCliEnsured.add(toolkitSlug);
|
|
10679
11486
|
return;
|
|
10680
11487
|
}
|
|
10681
|
-
brewBinDir =
|
|
11488
|
+
brewBinDir = dirname9(brewPath);
|
|
10682
11489
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
10683
11490
|
log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
|
|
10684
11491
|
if (isRoot) {
|
|
@@ -10915,8 +11722,8 @@ function claudeManagedSettingsPath() {
|
|
|
10915
11722
|
function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
10916
11723
|
try {
|
|
10917
11724
|
let settings = {};
|
|
10918
|
-
if (
|
|
10919
|
-
const raw =
|
|
11725
|
+
if (existsSync14(path)) {
|
|
11726
|
+
const raw = readFileSync26(path, "utf-8").trim();
|
|
10920
11727
|
if (raw) {
|
|
10921
11728
|
let parsed;
|
|
10922
11729
|
try {
|
|
@@ -10932,8 +11739,8 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
|
10932
11739
|
}
|
|
10933
11740
|
if (settings.channelsEnabled === true) return "ok";
|
|
10934
11741
|
settings.channelsEnabled = true;
|
|
10935
|
-
|
|
10936
|
-
|
|
11742
|
+
mkdirSync11(dirname9(path), { recursive: true });
|
|
11743
|
+
writeFileSync14(path, `${JSON.stringify(settings, null, 2)}
|
|
10937
11744
|
`);
|
|
10938
11745
|
log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
|
|
10939
11746
|
return "ok";
|
|
@@ -10971,7 +11778,7 @@ async function ensureOpencodeBinary() {
|
|
|
10971
11778
|
try {
|
|
10972
11779
|
const prefix = execFileSync2("npm", ["prefix", "-g"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
10973
11780
|
if (prefix) {
|
|
10974
|
-
const npmBin =
|
|
11781
|
+
const npmBin = join33(prefix, "bin");
|
|
10975
11782
|
const current = (process.env.PATH ?? "").split(pathDelimiter);
|
|
10976
11783
|
if (!current.includes(npmBin)) {
|
|
10977
11784
|
process.env.PATH = [npmBin, ...current.filter(Boolean)].join(pathDelimiter);
|
|
@@ -11028,11 +11835,11 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
11028
11835
|
log(`Claude Code install failed: ${err.message}`);
|
|
11029
11836
|
return;
|
|
11030
11837
|
}
|
|
11031
|
-
const brewBinDir =
|
|
11838
|
+
const brewBinDir = dirname9(brewPath);
|
|
11032
11839
|
if (!process.env.PATH?.split(":").includes(brewBinDir)) {
|
|
11033
11840
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
11034
11841
|
}
|
|
11035
|
-
if (
|
|
11842
|
+
if (existsSync14("/home/linuxbrew/.linuxbrew/bin/claude")) {
|
|
11036
11843
|
log("Claude Code installed successfully");
|
|
11037
11844
|
} else {
|
|
11038
11845
|
log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
|
|
@@ -11088,7 +11895,7 @@ ${r.stderr}`;
|
|
|
11088
11895
|
}
|
|
11089
11896
|
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
11090
11897
|
function selfUpdateAppliedMarkerPath() {
|
|
11091
|
-
return
|
|
11898
|
+
return join33(homedir15(), ".augmented", ".last-self-update-applied");
|
|
11092
11899
|
}
|
|
11093
11900
|
var selfUpdateUpToDateLogged = false;
|
|
11094
11901
|
var selfUpdatePinnedLogged = false;
|
|
@@ -11117,7 +11924,7 @@ async function checkAndUpdateCli(opts) {
|
|
|
11117
11924
|
const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
|
|
11118
11925
|
if (!isBrewFormula && !isNpmGlobal) return "noop";
|
|
11119
11926
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
11120
|
-
const markerPath =
|
|
11927
|
+
const markerPath = join33(homedir15(), ".augmented", ".last-update-check");
|
|
11121
11928
|
if (!force) {
|
|
11122
11929
|
try {
|
|
11123
11930
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
@@ -11523,7 +12330,7 @@ async function runClaudeRuntimeAuthProbe() {
|
|
|
11523
12330
|
];
|
|
11524
12331
|
try {
|
|
11525
12332
|
const { stdout, stderr } = await execFilePromiseLong(resolveClaudeBinary(), args, {
|
|
11526
|
-
cwd:
|
|
12333
|
+
cwd: homedir15(),
|
|
11527
12334
|
timeout: RUNTIME_AUTH_PROBE_TIMEOUT_MS,
|
|
11528
12335
|
stdin: "ignore",
|
|
11529
12336
|
env: childEnv,
|
|
@@ -11569,14 +12376,14 @@ async function checkClaudeAuth() {
|
|
|
11569
12376
|
}
|
|
11570
12377
|
var evalEmptyMcpConfigPath = null;
|
|
11571
12378
|
function ensureEvalEmptyMcpConfig() {
|
|
11572
|
-
if (evalEmptyMcpConfigPath &&
|
|
11573
|
-
const dir =
|
|
12379
|
+
if (evalEmptyMcpConfigPath && existsSync14(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
|
|
12380
|
+
const dir = join33(homedir15(), ".augmented");
|
|
11574
12381
|
try {
|
|
11575
|
-
|
|
12382
|
+
mkdirSync11(dir, { recursive: true });
|
|
11576
12383
|
} catch {
|
|
11577
12384
|
}
|
|
11578
|
-
const p =
|
|
11579
|
-
|
|
12385
|
+
const p = join33(dir, ".eval-empty-mcp.json");
|
|
12386
|
+
writeFileSync14(p, JSON.stringify({ mcpServers: {} }));
|
|
11580
12387
|
evalEmptyMcpConfigPath = p;
|
|
11581
12388
|
return p;
|
|
11582
12389
|
}
|
|
@@ -11601,7 +12408,7 @@ async function runEvalClaude(prompt, model) {
|
|
|
11601
12408
|
""
|
|
11602
12409
|
];
|
|
11603
12410
|
const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), args, {
|
|
11604
|
-
cwd:
|
|
12411
|
+
cwd: homedir15(),
|
|
11605
12412
|
timeout: 12e4,
|
|
11606
12413
|
stdin: "ignore",
|
|
11607
12414
|
env: childEnv,
|
|
@@ -11613,6 +12420,9 @@ async function runEvalClaude(prompt, model) {
|
|
|
11613
12420
|
function memoryExtractionEnabled() {
|
|
11614
12421
|
return hostFlagStore().getBoolean("memory-extraction");
|
|
11615
12422
|
}
|
|
12423
|
+
function toolCallAuditEnabled() {
|
|
12424
|
+
return hostFlagStore().getBoolean("tool-call-audit");
|
|
12425
|
+
}
|
|
11616
12426
|
function conversationScorerSuppressed() {
|
|
11617
12427
|
return meteredScorerSuppressed(
|
|
11618
12428
|
getCachedClaudeAuthMode(),
|
|
@@ -11667,10 +12477,10 @@ function resolveConversationEvalBackend() {
|
|
|
11667
12477
|
return conversationEvalBackend;
|
|
11668
12478
|
}
|
|
11669
12479
|
function getStateFile() {
|
|
11670
|
-
return
|
|
12480
|
+
return join33(config?.configDir ?? join33(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
|
|
11671
12481
|
}
|
|
11672
12482
|
function channelHashCacheDir() {
|
|
11673
|
-
return config?.configDir ??
|
|
12483
|
+
return config?.configDir ?? join33(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
11674
12484
|
}
|
|
11675
12485
|
function loadChannelHashCache2() {
|
|
11676
12486
|
loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
|
|
@@ -11724,7 +12534,7 @@ function removeDeliveryBaselineEntries(agentId) {
|
|
|
11724
12534
|
var _channelQuarantineStore = null;
|
|
11725
12535
|
function channelQuarantineStore() {
|
|
11726
12536
|
if (!_channelQuarantineStore) {
|
|
11727
|
-
const dir = config?.configDir ??
|
|
12537
|
+
const dir = config?.configDir ?? join33(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
11728
12538
|
_channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
|
|
11729
12539
|
}
|
|
11730
12540
|
return _channelQuarantineStore;
|
|
@@ -11741,7 +12551,7 @@ function claudeMdSizeFor(codeName) {
|
|
|
11741
12551
|
var _hostFlagStore = null;
|
|
11742
12552
|
function hostFlagStore() {
|
|
11743
12553
|
if (!_hostFlagStore) {
|
|
11744
|
-
const dir = config?.configDir ??
|
|
12554
|
+
const dir = config?.configDir ?? join33(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
11745
12555
|
_hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
|
|
11746
12556
|
}
|
|
11747
12557
|
return _hostFlagStore;
|
|
@@ -11814,13 +12624,13 @@ function parseSkillFrontmatter(content) {
|
|
|
11814
12624
|
return out;
|
|
11815
12625
|
}
|
|
11816
12626
|
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
11817
|
-
const { readdirSync:
|
|
11818
|
-
const skillsDir =
|
|
11819
|
-
const claudeMdPath =
|
|
12627
|
+
const { readdirSync: readdirSync10, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync15 } = await import("fs");
|
|
12628
|
+
const skillsDir = join33(configDir, codeName, "project", ".claude", "skills");
|
|
12629
|
+
const claudeMdPath = join33(configDir, codeName, "project", "CLAUDE.md");
|
|
11820
12630
|
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
11821
12631
|
const entries = [];
|
|
11822
|
-
for (const dir of
|
|
11823
|
-
const skillFile =
|
|
12632
|
+
for (const dir of readdirSync10(skillsDir).sort()) {
|
|
12633
|
+
const skillFile = join33(skillsDir, dir, "SKILL.md");
|
|
11824
12634
|
if (!ex(skillFile)) continue;
|
|
11825
12635
|
try {
|
|
11826
12636
|
const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
|
|
@@ -11864,7 +12674,7 @@ ${SKILLS_INDEX_END}`;
|
|
|
11864
12674
|
next = current.trimEnd() + "\n\n" + section + "\n";
|
|
11865
12675
|
}
|
|
11866
12676
|
if (next !== current) {
|
|
11867
|
-
|
|
12677
|
+
writeFileSync15(claudeMdPath, next, "utf-8");
|
|
11868
12678
|
log2(
|
|
11869
12679
|
injectSkillList ? `Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)` : `Refreshed CLAUDE.md managed block for '${codeName}' (skill list suppressed by claude-md-skills-index=false; ${entries.length} skills on disk)`
|
|
11870
12680
|
);
|
|
@@ -11887,10 +12697,10 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
11887
12697
|
if (codeNames.length === 0) return;
|
|
11888
12698
|
void (async () => {
|
|
11889
12699
|
try {
|
|
11890
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
12700
|
+
const { collectDiagnostics } = await import("../persistent-session-DVTSZAPE.js");
|
|
11891
12701
|
await api.post("/host/heartbeat", {
|
|
11892
12702
|
host_id: hostId,
|
|
11893
|
-
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor)
|
|
12703
|
+
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
|
|
11894
12704
|
});
|
|
11895
12705
|
} catch (err) {
|
|
11896
12706
|
log(`[restart] post-respawn diagnostics flush failed: ${err.message}`);
|
|
@@ -11984,7 +12794,7 @@ async function pollCycle() {
|
|
|
11984
12794
|
const now = Date.now();
|
|
11985
12795
|
if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {
|
|
11986
12796
|
try {
|
|
11987
|
-
const firstAgent =
|
|
12797
|
+
const firstAgent = state7.agents[0];
|
|
11988
12798
|
const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework(DEFAULT_FRAMEWORK);
|
|
11989
12799
|
if (versionAdapter.getVersion) {
|
|
11990
12800
|
cachedFrameworkVersion = await versionAdapter.getVersion();
|
|
@@ -11995,9 +12805,9 @@ async function pollCycle() {
|
|
|
11995
12805
|
}
|
|
11996
12806
|
try {
|
|
11997
12807
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
11998
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
12808
|
+
const { collectDiagnostics } = await import("../persistent-session-DVTSZAPE.js");
|
|
11999
12809
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
12000
|
-
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor) : void 0;
|
|
12810
|
+
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics) : void 0;
|
|
12001
12811
|
let tailscaleHostname;
|
|
12002
12812
|
try {
|
|
12003
12813
|
const { execSync: es } = await import("child_process");
|
|
@@ -12025,10 +12835,10 @@ async function pollCycle() {
|
|
|
12025
12835
|
claudeAuth = await detectClaudeAuth();
|
|
12026
12836
|
} catch (err) {
|
|
12027
12837
|
const errText = err instanceof Error ? err.message : String(err);
|
|
12028
|
-
const errId =
|
|
12838
|
+
const errId = createHash17("sha256").update(errText).digest("hex").slice(0, 12);
|
|
12029
12839
|
log(`Claude auth detection failed (error_id=${errId})`);
|
|
12030
12840
|
}
|
|
12031
|
-
const hostHasClaudeCode =
|
|
12841
|
+
const hostHasClaudeCode = state7.agents.some(
|
|
12032
12842
|
(a) => agentFrameworkCache.get(a.codeName) === "claude-code"
|
|
12033
12843
|
);
|
|
12034
12844
|
if (hostHasClaudeCode) {
|
|
@@ -12078,7 +12888,7 @@ async function pollCycle() {
|
|
|
12078
12888
|
// ENG-6692: ack the last consumed "Update CLI now" timestamp so the API
|
|
12079
12889
|
// clears hosts.update_requested_at. Echoes our persisted dedup marker;
|
|
12080
12890
|
// null until we've ever consumed one.
|
|
12081
|
-
update_request_processed_at:
|
|
12891
|
+
update_request_processed_at: state7.lastUpdateRequestProcessedAt ?? null
|
|
12082
12892
|
});
|
|
12083
12893
|
if (hbResp?.maintenance_window) {
|
|
12084
12894
|
cachedMaintenanceWindow = hbResp.maintenance_window;
|
|
@@ -12118,7 +12928,7 @@ async function pollCycle() {
|
|
|
12118
12928
|
collectPanelessActivityProbes,
|
|
12119
12929
|
getResponsivenessIntervalMs,
|
|
12120
12930
|
occupancyQualificationClassifications
|
|
12121
|
-
} = await import("../responsiveness-probe-
|
|
12931
|
+
} = await import("../responsiveness-probe-KEFPJXQ6.js");
|
|
12122
12932
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
12123
12933
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
12124
12934
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -12209,7 +13019,7 @@ async function pollCycle() {
|
|
|
12209
13019
|
collectResponsivenessProbes,
|
|
12210
13020
|
livePendingInboundOldestAgeSeconds,
|
|
12211
13021
|
parkPendingInbound
|
|
12212
|
-
} = await import("../responsiveness-probe-
|
|
13022
|
+
} = await import("../responsiveness-probe-KEFPJXQ6.js");
|
|
12213
13023
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
|
|
12214
13024
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
12215
13025
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -12298,13 +13108,13 @@ async function pollCycle() {
|
|
|
12298
13108
|
);
|
|
12299
13109
|
if (hostFlagStore().getBoolean("wedge-transient-notice")) {
|
|
12300
13110
|
try {
|
|
12301
|
-
const paneTail =
|
|
13111
|
+
const paneTail = readFileSync26(paneLogPath(codeName), "utf8").slice(-65536);
|
|
12302
13112
|
const transient = detectTransientApiErrorInLog(paneTail);
|
|
12303
13113
|
if (transient) {
|
|
12304
|
-
const wedgeHome =
|
|
12305
|
-
if (
|
|
13114
|
+
const wedgeHome = join33(homedir15(), ".augmented", codeName);
|
|
13115
|
+
if (existsSync14(wedgeHome)) {
|
|
12306
13116
|
atomicWriteFileSync(
|
|
12307
|
-
|
|
13117
|
+
join33(wedgeHome, "watchdog-give-up.json"),
|
|
12308
13118
|
JSON.stringify({
|
|
12309
13119
|
gave_up_at: wedgeNow.toISOString(),
|
|
12310
13120
|
reason: "transient_overload"
|
|
@@ -12434,7 +13244,7 @@ async function pollCycle() {
|
|
|
12434
13244
|
const requested = agent.restart_requested_at ?? null;
|
|
12435
13245
|
if (!requested) continue;
|
|
12436
13246
|
if (restartInFlight.has(agent.agent_id)) continue;
|
|
12437
|
-
const prev =
|
|
13247
|
+
const prev = state7.agents.find((a) => a.agentId === agent.agent_id);
|
|
12438
13248
|
const lastProcessed = prev?.lastRestartProcessedAt ?? null;
|
|
12439
13249
|
const alreadyServiced = lastProcessed != null && Date.parse(lastProcessed) >= Date.parse(requested);
|
|
12440
13250
|
if (!alreadyServiced) {
|
|
@@ -12513,7 +13323,7 @@ async function pollCycle() {
|
|
|
12513
13323
|
const processOrder = fastRespawn ? reorderRestartedFirst(agents, new Set(restartAcks.keys())) : agents;
|
|
12514
13324
|
for (const agent of processOrder) {
|
|
12515
13325
|
if (restartInFlight.has(agent.agent_id)) {
|
|
12516
|
-
const existing =
|
|
13326
|
+
const existing = state7.agents.find((a) => a.agentId === agent.agent_id);
|
|
12517
13327
|
if (existing) {
|
|
12518
13328
|
agentStates.push(existing);
|
|
12519
13329
|
continue;
|
|
@@ -12523,7 +13333,7 @@ async function pollCycle() {
|
|
|
12523
13333
|
await processAgent(agent, agentStates);
|
|
12524
13334
|
} catch (err) {
|
|
12525
13335
|
log(`Error processing agent '${agent.code_name}': ${err.message}`);
|
|
12526
|
-
const existing =
|
|
13336
|
+
const existing = state7.agents.find((a) => a.agentId === agent.agent_id);
|
|
12527
13337
|
if (existing) {
|
|
12528
13338
|
agentStates.push(existing);
|
|
12529
13339
|
} else {
|
|
@@ -12552,12 +13362,12 @@ async function pollCycle() {
|
|
|
12552
13362
|
void maybeReportActivityCache({ api, log });
|
|
12553
13363
|
const restartAckStateChanged = applyRestartAcks({
|
|
12554
13364
|
agentStates,
|
|
12555
|
-
priorAgents:
|
|
13365
|
+
priorAgents: state7.agents,
|
|
12556
13366
|
restartAcks
|
|
12557
13367
|
});
|
|
12558
13368
|
if (restartAckStateChanged) {
|
|
12559
13369
|
try {
|
|
12560
|
-
const ackedState = { ...
|
|
13370
|
+
const ackedState = { ...state7, agents: agentStates };
|
|
12561
13371
|
atomicWriteFileSync(getStateFile(), JSON.stringify(ackedState, null, 2));
|
|
12562
13372
|
} catch (err) {
|
|
12563
13373
|
log(`[restart] failed to persist ack immediately: ${err.message}`);
|
|
@@ -12579,13 +13389,13 @@ async function pollCycle() {
|
|
|
12579
13389
|
} catch {
|
|
12580
13390
|
}
|
|
12581
13391
|
const currentIds = new Set(agents.map((a) => a.agent_id));
|
|
12582
|
-
for (const prev of
|
|
13392
|
+
for (const prev of state7.agents) {
|
|
12583
13393
|
if (!currentIds.has(prev.agentId)) {
|
|
12584
13394
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
12585
13395
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
12586
13396
|
stopAgentRuntime2(prev.codeName, "removed-from-host");
|
|
12587
13397
|
killAgentChannelProcesses(prev.codeName, { log });
|
|
12588
|
-
const agentDir =
|
|
13398
|
+
const agentDir = join33(adapter.getAgentDir(prev.codeName), "provision");
|
|
12589
13399
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
12590
13400
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
12591
13401
|
}
|
|
@@ -12672,10 +13482,10 @@ async function pollCycle() {
|
|
|
12672
13482
|
// pending-inbound marker. Best-effort: a write failure is logged by
|
|
12673
13483
|
// the watchdog, never fails the poll cycle.
|
|
12674
13484
|
signalGiveUp: (codeName) => {
|
|
12675
|
-
const dir =
|
|
12676
|
-
if (!
|
|
13485
|
+
const dir = join33(homedir15(), ".augmented", codeName);
|
|
13486
|
+
if (!existsSync14(dir)) return;
|
|
12677
13487
|
atomicWriteFileSync(
|
|
12678
|
-
|
|
13488
|
+
join33(dir, "watchdog-give-up.json"),
|
|
12679
13489
|
JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
12680
13490
|
);
|
|
12681
13491
|
}
|
|
@@ -12716,10 +13526,10 @@ async function pollCycle() {
|
|
|
12716
13526
|
}
|
|
12717
13527
|
} catch {
|
|
12718
13528
|
}
|
|
12719
|
-
|
|
12720
|
-
...
|
|
13529
|
+
state7 = {
|
|
13530
|
+
...state7,
|
|
12721
13531
|
lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12722
|
-
pollCount:
|
|
13532
|
+
pollCount: state7.pollCount + 1,
|
|
12723
13533
|
agents: agentStates,
|
|
12724
13534
|
// ENG-5441: serialise trip state on every poll so manager restarts
|
|
12725
13535
|
// never silently clear a tripped breaker. Cheap — only tripped
|
|
@@ -12733,9 +13543,9 @@ async function pollCycle() {
|
|
|
12733
13543
|
consecutivePollFailures = 0;
|
|
12734
13544
|
}
|
|
12735
13545
|
verifyPendingRestarts(Date.now());
|
|
12736
|
-
send({ type: "state-update", state:
|
|
13546
|
+
send({ type: "state-update", state: state7 });
|
|
12737
13547
|
} catch (err) {
|
|
12738
|
-
|
|
13548
|
+
state7.errorCount++;
|
|
12739
13549
|
const message = err.message;
|
|
12740
13550
|
log(`Poll error: ${message}`);
|
|
12741
13551
|
send({ type: "error", message });
|
|
@@ -12820,10 +13630,18 @@ async function processAgent(agent, agentStates) {
|
|
|
12820
13630
|
onModelCallError: (err) => reportManagerModelCallError("memory_extraction", err, memBackend.model ?? null)
|
|
12821
13631
|
});
|
|
12822
13632
|
}
|
|
13633
|
+
if (toolCallAuditEnabled()) {
|
|
13634
|
+
void maybeScanToolCalls({
|
|
13635
|
+
api,
|
|
13636
|
+
codeName: agent.code_name,
|
|
13637
|
+
agentId: agent.agent_id,
|
|
13638
|
+
log
|
|
13639
|
+
});
|
|
13640
|
+
}
|
|
12823
13641
|
}
|
|
12824
13642
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12825
13643
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
12826
|
-
let agentDir =
|
|
13644
|
+
let agentDir = join33(adapter.getAgentDir(agent.code_name), "provision");
|
|
12827
13645
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
12828
13646
|
if (previousKnownStatus !== agent.status) {
|
|
12829
13647
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -12863,7 +13681,7 @@ async function processAgent(agent, agentStates) {
|
|
|
12863
13681
|
const residuals = {
|
|
12864
13682
|
gatewayRunning: false,
|
|
12865
13683
|
portAllocated: false,
|
|
12866
|
-
provisionDirExists:
|
|
13684
|
+
provisionDirExists: existsSync14(agentDir)
|
|
12867
13685
|
};
|
|
12868
13686
|
if (!hasRevokedResiduals(residuals)) {
|
|
12869
13687
|
agentStates.push({
|
|
@@ -12920,11 +13738,11 @@ async function processAgent(agent, agentStates) {
|
|
|
12920
13738
|
const marker = getAutoResumeMarker(agent.code_name);
|
|
12921
13739
|
const isSelfResume = marker !== void 0 && Date.now() - marker.autoResumedAt < AUTO_RESUME_SELF_WINDOW_MS;
|
|
12922
13740
|
if (!isSelfResume && deleteAutoResumeMarker(agent.code_name)) {
|
|
12923
|
-
|
|
12924
|
-
...
|
|
13741
|
+
state7 = {
|
|
13742
|
+
...state7,
|
|
12925
13743
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
12926
13744
|
};
|
|
12927
|
-
send({ type: "state-update", state:
|
|
13745
|
+
send({ type: "state-update", state: state7 });
|
|
12928
13746
|
log(`[auto-resume] Cleared auto-resume marker for '${agent.code_name}' on operator resume \u2014 credit re-armed (ENG-6088)`);
|
|
12929
13747
|
}
|
|
12930
13748
|
}
|
|
@@ -12955,7 +13773,7 @@ async function processAgent(agent, agentStates) {
|
|
|
12955
13773
|
});
|
|
12956
13774
|
} catch (err) {
|
|
12957
13775
|
log(`Refresh failed for '${agent.code_name}': ${err.message}`);
|
|
12958
|
-
const existing =
|
|
13776
|
+
const existing = state7.agents.find((a) => a.agentId === agent.agent_id);
|
|
12959
13777
|
agentStates.push(existing ?? {
|
|
12960
13778
|
agentId: agent.agent_id,
|
|
12961
13779
|
codeName: agent.code_name,
|
|
@@ -12997,7 +13815,7 @@ async function processAgent(agent, agentStates) {
|
|
|
12997
13815
|
const frameworkId = refreshData.agent.framework ?? DEFAULT_FRAMEWORK;
|
|
12998
13816
|
agentFrameworkCache.set(agent.code_name, frameworkId);
|
|
12999
13817
|
const frameworkAdapter = getFramework(frameworkId);
|
|
13000
|
-
agentDir =
|
|
13818
|
+
agentDir = join33(frameworkAdapter.getAgentDir(agent.code_name), "provision");
|
|
13001
13819
|
cacheAgentDeliveryMetadata(agent.code_name, refreshData);
|
|
13002
13820
|
agentRestartTimezoneInputs.set(agent.code_name, {
|
|
13003
13821
|
agentTimezone: typeof refreshData.agent.timezone === "string" ? refreshData.agent.timezone : null,
|
|
@@ -13019,7 +13837,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13019
13837
|
const charterVersion = refreshData.charter.version;
|
|
13020
13838
|
const toolsVersion = refreshData.tools.version;
|
|
13021
13839
|
const known = agentState.knownVersions.get(agent.agent_id);
|
|
13022
|
-
let lastProvisionAt =
|
|
13840
|
+
let lastProvisionAt = state7.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
|
|
13023
13841
|
const quarantinedChannels = channelQuarantineStore().getQuarantinedKeys(agent.code_name);
|
|
13024
13842
|
const currentChannelIds = setWithout(
|
|
13025
13843
|
launchableChannelIds(refreshData.channel_configs),
|
|
@@ -13044,9 +13862,9 @@ async function processAgent(agent, agentStates) {
|
|
|
13044
13862
|
try {
|
|
13045
13863
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter, renderIntegrationsSection);
|
|
13046
13864
|
const changedFiles = [];
|
|
13047
|
-
|
|
13865
|
+
mkdirSync11(agentDir, { recursive: true });
|
|
13048
13866
|
for (const artifact of artifacts) {
|
|
13049
|
-
const filePath =
|
|
13867
|
+
const filePath = join33(agentDir, artifact.relativePath);
|
|
13050
13868
|
let existingHash;
|
|
13051
13869
|
let newHash;
|
|
13052
13870
|
let writeContent = artifact.content;
|
|
@@ -13065,8 +13883,8 @@ async function processAgent(agent, agentStates) {
|
|
|
13065
13883
|
};
|
|
13066
13884
|
newHash = sha256(stripDynamicSections(artifact.content));
|
|
13067
13885
|
try {
|
|
13068
|
-
const projectClaudeMd =
|
|
13069
|
-
const existing =
|
|
13886
|
+
const projectClaudeMd = join33(config.configDir, agent.code_name, "project", "CLAUDE.md");
|
|
13887
|
+
const existing = readFileSync26(projectClaudeMd, "utf-8");
|
|
13070
13888
|
existingHash = sha256(stripDynamicSections(existing));
|
|
13071
13889
|
} catch {
|
|
13072
13890
|
existingHash = null;
|
|
@@ -13084,7 +13902,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13084
13902
|
const generatorKeys = Object.keys(generatorServers);
|
|
13085
13903
|
let existingRaw = "";
|
|
13086
13904
|
try {
|
|
13087
|
-
existingRaw =
|
|
13905
|
+
existingRaw = readFileSync26(filePath, "utf-8");
|
|
13088
13906
|
} catch {
|
|
13089
13907
|
}
|
|
13090
13908
|
const existingServers = parseMcp(existingRaw);
|
|
@@ -13100,7 +13918,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13100
13918
|
} else if (artifact.relativePath === "opencode.json") {
|
|
13101
13919
|
let existingRaw = null;
|
|
13102
13920
|
try {
|
|
13103
|
-
existingRaw =
|
|
13921
|
+
existingRaw = readFileSync26(filePath, "utf-8");
|
|
13104
13922
|
} catch {
|
|
13105
13923
|
}
|
|
13106
13924
|
const mergeResult = mergeOpencodeConfigArtifact(artifact.content, existingRaw);
|
|
@@ -13116,26 +13934,26 @@ async function processAgent(agent, agentStates) {
|
|
|
13116
13934
|
}
|
|
13117
13935
|
}
|
|
13118
13936
|
if (changedFiles.length > 0) {
|
|
13119
|
-
const isFirst = !
|
|
13937
|
+
const isFirst = !existsSync14(join33(agentDir, "CHARTER.md"));
|
|
13120
13938
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
13121
13939
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
13122
13940
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
13123
13941
|
for (const file of changedFiles) {
|
|
13124
|
-
const filePath =
|
|
13125
|
-
|
|
13942
|
+
const filePath = join33(agentDir, file.relativePath);
|
|
13943
|
+
mkdirSync11(dirname9(filePath), { recursive: true });
|
|
13126
13944
|
if (file.relativePath === ".mcp.json") {
|
|
13127
13945
|
safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
|
|
13128
13946
|
} else {
|
|
13129
|
-
|
|
13947
|
+
writeFileSync14(filePath, file.content);
|
|
13130
13948
|
}
|
|
13131
13949
|
}
|
|
13132
13950
|
try {
|
|
13133
|
-
const provSkillsDir =
|
|
13134
|
-
if (
|
|
13135
|
-
for (const folder of
|
|
13951
|
+
const provSkillsDir = join33(agentDir, ".claude", "skills");
|
|
13952
|
+
if (existsSync14(provSkillsDir)) {
|
|
13953
|
+
for (const folder of readdirSync9(provSkillsDir)) {
|
|
13136
13954
|
if (folder.startsWith("knowledge-")) {
|
|
13137
13955
|
try {
|
|
13138
|
-
rmSync5(
|
|
13956
|
+
rmSync5(join33(provSkillsDir, folder), { recursive: true });
|
|
13139
13957
|
} catch {
|
|
13140
13958
|
}
|
|
13141
13959
|
}
|
|
@@ -13148,7 +13966,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13148
13966
|
const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
|
|
13149
13967
|
const hashes = /* @__PURE__ */ new Map();
|
|
13150
13968
|
for (const file of trackedFiles2) {
|
|
13151
|
-
const h = hashFile(
|
|
13969
|
+
const h = hashFile(join33(agentDir, file));
|
|
13152
13970
|
if (h) hashes.set(file, h);
|
|
13153
13971
|
}
|
|
13154
13972
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -13166,14 +13984,14 @@ async function processAgent(agent, agentStates) {
|
|
|
13166
13984
|
}
|
|
13167
13985
|
if (Array.isArray(refreshData.workflows)) {
|
|
13168
13986
|
try {
|
|
13169
|
-
const provWorkflowsDir =
|
|
13170
|
-
if (
|
|
13987
|
+
const provWorkflowsDir = join33(agentDir, ".claude", "workflows");
|
|
13988
|
+
if (existsSync14(provWorkflowsDir)) {
|
|
13171
13989
|
const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
|
|
13172
|
-
for (const file of
|
|
13990
|
+
for (const file of readdirSync9(provWorkflowsDir)) {
|
|
13173
13991
|
if (!file.endsWith(".js")) continue;
|
|
13174
13992
|
if (expected.has(file)) continue;
|
|
13175
13993
|
try {
|
|
13176
|
-
rmSync5(
|
|
13994
|
+
rmSync5(join33(provWorkflowsDir, file));
|
|
13177
13995
|
} catch {
|
|
13178
13996
|
}
|
|
13179
13997
|
}
|
|
@@ -13252,10 +14070,10 @@ async function processAgent(agent, agentStates) {
|
|
|
13252
14070
|
}
|
|
13253
14071
|
let lastDriftCheckAt = now;
|
|
13254
14072
|
const written = agentState.writtenHashes.get(agent.agent_id);
|
|
13255
|
-
if (written &&
|
|
14073
|
+
if (written && existsSync14(agentDir)) {
|
|
13256
14074
|
const driftedFiles = [];
|
|
13257
14075
|
for (const [file, expectedHash] of written) {
|
|
13258
|
-
const localHash = hashFile(
|
|
14076
|
+
const localHash = hashFile(join33(agentDir, file));
|
|
13259
14077
|
if (localHash && localHash !== expectedHash) {
|
|
13260
14078
|
driftedFiles.push(file);
|
|
13261
14079
|
}
|
|
@@ -13266,7 +14084,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13266
14084
|
try {
|
|
13267
14085
|
const localHashes = {};
|
|
13268
14086
|
for (const file of driftedFiles) {
|
|
13269
|
-
localHashes[file] = hashFile(
|
|
14087
|
+
localHashes[file] = hashFile(join33(agentDir, file));
|
|
13270
14088
|
}
|
|
13271
14089
|
await api.post("/host/drift", {
|
|
13272
14090
|
agent_id: agent.agent_id,
|
|
@@ -13468,15 +14286,15 @@ async function processAgent(agent, agentStates) {
|
|
|
13468
14286
|
const addedChannels = [...restartDecision.added];
|
|
13469
14287
|
const writeDmNoticeMarkers = isChannelAddRestart ? () => {
|
|
13470
14288
|
try {
|
|
13471
|
-
const agentAugmentedDir =
|
|
13472
|
-
|
|
14289
|
+
const agentAugmentedDir = join33(homedir15(), ".augmented", agent.code_name);
|
|
14290
|
+
mkdirSync11(agentAugmentedDir, { recursive: true });
|
|
13473
14291
|
const markerJson = JSON.stringify({
|
|
13474
14292
|
version: 1,
|
|
13475
14293
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13476
14294
|
added: addedChannels
|
|
13477
14295
|
});
|
|
13478
14296
|
for (const file of ["slack-channel-add-restart.json", "telegram-channel-add-restart.json"]) {
|
|
13479
|
-
atomicWriteFileSync(
|
|
14297
|
+
atomicWriteFileSync(join33(agentAugmentedDir, file), markerJson);
|
|
13480
14298
|
}
|
|
13481
14299
|
} catch (err) {
|
|
13482
14300
|
log(`[hot-reload] channel-add DM-notice marker write failed for '${agent.code_name}' (non-fatal): ${err.message}`);
|
|
@@ -13552,7 +14370,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13552
14370
|
const behaviourSubset = extractMsTeamsBehaviourSubset(
|
|
13553
14371
|
msteamsEntry?.config
|
|
13554
14372
|
);
|
|
13555
|
-
const behaviourHash =
|
|
14373
|
+
const behaviourHash = createHash17("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
|
|
13556
14374
|
const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
|
|
13557
14375
|
const msteamsBehaviourRestrictive = isMsTeamsBehaviourRestrictive(behaviourSubset);
|
|
13558
14376
|
const behaviourDecision = decideSenderPolicyRestart({
|
|
@@ -13608,7 +14426,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13608
14426
|
const slackBehaviourSubset = extractSlackBehaviourSubset(
|
|
13609
14427
|
slackEntry?.config
|
|
13610
14428
|
);
|
|
13611
|
-
const slackBehaviourHash =
|
|
14429
|
+
const slackBehaviourHash = createHash17("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
|
|
13612
14430
|
const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
|
|
13613
14431
|
const slackBehaviourRestrictive = isSlackBehaviourRestrictive(slackBehaviourSubset);
|
|
13614
14432
|
const slackBehaviourDecision = decideSenderPolicyRestart({
|
|
@@ -13665,24 +14483,24 @@ async function processAgent(agent, agentStates) {
|
|
|
13665
14483
|
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK) === "claude-code") {
|
|
13666
14484
|
try {
|
|
13667
14485
|
const agentProvisionDir = agentDir;
|
|
13668
|
-
const projectDir =
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
const provisionMcpPath =
|
|
13672
|
-
const projectMcpPath =
|
|
14486
|
+
const projectDir = join33(homedir15(), ".augmented", agent.code_name, "project");
|
|
14487
|
+
mkdirSync11(agentProvisionDir, { recursive: true });
|
|
14488
|
+
mkdirSync11(projectDir, { recursive: true });
|
|
14489
|
+
const provisionMcpPath = join33(agentProvisionDir, ".mcp.json");
|
|
14490
|
+
const projectMcpPath = join33(projectDir, ".mcp.json");
|
|
13673
14491
|
let mcpConfig = { mcpServers: {} };
|
|
13674
14492
|
try {
|
|
13675
|
-
mcpConfig = JSON.parse(
|
|
14493
|
+
mcpConfig = JSON.parse(readFileSync26(provisionMcpPath, "utf-8"));
|
|
13676
14494
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
13677
14495
|
} catch {
|
|
13678
14496
|
}
|
|
13679
|
-
const localDirectChatChannel =
|
|
14497
|
+
const localDirectChatChannel = join33(homedir15(), ".augmented", "_mcp", "direct-chat-channel.js");
|
|
13680
14498
|
const directChatTeamSettings = refreshData.team?.settings;
|
|
13681
14499
|
const directChatTz = (() => {
|
|
13682
14500
|
const tz = directChatTeamSettings?.["timezone"];
|
|
13683
14501
|
return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
|
|
13684
14502
|
})();
|
|
13685
|
-
if (
|
|
14503
|
+
if (existsSync14(localDirectChatChannel)) {
|
|
13686
14504
|
const directChatEnv = {
|
|
13687
14505
|
AGT_HOST: requireHost(),
|
|
13688
14506
|
// ENG-5901 Track D: templated — the manager exports the real
|
|
@@ -13702,7 +14520,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13702
14520
|
// ~/.augmented/<codeName>/.current-turn-initiator.json. Note getAgentDir
|
|
13703
14521
|
// returns the agent root (NOT the /provision subdir `agentDir` points at),
|
|
13704
14522
|
// so it byte-matches the broker readers' path.
|
|
13705
|
-
AGT_TURN_INITIATOR_FILE:
|
|
14523
|
+
AGT_TURN_INITIATOR_FILE: join33(
|
|
13706
14524
|
frameworkAdapter.getAgentDir(agent.code_name),
|
|
13707
14525
|
".current-turn-initiator.json"
|
|
13708
14526
|
)
|
|
@@ -13722,8 +14540,8 @@ async function processAgent(agent, agentStates) {
|
|
|
13722
14540
|
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
13723
14541
|
}
|
|
13724
14542
|
}
|
|
13725
|
-
const staleChannelsPath =
|
|
13726
|
-
if (
|
|
14543
|
+
const staleChannelsPath = join33(projectDir, ".mcp-channels.json");
|
|
14544
|
+
if (existsSync14(staleChannelsPath)) {
|
|
13727
14545
|
try {
|
|
13728
14546
|
rmSync5(staleChannelsPath, { force: true });
|
|
13729
14547
|
} catch {
|
|
@@ -13733,7 +14551,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13733
14551
|
log(`Failed to provision direct-chat channel for '${agent.code_name}': ${err.message}`);
|
|
13734
14552
|
}
|
|
13735
14553
|
}
|
|
13736
|
-
let lastSecretsProvisionAt =
|
|
14554
|
+
let lastSecretsProvisionAt = state7.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
|
|
13737
14555
|
let secretsHash = agentState.knownSecretsHashes.get(agent.agent_id) ?? null;
|
|
13738
14556
|
try {
|
|
13739
14557
|
const secretsData = await api.post("/host/secrets", { agent_id: agent.agent_id });
|
|
@@ -13812,7 +14630,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13812
14630
|
}
|
|
13813
14631
|
if (hostFlagStore().getBoolean("connectivity-probe")) {
|
|
13814
14632
|
try {
|
|
13815
|
-
const probeProjectDir =
|
|
14633
|
+
const probeProjectDir = join33(homedir15(), ".augmented", agent.code_name, "project");
|
|
13816
14634
|
let probeSet = integrations;
|
|
13817
14635
|
try {
|
|
13818
14636
|
const quarantined = await api.post("/host/agent-integrations/quarantined", { agent_id: agent.agent_id });
|
|
@@ -13858,7 +14676,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13858
14676
|
const forceDue = attemptsLeft > 0;
|
|
13859
14677
|
let probeRan = false;
|
|
13860
14678
|
try {
|
|
13861
|
-
const probeProjectDir =
|
|
14679
|
+
const probeProjectDir = join33(homedir15(), ".augmented", agent.code_name, "project");
|
|
13862
14680
|
probeRan = await runAgentSessionToolBindProbes(agent, integrations, probeProjectDir, { forceDue });
|
|
13863
14681
|
} catch (err) {
|
|
13864
14682
|
log(`Session-tool-bind probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -13935,11 +14753,11 @@ async function processAgent(agent, agentStates) {
|
|
|
13935
14753
|
const intHash = computeIntegrationsHash(integrations);
|
|
13936
14754
|
const prevIntHash = agentState.knownIntegrationHashes.get(agent.agent_id);
|
|
13937
14755
|
if (intHash !== prevIntHash) {
|
|
13938
|
-
const projectDir =
|
|
13939
|
-
const envIntPath =
|
|
14756
|
+
const projectDir = join33(homedir15(), ".augmented", agent.code_name, "project");
|
|
14757
|
+
const envIntPath = join33(projectDir, ".env.integrations");
|
|
13940
14758
|
let preWriteEnv;
|
|
13941
14759
|
try {
|
|
13942
|
-
preWriteEnv =
|
|
14760
|
+
preWriteEnv = readFileSync26(envIntPath, "utf-8");
|
|
13943
14761
|
} catch {
|
|
13944
14762
|
preWriteEnv = void 0;
|
|
13945
14763
|
}
|
|
@@ -13958,9 +14776,9 @@ async function processAgent(agent, agentStates) {
|
|
|
13958
14776
|
}
|
|
13959
14777
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
13960
14778
|
try {
|
|
13961
|
-
const projectMcpPath =
|
|
13962
|
-
const postWriteEnv =
|
|
13963
|
-
const mcpContent =
|
|
14779
|
+
const projectMcpPath = join33(projectDir, ".mcp.json");
|
|
14780
|
+
const postWriteEnv = readFileSync26(envIntPath, "utf-8");
|
|
14781
|
+
const mcpContent = readFileSync26(projectMcpPath, "utf-8");
|
|
13964
14782
|
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
13965
14783
|
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
13966
14784
|
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
@@ -14051,10 +14869,10 @@ async function processAgent(agent, agentStates) {
|
|
|
14051
14869
|
desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
|
|
14052
14870
|
}
|
|
14053
14871
|
const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
|
|
14054
|
-
const headersHash =
|
|
14872
|
+
const headersHash = createHash17("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
|
|
14055
14873
|
return `${e.serverId}|${e.url}|${headersHash}`;
|
|
14056
14874
|
}).join("\n");
|
|
14057
|
-
const mcpHash =
|
|
14875
|
+
const mcpHash = createHash17("sha256").update(hashBasis).digest("hex").slice(0, 16);
|
|
14058
14876
|
const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
|
|
14059
14877
|
const structureHash = managedMcpStructureHash(desiredEntries);
|
|
14060
14878
|
const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
|
|
@@ -14069,7 +14887,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14069
14887
|
if (mcpHash !== prevMcpHash) {
|
|
14070
14888
|
for (const e of desiredEntries) {
|
|
14071
14889
|
frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
|
|
14072
|
-
const urlHash =
|
|
14890
|
+
const urlHash = createHash17("sha256").update(e.url).digest("hex").slice(0, 12);
|
|
14073
14891
|
log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
|
|
14074
14892
|
}
|
|
14075
14893
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.readMcpServers) {
|
|
@@ -14171,7 +14989,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14171
14989
|
if (frameworkAdapter.installSkillFiles) {
|
|
14172
14990
|
const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
|
|
14173
14991
|
const installedIntegrationSkills = [];
|
|
14174
|
-
const { createHash:
|
|
14992
|
+
const { createHash: createHash18 } = await import("crypto");
|
|
14175
14993
|
const refreshAny = refreshData;
|
|
14176
14994
|
const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
|
|
14177
14995
|
const contextBySlug = /* @__PURE__ */ new Map();
|
|
@@ -14200,7 +15018,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14200
15018
|
)
|
|
14201
15019
|
}));
|
|
14202
15020
|
const bundle = buildIntegrationBundle(renderedScopes);
|
|
14203
|
-
const contentHash =
|
|
15021
|
+
const contentHash = createHash18("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
14204
15022
|
if (!shouldWriteIntegrationSkill(
|
|
14205
15023
|
agentState.knownSkillHashes,
|
|
14206
15024
|
agent.agent_id,
|
|
@@ -14223,23 +15041,23 @@ async function processAgent(agent, agentStates) {
|
|
|
14223
15041
|
}
|
|
14224
15042
|
}
|
|
14225
15043
|
try {
|
|
14226
|
-
const { readdirSync:
|
|
14227
|
-
const { homedir:
|
|
15044
|
+
const { readdirSync: readdirSync10, rmSync: rmSync6 } = await import("fs");
|
|
15045
|
+
const { homedir: homedir16 } = await import("os");
|
|
14228
15046
|
const frameworkId2 = frameworkAdapter.id;
|
|
14229
15047
|
const candidateSkillDirs = [
|
|
14230
15048
|
// Claude Code — framework runtime tree
|
|
14231
|
-
|
|
15049
|
+
join33(homedir16(), ".augmented", agent.code_name, "skills"),
|
|
14232
15050
|
// Claude Code — project tree
|
|
14233
|
-
|
|
15051
|
+
join33(homedir16(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
14234
15052
|
// Defensive: legacy provision-side path, not currently an
|
|
14235
15053
|
// install target but cheap to sweep.
|
|
14236
|
-
|
|
15054
|
+
join33(agentDir, ".claude", "skills")
|
|
14237
15055
|
];
|
|
14238
|
-
const existingDirs = candidateSkillDirs.filter((d) =>
|
|
15056
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync14(d));
|
|
14239
15057
|
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
14240
15058
|
for (const dir of existingDirs) {
|
|
14241
15059
|
try {
|
|
14242
|
-
for (const entry of
|
|
15060
|
+
for (const entry of readdirSync10(dir)) {
|
|
14243
15061
|
if (entry.startsWith("plugin-") || entry.startsWith("integration-")) {
|
|
14244
15062
|
discoveredEntries.add(entry);
|
|
14245
15063
|
}
|
|
@@ -14254,7 +15072,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14254
15072
|
entry,
|
|
14255
15073
|
dirs: existingDirs,
|
|
14256
15074
|
removeDir: (p) => {
|
|
14257
|
-
if (
|
|
15075
|
+
if (existsSync14(p)) {
|
|
14258
15076
|
rmSync6(p, { recursive: true, force: true });
|
|
14259
15077
|
}
|
|
14260
15078
|
}
|
|
@@ -14274,7 +15092,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14274
15092
|
const sharedSkillsPayload = refreshAny.shared_skills;
|
|
14275
15093
|
const desiredResolved = globalSkillsPayload !== void 0 || sharedSkillsPayload !== void 0;
|
|
14276
15094
|
const manifestPath = managedSkillManifestPath(
|
|
14277
|
-
|
|
15095
|
+
join33(homedir15(), ".augmented", agent.code_name)
|
|
14278
15096
|
);
|
|
14279
15097
|
const prevIds = /* @__PURE__ */ new Set([
|
|
14280
15098
|
...agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
|
|
@@ -14283,7 +15101,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14283
15101
|
const plan = planGlobalSkillSync(
|
|
14284
15102
|
[...globalSkillsPayload ?? [], ...sharedSkillsPayload ?? []],
|
|
14285
15103
|
prevIds,
|
|
14286
|
-
(content) =>
|
|
15104
|
+
(content) => createHash18("sha256").update(content).digest("hex").slice(0, 12),
|
|
14287
15105
|
(skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`),
|
|
14288
15106
|
{ desiredResolved }
|
|
14289
15107
|
);
|
|
@@ -14294,15 +15112,15 @@ async function processAgent(agent, agentStates) {
|
|
|
14294
15112
|
}
|
|
14295
15113
|
if (plan.removes.length) {
|
|
14296
15114
|
const globalSkillDirs = [
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
15115
|
+
join33(homedir15(), ".augmented", agent.code_name, "skills"),
|
|
15116
|
+
join33(homedir15(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
15117
|
+
join33(agentDir, ".claude", "skills")
|
|
14300
15118
|
];
|
|
14301
15119
|
for (const id of plan.removes) {
|
|
14302
15120
|
let prunedAny = false;
|
|
14303
15121
|
for (const dir of globalSkillDirs) {
|
|
14304
|
-
const p =
|
|
14305
|
-
if (
|
|
15122
|
+
const p = join33(dir, id);
|
|
15123
|
+
if (existsSync14(p) && existsSync14(join33(p, "SKILL.md"))) {
|
|
14306
15124
|
rmSync5(p, { recursive: true, force: true });
|
|
14307
15125
|
prunedAny = true;
|
|
14308
15126
|
}
|
|
@@ -14334,7 +15152,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14334
15152
|
const slug = hook.integration_slug ?? hook.plugin_slug;
|
|
14335
15153
|
if (!slug) continue;
|
|
14336
15154
|
try {
|
|
14337
|
-
const scriptHash =
|
|
15155
|
+
const scriptHash = createHash18("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
14338
15156
|
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
|
|
14339
15157
|
if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
14340
15158
|
const result = await frameworkAdapter.executePluginHook({
|
|
@@ -14349,9 +15167,9 @@ async function processAgent(agent, agentStates) {
|
|
|
14349
15167
|
} else if (result.timedOut) {
|
|
14350
15168
|
log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
14351
15169
|
} else {
|
|
14352
|
-
const stderrHash =
|
|
15170
|
+
const stderrHash = createHash18("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
14353
15171
|
const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
|
|
14354
|
-
const missingCmdHash = missingCmd ?
|
|
15172
|
+
const missingCmdHash = missingCmd ? createHash18("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
|
|
14355
15173
|
log(
|
|
14356
15174
|
`Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
|
|
14357
15175
|
);
|
|
@@ -14504,10 +15322,7 @@ async function processAgent(agent, agentStates) {
|
|
|
14504
15322
|
log(
|
|
14505
15323
|
`[persistent-session-decision] agent=${agent.code_name} decision=${psResult.decision} spawn_attempted=${psResult.spawnAttempted} session_healthy_after=${psResult.sessionHealthyAfter}${detailSuffix}`
|
|
14506
15324
|
);
|
|
14507
|
-
|
|
14508
|
-
spawnAttempted: psResult.spawnAttempted,
|
|
14509
|
-
sessionHealthyAfter: psResult.sessionHealthyAfter
|
|
14510
|
-
});
|
|
15325
|
+
recordSpawnOutcome(agent.code_name, psResult);
|
|
14511
15326
|
const stuck = persistentSessionStuckTracker.record({
|
|
14512
15327
|
codeName: agent.code_name,
|
|
14513
15328
|
sessionHealthy: psResult.sessionHealthyAfter,
|
|
@@ -14537,8 +15352,8 @@ async function processAgent(agent, agentStates) {
|
|
|
14537
15352
|
const sess = getSessionState(agent.code_name);
|
|
14538
15353
|
let mcpJsonParsed = null;
|
|
14539
15354
|
try {
|
|
14540
|
-
const mcpPath =
|
|
14541
|
-
mcpJsonParsed = JSON.parse(
|
|
15355
|
+
const mcpPath = join33(getProjectDir(agent.code_name), ".mcp.json");
|
|
15356
|
+
mcpJsonParsed = JSON.parse(readFileSync26(mcpPath, "utf-8"));
|
|
14542
15357
|
} catch {
|
|
14543
15358
|
}
|
|
14544
15359
|
reapMissingMcpSessions({
|
|
@@ -14968,10 +15783,10 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
14968
15783
|
}
|
|
14969
15784
|
}
|
|
14970
15785
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
14971
|
-
if (trackedFiles.length > 0 &&
|
|
15786
|
+
if (trackedFiles.length > 0 && existsSync14(agentDir)) {
|
|
14972
15787
|
const hashes = /* @__PURE__ */ new Map();
|
|
14973
15788
|
for (const file of trackedFiles) {
|
|
14974
|
-
const h = hashFile(
|
|
15789
|
+
const h = hashFile(join33(agentDir, file));
|
|
14975
15790
|
if (h) hashes.set(file, h);
|
|
14976
15791
|
}
|
|
14977
15792
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -14986,7 +15801,7 @@ Retried to the limit after repeated stalls \u2014 needs a look.`).catch(() => {
|
|
|
14986
15801
|
refreshData.agent.onboarding_state
|
|
14987
15802
|
);
|
|
14988
15803
|
const obStep = obState.step;
|
|
14989
|
-
const markerPath =
|
|
15804
|
+
const markerPath = join33(homedir15(), ".augmented", agent.code_name, "onboarding-drive.json");
|
|
14990
15805
|
const marker = readOnboardingDriveMarker(markerPath);
|
|
14991
15806
|
const decision = decideOnboardingDrive(obStep, marker, Date.now(), obState.generation ?? 0);
|
|
14992
15807
|
if (decision.clearMarker) {
|
|
@@ -15074,7 +15889,7 @@ async function ensureOpencodeRuntime(agent, refreshData, agentTimezone) {
|
|
|
15074
15889
|
}
|
|
15075
15890
|
stopOpencodeSlackIngest(codeName, log);
|
|
15076
15891
|
stopOpencodeTelegramIngest(codeName, log);
|
|
15077
|
-
const opencodeProjectDir =
|
|
15892
|
+
const opencodeProjectDir = join33(getFramework("opencode").getAgentDir(codeName), "provision");
|
|
15078
15893
|
const serveEnv = {
|
|
15079
15894
|
AGT_HOST: requireHost(),
|
|
15080
15895
|
AGT_API_KEY: getApiKey() ?? void 0,
|
|
@@ -15129,8 +15944,8 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
15129
15944
|
});
|
|
15130
15945
|
}
|
|
15131
15946
|
const projectDir = getProjectDir(codeName);
|
|
15132
|
-
const mcpConfigPath =
|
|
15133
|
-
const claudeMdPath =
|
|
15947
|
+
const mcpConfigPath = join33(projectDir, ".mcp.json");
|
|
15948
|
+
const claudeMdPath = join33(projectDir, "CLAUDE.md");
|
|
15134
15949
|
if (restartBreaker.isTripped(codeName)) {
|
|
15135
15950
|
const trip = restartBreaker.getTrip(codeName);
|
|
15136
15951
|
return {
|
|
@@ -15341,7 +16156,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
15341
16156
|
const ctx = getLastFailureContext(codeName);
|
|
15342
16157
|
const recovery = prepareForRespawn(codeName);
|
|
15343
16158
|
const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
|
|
15344
|
-
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${
|
|
16159
|
+
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash17("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
|
|
15345
16160
|
const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
|
|
15346
16161
|
const recoverySummary = recovery ? `; recovery=${recovery}` : "";
|
|
15347
16162
|
log(
|
|
@@ -15355,7 +16170,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash16("sha256")
|
|
|
15355
16170
|
);
|
|
15356
16171
|
getHostId().then((hostId) => {
|
|
15357
16172
|
if (!hostId) return;
|
|
15358
|
-
const paneTailHash = zombie.paneTail ? `sha256:${
|
|
16173
|
+
const paneTailHash = zombie.paneTail ? `sha256:${createHash17("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
|
|
15359
16174
|
return api.post("/host/events", {
|
|
15360
16175
|
host_id: hostId,
|
|
15361
16176
|
agent_code_name: codeName,
|
|
@@ -15510,7 +16325,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash16("sha256")
|
|
|
15510
16325
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
15511
16326
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
15512
16327
|
}
|
|
15513
|
-
const stableTasksHash =
|
|
16328
|
+
const stableTasksHash = createHash17("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
15514
16329
|
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
15515
16330
|
if (stableTasksHash !== prevHash) {
|
|
15516
16331
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
@@ -15521,9 +16336,9 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash16("sha256")
|
|
|
15521
16336
|
} else if (!claudeSchedulerStates.has(codeName)) {
|
|
15522
16337
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
15523
16338
|
}
|
|
15524
|
-
const
|
|
15525
|
-
if (
|
|
15526
|
-
const ready = getReadyTasks(
|
|
16339
|
+
const state8 = claudeSchedulerStates.get(codeName);
|
|
16340
|
+
if (state8) {
|
|
16341
|
+
const ready = getReadyTasks(state8, inFlightClaudeTasks);
|
|
15527
16342
|
if (ready.length > 0) {
|
|
15528
16343
|
log(`[persistent-session] ${ready.length} ready task(s) for '${codeName}': ${ready.map((t) => `${t.name}(next=${t.nextFireAt ? new Date(t.nextFireAt).toISOString() : "null"})`).join(", ")}`);
|
|
15529
16344
|
}
|
|
@@ -15711,7 +16526,7 @@ function restartReasonStampsTiming(reason) {
|
|
|
15711
16526
|
return reason === "integration-change";
|
|
15712
16527
|
}
|
|
15713
16528
|
async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
15714
|
-
const prev =
|
|
16529
|
+
const prev = state7.agents.find((a) => a.agentId === agentId);
|
|
15715
16530
|
const codeName = prev?.codeName;
|
|
15716
16531
|
if (!codeName) return;
|
|
15717
16532
|
const lastProcessed = prev?.lastRestartProcessedAt ?? null;
|
|
@@ -15754,6 +16569,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
15754
16569
|
[],
|
|
15755
16570
|
refreshData
|
|
15756
16571
|
);
|
|
16572
|
+
recordSpawnOutcome(codeName, result);
|
|
15757
16573
|
if (result.decision !== "spawn" || !result.sessionHealthyAfter) {
|
|
15758
16574
|
log(`[restart-lane] respawn not confirmed for '${codeName}' (decision=${result.decision}) - leaving for slow poll`);
|
|
15759
16575
|
return;
|
|
@@ -15761,10 +16577,10 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
15761
16577
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
15762
16578
|
void (async () => {
|
|
15763
16579
|
try {
|
|
15764
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16580
|
+
const { collectDiagnostics } = await import("../persistent-session-DVTSZAPE.js");
|
|
15765
16581
|
await api.post("/host/heartbeat", {
|
|
15766
16582
|
host_id: hostId,
|
|
15767
|
-
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor)
|
|
16583
|
+
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
|
|
15768
16584
|
});
|
|
15769
16585
|
} catch (err) {
|
|
15770
16586
|
log(`[restart-lane] post-respawn diagnostics flush failed for '${codeName}': ${err.message}`);
|
|
@@ -15772,7 +16588,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
15772
16588
|
})();
|
|
15773
16589
|
prev.lastRestartProcessedAt = requestedAt;
|
|
15774
16590
|
try {
|
|
15775
|
-
atomicWriteFileSync(getStateFile(), JSON.stringify(
|
|
16591
|
+
atomicWriteFileSync(getStateFile(), JSON.stringify(state7, null, 2));
|
|
15776
16592
|
} catch (err) {
|
|
15777
16593
|
log(`[restart-lane] failed to persist ack for '${codeName}': ${err.message}`);
|
|
15778
16594
|
}
|
|
@@ -15782,7 +16598,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
15782
16598
|
}
|
|
15783
16599
|
}
|
|
15784
16600
|
async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
15785
|
-
const prev =
|
|
16601
|
+
const prev = state7.agents.find((a) => a.codeName === codeName);
|
|
15786
16602
|
if (!prev) return;
|
|
15787
16603
|
const agentId = prev.agentId;
|
|
15788
16604
|
if (restartInFlight.has(agentId)) return;
|
|
@@ -15803,6 +16619,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
15803
16619
|
[],
|
|
15804
16620
|
refreshData
|
|
15805
16621
|
);
|
|
16622
|
+
recordSpawnOutcome(codeName, result);
|
|
15806
16623
|
if (result.decision !== "spawn" || !result.sessionHealthyAfter) {
|
|
15807
16624
|
log(
|
|
15808
16625
|
`[fast-mcp-respawn] respawn not confirmed for '${codeName}' (decision=${result.decision}) - leaving for slow poll`
|
|
@@ -15811,10 +16628,10 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
15811
16628
|
}
|
|
15812
16629
|
try {
|
|
15813
16630
|
const hostId = await getHostId();
|
|
15814
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16631
|
+
const { collectDiagnostics } = await import("../persistent-session-DVTSZAPE.js");
|
|
15815
16632
|
await api.post("/host/heartbeat", {
|
|
15816
16633
|
host_id: hostId,
|
|
15817
|
-
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor)
|
|
16634
|
+
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
|
|
15818
16635
|
});
|
|
15819
16636
|
} catch (err) {
|
|
15820
16637
|
log(`[fast-mcp-respawn] post-respawn diagnostics flush failed for '${codeName}': ${err.message}`);
|
|
@@ -15919,7 +16736,7 @@ function ensureRealtimeKanbanStarted(agentStates) {
|
|
|
15919
16736
|
);
|
|
15920
16737
|
}
|
|
15921
16738
|
}).catch((err) => {
|
|
15922
|
-
const errId =
|
|
16739
|
+
const errId = createHash17("sha256").update(err instanceof Error ? err.message : String(err)).digest("hex").slice(0, 12);
|
|
15923
16740
|
log(
|
|
15924
16741
|
`[realtime] Work trigger enqueue threw for '${agent.codeName}' item_id=${item.id} error_id=${errId} \u2014 hybrid nudge will retry`
|
|
15925
16742
|
);
|
|
@@ -16073,7 +16890,7 @@ async function processDirectChatMessageOpencode(agent, msg) {
|
|
|
16073
16890
|
body
|
|
16074
16891
|
});
|
|
16075
16892
|
recordCursorAdvanceOutcome(
|
|
16076
|
-
|
|
16893
|
+
dirname9(paneLogPath(agent.codeName)),
|
|
16077
16894
|
"direct-chat-manager",
|
|
16078
16895
|
"reply",
|
|
16079
16896
|
verdict
|
|
@@ -16103,7 +16920,7 @@ async function processDirectChatMessageOpencode(agent, msg) {
|
|
|
16103
16920
|
} catch (err) {
|
|
16104
16921
|
if (!recorded) {
|
|
16105
16922
|
recordCursorAdvanceOutcome(
|
|
16106
|
-
|
|
16923
|
+
dirname9(paneLogPath(agent.codeName)),
|
|
16107
16924
|
"direct-chat-manager",
|
|
16108
16925
|
"reply",
|
|
16109
16926
|
{ outcome: "failed", error: err.message, expected: 1 }
|
|
@@ -16154,9 +16971,9 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
16154
16971
|
const useDoorbell = hostFlagStore().getBoolean("direct-chat-doorbell") || isolationMode(agent.codeName) === "docker";
|
|
16155
16972
|
if (useDoorbell) {
|
|
16156
16973
|
try {
|
|
16157
|
-
const doorbell = directChatDoorbellPath(agent.agentId,
|
|
16158
|
-
|
|
16159
|
-
|
|
16974
|
+
const doorbell = directChatDoorbellPath(agent.agentId, homedir15());
|
|
16975
|
+
mkdirSync11(dirname9(doorbell), { recursive: true });
|
|
16976
|
+
writeFileSync14(doorbell, String(Date.now()));
|
|
16160
16977
|
log(`[direct-chat] Doorbell rung for '${agent.codeName}' (msg=${msg.id}) \u2014 in-session MCP will pull via the cursor`);
|
|
16161
16978
|
return;
|
|
16162
16979
|
} catch (err) {
|
|
@@ -16205,10 +17022,10 @@ function getKanbanNudgeState(codeName) {
|
|
|
16205
17022
|
function loadKanbanNudgeStateFromDisk() {
|
|
16206
17023
|
loadKanbanNudgeState(kanbanNudgeStateByCode, channelHashCacheDir());
|
|
16207
17024
|
}
|
|
16208
|
-
function setKanbanNudgeState(codeName,
|
|
17025
|
+
function setKanbanNudgeState(codeName, state8) {
|
|
16209
17026
|
const key = kanbanNudgeStateKey(codeName);
|
|
16210
17027
|
if (key !== codeName) kanbanNudgeStateByCode.delete(codeName);
|
|
16211
|
-
kanbanNudgeStateByCode.set(key,
|
|
17028
|
+
kanbanNudgeStateByCode.set(key, state8);
|
|
16212
17029
|
saveKanbanNudgeState(kanbanNudgeStateByCode, channelHashCacheDir());
|
|
16213
17030
|
}
|
|
16214
17031
|
function clearKanbanNudgeState(codeName) {
|
|
@@ -16283,9 +17100,9 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
|
|
|
16283
17100
|
}
|
|
16284
17101
|
if (run_id) openInjectedRunByCode.set(codeName, run_id);
|
|
16285
17102
|
try {
|
|
16286
|
-
const doorbell = directChatDoorbellPath(agentId,
|
|
16287
|
-
|
|
16288
|
-
|
|
17103
|
+
const doorbell = directChatDoorbellPath(agentId, homedir15());
|
|
17104
|
+
mkdirSync11(dirname9(doorbell), { recursive: true });
|
|
17105
|
+
writeFileSync14(doorbell, String(Date.now()));
|
|
16289
17106
|
} catch (err) {
|
|
16290
17107
|
log(`[kanban] doorbell ring failed for '${codeName}': ${err.message} (notice still queued)`);
|
|
16291
17108
|
}
|
|
@@ -16389,7 +17206,7 @@ async function processClaudePairSessions(agents) {
|
|
|
16389
17206
|
killPairSession,
|
|
16390
17207
|
pairTmuxSession,
|
|
16391
17208
|
finalizeClaudePairOnboarding
|
|
16392
|
-
} = await import("../claude-pair-runtime-
|
|
17209
|
+
} = await import("../claude-pair-runtime-4C5NQQHR.js");
|
|
16393
17210
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
16394
17211
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
16395
17212
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -16643,8 +17460,8 @@ function parseMemoryFile(raw, fallbackName) {
|
|
|
16643
17460
|
};
|
|
16644
17461
|
}
|
|
16645
17462
|
async function syncMemories(agent, configDir, log2) {
|
|
16646
|
-
const projectDir =
|
|
16647
|
-
const memoryDir =
|
|
17463
|
+
const projectDir = join33(configDir, agent.code_name, "project");
|
|
17464
|
+
const memoryDir = join33(projectDir, "memory");
|
|
16648
17465
|
const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
|
|
16649
17466
|
if (isFreshSync) {
|
|
16650
17467
|
log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
|
|
@@ -16655,15 +17472,15 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
16655
17472
|
}
|
|
16656
17473
|
pendingFreshMemorySync.delete(agent.agent_id);
|
|
16657
17474
|
}
|
|
16658
|
-
if (
|
|
17475
|
+
if (existsSync14(memoryDir)) {
|
|
16659
17476
|
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
16660
17477
|
const currentHashes = /* @__PURE__ */ new Map();
|
|
16661
17478
|
const changedMemories = [];
|
|
16662
|
-
for (const file of
|
|
17479
|
+
for (const file of readdirSync9(memoryDir)) {
|
|
16663
17480
|
if (!file.endsWith(".md")) continue;
|
|
16664
17481
|
try {
|
|
16665
|
-
const raw =
|
|
16666
|
-
const fileHash =
|
|
17482
|
+
const raw = readFileSync26(join33(memoryDir, file), "utf-8");
|
|
17483
|
+
const fileHash = createHash17("sha256").update(raw).digest("hex").slice(0, 16);
|
|
16667
17484
|
currentHashes.set(file, fileHash);
|
|
16668
17485
|
if (prevHashes.get(file) === fileHash) continue;
|
|
16669
17486
|
const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
|
|
@@ -16687,7 +17504,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
16687
17504
|
} catch (err) {
|
|
16688
17505
|
for (const mem of changedMemories) {
|
|
16689
17506
|
for (const [file] of currentHashes) {
|
|
16690
|
-
const parsed = parseMemoryFile(
|
|
17507
|
+
const parsed = parseMemoryFile(readFileSync26(join33(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
16691
17508
|
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
16692
17509
|
}
|
|
16693
17510
|
}
|
|
@@ -16700,29 +17517,29 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
16700
17517
|
}
|
|
16701
17518
|
}
|
|
16702
17519
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
16703
|
-
const localFiles =
|
|
16704
|
-
const localListHash =
|
|
17520
|
+
const localFiles = existsSync14(memoryDir) ? readdirSync9(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
17521
|
+
const localListHash = createHash17("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
16705
17522
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
16706
17523
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
16707
17524
|
try {
|
|
16708
17525
|
const dbMemories = await api.post("/host/memories", {
|
|
16709
17526
|
agent_id: agent.agent_id
|
|
16710
17527
|
});
|
|
16711
|
-
const responseHash =
|
|
17528
|
+
const responseHash = createHash17("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
|
|
16712
17529
|
if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
|
|
16713
17530
|
return true;
|
|
16714
17531
|
}
|
|
16715
17532
|
lastDownloadHash.set(agent.agent_id, responseHash);
|
|
16716
17533
|
lastLocalFileHash.set(agent.agent_id, localListHash);
|
|
16717
17534
|
if (dbMemories.memories?.length) {
|
|
16718
|
-
|
|
17535
|
+
mkdirSync11(memoryDir, { recursive: true });
|
|
16719
17536
|
let written = 0;
|
|
16720
17537
|
let overwritten = 0;
|
|
16721
17538
|
for (let i = 0; i < dbMemories.memories.length; i++) {
|
|
16722
17539
|
const mem = dbMemories.memories[i];
|
|
16723
17540
|
const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
16724
17541
|
const slug = rawSlug || `memory-${i}`;
|
|
16725
|
-
const filePath =
|
|
17542
|
+
const filePath = join33(memoryDir, `${slug}.md`);
|
|
16726
17543
|
const desired = `---
|
|
16727
17544
|
name: ${JSON.stringify(mem.name)}
|
|
16728
17545
|
type: ${mem.type}
|
|
@@ -16731,23 +17548,23 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
|
16731
17548
|
|
|
16732
17549
|
${mem.content}
|
|
16733
17550
|
`;
|
|
16734
|
-
if (
|
|
17551
|
+
if (existsSync14(filePath)) {
|
|
16735
17552
|
let existing = "";
|
|
16736
17553
|
try {
|
|
16737
|
-
existing =
|
|
17554
|
+
existing = readFileSync26(filePath, "utf-8");
|
|
16738
17555
|
} catch {
|
|
16739
17556
|
}
|
|
16740
17557
|
if (existing === desired) continue;
|
|
16741
|
-
|
|
17558
|
+
writeFileSync14(filePath, desired);
|
|
16742
17559
|
overwritten++;
|
|
16743
17560
|
} else {
|
|
16744
|
-
|
|
17561
|
+
writeFileSync14(filePath, desired);
|
|
16745
17562
|
written++;
|
|
16746
17563
|
}
|
|
16747
17564
|
}
|
|
16748
17565
|
if (written > 0 || overwritten > 0) {
|
|
16749
|
-
const updatedFiles =
|
|
16750
|
-
lastLocalFileHash.set(agent.agent_id,
|
|
17566
|
+
const updatedFiles = readdirSync9(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
17567
|
+
lastLocalFileHash.set(agent.agent_id, createHash17("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
|
|
16751
17568
|
log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
|
|
16752
17569
|
}
|
|
16753
17570
|
}
|
|
@@ -16758,7 +17575,7 @@ ${mem.content}
|
|
|
16758
17575
|
}
|
|
16759
17576
|
}
|
|
16760
17577
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
16761
|
-
if (
|
|
17578
|
+
if (existsSync14(agentDir)) {
|
|
16762
17579
|
try {
|
|
16763
17580
|
rmSync5(agentDir, { recursive: true, force: true });
|
|
16764
17581
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -16821,7 +17638,7 @@ async function driveArtifactStreaming() {
|
|
|
16821
17638
|
}
|
|
16822
17639
|
async function driveArtifactStreamingInner() {
|
|
16823
17640
|
const liveIds = /* @__PURE__ */ new Set();
|
|
16824
|
-
for (const agent of
|
|
17641
|
+
for (const agent of state7.agents) {
|
|
16825
17642
|
if (!agent.agentId || !agent.codeName || agent.status !== "active") continue;
|
|
16826
17643
|
liveIds.add(agent.agentId);
|
|
16827
17644
|
let scanner = artifactScanners.get(agent.agentId);
|
|
@@ -16997,12 +17814,12 @@ function startManager(opts) {
|
|
|
16997
17814
|
config = opts;
|
|
16998
17815
|
try {
|
|
16999
17816
|
const stateFile = getStateFile();
|
|
17000
|
-
if (
|
|
17001
|
-
const raw =
|
|
17817
|
+
if (existsSync14(stateFile)) {
|
|
17818
|
+
const raw = readFileSync26(stateFile, "utf-8");
|
|
17002
17819
|
const parsed = JSON.parse(raw);
|
|
17003
17820
|
if (Array.isArray(parsed.agents)) {
|
|
17004
|
-
|
|
17005
|
-
log(`[startup] rehydrated ${
|
|
17821
|
+
state7.agents = parsed.agents;
|
|
17822
|
+
log(`[startup] rehydrated ${state7.agents.length} agent state(s) from ${stateFile}`);
|
|
17006
17823
|
}
|
|
17007
17824
|
if (parsed.circuitBreakerTrips && typeof parsed.circuitBreakerTrips === "object") {
|
|
17008
17825
|
restartBreaker.hydrate(parsed.circuitBreakerTrips);
|
|
@@ -17015,9 +17832,9 @@ function startManager(opts) {
|
|
|
17015
17832
|
if (n > 0) log(`[startup] rehydrated ${n} auto-resume marker(s) (ENG-6088)`);
|
|
17016
17833
|
}
|
|
17017
17834
|
if (typeof parsed.lastUpdateRequestProcessedAt === "string" || parsed.lastUpdateRequestProcessedAt === null) {
|
|
17018
|
-
|
|
17019
|
-
if (
|
|
17020
|
-
log(`[startup] rehydrated update-request ack at ${
|
|
17835
|
+
state7.lastUpdateRequestProcessedAt = parsed.lastUpdateRequestProcessedAt;
|
|
17836
|
+
if (state7.lastUpdateRequestProcessedAt) {
|
|
17837
|
+
log(`[startup] rehydrated update-request ack at ${state7.lastUpdateRequestProcessedAt} (ENG-6692)`);
|
|
17021
17838
|
}
|
|
17022
17839
|
}
|
|
17023
17840
|
}
|
|
@@ -17025,7 +17842,7 @@ function startManager(opts) {
|
|
|
17025
17842
|
log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
|
|
17026
17843
|
}
|
|
17027
17844
|
log(
|
|
17028
|
-
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${
|
|
17845
|
+
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join33(homedir15(), ".augmented", "manager.log")}`
|
|
17029
17846
|
);
|
|
17030
17847
|
deployMcpAssets();
|
|
17031
17848
|
reapOrphanChannelMcps({ log });
|
|
@@ -17037,7 +17854,7 @@ function startManager(opts) {
|
|
|
17037
17854
|
}
|
|
17038
17855
|
try {
|
|
17039
17856
|
refreshSlackRestartContextHints(
|
|
17040
|
-
|
|
17857
|
+
state7.agents.map((a) => a.codeName),
|
|
17041
17858
|
{ log }
|
|
17042
17859
|
);
|
|
17043
17860
|
} catch (err) {
|
|
@@ -17054,7 +17871,7 @@ async function reapOrphanedClaudePids() {
|
|
|
17054
17871
|
const looksLikeClaude = (pid) => {
|
|
17055
17872
|
if (process.platform !== "linux") return true;
|
|
17056
17873
|
try {
|
|
17057
|
-
const comm =
|
|
17874
|
+
const comm = readFileSync26(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
|
|
17058
17875
|
return comm.includes("claude");
|
|
17059
17876
|
} catch {
|
|
17060
17877
|
return false;
|
|
@@ -17151,18 +17968,18 @@ function restartRunningChannelMcps(basenames) {
|
|
|
17151
17968
|
}
|
|
17152
17969
|
}
|
|
17153
17970
|
function deployMcpAssets() {
|
|
17154
|
-
const targetDir =
|
|
17155
|
-
|
|
17156
|
-
const moduleDir =
|
|
17971
|
+
const targetDir = join33(homedir15(), ".augmented", "_mcp");
|
|
17972
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
17973
|
+
const moduleDir = dirname9(fileURLToPath(import.meta.url));
|
|
17157
17974
|
let mcpSourceDir = "";
|
|
17158
17975
|
let dir = moduleDir;
|
|
17159
17976
|
for (let i = 0; i < 6; i++) {
|
|
17160
|
-
const candidate =
|
|
17161
|
-
if (
|
|
17977
|
+
const candidate = join33(dir, "dist", "mcp");
|
|
17978
|
+
if (existsSync14(join33(candidate, "index.js"))) {
|
|
17162
17979
|
mcpSourceDir = candidate;
|
|
17163
17980
|
break;
|
|
17164
17981
|
}
|
|
17165
|
-
const parent =
|
|
17982
|
+
const parent = dirname9(dir);
|
|
17166
17983
|
if (parent === dir) break;
|
|
17167
17984
|
dir = parent;
|
|
17168
17985
|
}
|
|
@@ -17173,8 +17990,8 @@ function deployMcpAssets() {
|
|
|
17173
17990
|
const changedBasenames = [];
|
|
17174
17991
|
const fileHash = (p) => {
|
|
17175
17992
|
try {
|
|
17176
|
-
if (!
|
|
17177
|
-
return
|
|
17993
|
+
if (!existsSync14(p)) return null;
|
|
17994
|
+
return createHash17("sha256").update(readFileSync26(p)).digest("hex");
|
|
17178
17995
|
} catch {
|
|
17179
17996
|
return null;
|
|
17180
17997
|
}
|
|
@@ -17245,9 +18062,9 @@ function deployMcpAssets() {
|
|
|
17245
18062
|
// needs restarting to pick up a token rotation.
|
|
17246
18063
|
"xero.js"
|
|
17247
18064
|
]) {
|
|
17248
|
-
const src =
|
|
17249
|
-
const dst =
|
|
17250
|
-
if (!
|
|
18065
|
+
const src = join33(mcpSourceDir, file);
|
|
18066
|
+
const dst = join33(targetDir, file);
|
|
18067
|
+
if (!existsSync14(src)) continue;
|
|
17251
18068
|
const before = fileHash(dst);
|
|
17252
18069
|
try {
|
|
17253
18070
|
copyFileSync(src, dst);
|
|
@@ -17264,23 +18081,23 @@ function deployMcpAssets() {
|
|
|
17264
18081
|
log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
|
|
17265
18082
|
restartRunningChannelMcps(changedBasenames);
|
|
17266
18083
|
}
|
|
17267
|
-
const localMcpPath =
|
|
18084
|
+
const localMcpPath = join33(targetDir, "index.js");
|
|
17268
18085
|
try {
|
|
17269
|
-
const agentsDir =
|
|
17270
|
-
if (
|
|
17271
|
-
for (const entry of
|
|
18086
|
+
const agentsDir = join33(homedir15(), ".augmented", "agents");
|
|
18087
|
+
if (existsSync14(agentsDir)) {
|
|
18088
|
+
for (const entry of readdirSync9(agentsDir, { withFileTypes: true })) {
|
|
17272
18089
|
if (!entry.isDirectory()) continue;
|
|
17273
18090
|
for (const subdir of ["provision", "project"]) {
|
|
17274
|
-
const mcpJsonPath =
|
|
18091
|
+
const mcpJsonPath = join33(agentsDir, entry.name, subdir, ".mcp.json");
|
|
17275
18092
|
try {
|
|
17276
|
-
const raw =
|
|
18093
|
+
const raw = readFileSync26(mcpJsonPath, "utf-8");
|
|
17277
18094
|
if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
|
|
17278
18095
|
const mcpConfig = JSON.parse(raw);
|
|
17279
18096
|
const augServer = mcpConfig.mcpServers?.["augmented"];
|
|
17280
18097
|
if (!augServer) continue;
|
|
17281
18098
|
augServer.command = "node";
|
|
17282
18099
|
augServer.args = [localMcpPath];
|
|
17283
|
-
|
|
18100
|
+
writeFileSync14(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
17284
18101
|
log(`[manager] Patched ${entry.name}/${subdir}/.mcp.json: npx \u2192 node`);
|
|
17285
18102
|
} catch {
|
|
17286
18103
|
}
|