@integrity-labs/agt-cli 0.28.25 → 0.28.27
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.
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
provisionStopHook,
|
|
23
23
|
requireHost,
|
|
24
24
|
safeWriteJsonAtomic
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-G6KRGRHY.js";
|
|
26
26
|
import {
|
|
27
27
|
getProjectDir as getProjectDir2,
|
|
28
28
|
getReadyTasks,
|
|
@@ -105,10 +105,9 @@ import {
|
|
|
105
105
|
|
|
106
106
|
// src/lib/manager-worker.ts
|
|
107
107
|
import { createHash as createHash4 } from "crypto";
|
|
108
|
-
import { readFileSync as
|
|
109
|
-
import https from "https";
|
|
108
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync8, rmSync as rmSync3, readdirSync as readdirSync5, statSync as statSync4, unlinkSync, copyFileSync } from "fs";
|
|
110
109
|
import { execFileSync as syncExecFile } from "child_process";
|
|
111
|
-
import { join as
|
|
110
|
+
import { join as join14, dirname as dirname3 } from "path";
|
|
112
111
|
import { homedir as homedir7 } from "os";
|
|
113
112
|
import { fileURLToPath } from "url";
|
|
114
113
|
|
|
@@ -3417,57 +3416,13 @@ function killAgentChannelProcesses(codeName, opts) {
|
|
|
3417
3416
|
return pids;
|
|
3418
3417
|
}
|
|
3419
3418
|
|
|
3420
|
-
// src/lib/delivery-hint.ts
|
|
3421
|
-
var DEFAULT_PROBABILITY = 0.1;
|
|
3422
|
-
function envSuffixFor(codeName) {
|
|
3423
|
-
return codeName.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
3424
|
-
}
|
|
3425
|
-
function hintProbability(codeName, env = process.env) {
|
|
3426
|
-
const suffix = codeName ? `__${envSuffixFor(codeName)}` : "";
|
|
3427
|
-
if (suffix && env[`AGT_DELIVERY_HINT_DISABLED${suffix}`] === "1") return 0;
|
|
3428
|
-
const perAgent = suffix ? env[`AGT_DELIVERY_HINT_PROBABILITY${suffix}`] : void 0;
|
|
3429
|
-
if (perAgent != null) return clampProbability(perAgent);
|
|
3430
|
-
if (env["AGT_DELIVERY_HINT_DISABLED"] === "1") return 0;
|
|
3431
|
-
const host = env["AGT_DELIVERY_HINT_PROBABILITY"];
|
|
3432
|
-
if (host != null) return clampProbability(host);
|
|
3433
|
-
return DEFAULT_PROBABILITY;
|
|
3434
|
-
}
|
|
3435
|
-
function clampProbability(raw) {
|
|
3436
|
-
const parsed = parseFloat(raw);
|
|
3437
|
-
if (!Number.isFinite(parsed)) return DEFAULT_PROBABILITY;
|
|
3438
|
-
if (parsed < 0) return 0;
|
|
3439
|
-
if (parsed > 1) return 1;
|
|
3440
|
-
return parsed;
|
|
3441
|
-
}
|
|
3442
|
-
function shouldIncludeHint(probability, rng = Math.random) {
|
|
3443
|
-
if (probability <= 0) return false;
|
|
3444
|
-
if (probability >= 1) return true;
|
|
3445
|
-
return rng() < probability;
|
|
3446
|
-
}
|
|
3447
|
-
var HINT_VARIANTS = Object.freeze([
|
|
3448
|
-
'Quick note: you can change this scheduled task just by asking me \u2014 e.g. "Change the schedule for this task" or "Make this report less verbose in future".',
|
|
3449
|
-
`By the way, this is on a schedule \u2014 if you'd like to tweak it, just say something like "run this weekly instead of daily" or "make this report less verbose in future" and I'll handle it.`,
|
|
3450
|
-
'Heads up: I deliver this on a schedule you can edit conversationally. Try "Change the schedule for this task" or "Make this report shorter" whenever you want to adjust it.',
|
|
3451
|
-
`PS \u2014 no UI needed to tune this. Just tell me "Send this at 9am instead" or "Skip weekends for this report" and I'll update the schedule.`,
|
|
3452
|
-
'FYI this is a scheduled delivery. You can reshape it in plain English \u2014 e.g. "Make this fortnightly" or "Only include items from the last 24 hours".',
|
|
3453
|
-
`You're the boss of this schedule \u2014 ask me things like "Pause this for two weeks" or "Include a summary at the top next time" and I'll apply it.`,
|
|
3454
|
-
`Side note: you can say "Change this to Mondays only" or "Drop the preamble in future reports" and I'll update the task. No config screen required.`,
|
|
3455
|
-
'Reminder: I run this on a schedule you can edit by talking. Good openers: "Change when this fires" or "Make future reports more concise".',
|
|
3456
|
-
`Small tip \u2014 this delivery is editable on the fly. Try "Move this to afternoons" or "Cut the detail down in future runs" and I'll retune it.`,
|
|
3457
|
-
`If this cadence or format isn't quite right, just ask \u2014 "Only run this on weekdays" or "Shorter summaries from now on" both work, no form to fill out.`
|
|
3458
|
-
]);
|
|
3459
|
-
function pickHintVariant(rng = Math.random) {
|
|
3460
|
-
const idx = Math.floor(rng() * HINT_VARIANTS.length) % HINT_VARIANTS.length;
|
|
3461
|
-
return HINT_VARIANTS[idx];
|
|
3462
|
-
}
|
|
3463
|
-
|
|
3464
3419
|
// src/lib/delivery-schedule-link.ts
|
|
3465
|
-
function
|
|
3420
|
+
function envSuffixFor(codeName) {
|
|
3466
3421
|
return codeName.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
3467
3422
|
}
|
|
3468
3423
|
function scheduleLinkFooterEnabled(codeName, env = process.env) {
|
|
3469
3424
|
if (codeName) {
|
|
3470
|
-
const perAgent = env[`AGT_SCHEDULE_LINK_FOOTER_DISABLED__${
|
|
3425
|
+
const perAgent = env[`AGT_SCHEDULE_LINK_FOOTER_DISABLED__${envSuffixFor(codeName)}`];
|
|
3471
3426
|
if (perAgent === "1") return false;
|
|
3472
3427
|
}
|
|
3473
3428
|
if (env["AGT_SCHEDULE_LINK_FOOTER_DISABLED"] === "1") return false;
|
|
@@ -3831,133 +3786,583 @@ async function applyClaudeAuthToEnv(childEnv, label) {
|
|
|
3831
3786
|
}
|
|
3832
3787
|
}
|
|
3833
3788
|
|
|
3834
|
-
// src/lib/
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
// token burn is owned by the cost guardrail (ENG-5556); a producing-but-silent
|
|
3844
|
-
// loop still trips the synthetic-probe alarm. So the backstop is now opt-in:
|
|
3845
|
-
// set AGT_WEDGE_INBOUND_HARD_WAIT_SECONDS to a positive value to re-enable it
|
|
3846
|
-
// (floored at inboundWaitSeconds). 0 = the frozen/hung wedge (transcript
|
|
3847
|
-
// static) is still caught by the soft path; only the *producing* path is spared.
|
|
3848
|
-
inboundHardWaitSeconds: 0,
|
|
3849
|
-
paneStaleSeconds: 120,
|
|
3850
|
-
transcriptStaleSeconds: 60,
|
|
3851
|
-
minCycles: 3
|
|
3852
|
-
};
|
|
3853
|
-
function parseMode(raw) {
|
|
3854
|
-
const v = (raw ?? "").trim().toLowerCase();
|
|
3855
|
-
return v === "shadow" || v === "enforce" ? v : "off";
|
|
3856
|
-
}
|
|
3857
|
-
function parsePositiveInt(raw, fallback, floor) {
|
|
3858
|
-
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
3859
|
-
return Number.isInteger(n) && n >= floor ? n : fallback;
|
|
3860
|
-
}
|
|
3861
|
-
function resolveWedgeConfig(env = process.env) {
|
|
3862
|
-
const inboundWaitSeconds = parsePositiveInt(
|
|
3863
|
-
env.AGT_WEDGE_INBOUND_WAIT_SECONDS,
|
|
3864
|
-
DEFAULTS.inboundWaitSeconds,
|
|
3865
|
-
30
|
|
3866
|
-
);
|
|
3867
|
-
const inboundHardWaitRaw = parsePositiveInt(
|
|
3868
|
-
env.AGT_WEDGE_INBOUND_HARD_WAIT_SECONDS,
|
|
3869
|
-
DEFAULTS.inboundHardWaitSeconds,
|
|
3870
|
-
0
|
|
3871
|
-
);
|
|
3872
|
-
const inboundHardWaitSeconds = inboundHardWaitRaw <= 0 ? 0 : Math.max(inboundWaitSeconds, inboundHardWaitRaw);
|
|
3873
|
-
return {
|
|
3874
|
-
mode: parseMode(env.AGT_WEDGE_RESTART_MODE),
|
|
3875
|
-
inboundWaitSeconds,
|
|
3876
|
-
inboundHardWaitSeconds,
|
|
3877
|
-
paneStaleSeconds: parsePositiveInt(env.AGT_WEDGE_PANE_STALE_SECONDS, DEFAULTS.paneStaleSeconds, 30),
|
|
3878
|
-
transcriptStaleSeconds: parsePositiveInt(
|
|
3879
|
-
env.AGT_WEDGE_TRANSCRIPT_STALE_SECONDS,
|
|
3880
|
-
DEFAULTS.transcriptStaleSeconds,
|
|
3881
|
-
15
|
|
3882
|
-
),
|
|
3883
|
-
minCycles: parsePositiveInt(env.AGT_WEDGE_MIN_CYCLES, DEFAULTS.minCycles, 2)
|
|
3884
|
-
};
|
|
3885
|
-
}
|
|
3886
|
-
function isSessionProducing(signals, config2) {
|
|
3887
|
-
const subagentAge = signals.subagentActivityAgeSeconds;
|
|
3888
|
-
if (subagentAge !== null && subagentAge < config2.transcriptStaleSeconds) return true;
|
|
3889
|
-
const transcriptAge = signals.transcriptActivityAgeSeconds;
|
|
3890
|
-
if (transcriptAge !== null) return transcriptAge < config2.transcriptStaleSeconds;
|
|
3891
|
-
const paneAge = signals.paneActivityAgeSeconds;
|
|
3892
|
-
return paneAge !== null && paneAge < config2.paneStaleSeconds;
|
|
3893
|
-
}
|
|
3894
|
-
function wedgeExemptionReason(signals, config2) {
|
|
3895
|
-
if (signals.subagentActivityAgeSeconds === null) return null;
|
|
3896
|
-
if (isWedgeCandidateCycle(signals, config2)) return null;
|
|
3897
|
-
const withoutSubagent = { ...signals, subagentActivityAgeSeconds: null };
|
|
3898
|
-
return isWedgeCandidateCycle(withoutSubagent, config2) ? "background-task-in-flight" : null;
|
|
3789
|
+
// src/lib/manager/kanban/parsers.ts
|
|
3790
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
|
|
3791
|
+
import { join as join12 } from "path";
|
|
3792
|
+
var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-summary"]);
|
|
3793
|
+
var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
|
|
3794
|
+
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
3795
|
+
var KANBAN_WORK_TEMPLATES = /* @__PURE__ */ new Set(["kanban-work"]);
|
|
3796
|
+
function isPlainScheduledTemplate(templateId) {
|
|
3797
|
+
return !STANDUP_TEMPLATES.has(templateId) && !TASK_UPDATE_TEMPLATES.has(templateId) && !PLAN_TEMPLATES.has(templateId) && !KANBAN_WORK_TEMPLATES.has(templateId);
|
|
3899
3798
|
}
|
|
3900
|
-
function
|
|
3901
|
-
const inboundAge = signals.pendingInboundOldestAgeSeconds;
|
|
3902
|
-
if (inboundAge === null) return false;
|
|
3903
|
-
if (inboundAge < config2.inboundWaitSeconds) return false;
|
|
3904
|
-
if (isSessionProducing(signals, config2)) {
|
|
3905
|
-
if (config2.inboundHardWaitSeconds <= 0) return false;
|
|
3906
|
-
return inboundAge >= config2.inboundHardWaitSeconds;
|
|
3907
|
-
}
|
|
3799
|
+
function isKanbanHybridEnabled() {
|
|
3908
3800
|
return true;
|
|
3909
3801
|
}
|
|
3910
|
-
function
|
|
3911
|
-
|
|
3912
|
-
return
|
|
3802
|
+
function isKanbanHybridDryRun() {
|
|
3803
|
+
const v = process.env["AGT_KANBAN_HYBRID_DRY_RUN"];
|
|
3804
|
+
return v === "1" || v?.toLowerCase() === "true";
|
|
3913
3805
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
threshold: 3,
|
|
3918
|
-
cooldownSeconds: 1800
|
|
3919
|
-
// 30 min — matches the kanban stale-auto-fail window
|
|
3920
|
-
};
|
|
3921
|
-
function parsePositiveInt2(raw, fallback, floor) {
|
|
3922
|
-
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
3923
|
-
return Number.isInteger(n) && n >= floor ? n : fallback;
|
|
3806
|
+
var HYBRID_ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
|
|
3807
|
+
function isHybridActionable(item) {
|
|
3808
|
+
return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== "scheduled_task";
|
|
3924
3809
|
}
|
|
3925
|
-
function
|
|
3926
|
-
return
|
|
3927
|
-
threshold: parsePositiveInt2(env.AGT_WEDGE_POISON_CARD_THRESHOLD, DEFAULTS2.threshold, 2),
|
|
3928
|
-
cooldownMs: parsePositiveInt2(
|
|
3929
|
-
env.AGT_WEDGE_POISON_CARD_COOLDOWN_SECONDS,
|
|
3930
|
-
DEFAULTS2.cooldownSeconds,
|
|
3931
|
-
300
|
|
3932
|
-
) * 1e3
|
|
3933
|
-
};
|
|
3810
|
+
function isSlashCommand(command) {
|
|
3811
|
+
return command.trimStart().startsWith("/");
|
|
3934
3812
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3813
|
+
var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary"]);
|
|
3814
|
+
function parseStandupSummary(summary) {
|
|
3815
|
+
const lines = summary.split("\n");
|
|
3816
|
+
let yesterday = "";
|
|
3817
|
+
let today = "";
|
|
3818
|
+
let blockers = "";
|
|
3819
|
+
let currentSection = null;
|
|
3820
|
+
for (const line of lines) {
|
|
3821
|
+
const lower = line.toLowerCase();
|
|
3822
|
+
if (lower.includes("yesterday") || lower.includes("accomplished")) {
|
|
3823
|
+
currentSection = "yesterday";
|
|
3824
|
+
continue;
|
|
3825
|
+
} else if (lower.includes("today") || lower.includes("todo") || lower.includes("working on")) {
|
|
3826
|
+
currentSection = "todo";
|
|
3827
|
+
continue;
|
|
3828
|
+
} else if (lower.includes("blocker")) {
|
|
3829
|
+
currentSection = "blockers";
|
|
3830
|
+
continue;
|
|
3831
|
+
}
|
|
3832
|
+
const trimmed = line.replace(/^[-*•]\s*/, "").trim();
|
|
3833
|
+
if (!trimmed) continue;
|
|
3834
|
+
switch (currentSection) {
|
|
3835
|
+
case "yesterday":
|
|
3836
|
+
yesterday += (yesterday ? "\n" : "") + trimmed;
|
|
3837
|
+
break;
|
|
3838
|
+
case "todo":
|
|
3839
|
+
today += (today ? "\n" : "") + trimmed;
|
|
3840
|
+
break;
|
|
3841
|
+
case "blockers":
|
|
3842
|
+
blockers += (blockers ? "\n" : "") + trimmed;
|
|
3843
|
+
break;
|
|
3844
|
+
}
|
|
3938
3845
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
for (const id of inProgressCardIds) {
|
|
3942
|
-
if (next.has(id)) continue;
|
|
3943
|
-
const count = (states.get(id)?.count ?? 0) + 1;
|
|
3944
|
-
next.set(id, { count, lastWedgeAtMs: nowMs });
|
|
3945
|
-
if (count === config2.threshold) newlyPoisoned.push(id);
|
|
3846
|
+
if (!yesterday && !today && !blockers) {
|
|
3847
|
+
today = summary;
|
|
3946
3848
|
}
|
|
3947
|
-
return {
|
|
3849
|
+
return { yesterday, today, blockers };
|
|
3948
3850
|
}
|
|
3949
|
-
function
|
|
3950
|
-
const
|
|
3951
|
-
const
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3851
|
+
function parsePlanItems(summary) {
|
|
3852
|
+
const items = [];
|
|
3853
|
+
const lines = summary.split("\n");
|
|
3854
|
+
let currentItem = null;
|
|
3855
|
+
for (const line of lines) {
|
|
3856
|
+
const trimmed = line.trim();
|
|
3857
|
+
const itemMatch = trimmed.match(/^(?:\d+[\.\)]\s*|[-*•]\s*)\[?(HIGH|MEDIUM|LOW|MED)\]?\s*(.+)/i);
|
|
3858
|
+
if (itemMatch) {
|
|
3859
|
+
if (currentItem) items.push(currentItem);
|
|
3860
|
+
const priorityStr = itemMatch[1].toUpperCase();
|
|
3861
|
+
const rest = itemMatch[2];
|
|
3862
|
+
let estimatedMinutes;
|
|
3863
|
+
const timeMatch = rest.match(/\(~?(\d+)\s*(min(?:utes?)?|hr?(?:ours?)?|h)\)/i);
|
|
3864
|
+
if (timeMatch) {
|
|
3865
|
+
const val = parseInt(timeMatch[1], 10);
|
|
3866
|
+
const unit = timeMatch[2].toLowerCase();
|
|
3867
|
+
estimatedMinutes = unit.startsWith("h") ? val * 60 : val;
|
|
3868
|
+
}
|
|
3869
|
+
const title = sanitizeKanbanString(
|
|
3870
|
+
rest.replace(/\(~?\d+\s*(?:min(?:utes?)?|hr?(?:ours?)?|h)\)/i, ""),
|
|
3871
|
+
MAX_KANBAN_TITLE_LENGTH
|
|
3872
|
+
);
|
|
3873
|
+
if (!title) continue;
|
|
3874
|
+
const priorityMap = { HIGH: 1, MEDIUM: 2, MED: 2, LOW: 3 };
|
|
3875
|
+
const priority = priorityMap[priorityStr] ?? 2;
|
|
3876
|
+
if (![1, 2, 3].includes(priority)) continue;
|
|
3877
|
+
currentItem = {
|
|
3878
|
+
title,
|
|
3879
|
+
priority,
|
|
3880
|
+
estimated_minutes: estimatedMinutes,
|
|
3881
|
+
status: "todo"
|
|
3882
|
+
};
|
|
3883
|
+
} else if (currentItem && trimmed && !trimmed.match(/^(?:PLAN|---)/i)) {
|
|
3884
|
+
const descLine = sanitizeKanbanString(trimmed, MAX_KANBAN_NOTES_LENGTH);
|
|
3885
|
+
currentItem.description = currentItem.description ? sanitizeKanbanString(currentItem.description + "\n" + descLine, MAX_KANBAN_NOTES_LENGTH) : descLine;
|
|
3886
|
+
}
|
|
3956
3887
|
}
|
|
3957
|
-
|
|
3888
|
+
if (currentItem) items.push(currentItem);
|
|
3889
|
+
return items;
|
|
3958
3890
|
}
|
|
3959
|
-
|
|
3960
|
-
|
|
3891
|
+
var VALID_KANBAN_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress", "done"]);
|
|
3892
|
+
var MAX_KANBAN_TITLE_LENGTH = 500;
|
|
3893
|
+
var MAX_KANBAN_NOTES_LENGTH = 2e3;
|
|
3894
|
+
function sanitizeKanbanString(value, maxLen) {
|
|
3895
|
+
if (!value) return "";
|
|
3896
|
+
return value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").replace(/\{\{.*?\}\}/g, "").replace(/^(System|Assistant|Human):\s*/gmi, "").replace(/#{2,}/g, "").replace(/\s+/g, " ").trim().slice(0, maxLen);
|
|
3897
|
+
}
|
|
3898
|
+
function sanitizeBoardItem(item) {
|
|
3899
|
+
return {
|
|
3900
|
+
...item,
|
|
3901
|
+
title: sanitizeKanbanString(item.title, 200),
|
|
3902
|
+
status: sanitizeKanbanString(item.status, 50),
|
|
3903
|
+
...item.deliverable ? { deliverable: sanitizeKanbanString(item.deliverable, 500) } : {}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
var builtInSkillCache = /* @__PURE__ */ new Map();
|
|
3907
|
+
function getBuiltInSkillContent(skillId) {
|
|
3908
|
+
if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId);
|
|
3909
|
+
try {
|
|
3910
|
+
const candidates = [
|
|
3911
|
+
join12(process.cwd(), "skills", skillId, "SKILL.md"),
|
|
3912
|
+
join12(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "..", "..", "skills", skillId, "SKILL.md")
|
|
3913
|
+
];
|
|
3914
|
+
for (const candidate of candidates) {
|
|
3915
|
+
if (existsSync6(candidate)) {
|
|
3916
|
+
const content = readFileSync10(candidate, "utf-8");
|
|
3917
|
+
const files = [{ relativePath: "SKILL.md", content }];
|
|
3918
|
+
builtInSkillCache.set(skillId, files);
|
|
3919
|
+
return files;
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
builtInSkillCache.set(skillId, null);
|
|
3923
|
+
return null;
|
|
3924
|
+
} catch {
|
|
3925
|
+
builtInSkillCache.set(skillId, null);
|
|
3926
|
+
return null;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
function parseKanbanUpdates(summary) {
|
|
3930
|
+
const updates = [];
|
|
3931
|
+
const markerMatch = /kanban update:/i.exec(summary);
|
|
3932
|
+
if (!markerMatch) return updates;
|
|
3933
|
+
const kanbanSection = summary.slice(markerMatch.index + markerMatch[0].length);
|
|
3934
|
+
const lines = kanbanSection.split("\n");
|
|
3935
|
+
for (const line of lines) {
|
|
3936
|
+
const trimmed = line.trim();
|
|
3937
|
+
const match = trimmed.match(/^[-*•]\s*"([^"]+)":\s*(backlog|todo|today|in_progress|done)(?:\s*\((.+)\))?/i);
|
|
3938
|
+
if (match) {
|
|
3939
|
+
const rawStatus = match[2].toLowerCase();
|
|
3940
|
+
const status = rawStatus === "today" ? "todo" : rawStatus;
|
|
3941
|
+
if (!VALID_KANBAN_STATUSES.has(status)) continue;
|
|
3942
|
+
const title = sanitizeKanbanString(match[1], MAX_KANBAN_TITLE_LENGTH);
|
|
3943
|
+
if (!title) continue;
|
|
3944
|
+
const parenthetical = match[3] ?? void 0;
|
|
3945
|
+
let notes;
|
|
3946
|
+
let result;
|
|
3947
|
+
if (parenthetical && status === "done") {
|
|
3948
|
+
const resultMatch = parenthetical.match(/^result:\s*(.+)/i);
|
|
3949
|
+
if (resultMatch) {
|
|
3950
|
+
result = sanitizeKanbanString(resultMatch[1], MAX_KANBAN_NOTES_LENGTH);
|
|
3951
|
+
} else {
|
|
3952
|
+
notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);
|
|
3953
|
+
}
|
|
3954
|
+
} else if (parenthetical) {
|
|
3955
|
+
notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);
|
|
3956
|
+
}
|
|
3957
|
+
updates.push({
|
|
3958
|
+
title,
|
|
3959
|
+
status,
|
|
3960
|
+
notes,
|
|
3961
|
+
result
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
return updates;
|
|
3966
|
+
}
|
|
3967
|
+
function formatBoardForPrompt(items, template) {
|
|
3968
|
+
if (items.length === 0) return "";
|
|
3969
|
+
const priorityLabel = (p) => p === 1 ? "HIGH" : p === 3 ? "LOW" : "MED";
|
|
3970
|
+
const timeLabel = (m) => m ? ` (~${m >= 60 ? `${Math.round(m / 60)}hr` : `${m}min`})` : "";
|
|
3971
|
+
const deliverableLine = (d) => d ? `
|
|
3972
|
+
Deliverable: ${d}` : "";
|
|
3973
|
+
const sourceLine = (channel, thread, url) => {
|
|
3974
|
+
const parts = [];
|
|
3975
|
+
if (channel && thread) parts.push(`${channel} thread ${thread}`);
|
|
3976
|
+
if (url) parts.push(url);
|
|
3977
|
+
return parts.length > 0 ? `
|
|
3978
|
+
source: ${parts.join(" \u2022 ")}` : "";
|
|
3979
|
+
};
|
|
3980
|
+
const grouped = {};
|
|
3981
|
+
for (const item of items) {
|
|
3982
|
+
const key = item.status;
|
|
3983
|
+
if (!grouped[key]) grouped[key] = [];
|
|
3984
|
+
grouped[key].push(item);
|
|
3985
|
+
}
|
|
3986
|
+
const hasSourceThread = items.some(
|
|
3987
|
+
(i) => (i.status === "todo" || i.status === "in_progress") && !!i.source_integration && !!i.source_external_id
|
|
3988
|
+
);
|
|
3989
|
+
const lines = [];
|
|
3990
|
+
if (template === "morning-plan") {
|
|
3991
|
+
lines.push("=== CURRENT BOARD ===");
|
|
3992
|
+
for (const [status, label] of [["backlog", "BACKLOG (carry-over)"], ["todo", "TO DO"], ["in_progress", "IN PROGRESS"]]) {
|
|
3993
|
+
const statusItems = grouped[status];
|
|
3994
|
+
if (statusItems && statusItems.length > 0) {
|
|
3995
|
+
lines.push(`${label}:`);
|
|
3996
|
+
statusItems.forEach((item, i) => {
|
|
3997
|
+
lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}${sourceLine(item.source_integration, item.source_external_id, item.source_url)}`);
|
|
3998
|
+
});
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
lines.push("=====================");
|
|
4002
|
+
lines.push("");
|
|
4003
|
+
lines.push("Create today's plan. You may:");
|
|
4004
|
+
lines.push('- Move backlog items to "todo"');
|
|
4005
|
+
lines.push("- Add new items you've identified");
|
|
4006
|
+
lines.push("- Reprioritise existing items");
|
|
4007
|
+
lines.push("");
|
|
4008
|
+
} else {
|
|
4009
|
+
lines.push("=== YOUR KANBAN BOARD ===");
|
|
4010
|
+
for (const [status, label] of [["todo", "TO DO"], ["in_progress", "IN PROGRESS"], ["backlog", "BACKLOG"]]) {
|
|
4011
|
+
const statusItems = grouped[status];
|
|
4012
|
+
if (statusItems && statusItems.length > 0) {
|
|
4013
|
+
lines.push(`${label}:`);
|
|
4014
|
+
statusItems.forEach((item, i) => {
|
|
4015
|
+
lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}${sourceLine(item.source_integration, item.source_external_id, item.source_url)}`);
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
const doneItems = grouped["done"];
|
|
4020
|
+
if (doneItems && doneItems.length > 0) {
|
|
4021
|
+
lines.push("DONE TODAY:");
|
|
4022
|
+
doneItems.forEach((item, i) => {
|
|
4023
|
+
lines.push(` ${i + 1}. ${item.title}`);
|
|
4024
|
+
});
|
|
4025
|
+
}
|
|
4026
|
+
lines.push("=========================");
|
|
4027
|
+
lines.push("");
|
|
4028
|
+
lines.push("IMPORTANT: Use kanban MCP tools to update the board IN REAL TIME:");
|
|
4029
|
+
lines.push("1. FIRST call kanban_move to move your chosen item to in_progress BEFORE starting work");
|
|
4030
|
+
lines.push("2. Do the work");
|
|
4031
|
+
lines.push("3. Call kanban_done with a result summary when finished");
|
|
4032
|
+
lines.push("4. If blocked, call kanban_update with notes, then pick the next item");
|
|
4033
|
+
lines.push("");
|
|
4034
|
+
if (hasSourceThread) {
|
|
4035
|
+
lines.push("THREAD CONTINUITY: If a card shows a `source:` line, the request originated");
|
|
4036
|
+
lines.push("in that Slack/Telegram thread. When you deliver the result, reply in that");
|
|
4037
|
+
lines.push("thread (not the default channel) so the user sees the answer where they");
|
|
4038
|
+
lines.push("asked. The card's `source_channel` + `source_thread_id` are the thread");
|
|
4039
|
+
lines.push("coordinates to use.");
|
|
4040
|
+
lines.push("");
|
|
4041
|
+
}
|
|
4042
|
+
lines.push("SELF-MANAGEMENT: When you receive a request from a channel (Slack, Telegram)");
|
|
4043
|
+
lines.push("that takes more than a quick response, create a task first with kanban_add,");
|
|
4044
|
+
lines.push("move to in_progress, do the work, then kanban_done with a result summary.");
|
|
4045
|
+
lines.push("");
|
|
4046
|
+
lines.push("If MCP tools are unavailable, include a KANBAN UPDATE section in your output:");
|
|
4047
|
+
lines.push("KANBAN UPDATE:");
|
|
4048
|
+
lines.push('- "item title": new_status (optional notes)');
|
|
4049
|
+
lines.push('- "item title": done (result: <what you produced>)');
|
|
4050
|
+
lines.push("Statuses: backlog, today, in_progress, done");
|
|
4051
|
+
lines.push("");
|
|
4052
|
+
}
|
|
4053
|
+
return lines.join("\n");
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
// src/lib/manager/channels/state.ts
|
|
4057
|
+
var agentChannelTokens = /* @__PURE__ */ new Map();
|
|
4058
|
+
var alertSlackWebhook = null;
|
|
4059
|
+
function getAlertSlackWebhook() {
|
|
4060
|
+
return alertSlackWebhook;
|
|
4061
|
+
}
|
|
4062
|
+
function setAlertSlackWebhook(value) {
|
|
4063
|
+
alertSlackWebhook = value;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// src/lib/delivery-hint.ts
|
|
4067
|
+
var DEFAULT_PROBABILITY = 0.1;
|
|
4068
|
+
function envSuffixFor2(codeName) {
|
|
4069
|
+
return codeName.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
4070
|
+
}
|
|
4071
|
+
function hintProbability(codeName, env = process.env) {
|
|
4072
|
+
const suffix = codeName ? `__${envSuffixFor2(codeName)}` : "";
|
|
4073
|
+
if (suffix && env[`AGT_DELIVERY_HINT_DISABLED${suffix}`] === "1") return 0;
|
|
4074
|
+
const perAgent = suffix ? env[`AGT_DELIVERY_HINT_PROBABILITY${suffix}`] : void 0;
|
|
4075
|
+
if (perAgent != null) return clampProbability(perAgent);
|
|
4076
|
+
if (env["AGT_DELIVERY_HINT_DISABLED"] === "1") return 0;
|
|
4077
|
+
const host = env["AGT_DELIVERY_HINT_PROBABILITY"];
|
|
4078
|
+
if (host != null) return clampProbability(host);
|
|
4079
|
+
return DEFAULT_PROBABILITY;
|
|
4080
|
+
}
|
|
4081
|
+
function clampProbability(raw) {
|
|
4082
|
+
const parsed = parseFloat(raw);
|
|
4083
|
+
if (!Number.isFinite(parsed)) return DEFAULT_PROBABILITY;
|
|
4084
|
+
if (parsed < 0) return 0;
|
|
4085
|
+
if (parsed > 1) return 1;
|
|
4086
|
+
return parsed;
|
|
4087
|
+
}
|
|
4088
|
+
function shouldIncludeHint(probability, rng = Math.random) {
|
|
4089
|
+
if (probability <= 0) return false;
|
|
4090
|
+
if (probability >= 1) return true;
|
|
4091
|
+
return rng() < probability;
|
|
4092
|
+
}
|
|
4093
|
+
var HINT_VARIANTS = Object.freeze([
|
|
4094
|
+
'Quick note: you can change this scheduled task just by asking me \u2014 e.g. "Change the schedule for this task" or "Make this report less verbose in future".',
|
|
4095
|
+
`By the way, this is on a schedule \u2014 if you'd like to tweak it, just say something like "run this weekly instead of daily" or "make this report less verbose in future" and I'll handle it.`,
|
|
4096
|
+
'Heads up: I deliver this on a schedule you can edit conversationally. Try "Change the schedule for this task" or "Make this report shorter" whenever you want to adjust it.',
|
|
4097
|
+
`PS \u2014 no UI needed to tune this. Just tell me "Send this at 9am instead" or "Skip weekends for this report" and I'll update the schedule.`,
|
|
4098
|
+
'FYI this is a scheduled delivery. You can reshape it in plain English \u2014 e.g. "Make this fortnightly" or "Only include items from the last 24 hours".',
|
|
4099
|
+
`You're the boss of this schedule \u2014 ask me things like "Pause this for two weeks" or "Include a summary at the top next time" and I'll apply it.`,
|
|
4100
|
+
`Side note: you can say "Change this to Mondays only" or "Drop the preamble in future reports" and I'll update the task. No config screen required.`,
|
|
4101
|
+
'Reminder: I run this on a schedule you can edit by talking. Good openers: "Change when this fires" or "Make future reports more concise".',
|
|
4102
|
+
`Small tip \u2014 this delivery is editable on the fly. Try "Move this to afternoons" or "Cut the detail down in future runs" and I'll retune it.`,
|
|
4103
|
+
`If this cadence or format isn't quite right, just ask \u2014 "Only run this on weekdays" or "Shorter summaries from now on" both work, no form to fill out.`
|
|
4104
|
+
]);
|
|
4105
|
+
function pickHintVariant(rng = Math.random) {
|
|
4106
|
+
const idx = Math.floor(rng() * HINT_VARIANTS.length) % HINT_VARIANTS.length;
|
|
4107
|
+
return HINT_VARIANTS[idx];
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// src/lib/manager/channels/slack.ts
|
|
4111
|
+
async function sendSlackWebhookMessage(text) {
|
|
4112
|
+
const alertSlackWebhook2 = getAlertSlackWebhook();
|
|
4113
|
+
if (!alertSlackWebhook2) {
|
|
4114
|
+
log("sendSlackWebhookMessage: no alertSlackWebhook configured \u2014 message dropped");
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
try {
|
|
4118
|
+
const controller = new AbortController();
|
|
4119
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
4120
|
+
try {
|
|
4121
|
+
const response = await fetch(alertSlackWebhook2, {
|
|
4122
|
+
method: "POST",
|
|
4123
|
+
headers: { "Content-Type": "application/json" },
|
|
4124
|
+
body: JSON.stringify({ text }),
|
|
4125
|
+
signal: controller.signal
|
|
4126
|
+
});
|
|
4127
|
+
if (!response.ok) {
|
|
4128
|
+
log(`Slack webhook failed: ${response.status} ${response.statusText}`);
|
|
4129
|
+
}
|
|
4130
|
+
} finally {
|
|
4131
|
+
clearTimeout(timeout);
|
|
4132
|
+
}
|
|
4133
|
+
} catch (err) {
|
|
4134
|
+
log(`Slack webhook error: ${err.message}`);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
async function sendSlackChannelMessage(agentCodeName, channelId, text) {
|
|
4138
|
+
const result = await postSlackChannelMessage(agentCodeName, channelId, text);
|
|
4139
|
+
return result.ok;
|
|
4140
|
+
}
|
|
4141
|
+
async function postSlackChannelMessage(agentCodeName, channelId, text, threadTs) {
|
|
4142
|
+
const botToken = agentChannelTokens.get(agentCodeName)?.slack;
|
|
4143
|
+
if (!botToken) {
|
|
4144
|
+
log(`No Slack bot token cached for '${agentCodeName}' \u2014 cannot post to ${channelId}`);
|
|
4145
|
+
return { ok: false };
|
|
4146
|
+
}
|
|
4147
|
+
try {
|
|
4148
|
+
const controller = new AbortController();
|
|
4149
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
4150
|
+
try {
|
|
4151
|
+
const body = { channel: channelId, text };
|
|
4152
|
+
if (threadTs) body.thread_ts = threadTs;
|
|
4153
|
+
const response = await fetch("https://slack.com/api/chat.postMessage", {
|
|
4154
|
+
method: "POST",
|
|
4155
|
+
headers: {
|
|
4156
|
+
"Authorization": `Bearer ${botToken}`,
|
|
4157
|
+
"Content-Type": "application/json"
|
|
4158
|
+
},
|
|
4159
|
+
body: JSON.stringify(body),
|
|
4160
|
+
signal: controller.signal
|
|
4161
|
+
});
|
|
4162
|
+
clearTimeout(timeout);
|
|
4163
|
+
const data = await response.json();
|
|
4164
|
+
if (!data.ok) {
|
|
4165
|
+
log(`Slack chat.postMessage failed for '${agentCodeName}' to ${channelId}: ${data.error}`);
|
|
4166
|
+
return { ok: false, error: data.error };
|
|
4167
|
+
}
|
|
4168
|
+
return { ok: true, ts: data.ts };
|
|
4169
|
+
} finally {
|
|
4170
|
+
clearTimeout(timeout);
|
|
4171
|
+
}
|
|
4172
|
+
} catch (err) {
|
|
4173
|
+
log(`Slack channel message error for '${agentCodeName}': ${err.message}`);
|
|
4174
|
+
return { ok: false };
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
async function maybePostSlackThreadHint(agentCodeName, channelId, primaryTs) {
|
|
4178
|
+
if (!shouldIncludeHint(hintProbability(agentCodeName))) return;
|
|
4179
|
+
if (!primaryTs) {
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
const hint = pickHintVariant();
|
|
4183
|
+
const result = await postSlackChannelMessage(agentCodeName, channelId, hint, primaryTs);
|
|
4184
|
+
if (result.ok) {
|
|
4185
|
+
log(`[delivery-hint] Slack thread hint posted for '${agentCodeName}'`);
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
// src/lib/manager/channels/telegram.ts
|
|
4190
|
+
import https from "https";
|
|
4191
|
+
function telegramApiCall(botToken, method, body) {
|
|
4192
|
+
return new Promise((resolve, reject) => {
|
|
4193
|
+
const postData = JSON.stringify(body);
|
|
4194
|
+
const req = https.request({
|
|
4195
|
+
hostname: "api.telegram.org",
|
|
4196
|
+
port: 443,
|
|
4197
|
+
path: `/bot${botToken}/${method}`,
|
|
4198
|
+
method: "POST",
|
|
4199
|
+
family: 4,
|
|
4200
|
+
timeout: 1e4,
|
|
4201
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(postData) }
|
|
4202
|
+
}, (res) => {
|
|
4203
|
+
let data = "";
|
|
4204
|
+
res.on("data", (d) => {
|
|
4205
|
+
data += d;
|
|
4206
|
+
});
|
|
4207
|
+
res.on("end", () => {
|
|
4208
|
+
try {
|
|
4209
|
+
resolve(JSON.parse(data));
|
|
4210
|
+
} catch {
|
|
4211
|
+
reject(new Error("Invalid JSON from Telegram API"));
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
});
|
|
4215
|
+
req.on("error", reject);
|
|
4216
|
+
req.on("timeout", () => {
|
|
4217
|
+
req.destroy();
|
|
4218
|
+
reject(new Error("Telegram API timeout"));
|
|
4219
|
+
});
|
|
4220
|
+
req.write(postData);
|
|
4221
|
+
req.end();
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
4224
|
+
async function maybeSendTelegramFollowUpHint(agentCodeName, botToken, chatId) {
|
|
4225
|
+
if (!shouldIncludeHint(hintProbability(agentCodeName))) return;
|
|
4226
|
+
const hint = pickHintVariant();
|
|
4227
|
+
try {
|
|
4228
|
+
const result = await telegramApiCall(botToken, "sendMessage", { chat_id: chatId, text: hint });
|
|
4229
|
+
if (!result.ok) {
|
|
4230
|
+
log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${result.description ?? "unknown"}`);
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
log(`[delivery-hint] Telegram follow-up hint posted for '${agentCodeName}'`);
|
|
4234
|
+
} catch (err) {
|
|
4235
|
+
log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${err.message}`);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
// src/lib/wedge-detection.ts
|
|
4240
|
+
var DEFAULTS = {
|
|
4241
|
+
inboundWaitSeconds: 120,
|
|
4242
|
+
// ENG-6264: DISABLED by default (0). A session that's actively producing
|
|
4243
|
+
// tokens is never force-respawned — a working agent must not be killed just
|
|
4244
|
+
// because a message has been queued behind its turn, no matter how long.
|
|
4245
|
+
// ENG-6238 made this an absolute backstop (1200s) to still catch a
|
|
4246
|
+
// producing-but-never-draining runaway loop, but that re-introduced the exact
|
|
4247
|
+
// failure we set out to kill: cutting off real work on a long turn. Runaway
|
|
4248
|
+
// token burn is owned by the cost guardrail (ENG-5556); a producing-but-silent
|
|
4249
|
+
// loop still trips the synthetic-probe alarm. So the backstop is now opt-in:
|
|
4250
|
+
// set AGT_WEDGE_INBOUND_HARD_WAIT_SECONDS to a positive value to re-enable it
|
|
4251
|
+
// (floored at inboundWaitSeconds). 0 = the frozen/hung wedge (transcript
|
|
4252
|
+
// static) is still caught by the soft path; only the *producing* path is spared.
|
|
4253
|
+
inboundHardWaitSeconds: 0,
|
|
4254
|
+
paneStaleSeconds: 120,
|
|
4255
|
+
transcriptStaleSeconds: 60,
|
|
4256
|
+
minCycles: 3
|
|
4257
|
+
};
|
|
4258
|
+
function parseMode(raw) {
|
|
4259
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
4260
|
+
return v === "shadow" || v === "enforce" ? v : "off";
|
|
4261
|
+
}
|
|
4262
|
+
function parsePositiveInt(raw, fallback, floor) {
|
|
4263
|
+
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
4264
|
+
return Number.isInteger(n) && n >= floor ? n : fallback;
|
|
4265
|
+
}
|
|
4266
|
+
function resolveWedgeConfig(env = process.env) {
|
|
4267
|
+
const inboundWaitSeconds = parsePositiveInt(
|
|
4268
|
+
env.AGT_WEDGE_INBOUND_WAIT_SECONDS,
|
|
4269
|
+
DEFAULTS.inboundWaitSeconds,
|
|
4270
|
+
30
|
|
4271
|
+
);
|
|
4272
|
+
const inboundHardWaitRaw = parsePositiveInt(
|
|
4273
|
+
env.AGT_WEDGE_INBOUND_HARD_WAIT_SECONDS,
|
|
4274
|
+
DEFAULTS.inboundHardWaitSeconds,
|
|
4275
|
+
0
|
|
4276
|
+
);
|
|
4277
|
+
const inboundHardWaitSeconds = inboundHardWaitRaw <= 0 ? 0 : Math.max(inboundWaitSeconds, inboundHardWaitRaw);
|
|
4278
|
+
return {
|
|
4279
|
+
mode: parseMode(env.AGT_WEDGE_RESTART_MODE),
|
|
4280
|
+
inboundWaitSeconds,
|
|
4281
|
+
inboundHardWaitSeconds,
|
|
4282
|
+
paneStaleSeconds: parsePositiveInt(env.AGT_WEDGE_PANE_STALE_SECONDS, DEFAULTS.paneStaleSeconds, 30),
|
|
4283
|
+
transcriptStaleSeconds: parsePositiveInt(
|
|
4284
|
+
env.AGT_WEDGE_TRANSCRIPT_STALE_SECONDS,
|
|
4285
|
+
DEFAULTS.transcriptStaleSeconds,
|
|
4286
|
+
15
|
|
4287
|
+
),
|
|
4288
|
+
minCycles: parsePositiveInt(env.AGT_WEDGE_MIN_CYCLES, DEFAULTS.minCycles, 2)
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
function isSessionProducing(signals, config2) {
|
|
4292
|
+
const subagentAge = signals.subagentActivityAgeSeconds;
|
|
4293
|
+
if (subagentAge !== null && subagentAge < config2.transcriptStaleSeconds) return true;
|
|
4294
|
+
const transcriptAge = signals.transcriptActivityAgeSeconds;
|
|
4295
|
+
if (transcriptAge !== null) return transcriptAge < config2.transcriptStaleSeconds;
|
|
4296
|
+
const paneAge = signals.paneActivityAgeSeconds;
|
|
4297
|
+
return paneAge !== null && paneAge < config2.paneStaleSeconds;
|
|
4298
|
+
}
|
|
4299
|
+
function wedgeExemptionReason(signals, config2) {
|
|
4300
|
+
if (signals.subagentActivityAgeSeconds === null) return null;
|
|
4301
|
+
if (isWedgeCandidateCycle(signals, config2)) return null;
|
|
4302
|
+
const withoutSubagent = { ...signals, subagentActivityAgeSeconds: null };
|
|
4303
|
+
return isWedgeCandidateCycle(withoutSubagent, config2) ? "background-task-in-flight" : null;
|
|
4304
|
+
}
|
|
4305
|
+
function isWedgeCandidateCycle(signals, config2) {
|
|
4306
|
+
const inboundAge = signals.pendingInboundOldestAgeSeconds;
|
|
4307
|
+
if (inboundAge === null) return false;
|
|
4308
|
+
if (inboundAge < config2.inboundWaitSeconds) return false;
|
|
4309
|
+
if (isSessionProducing(signals, config2)) {
|
|
4310
|
+
if (config2.inboundHardWaitSeconds <= 0) return false;
|
|
4311
|
+
return inboundAge >= config2.inboundHardWaitSeconds;
|
|
4312
|
+
}
|
|
4313
|
+
return true;
|
|
4314
|
+
}
|
|
4315
|
+
function decideWedgeRestart(input, config2) {
|
|
4316
|
+
if (!isWedgeCandidateCycle(input, config2)) return "none";
|
|
4317
|
+
return input.consecutiveWedgeCycles >= config2.minCycles ? "wedged" : "none";
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
// src/lib/wedge-poison-card.ts
|
|
4321
|
+
var DEFAULTS2 = {
|
|
4322
|
+
threshold: 3,
|
|
4323
|
+
cooldownSeconds: 1800
|
|
4324
|
+
// 30 min — matches the kanban stale-auto-fail window
|
|
4325
|
+
};
|
|
4326
|
+
function parsePositiveInt2(raw, fallback, floor) {
|
|
4327
|
+
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
4328
|
+
return Number.isInteger(n) && n >= floor ? n : fallback;
|
|
4329
|
+
}
|
|
4330
|
+
function resolvePoisonCardConfig(env = process.env) {
|
|
4331
|
+
return {
|
|
4332
|
+
threshold: parsePositiveInt2(env.AGT_WEDGE_POISON_CARD_THRESHOLD, DEFAULTS2.threshold, 2),
|
|
4333
|
+
cooldownMs: parsePositiveInt2(
|
|
4334
|
+
env.AGT_WEDGE_POISON_CARD_COOLDOWN_SECONDS,
|
|
4335
|
+
DEFAULTS2.cooldownSeconds,
|
|
4336
|
+
300
|
|
4337
|
+
) * 1e3
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
function recordWedgeForCards(states, inProgressCardIds, nowMs, config2) {
|
|
4341
|
+
if (inProgressCardIds.length === 0) {
|
|
4342
|
+
return { next: new Map(states), newlyPoisoned: [] };
|
|
4343
|
+
}
|
|
4344
|
+
const next = /* @__PURE__ */ new Map();
|
|
4345
|
+
const newlyPoisoned = [];
|
|
4346
|
+
for (const id of inProgressCardIds) {
|
|
4347
|
+
if (next.has(id)) continue;
|
|
4348
|
+
const count = (states.get(id)?.count ?? 0) + 1;
|
|
4349
|
+
next.set(id, { count, lastWedgeAtMs: nowMs });
|
|
4350
|
+
if (count === config2.threshold) newlyPoisoned.push(id);
|
|
4351
|
+
}
|
|
4352
|
+
return { next, newlyPoisoned };
|
|
4353
|
+
}
|
|
4354
|
+
function pruneCardStates(states, liveInProgressCardIds, nowMs, config2) {
|
|
4355
|
+
const live = liveInProgressCardIds instanceof Set ? liveInProgressCardIds : new Set(liveInProgressCardIds);
|
|
4356
|
+
const next = /* @__PURE__ */ new Map();
|
|
4357
|
+
for (const [id, state7] of states) {
|
|
4358
|
+
if (!live.has(id)) continue;
|
|
4359
|
+
if (nowMs - state7.lastWedgeAtMs > config2.cooldownMs) continue;
|
|
4360
|
+
next.set(id, state7);
|
|
4361
|
+
}
|
|
4362
|
+
return next;
|
|
4363
|
+
}
|
|
4364
|
+
function isCardPoisoned(states, cardId, config2) {
|
|
4365
|
+
return (states.get(cardId)?.count ?? 0) >= config2.threshold;
|
|
3961
4366
|
}
|
|
3962
4367
|
function partitionActionableByPoison(actionable, states, config2) {
|
|
3963
4368
|
const allowed = [];
|
|
@@ -3970,24 +4375,24 @@ function partitionActionableByPoison(actionable, states, config2) {
|
|
|
3970
4375
|
}
|
|
3971
4376
|
|
|
3972
4377
|
// src/lib/restart-flags.ts
|
|
3973
|
-
import { existsSync as
|
|
4378
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3974
4379
|
import { homedir as homedir6 } from "os";
|
|
3975
|
-
import { join as
|
|
4380
|
+
import { join as join13 } from "path";
|
|
3976
4381
|
import { randomUUID } from "crypto";
|
|
3977
4382
|
function restartFlagsDir() {
|
|
3978
|
-
return
|
|
4383
|
+
return join13(homedir6(), ".augmented", "restart-flags");
|
|
3979
4384
|
}
|
|
3980
4385
|
function flagPath(codeName) {
|
|
3981
|
-
return
|
|
4386
|
+
return join13(restartFlagsDir(), `${codeName}.flag`);
|
|
3982
4387
|
}
|
|
3983
4388
|
function readRestartFlags() {
|
|
3984
4389
|
const dir = restartFlagsDir();
|
|
3985
|
-
if (!
|
|
4390
|
+
if (!existsSync7(dir)) return [];
|
|
3986
4391
|
const out = [];
|
|
3987
4392
|
for (const entry of readdirSync4(dir)) {
|
|
3988
4393
|
if (!entry.endsWith(".flag")) continue;
|
|
3989
4394
|
try {
|
|
3990
|
-
const raw =
|
|
4395
|
+
const raw = readFileSync11(join13(dir, entry), "utf8");
|
|
3991
4396
|
const parsed = JSON.parse(raw);
|
|
3992
4397
|
if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
|
|
3993
4398
|
parsed.codeName = entry.replace(/\.flag$/, "");
|
|
@@ -4005,7 +4410,7 @@ function readRestartFlags() {
|
|
|
4005
4410
|
}
|
|
4006
4411
|
function deleteRestartFlag(codeName) {
|
|
4007
4412
|
const path = flagPath(codeName);
|
|
4008
|
-
if (
|
|
4413
|
+
if (existsSync7(path)) {
|
|
4009
4414
|
rmSync2(path, { force: true });
|
|
4010
4415
|
}
|
|
4011
4416
|
}
|
|
@@ -4540,8 +4945,8 @@ function applyRestartAcks(args) {
|
|
|
4540
4945
|
var GATEWAY_PORT_BASE = 18800;
|
|
4541
4946
|
var GATEWAY_PORT_STEP = 10;
|
|
4542
4947
|
var GATEWAY_PORT_MAX = 18899;
|
|
4543
|
-
var AUGMENTED_DIR =
|
|
4544
|
-
var GATEWAY_PORTS_FILE =
|
|
4948
|
+
var AUGMENTED_DIR = join14(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
4949
|
+
var GATEWAY_PORTS_FILE = join14(AUGMENTED_DIR, "gateway-ports.json");
|
|
4545
4950
|
var CHANNEL_SWEEP_INTERVAL_MS = (() => {
|
|
4546
4951
|
const raw = parseInt(process.env["AGT_CHANNEL_SWEEP_INTERVAL_MS"] ?? "", 10);
|
|
4547
4952
|
if (!Number.isFinite(raw)) return 5 * 60 * 1e3;
|
|
@@ -5006,7 +5411,7 @@ var runningMcpServerKeys = /* @__PURE__ */ new Map();
|
|
|
5006
5411
|
var runningChannelSecretHashes = /* @__PURE__ */ new Map();
|
|
5007
5412
|
function projectMcpHash(_codeName, projectDir) {
|
|
5008
5413
|
try {
|
|
5009
|
-
const raw =
|
|
5414
|
+
const raw = readFileSync12(join14(projectDir, ".mcp.json"), "utf-8");
|
|
5010
5415
|
return createHash4("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
5011
5416
|
} catch {
|
|
5012
5417
|
return null;
|
|
@@ -5014,7 +5419,7 @@ function projectMcpHash(_codeName, projectDir) {
|
|
|
5014
5419
|
}
|
|
5015
5420
|
function projectMcpKeys(_codeName, projectDir) {
|
|
5016
5421
|
try {
|
|
5017
|
-
const raw =
|
|
5422
|
+
const raw = readFileSync12(join14(projectDir, ".mcp.json"), "utf-8");
|
|
5018
5423
|
const parsed = JSON.parse(raw);
|
|
5019
5424
|
const servers = parsed.mcpServers;
|
|
5020
5425
|
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
@@ -5025,7 +5430,7 @@ function projectMcpKeys(_codeName, projectDir) {
|
|
|
5025
5430
|
}
|
|
5026
5431
|
function readMcpHttpServerConfig(projectDir, serverKey, env) {
|
|
5027
5432
|
try {
|
|
5028
|
-
const raw =
|
|
5433
|
+
const raw = readFileSync12(join14(projectDir, ".mcp.json"), "utf-8");
|
|
5029
5434
|
const servers = JSON.parse(raw).mcpServers ?? {};
|
|
5030
5435
|
const entry = servers[serverKey];
|
|
5031
5436
|
if (entry && typeof entry.url === "string" && (entry.type === "http" || entry.type === void 0)) {
|
|
@@ -5063,9 +5468,9 @@ async function runAgentConnectivityProbes(agent, integrations, projectDir) {
|
|
|
5063
5468
|
if (integrations.length === 0) return;
|
|
5064
5469
|
const probeEnv = { ...process.env };
|
|
5065
5470
|
try {
|
|
5066
|
-
const envIntPath =
|
|
5067
|
-
if (
|
|
5068
|
-
Object.assign(probeEnv, parseEnvIntegrations(
|
|
5471
|
+
const envIntPath = join14(projectDir, ".env.integrations");
|
|
5472
|
+
if (existsSync8(envIntPath)) {
|
|
5473
|
+
Object.assign(probeEnv, parseEnvIntegrations(readFileSync12(envIntPath, "utf-8")));
|
|
5069
5474
|
}
|
|
5070
5475
|
} catch {
|
|
5071
5476
|
}
|
|
@@ -5270,7 +5675,7 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
|
5270
5675
|
function projectChannelSecretHash(projectDir) {
|
|
5271
5676
|
try {
|
|
5272
5677
|
const entries = parseEnvIntegrations(
|
|
5273
|
-
|
|
5678
|
+
readFileSync12(join14(projectDir, ".env.integrations"), "utf-8")
|
|
5274
5679
|
);
|
|
5275
5680
|
return channelSecretValueHash(entries, CHANNEL_SECRET_ENV_KEYS);
|
|
5276
5681
|
} catch {
|
|
@@ -5311,13 +5716,8 @@ var STALE_TASK_THRESHOLD_MS = (() => {
|
|
|
5311
5716
|
if (Number.isFinite(minutes) && minutes > 0) return minutes * 60 * 1e3;
|
|
5312
5717
|
return 30 * 60 * 1e3;
|
|
5313
5718
|
})();
|
|
5314
|
-
var alertSlackWebhook = null;
|
|
5315
5719
|
var alertedJobs = /* @__PURE__ */ new Set();
|
|
5316
5720
|
var taskDisplayInfo = /* @__PURE__ */ new Map();
|
|
5317
|
-
var agentChannelTokens = /* @__PURE__ */ new Map();
|
|
5318
|
-
function __setAgentChannelTokensForTest(codeName, tokens) {
|
|
5319
|
-
agentChannelTokens.set(codeName, tokens);
|
|
5320
|
-
}
|
|
5321
5721
|
var activeChannels = /* @__PURE__ */ new Map();
|
|
5322
5722
|
var gatewaysStartedThisCycle = /* @__PURE__ */ new Set();
|
|
5323
5723
|
var state6 = {
|
|
@@ -5341,6 +5741,7 @@ function resolveAgentFramework(codeName) {
|
|
|
5341
5741
|
var gatewayPool = null;
|
|
5342
5742
|
function clearAgentCaches(agentId, codeName) {
|
|
5343
5743
|
const channelCacheMutated = clearAgentState(agentId, codeName);
|
|
5744
|
+
agentChannelTokens.delete(codeName);
|
|
5344
5745
|
agentFrameworkCache.delete(codeName);
|
|
5345
5746
|
kanbanBoardCache.delete(codeName);
|
|
5346
5747
|
lastHarvestAt.delete(codeName);
|
|
@@ -5362,7 +5763,7 @@ var cachedMaintenanceWindow = null;
|
|
|
5362
5763
|
var lastVersionCheckAt = 0;
|
|
5363
5764
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
5364
5765
|
var lastResponsivenessProbeAt = 0;
|
|
5365
|
-
var agtCliVersion = true ? "0.28.
|
|
5766
|
+
var agtCliVersion = true ? "0.28.27" : "dev";
|
|
5366
5767
|
function resolveBrewPath(execFileSync4) {
|
|
5367
5768
|
try {
|
|
5368
5769
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -5375,7 +5776,7 @@ function resolveBrewPath(execFileSync4) {
|
|
|
5375
5776
|
"/usr/local/bin/brew"
|
|
5376
5777
|
];
|
|
5377
5778
|
for (const path of fallbacks) {
|
|
5378
|
-
if (
|
|
5779
|
+
if (existsSync8(path)) return path;
|
|
5379
5780
|
}
|
|
5380
5781
|
return null;
|
|
5381
5782
|
}
|
|
@@ -5385,7 +5786,7 @@ function claudeBinaryInstalled(execFileSync4) {
|
|
|
5385
5786
|
"/opt/homebrew/bin/claude",
|
|
5386
5787
|
"/usr/local/bin/claude"
|
|
5387
5788
|
];
|
|
5388
|
-
if (canonical.some((path) =>
|
|
5789
|
+
if (canonical.some((path) => existsSync8(path))) return true;
|
|
5389
5790
|
try {
|
|
5390
5791
|
execFileSync4("which", ["claude"], { timeout: 5e3 });
|
|
5391
5792
|
return true;
|
|
@@ -5538,8 +5939,8 @@ function claudeManagedSettingsPath() {
|
|
|
5538
5939
|
function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
5539
5940
|
try {
|
|
5540
5941
|
let settings = {};
|
|
5541
|
-
if (
|
|
5542
|
-
const raw =
|
|
5942
|
+
if (existsSync8(path)) {
|
|
5943
|
+
const raw = readFileSync12(path, "utf-8").trim();
|
|
5543
5944
|
if (raw) {
|
|
5544
5945
|
let parsed;
|
|
5545
5946
|
try {
|
|
@@ -5601,7 +6002,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
5601
6002
|
if (!process.env.PATH?.split(":").includes(brewBinDir)) {
|
|
5602
6003
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
5603
6004
|
}
|
|
5604
|
-
if (
|
|
6005
|
+
if (existsSync8("/home/linuxbrew/.linuxbrew/bin/claude")) {
|
|
5605
6006
|
log("Claude Code installed successfully");
|
|
5606
6007
|
} else {
|
|
5607
6008
|
log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
|
|
@@ -5613,7 +6014,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
5613
6014
|
var CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
5614
6015
|
var claudeCodeUpgradeInFlight = false;
|
|
5615
6016
|
function claudeCodeUpgradeMarkerPath() {
|
|
5616
|
-
return
|
|
6017
|
+
return join14(homedir7(), ".augmented", ".last-claude-code-upgrade-check");
|
|
5617
6018
|
}
|
|
5618
6019
|
function stampClaudeCodeUpgradeMarker() {
|
|
5619
6020
|
try {
|
|
@@ -5623,7 +6024,7 @@ function stampClaudeCodeUpgradeMarker() {
|
|
|
5623
6024
|
}
|
|
5624
6025
|
function claudeCodeUpgradeThrottled() {
|
|
5625
6026
|
try {
|
|
5626
|
-
const lastCheck = parseInt(
|
|
6027
|
+
const lastCheck = parseInt(readFileSync12(claudeCodeUpgradeMarkerPath(), "utf-8").trim(), 10);
|
|
5627
6028
|
if (!Number.isFinite(lastCheck)) return false;
|
|
5628
6029
|
return Date.now() - lastCheck < CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS;
|
|
5629
6030
|
} catch {
|
|
@@ -5676,7 +6077,7 @@ ${r.stderr}`;
|
|
|
5676
6077
|
}
|
|
5677
6078
|
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
5678
6079
|
function selfUpdateAppliedMarkerPath() {
|
|
5679
|
-
return
|
|
6080
|
+
return join14(homedir7(), ".augmented", ".last-self-update-applied");
|
|
5680
6081
|
}
|
|
5681
6082
|
var selfUpdateUpToDateLogged = false;
|
|
5682
6083
|
var restartAfterUpgrade = false;
|
|
@@ -5699,7 +6100,7 @@ async function checkAndUpdateCli() {
|
|
|
5699
6100
|
const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
|
|
5700
6101
|
if (!isBrewFormula && !isNpmGlobal) return;
|
|
5701
6102
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
5702
|
-
const markerPath =
|
|
6103
|
+
const markerPath = join14(homedir7(), ".augmented", ".last-update-check");
|
|
5703
6104
|
try {
|
|
5704
6105
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
5705
6106
|
if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
|
|
@@ -5956,13 +6357,13 @@ async function checkClaudeAuth() {
|
|
|
5956
6357
|
}
|
|
5957
6358
|
var evalEmptyMcpConfigPath = null;
|
|
5958
6359
|
function ensureEvalEmptyMcpConfig() {
|
|
5959
|
-
if (evalEmptyMcpConfigPath &&
|
|
5960
|
-
const dir =
|
|
6360
|
+
if (evalEmptyMcpConfigPath && existsSync8(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
|
|
6361
|
+
const dir = join14(homedir7(), ".augmented");
|
|
5961
6362
|
try {
|
|
5962
6363
|
mkdirSync4(dir, { recursive: true });
|
|
5963
6364
|
} catch {
|
|
5964
6365
|
}
|
|
5965
|
-
const p =
|
|
6366
|
+
const p = join14(dir, ".eval-empty-mcp.json");
|
|
5966
6367
|
writeFileSync4(p, JSON.stringify({ mcpServers: {} }));
|
|
5967
6368
|
evalEmptyMcpConfigPath = p;
|
|
5968
6369
|
return p;
|
|
@@ -6037,7 +6438,7 @@ function resolveConversationEvalBackend() {
|
|
|
6037
6438
|
}
|
|
6038
6439
|
function loadGatewayPorts() {
|
|
6039
6440
|
try {
|
|
6040
|
-
return JSON.parse(
|
|
6441
|
+
return JSON.parse(readFileSync12(GATEWAY_PORTS_FILE, "utf-8"));
|
|
6041
6442
|
} catch {
|
|
6042
6443
|
return {};
|
|
6043
6444
|
}
|
|
@@ -6067,10 +6468,10 @@ function freePort(codeName) {
|
|
|
6067
6468
|
}
|
|
6068
6469
|
}
|
|
6069
6470
|
function getStateFile() {
|
|
6070
|
-
return
|
|
6471
|
+
return join14(config?.configDir ?? join14(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
|
|
6071
6472
|
}
|
|
6072
6473
|
function channelHashCacheDir() {
|
|
6073
|
-
return config?.configDir ??
|
|
6474
|
+
return config?.configDir ?? join14(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
6074
6475
|
}
|
|
6075
6476
|
function loadChannelHashCache2() {
|
|
6076
6477
|
loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
|
|
@@ -6081,7 +6482,7 @@ function saveChannelHashCache2() {
|
|
|
6081
6482
|
var _channelQuarantineStore = null;
|
|
6082
6483
|
function channelQuarantineStore() {
|
|
6083
6484
|
if (!_channelQuarantineStore) {
|
|
6084
|
-
const dir = config?.configDir ??
|
|
6485
|
+
const dir = config?.configDir ?? join14(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
6085
6486
|
_channelQuarantineStore = new ChannelQuarantineStore(defaultQuarantinePath(dir));
|
|
6086
6487
|
}
|
|
6087
6488
|
return _channelQuarantineStore;
|
|
@@ -6089,7 +6490,7 @@ function channelQuarantineStore() {
|
|
|
6089
6490
|
var _hostFlagStore = null;
|
|
6090
6491
|
function hostFlagStore() {
|
|
6091
6492
|
if (!_hostFlagStore) {
|
|
6092
|
-
const dir = config?.configDir ??
|
|
6493
|
+
const dir = config?.configDir ?? join14(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
6093
6494
|
_hostFlagStore = new HostFlagStore({ cachePath: defaultFlagsCachePath(dir), log });
|
|
6094
6495
|
}
|
|
6095
6496
|
return _hostFlagStore;
|
|
@@ -6141,12 +6542,12 @@ function parseSkillFrontmatter(content) {
|
|
|
6141
6542
|
}
|
|
6142
6543
|
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
6143
6544
|
const { readdirSync: readdirSync6, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync5 } = await import("fs");
|
|
6144
|
-
const skillsDir =
|
|
6145
|
-
const claudeMdPath =
|
|
6545
|
+
const skillsDir = join14(configDir, codeName, "project", ".claude", "skills");
|
|
6546
|
+
const claudeMdPath = join14(configDir, codeName, "project", "CLAUDE.md");
|
|
6146
6547
|
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
6147
6548
|
const entries = [];
|
|
6148
6549
|
for (const dir of readdirSync6(skillsDir).sort()) {
|
|
6149
|
-
const skillFile =
|
|
6550
|
+
const skillFile = join14(skillsDir, dir, "SKILL.md");
|
|
6150
6551
|
if (!ex(skillFile)) continue;
|
|
6151
6552
|
try {
|
|
6152
6553
|
const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
|
|
@@ -6194,10 +6595,10 @@ ${SKILLS_INDEX_END}`;
|
|
|
6194
6595
|
}
|
|
6195
6596
|
async function migrateToProfiles() {
|
|
6196
6597
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6197
|
-
const sharedConfigPath =
|
|
6598
|
+
const sharedConfigPath = join14(homeDir, ".openclaw", "openclaw.json");
|
|
6198
6599
|
let sharedConfig;
|
|
6199
6600
|
try {
|
|
6200
|
-
sharedConfig = JSON.parse(
|
|
6601
|
+
sharedConfig = JSON.parse(readFileSync12(sharedConfigPath, "utf-8"));
|
|
6201
6602
|
} catch {
|
|
6202
6603
|
return;
|
|
6203
6604
|
}
|
|
@@ -6210,19 +6611,19 @@ async function migrateToProfiles() {
|
|
|
6210
6611
|
const codeName = agentEntry["id"];
|
|
6211
6612
|
if (!codeName) continue;
|
|
6212
6613
|
if (codeName === "main") continue;
|
|
6213
|
-
const profileDir =
|
|
6214
|
-
if (
|
|
6614
|
+
const profileDir = join14(homeDir, `.openclaw-${codeName}`);
|
|
6615
|
+
if (existsSync8(join14(profileDir, "openclaw.json"))) continue;
|
|
6215
6616
|
log(`Migrating agent '${codeName}' to per-agent profile`);
|
|
6216
6617
|
if (adapter.seedProfileConfig) {
|
|
6217
6618
|
adapter.seedProfileConfig(codeName);
|
|
6218
6619
|
}
|
|
6219
|
-
const sharedAuthDir =
|
|
6220
|
-
const profileAuthDir =
|
|
6221
|
-
const authFile =
|
|
6222
|
-
if (
|
|
6620
|
+
const sharedAuthDir = join14(homeDir, ".openclaw", "agents", codeName, "agent");
|
|
6621
|
+
const profileAuthDir = join14(profileDir, "agents", codeName, "agent");
|
|
6622
|
+
const authFile = join14(sharedAuthDir, "auth-profiles.json");
|
|
6623
|
+
if (existsSync8(authFile)) {
|
|
6223
6624
|
mkdirSync4(profileAuthDir, { recursive: true });
|
|
6224
|
-
const authContent =
|
|
6225
|
-
writeFileSync4(
|
|
6625
|
+
const authContent = readFileSync12(authFile, "utf-8");
|
|
6626
|
+
writeFileSync4(join14(profileAuthDir, "auth-profiles.json"), authContent);
|
|
6226
6627
|
}
|
|
6227
6628
|
allocatePort(codeName);
|
|
6228
6629
|
migrated++;
|
|
@@ -6238,7 +6639,7 @@ function readGatewayToken(codeName) {
|
|
|
6238
6639
|
}
|
|
6239
6640
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6240
6641
|
try {
|
|
6241
|
-
const cfg = JSON.parse(
|
|
6642
|
+
const cfg = JSON.parse(readFileSync12(join14(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
|
|
6242
6643
|
return cfg?.gateway?.auth?.token;
|
|
6243
6644
|
} catch {
|
|
6244
6645
|
return void 0;
|
|
@@ -6247,10 +6648,10 @@ function readGatewayToken(codeName) {
|
|
|
6247
6648
|
var GATEWAY_HUNG_TIMEOUT_MS = 5 * 6e4;
|
|
6248
6649
|
function isGatewayHung(codeName) {
|
|
6249
6650
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6250
|
-
const jobsPath =
|
|
6251
|
-
if (!
|
|
6651
|
+
const jobsPath = join14(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
6652
|
+
if (!existsSync8(jobsPath)) return false;
|
|
6252
6653
|
try {
|
|
6253
|
-
const data = JSON.parse(
|
|
6654
|
+
const data = JSON.parse(readFileSync12(jobsPath, "utf-8"));
|
|
6254
6655
|
const jobs = data.jobs ?? data;
|
|
6255
6656
|
if (!Array.isArray(jobs)) return false;
|
|
6256
6657
|
const now = Date.now();
|
|
@@ -6283,15 +6684,15 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
6283
6684
|
}
|
|
6284
6685
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
6285
6686
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6286
|
-
const cronJobsPath =
|
|
6687
|
+
const cronJobsPath = join14(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
6287
6688
|
clearStaleCronRunState(cronJobsPath);
|
|
6288
6689
|
} else {
|
|
6289
6690
|
if (status.port) {
|
|
6290
6691
|
try {
|
|
6291
6692
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6292
|
-
const configPath =
|
|
6293
|
-
if (
|
|
6294
|
-
const cfg = JSON.parse(
|
|
6693
|
+
const configPath = join14(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
6694
|
+
if (existsSync8(configPath)) {
|
|
6695
|
+
const cfg = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
6295
6696
|
if (cfg.gateway?.port !== status.port) {
|
|
6296
6697
|
if (!cfg.gateway) cfg.gateway = {};
|
|
6297
6698
|
cfg.gateway.port = status.port;
|
|
@@ -6315,9 +6716,9 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
6315
6716
|
gatewaysStartedThisCycle.add(codeName);
|
|
6316
6717
|
try {
|
|
6317
6718
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
6318
|
-
const configPath =
|
|
6319
|
-
if (
|
|
6320
|
-
const cfg = JSON.parse(
|
|
6719
|
+
const configPath = join14(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
6720
|
+
if (existsSync8(configPath)) {
|
|
6721
|
+
const cfg = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
6321
6722
|
if (!cfg.gateway) cfg.gateway = {};
|
|
6322
6723
|
cfg.gateway.port = port;
|
|
6323
6724
|
writeFileSync4(configPath, JSON.stringify(cfg, null, 2));
|
|
@@ -6873,7 +7274,7 @@ async function pollCycle() {
|
|
|
6873
7274
|
}
|
|
6874
7275
|
killAgentChannelProcesses(prev.codeName, { log });
|
|
6875
7276
|
freePort(prev.codeName);
|
|
6876
|
-
const agentDir =
|
|
7277
|
+
const agentDir = join14(adapter.getAgentDir(prev.codeName), "provision");
|
|
6877
7278
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
6878
7279
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
6879
7280
|
}
|
|
@@ -6959,10 +7360,10 @@ async function pollCycle() {
|
|
|
6959
7360
|
// pending-inbound marker. Best-effort: a write failure is logged by
|
|
6960
7361
|
// the watchdog, never fails the poll cycle.
|
|
6961
7362
|
signalGiveUp: (codeName) => {
|
|
6962
|
-
const dir =
|
|
6963
|
-
if (!
|
|
7363
|
+
const dir = join14(homedir7(), ".augmented", codeName);
|
|
7364
|
+
if (!existsSync8(dir)) return;
|
|
6964
7365
|
atomicWriteFileSync(
|
|
6965
|
-
|
|
7366
|
+
join14(dir, "watchdog-give-up.json"),
|
|
6966
7367
|
JSON.stringify({ gave_up_at: (/* @__PURE__ */ new Date()).toISOString() })
|
|
6967
7368
|
);
|
|
6968
7369
|
}
|
|
@@ -7089,7 +7490,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7089
7490
|
}
|
|
7090
7491
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7091
7492
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
7092
|
-
let agentDir =
|
|
7493
|
+
let agentDir = join14(adapter.getAgentDir(agent.code_name), "provision");
|
|
7093
7494
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
7094
7495
|
if (previousKnownStatus !== agent.status) {
|
|
7095
7496
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -7141,7 +7542,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7141
7542
|
const residuals = {
|
|
7142
7543
|
gatewayRunning: gatewayLiveness.running,
|
|
7143
7544
|
portAllocated: Object.prototype.hasOwnProperty.call(ports, agent.code_name),
|
|
7144
|
-
provisionDirExists:
|
|
7545
|
+
provisionDirExists: existsSync8(agentDir)
|
|
7145
7546
|
};
|
|
7146
7547
|
if (!hasRevokedResiduals(residuals)) {
|
|
7147
7548
|
agentStates.push({
|
|
@@ -7264,10 +7665,10 @@ async function processAgent(agent, agentStates) {
|
|
|
7264
7665
|
});
|
|
7265
7666
|
return;
|
|
7266
7667
|
}
|
|
7267
|
-
if (!
|
|
7668
|
+
if (!getAlertSlackWebhook() && refreshData.team?.settings) {
|
|
7268
7669
|
const webhook = refreshData.team.settings["alert_slack_webhook"];
|
|
7269
7670
|
if (typeof webhook === "string" && webhook.startsWith("https://")) {
|
|
7270
|
-
|
|
7671
|
+
setAlertSlackWebhook(webhook);
|
|
7271
7672
|
}
|
|
7272
7673
|
}
|
|
7273
7674
|
if (!refreshData.charter || !refreshData.tools) {
|
|
@@ -7293,7 +7694,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7293
7694
|
const frameworkId = refreshData.agent.framework ?? "openclaw";
|
|
7294
7695
|
agentFrameworkCache.set(agent.code_name, frameworkId);
|
|
7295
7696
|
const frameworkAdapter = getFramework(frameworkId);
|
|
7296
|
-
agentDir =
|
|
7697
|
+
agentDir = join14(frameworkAdapter.getAgentDir(agent.code_name), "provision");
|
|
7297
7698
|
cacheAgentDeliveryMetadata(agent.code_name, refreshData);
|
|
7298
7699
|
if (frameworkAdapter.migrateSecretStorage && !migratedSecretStorage.has(agent.code_name)) {
|
|
7299
7700
|
try {
|
|
@@ -7336,7 +7737,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7336
7737
|
const changedFiles = [];
|
|
7337
7738
|
mkdirSync4(agentDir, { recursive: true });
|
|
7338
7739
|
for (const artifact of artifacts) {
|
|
7339
|
-
const filePath =
|
|
7740
|
+
const filePath = join14(agentDir, artifact.relativePath);
|
|
7340
7741
|
let existingHash;
|
|
7341
7742
|
let newHash;
|
|
7342
7743
|
let writeContent = artifact.content;
|
|
@@ -7355,8 +7756,8 @@ async function processAgent(agent, agentStates) {
|
|
|
7355
7756
|
};
|
|
7356
7757
|
newHash = sha256(stripDynamicSections(artifact.content));
|
|
7357
7758
|
try {
|
|
7358
|
-
const projectClaudeMd =
|
|
7359
|
-
const existing =
|
|
7759
|
+
const projectClaudeMd = join14(config.configDir, agent.code_name, "project", "CLAUDE.md");
|
|
7760
|
+
const existing = readFileSync12(projectClaudeMd, "utf-8");
|
|
7360
7761
|
existingHash = sha256(stripDynamicSections(existing));
|
|
7361
7762
|
} catch {
|
|
7362
7763
|
existingHash = null;
|
|
@@ -7374,7 +7775,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7374
7775
|
const generatorKeys = Object.keys(generatorServers);
|
|
7375
7776
|
let existingRaw = "";
|
|
7376
7777
|
try {
|
|
7377
|
-
existingRaw =
|
|
7778
|
+
existingRaw = readFileSync12(filePath, "utf-8");
|
|
7378
7779
|
} catch {
|
|
7379
7780
|
}
|
|
7380
7781
|
const existingServers = parseMcp(existingRaw);
|
|
@@ -7396,12 +7797,12 @@ async function processAgent(agent, agentStates) {
|
|
|
7396
7797
|
}
|
|
7397
7798
|
}
|
|
7398
7799
|
if (changedFiles.length > 0) {
|
|
7399
|
-
const isFirst = !
|
|
7800
|
+
const isFirst = !existsSync8(join14(agentDir, "CHARTER.md"));
|
|
7400
7801
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
7401
7802
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
7402
7803
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
7403
7804
|
for (const file of changedFiles) {
|
|
7404
|
-
const filePath =
|
|
7805
|
+
const filePath = join14(agentDir, file.relativePath);
|
|
7405
7806
|
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
7406
7807
|
if (file.relativePath === ".mcp.json") {
|
|
7407
7808
|
safeWriteJsonAtomic(filePath, file.content, { mode: 384 });
|
|
@@ -7410,12 +7811,12 @@ async function processAgent(agent, agentStates) {
|
|
|
7410
7811
|
}
|
|
7411
7812
|
}
|
|
7412
7813
|
try {
|
|
7413
|
-
const provSkillsDir =
|
|
7414
|
-
if (
|
|
7814
|
+
const provSkillsDir = join14(agentDir, ".claude", "skills");
|
|
7815
|
+
if (existsSync8(provSkillsDir)) {
|
|
7415
7816
|
for (const folder of readdirSync5(provSkillsDir)) {
|
|
7416
7817
|
if (folder.startsWith("knowledge-")) {
|
|
7417
7818
|
try {
|
|
7418
|
-
rmSync3(
|
|
7819
|
+
rmSync3(join14(provSkillsDir, folder), { recursive: true });
|
|
7419
7820
|
} catch {
|
|
7420
7821
|
}
|
|
7421
7822
|
}
|
|
@@ -7428,7 +7829,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7428
7829
|
const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
|
|
7429
7830
|
const hashes = /* @__PURE__ */ new Map();
|
|
7430
7831
|
for (const file of trackedFiles2) {
|
|
7431
|
-
const h = hashFile(
|
|
7832
|
+
const h = hashFile(join14(agentDir, file));
|
|
7432
7833
|
if (h) hashes.set(file, h);
|
|
7433
7834
|
}
|
|
7434
7835
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -7446,14 +7847,14 @@ async function processAgent(agent, agentStates) {
|
|
|
7446
7847
|
}
|
|
7447
7848
|
if (Array.isArray(refreshData.workflows)) {
|
|
7448
7849
|
try {
|
|
7449
|
-
const provWorkflowsDir =
|
|
7450
|
-
if (
|
|
7850
|
+
const provWorkflowsDir = join14(agentDir, ".claude", "workflows");
|
|
7851
|
+
if (existsSync8(provWorkflowsDir)) {
|
|
7451
7852
|
const expected = new Set(refreshData.workflows.map((w) => `${w.name}.js`));
|
|
7452
7853
|
for (const file of readdirSync5(provWorkflowsDir)) {
|
|
7453
7854
|
if (!file.endsWith(".js")) continue;
|
|
7454
7855
|
if (expected.has(file)) continue;
|
|
7455
7856
|
try {
|
|
7456
|
-
rmSync3(
|
|
7857
|
+
rmSync3(join14(provWorkflowsDir, file));
|
|
7457
7858
|
} catch {
|
|
7458
7859
|
}
|
|
7459
7860
|
}
|
|
@@ -7510,10 +7911,10 @@ async function processAgent(agent, agentStates) {
|
|
|
7510
7911
|
}
|
|
7511
7912
|
let lastDriftCheckAt = now;
|
|
7512
7913
|
const written = agentState.writtenHashes.get(agent.agent_id);
|
|
7513
|
-
if (written &&
|
|
7914
|
+
if (written && existsSync8(agentDir)) {
|
|
7514
7915
|
const driftedFiles = [];
|
|
7515
7916
|
for (const [file, expectedHash] of written) {
|
|
7516
|
-
const localHash = hashFile(
|
|
7917
|
+
const localHash = hashFile(join14(agentDir, file));
|
|
7517
7918
|
if (localHash && localHash !== expectedHash) {
|
|
7518
7919
|
driftedFiles.push(file);
|
|
7519
7920
|
}
|
|
@@ -7524,7 +7925,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7524
7925
|
try {
|
|
7525
7926
|
const localHashes = {};
|
|
7526
7927
|
for (const file of driftedFiles) {
|
|
7527
|
-
localHashes[file] = hashFile(
|
|
7928
|
+
localHashes[file] = hashFile(join14(agentDir, file));
|
|
7528
7929
|
}
|
|
7529
7930
|
await api.post("/host/drift", {
|
|
7530
7931
|
agent_id: agent.agent_id,
|
|
@@ -7848,24 +8249,24 @@ async function processAgent(agent, agentStates) {
|
|
|
7848
8249
|
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? "openclaw") === "claude-code") {
|
|
7849
8250
|
try {
|
|
7850
8251
|
const agentProvisionDir = agentDir;
|
|
7851
|
-
const projectDir =
|
|
8252
|
+
const projectDir = join14(homedir7(), ".augmented", agent.code_name, "project");
|
|
7852
8253
|
mkdirSync4(agentProvisionDir, { recursive: true });
|
|
7853
8254
|
mkdirSync4(projectDir, { recursive: true });
|
|
7854
|
-
const provisionMcpPath =
|
|
7855
|
-
const projectMcpPath =
|
|
8255
|
+
const provisionMcpPath = join14(agentProvisionDir, ".mcp.json");
|
|
8256
|
+
const projectMcpPath = join14(projectDir, ".mcp.json");
|
|
7856
8257
|
let mcpConfig = { mcpServers: {} };
|
|
7857
8258
|
try {
|
|
7858
|
-
mcpConfig = JSON.parse(
|
|
8259
|
+
mcpConfig = JSON.parse(readFileSync12(provisionMcpPath, "utf-8"));
|
|
7859
8260
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
7860
8261
|
} catch {
|
|
7861
8262
|
}
|
|
7862
|
-
const localDirectChatChannel =
|
|
8263
|
+
const localDirectChatChannel = join14(homedir7(), ".augmented", "_mcp", "direct-chat-channel.js");
|
|
7863
8264
|
const directChatTeamSettings = refreshData.team?.settings;
|
|
7864
8265
|
const directChatTz = (() => {
|
|
7865
8266
|
const tz = directChatTeamSettings?.["timezone"];
|
|
7866
8267
|
return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
|
|
7867
8268
|
})();
|
|
7868
|
-
if (
|
|
8269
|
+
if (existsSync8(localDirectChatChannel)) {
|
|
7869
8270
|
const directChatEnv = {
|
|
7870
8271
|
AGT_HOST: requireHost(),
|
|
7871
8272
|
// ENG-5901 Track D: templated — the manager exports the real
|
|
@@ -7889,8 +8290,8 @@ async function processAgent(agent, agentStates) {
|
|
|
7889
8290
|
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
7890
8291
|
}
|
|
7891
8292
|
}
|
|
7892
|
-
const staleChannelsPath =
|
|
7893
|
-
if (
|
|
8293
|
+
const staleChannelsPath = join14(projectDir, ".mcp-channels.json");
|
|
8294
|
+
if (existsSync8(staleChannelsPath)) {
|
|
7894
8295
|
try {
|
|
7895
8296
|
rmSync3(staleChannelsPath, { force: true });
|
|
7896
8297
|
} catch {
|
|
@@ -7954,7 +8355,7 @@ async function processAgent(agent, agentStates) {
|
|
|
7954
8355
|
}
|
|
7955
8356
|
if (process.env.AGT_CONNECTIVITY_PROBE_ENABLED === "true") {
|
|
7956
8357
|
try {
|
|
7957
|
-
const probeProjectDir =
|
|
8358
|
+
const probeProjectDir = join14(homedir7(), ".augmented", agent.code_name, "project");
|
|
7958
8359
|
await runAgentConnectivityProbes(agent, integrations, probeProjectDir);
|
|
7959
8360
|
} catch (err) {
|
|
7960
8361
|
log(`Connectivity probe failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -7972,11 +8373,11 @@ async function processAgent(agent, agentStates) {
|
|
|
7972
8373
|
recordConfigChurnEvent(agent.agent_id, agent.code_name, FLAP_CHANNEL_INTEGRATIONS, intMembership);
|
|
7973
8374
|
}
|
|
7974
8375
|
if (intHash !== prevIntHash) {
|
|
7975
|
-
const projectDir =
|
|
7976
|
-
const envIntPath =
|
|
8376
|
+
const projectDir = join14(homedir7(), ".augmented", agent.code_name, "project");
|
|
8377
|
+
const envIntPath = join14(projectDir, ".env.integrations");
|
|
7977
8378
|
let preWriteEnv;
|
|
7978
8379
|
try {
|
|
7979
|
-
preWriteEnv =
|
|
8380
|
+
preWriteEnv = readFileSync12(envIntPath, "utf-8");
|
|
7980
8381
|
} catch {
|
|
7981
8382
|
preWriteEnv = void 0;
|
|
7982
8383
|
}
|
|
@@ -7988,9 +8389,9 @@ async function processAgent(agent, agentStates) {
|
|
|
7988
8389
|
let rotationHandled = true;
|
|
7989
8390
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
7990
8391
|
try {
|
|
7991
|
-
const projectMcpPath =
|
|
7992
|
-
const postWriteEnv =
|
|
7993
|
-
const mcpContent =
|
|
8392
|
+
const projectMcpPath = join14(projectDir, ".mcp.json");
|
|
8393
|
+
const postWriteEnv = readFileSync12(envIntPath, "utf-8");
|
|
8394
|
+
const mcpContent = readFileSync12(projectMcpPath, "utf-8");
|
|
7994
8395
|
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
7995
8396
|
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
7996
8397
|
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
@@ -8070,8 +8471,8 @@ async function processAgent(agent, agentStates) {
|
|
|
8070
8471
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
8071
8472
|
if (mcpPath) {
|
|
8072
8473
|
try {
|
|
8073
|
-
const { readFileSync:
|
|
8074
|
-
const mcpConfig = JSON.parse(
|
|
8474
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
8475
|
+
const mcpConfig = JSON.parse(readFileSync13(mcpPath, "utf-8"));
|
|
8075
8476
|
if (mcpConfig.mcpServers) {
|
|
8076
8477
|
const managedPrefixes = [
|
|
8077
8478
|
"composio_",
|
|
@@ -8172,8 +8573,8 @@ async function processAgent(agent, agentStates) {
|
|
|
8172
8573
|
if (agent.status === "active") {
|
|
8173
8574
|
if (frameworkAdapter.installPlugin) {
|
|
8174
8575
|
try {
|
|
8175
|
-
const pluginPath =
|
|
8176
|
-
if (
|
|
8576
|
+
const pluginPath = join14(process.cwd(), "packages", "openclaw-plugin-augmented", "src", "index.ts");
|
|
8577
|
+
if (existsSync8(pluginPath)) {
|
|
8177
8578
|
frameworkAdapter.installPlugin(agent.code_name, "augmented", pluginPath, {
|
|
8178
8579
|
agtHost: requireHost(),
|
|
8179
8580
|
agtApiKey: getApiKey() ?? void 0,
|
|
@@ -8243,16 +8644,16 @@ async function processAgent(agent, agentStates) {
|
|
|
8243
8644
|
const frameworkId2 = frameworkAdapter.id;
|
|
8244
8645
|
const candidateSkillDirs = [
|
|
8245
8646
|
// Claude Code — framework runtime tree
|
|
8246
|
-
|
|
8647
|
+
join14(homedir8(), ".augmented", agent.code_name, "skills"),
|
|
8247
8648
|
// Claude Code — project tree
|
|
8248
|
-
|
|
8649
|
+
join14(homedir8(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
8249
8650
|
// OpenClaw — framework runtime tree
|
|
8250
|
-
|
|
8651
|
+
join14(homedir8(), `.openclaw-${agent.code_name}`, "skills"),
|
|
8251
8652
|
// Defensive: legacy provision-side path, not currently an
|
|
8252
8653
|
// install target but cheap to sweep.
|
|
8253
|
-
|
|
8654
|
+
join14(agentDir, ".claude", "skills")
|
|
8254
8655
|
];
|
|
8255
|
-
const existingDirs = candidateSkillDirs.filter((d) =>
|
|
8656
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync8(d));
|
|
8256
8657
|
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
8257
8658
|
for (const dir of existingDirs) {
|
|
8258
8659
|
try {
|
|
@@ -8266,8 +8667,8 @@ async function processAgent(agent, agentStates) {
|
|
|
8266
8667
|
}
|
|
8267
8668
|
const removeSkillFolder = (entry, reason) => {
|
|
8268
8669
|
for (const dir of existingDirs) {
|
|
8269
|
-
const p =
|
|
8270
|
-
if (
|
|
8670
|
+
const p = join14(dir, entry);
|
|
8671
|
+
if (existsSync8(p)) {
|
|
8271
8672
|
rmSync4(p, { recursive: true, force: true });
|
|
8272
8673
|
}
|
|
8273
8674
|
}
|
|
@@ -8297,15 +8698,15 @@ async function processAgent(agent, agentStates) {
|
|
|
8297
8698
|
const { rmSync: rmSync4 } = await import("fs");
|
|
8298
8699
|
const { homedir: homedir8 } = await import("os");
|
|
8299
8700
|
const globalSkillDirs = [
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8701
|
+
join14(homedir8(), ".augmented", agent.code_name, "skills"),
|
|
8702
|
+
join14(homedir8(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
8703
|
+
join14(homedir8(), `.openclaw-${agent.code_name}`, "skills"),
|
|
8704
|
+
join14(agentDir, ".claude", "skills")
|
|
8304
8705
|
];
|
|
8305
8706
|
for (const id of plan.removes) {
|
|
8306
8707
|
for (const dir of globalSkillDirs) {
|
|
8307
|
-
const p =
|
|
8308
|
-
if (
|
|
8708
|
+
const p = join14(dir, id);
|
|
8709
|
+
if (existsSync8(p)) rmSync4(p, { recursive: true, force: true });
|
|
8309
8710
|
}
|
|
8310
8711
|
agentState.knownSkillHashes.delete(`global-skill:${agent.agent_id}:${id}`);
|
|
8311
8712
|
log(`Removed unpublished global skill '${id}' for '${agent.code_name}'`);
|
|
@@ -8480,8 +8881,8 @@ async function processAgent(agent, agentStates) {
|
|
|
8480
8881
|
const sess = getSessionState(agent.code_name);
|
|
8481
8882
|
let mcpJsonParsed = null;
|
|
8482
8883
|
try {
|
|
8483
|
-
const mcpPath =
|
|
8484
|
-
mcpJsonParsed = JSON.parse(
|
|
8884
|
+
const mcpPath = join14(getProjectDir(agent.code_name), ".mcp.json");
|
|
8885
|
+
mcpJsonParsed = JSON.parse(readFileSync12(mcpPath, "utf-8"));
|
|
8485
8886
|
} catch {
|
|
8486
8887
|
}
|
|
8487
8888
|
reapMissingMcpSessions({
|
|
@@ -8690,10 +9091,10 @@ async function processAgent(agent, agentStates) {
|
|
|
8690
9091
|
lastWorkTriggerAt.set(agent.code_name, triggerTs);
|
|
8691
9092
|
if (agentFw === "openclaw" && gatewayRunning && gatewayPort) {
|
|
8692
9093
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
8693
|
-
const jobsPath =
|
|
8694
|
-
if (
|
|
9094
|
+
const jobsPath = join14(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
|
|
9095
|
+
if (existsSync8(jobsPath)) {
|
|
8695
9096
|
try {
|
|
8696
|
-
const jobsData = JSON.parse(
|
|
9097
|
+
const jobsData = JSON.parse(readFileSync12(jobsPath, "utf-8"));
|
|
8697
9098
|
const kanbanJob = (jobsData.jobs ?? []).find(
|
|
8698
9099
|
(j) => typeof j.name === "string" && j.name.includes("kanban-work")
|
|
8699
9100
|
);
|
|
@@ -8827,10 +9228,10 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
|
|
|
8827
9228
|
}
|
|
8828
9229
|
}
|
|
8829
9230
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
8830
|
-
if (trackedFiles.length > 0 &&
|
|
9231
|
+
if (trackedFiles.length > 0 && existsSync8(agentDir)) {
|
|
8831
9232
|
const hashes = /* @__PURE__ */ new Map();
|
|
8832
9233
|
for (const file of trackedFiles) {
|
|
8833
|
-
const h = hashFile(
|
|
9234
|
+
const h = hashFile(join14(agentDir, file));
|
|
8834
9235
|
if (h) hashes.set(file, h);
|
|
8835
9236
|
}
|
|
8836
9237
|
agentState.writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -8864,19 +9265,19 @@ function cleanupStaleSessions(codeName) {
|
|
|
8864
9265
|
lastCleanupAt.set(codeName, Date.now());
|
|
8865
9266
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
8866
9267
|
for (const agentDir of ["main", codeName]) {
|
|
8867
|
-
const sessionsDir =
|
|
9268
|
+
const sessionsDir = join14(homeDir, `.openclaw-${codeName}`, "agents", agentDir, "sessions");
|
|
8868
9269
|
cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);
|
|
8869
9270
|
}
|
|
8870
|
-
const cronRunsDir =
|
|
9271
|
+
const cronRunsDir = join14(homeDir, `.openclaw-${codeName}`, "cron", "runs");
|
|
8871
9272
|
cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, ".jsonl");
|
|
8872
|
-
const cronJobsPath =
|
|
9273
|
+
const cronJobsPath = join14(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
8873
9274
|
clearStaleCronRunState(cronJobsPath);
|
|
8874
9275
|
}
|
|
8875
9276
|
function cleanupCronSessions(sessionsDir, keepCount) {
|
|
8876
|
-
const indexPath =
|
|
8877
|
-
if (!
|
|
9277
|
+
const indexPath = join14(sessionsDir, "sessions.json");
|
|
9278
|
+
if (!existsSync8(indexPath)) return;
|
|
8878
9279
|
try {
|
|
8879
|
-
const raw =
|
|
9280
|
+
const raw = readFileSync12(indexPath, "utf-8");
|
|
8880
9281
|
const index = JSON.parse(raw);
|
|
8881
9282
|
const cronRunKeys = Object.keys(index).filter((k) => k.includes(":cron:") && k.includes(":run:")).map((k) => ({
|
|
8882
9283
|
key: k,
|
|
@@ -8889,9 +9290,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
8889
9290
|
for (const entry of toDelete) {
|
|
8890
9291
|
delete index[entry.key];
|
|
8891
9292
|
if (entry.sessionId) {
|
|
8892
|
-
const sessionFile =
|
|
9293
|
+
const sessionFile = join14(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
8893
9294
|
try {
|
|
8894
|
-
if (
|
|
9295
|
+
if (existsSync8(sessionFile)) {
|
|
8895
9296
|
unlinkSync(sessionFile);
|
|
8896
9297
|
deletedFiles++;
|
|
8897
9298
|
}
|
|
@@ -8911,8 +9312,8 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
8911
9312
|
delete index[parentKey];
|
|
8912
9313
|
if (parentSessionId) {
|
|
8913
9314
|
try {
|
|
8914
|
-
const f =
|
|
8915
|
-
if (
|
|
9315
|
+
const f = join14(sessionsDir, `${parentSessionId}.jsonl`);
|
|
9316
|
+
if (existsSync8(f)) {
|
|
8916
9317
|
unlinkSync(f);
|
|
8917
9318
|
deletedFiles++;
|
|
8918
9319
|
}
|
|
@@ -8930,9 +9331,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
8930
9331
|
}
|
|
8931
9332
|
var STALE_RUN_TIMEOUT_MS = 5 * 6e4;
|
|
8932
9333
|
function clearStaleCronRunState(jobsPath) {
|
|
8933
|
-
if (!
|
|
9334
|
+
if (!existsSync8(jobsPath)) return;
|
|
8934
9335
|
try {
|
|
8935
|
-
const raw =
|
|
9336
|
+
const raw = readFileSync12(jobsPath, "utf-8");
|
|
8936
9337
|
const data = JSON.parse(raw);
|
|
8937
9338
|
const jobs = data.jobs ?? data;
|
|
8938
9339
|
if (!Array.isArray(jobs)) return;
|
|
@@ -8963,13 +9364,13 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
8963
9364
|
}
|
|
8964
9365
|
}
|
|
8965
9366
|
function cleanupOldFiles(dir, maxAgeDays, ext) {
|
|
8966
|
-
if (!
|
|
9367
|
+
if (!existsSync8(dir)) return;
|
|
8967
9368
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
8968
9369
|
let removed = 0;
|
|
8969
9370
|
try {
|
|
8970
9371
|
for (const f of readdirSync5(dir)) {
|
|
8971
9372
|
if (!f.endsWith(ext)) continue;
|
|
8972
|
-
const fullPath =
|
|
9373
|
+
const fullPath = join14(dir, f);
|
|
8973
9374
|
try {
|
|
8974
9375
|
const st = statSync4(fullPath);
|
|
8975
9376
|
if (st.mtimeMs < cutoff) {
|
|
@@ -9009,7 +9410,7 @@ var inFlightClaudeTasks = /* @__PURE__ */ new Set();
|
|
|
9009
9410
|
var claudeTaskConcurrency = /* @__PURE__ */ new Map();
|
|
9010
9411
|
var MAX_CLAUDE_CONCURRENCY = 2;
|
|
9011
9412
|
function claudePidFilePath() {
|
|
9012
|
-
return
|
|
9413
|
+
return join14(homedir7(), ".augmented", "manager-claude-pids.json");
|
|
9013
9414
|
}
|
|
9014
9415
|
var inFlightClaudePids = /* @__PURE__ */ new Map();
|
|
9015
9416
|
function registerClaudeSpawn(record) {
|
|
@@ -9349,7 +9750,7 @@ async function fireScheduledTaskViaKanban(codeName, agentId, task, prompt) {
|
|
|
9349
9750
|
}
|
|
9350
9751
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
9351
9752
|
const projectDir = getProjectDir2(codeName);
|
|
9352
|
-
const mcpConfigPath =
|
|
9753
|
+
const mcpConfigPath = join14(projectDir, ".mcp.json");
|
|
9353
9754
|
let runId = null;
|
|
9354
9755
|
let kanbanItemId = null;
|
|
9355
9756
|
let taskResult;
|
|
@@ -9357,11 +9758,11 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
9357
9758
|
const priorRuns = await fetchPriorScheduledRuns(agentId, task.taskId);
|
|
9358
9759
|
prompt = wrapScheduledTaskPrompt(prompt, { priorRuns });
|
|
9359
9760
|
try {
|
|
9360
|
-
const claudeMdPath =
|
|
9761
|
+
const claudeMdPath = join14(projectDir, "CLAUDE.md");
|
|
9361
9762
|
const serverNames = [];
|
|
9362
|
-
if (
|
|
9763
|
+
if (existsSync8(mcpConfigPath)) {
|
|
9363
9764
|
try {
|
|
9364
|
-
const d = JSON.parse(
|
|
9765
|
+
const d = JSON.parse(readFileSync12(mcpConfigPath, "utf-8"));
|
|
9365
9766
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
9366
9767
|
} catch {
|
|
9367
9768
|
}
|
|
@@ -9380,14 +9781,14 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
9380
9781
|
"--allowedTools",
|
|
9381
9782
|
allowedTools
|
|
9382
9783
|
];
|
|
9383
|
-
if (
|
|
9784
|
+
if (existsSync8(claudeMdPath)) {
|
|
9384
9785
|
claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
9385
9786
|
}
|
|
9386
9787
|
const childEnv = { ...process.env };
|
|
9387
|
-
const envIntPath =
|
|
9388
|
-
if (
|
|
9788
|
+
const envIntPath = join14(projectDir, ".env.integrations");
|
|
9789
|
+
if (existsSync8(envIntPath)) {
|
|
9389
9790
|
try {
|
|
9390
|
-
Object.assign(childEnv, parseEnvIntegrations(
|
|
9791
|
+
Object.assign(childEnv, parseEnvIntegrations(readFileSync12(envIntPath, "utf-8")));
|
|
9391
9792
|
} catch {
|
|
9392
9793
|
}
|
|
9393
9794
|
}
|
|
@@ -9559,8 +9960,8 @@ var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
|
|
|
9559
9960
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
9560
9961
|
const codeName = agent.code_name;
|
|
9561
9962
|
const projectDir = getProjectDir(codeName);
|
|
9562
|
-
const mcpConfigPath =
|
|
9563
|
-
const claudeMdPath =
|
|
9963
|
+
const mcpConfigPath = join14(projectDir, ".mcp.json");
|
|
9964
|
+
const claudeMdPath = join14(projectDir, "CLAUDE.md");
|
|
9564
9965
|
if (restartBreaker.isTripped(codeName)) {
|
|
9565
9966
|
const trip = restartBreaker.getTrip(codeName);
|
|
9566
9967
|
return {
|
|
@@ -10205,11 +10606,11 @@ ${escapeXml(msg.content)}
|
|
|
10205
10606
|
log(`[direct-chat] One-shot spawn for '${agent.codeName}' (msg=${msg.id}; host in-flight=${directChatSpawnGate.hostInFlight}, queued=${directChatSpawnGate.queuedCount})`);
|
|
10206
10607
|
const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-FATCLHDM.js");
|
|
10207
10608
|
const projDir = ccProjectDir(agent.codeName);
|
|
10208
|
-
const mcpConfigPath =
|
|
10609
|
+
const mcpConfigPath = join14(projDir, ".mcp.json");
|
|
10209
10610
|
const serverNames = [];
|
|
10210
|
-
if (
|
|
10611
|
+
if (existsSync8(mcpConfigPath)) {
|
|
10211
10612
|
try {
|
|
10212
|
-
const d = JSON.parse(
|
|
10613
|
+
const d = JSON.parse(readFileSync12(mcpConfigPath, "utf-8"));
|
|
10213
10614
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
10214
10615
|
} catch {
|
|
10215
10616
|
}
|
|
@@ -10228,15 +10629,15 @@ ${escapeXml(msg.content)}
|
|
|
10228
10629
|
"--allowedTools",
|
|
10229
10630
|
allowedTools
|
|
10230
10631
|
];
|
|
10231
|
-
const chatClaudeMd =
|
|
10232
|
-
if (
|
|
10632
|
+
const chatClaudeMd = join14(projDir, "CLAUDE.md");
|
|
10633
|
+
if (existsSync8(chatClaudeMd)) {
|
|
10233
10634
|
chatArgs.push("--system-prompt-file", chatClaudeMd);
|
|
10234
10635
|
}
|
|
10235
|
-
const envIntPath =
|
|
10636
|
+
const envIntPath = join14(projDir, ".env.integrations");
|
|
10236
10637
|
const childEnv = { ...process.env };
|
|
10237
|
-
if (
|
|
10638
|
+
if (existsSync8(envIntPath)) {
|
|
10238
10639
|
try {
|
|
10239
|
-
Object.assign(childEnv, parseEnvIntegrations(
|
|
10640
|
+
Object.assign(childEnv, parseEnvIntegrations(readFileSync12(envIntPath, "utf-8")));
|
|
10240
10641
|
} catch {
|
|
10241
10642
|
}
|
|
10242
10643
|
}
|
|
@@ -10310,32 +10711,8 @@ ${escapeXml(msg.content)}
|
|
|
10310
10711
|
releaseSpawnSlot?.();
|
|
10311
10712
|
}
|
|
10312
10713
|
}
|
|
10313
|
-
var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-summary"]);
|
|
10314
|
-
var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
|
|
10315
|
-
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
10316
|
-
var KANBAN_WORK_TEMPLATES = /* @__PURE__ */ new Set(["kanban-work"]);
|
|
10317
|
-
function isPlainScheduledTemplate(templateId) {
|
|
10318
|
-
return !STANDUP_TEMPLATES.has(templateId) && !TASK_UPDATE_TEMPLATES.has(templateId) && !PLAN_TEMPLATES.has(templateId) && !KANBAN_WORK_TEMPLATES.has(templateId);
|
|
10319
|
-
}
|
|
10320
|
-
function isKanbanHybridEnabled() {
|
|
10321
|
-
return true;
|
|
10322
|
-
}
|
|
10323
|
-
function isKanbanHybridDryRun() {
|
|
10324
|
-
const v = process.env["AGT_KANBAN_HYBRID_DRY_RUN"];
|
|
10325
|
-
return v === "1" || v?.toLowerCase() === "true";
|
|
10326
|
-
}
|
|
10327
|
-
var HYBRID_ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
|
|
10328
|
-
function isHybridActionable(item) {
|
|
10329
|
-
return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== "scheduled_task";
|
|
10330
|
-
}
|
|
10331
|
-
function hasHybridActionableItems(items) {
|
|
10332
|
-
return items.some(isHybridActionable);
|
|
10333
|
-
}
|
|
10334
10714
|
var lastKanbanInjectAt = /* @__PURE__ */ new Map();
|
|
10335
10715
|
var openInjectedRunByCode = /* @__PURE__ */ new Map();
|
|
10336
|
-
function isSlashCommand(command) {
|
|
10337
|
-
return command.trimStart().startsWith("/");
|
|
10338
|
-
}
|
|
10339
10716
|
function closeInjectedRunIfOpen(codeName, outcome, outcomeMessage) {
|
|
10340
10717
|
const runId = openInjectedRunByCode.get(codeName);
|
|
10341
10718
|
if (!runId) return;
|
|
@@ -10393,11 +10770,6 @@ ${formatRunMarker(run_id)}` : KANBAN_CHECK_COMMAND;
|
|
|
10393
10770
|
log(`[manager-worker] kanban inject failed for '${codeName}'`);
|
|
10394
10771
|
}
|
|
10395
10772
|
}
|
|
10396
|
-
var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary"]);
|
|
10397
|
-
var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
|
|
10398
|
-
function hasActionableItems(items) {
|
|
10399
|
-
return items.some((item) => ACTIONABLE_STATUSES.has(item.status));
|
|
10400
|
-
}
|
|
10401
10773
|
var lastHarvestAt = /* @__PURE__ */ new Map();
|
|
10402
10774
|
var HARVEST_INTERVAL_MS = 3 * 60 * 1e3;
|
|
10403
10775
|
var kanbanBoardCache = /* @__PURE__ */ new Map();
|
|
@@ -10456,307 +10828,66 @@ async function harvestCronResults(codeName, tasks, gatewayPort) {
|
|
|
10456
10828
|
}
|
|
10457
10829
|
}
|
|
10458
10830
|
}
|
|
10459
|
-
const completed = runs.filter((r) => r.action === "finished" && r.status === "ok" && r.summary).sort((a, b) => b.ts - a.ts);
|
|
10460
|
-
if (completed.length === 0) continue;
|
|
10461
|
-
const latest = completed[0];
|
|
10462
|
-
const lastSeen = lastCronRunTs.get(jobId) ?? 0;
|
|
10463
|
-
if (latest.ts <= lastSeen) continue;
|
|
10464
|
-
lastCronRunTs.set(jobId, latest.ts);
|
|
10465
|
-
const statusUpdate = { agent_code_name: codeName };
|
|
10466
|
-
if (STANDUP_TEMPLATES.has(templateId)) {
|
|
10467
|
-
const summary = latest.summary ?? "";
|
|
10468
|
-
statusUpdate.standup = parseStandupSummary(summary);
|
|
10469
|
-
}
|
|
10470
|
-
if (TASK_UPDATE_TEMPLATES.has(templateId)) {
|
|
10471
|
-
statusUpdate.current_tasks = latest.summary ?? "";
|
|
10472
|
-
}
|
|
10473
|
-
if (statusUpdate.standup || statusUpdate.current_tasks !== void 0) {
|
|
10474
|
-
try {
|
|
10475
|
-
await api.post("/host/agent-status", statusUpdate);
|
|
10476
|
-
log(`Updated ${templateId} for '${codeName}' from cron result`);
|
|
10477
|
-
} catch (err) {
|
|
10478
|
-
log(`Failed to update ${templateId} for '${codeName}': ${err.message}`);
|
|
10479
|
-
}
|
|
10480
|
-
}
|
|
10481
|
-
if (PLAN_TEMPLATES.has(templateId)) {
|
|
10482
|
-
const summary = latest.summary ?? "";
|
|
10483
|
-
const planItems = parsePlanItems(summary);
|
|
10484
|
-
if (planItems.length > 0) {
|
|
10485
|
-
try {
|
|
10486
|
-
const agentId = agentState.codeNameToAgentId.get(codeName);
|
|
10487
|
-
if (agentId) {
|
|
10488
|
-
await api.post("/host/kanban", {
|
|
10489
|
-
agent_id: agentId,
|
|
10490
|
-
add: planItems,
|
|
10491
|
-
archive_days: 7
|
|
10492
|
-
});
|
|
10493
|
-
log(`Added ${planItems.length} kanban items for '${codeName}' from morning-plan`);
|
|
10494
|
-
}
|
|
10495
|
-
} catch (err) {
|
|
10496
|
-
log(`Failed to update kanban for '${codeName}': ${err.message}`);
|
|
10497
|
-
}
|
|
10498
|
-
}
|
|
10499
|
-
}
|
|
10500
|
-
if (TASK_UPDATE_TEMPLATES.has(templateId) || KANBAN_WORK_TEMPLATES.has(templateId)) {
|
|
10501
|
-
const summary = latest.summary ?? "";
|
|
10502
|
-
const kanbanUpdates = parseKanbanUpdates(summary);
|
|
10503
|
-
if (kanbanUpdates.length > 0) {
|
|
10504
|
-
try {
|
|
10505
|
-
const agentId = agentState.codeNameToAgentId.get(codeName);
|
|
10506
|
-
if (agentId) {
|
|
10507
|
-
await api.post("/host/kanban", {
|
|
10508
|
-
agent_id: agentId,
|
|
10509
|
-
update: kanbanUpdates
|
|
10510
|
-
});
|
|
10511
|
-
log(`Updated ${kanbanUpdates.length} kanban items for '${codeName}'`);
|
|
10512
|
-
}
|
|
10513
|
-
} catch (err) {
|
|
10514
|
-
log(`Failed to update kanban for '${codeName}': ${err.message}`);
|
|
10515
|
-
}
|
|
10516
|
-
}
|
|
10517
|
-
}
|
|
10518
|
-
}
|
|
10519
|
-
}
|
|
10520
|
-
function parseStandupSummary(summary) {
|
|
10521
|
-
const lines = summary.split("\n");
|
|
10522
|
-
let yesterday = "";
|
|
10523
|
-
let today = "";
|
|
10524
|
-
let blockers = "";
|
|
10525
|
-
let currentSection = null;
|
|
10526
|
-
for (const line of lines) {
|
|
10527
|
-
const lower = line.toLowerCase();
|
|
10528
|
-
if (lower.includes("yesterday") || lower.includes("accomplished")) {
|
|
10529
|
-
currentSection = "yesterday";
|
|
10530
|
-
continue;
|
|
10531
|
-
} else if (lower.includes("todo") || lower.includes("working on")) {
|
|
10532
|
-
currentSection = "todo";
|
|
10533
|
-
continue;
|
|
10534
|
-
} else if (lower.includes("blocker")) {
|
|
10535
|
-
currentSection = "blockers";
|
|
10536
|
-
continue;
|
|
10537
|
-
}
|
|
10538
|
-
const trimmed = line.replace(/^[-*•]\s*/, "").trim();
|
|
10539
|
-
if (!trimmed) continue;
|
|
10540
|
-
switch (currentSection) {
|
|
10541
|
-
case "yesterday":
|
|
10542
|
-
yesterday += (yesterday ? "\n" : "") + trimmed;
|
|
10543
|
-
break;
|
|
10544
|
-
case "todo":
|
|
10545
|
-
today += (today ? "\n" : "") + trimmed;
|
|
10546
|
-
break;
|
|
10547
|
-
case "blockers":
|
|
10548
|
-
blockers += (blockers ? "\n" : "") + trimmed;
|
|
10549
|
-
break;
|
|
10550
|
-
}
|
|
10551
|
-
}
|
|
10552
|
-
if (!yesterday && !today && !blockers) {
|
|
10553
|
-
today = summary;
|
|
10554
|
-
}
|
|
10555
|
-
return { yesterday, today, blockers };
|
|
10556
|
-
}
|
|
10557
|
-
function parsePlanItems(summary) {
|
|
10558
|
-
const items = [];
|
|
10559
|
-
const lines = summary.split("\n");
|
|
10560
|
-
let currentItem = null;
|
|
10561
|
-
for (const line of lines) {
|
|
10562
|
-
const trimmed = line.trim();
|
|
10563
|
-
const itemMatch = trimmed.match(/^(?:\d+[\.\)]\s*|[-*•]\s*)\[?(HIGH|MEDIUM|LOW|MED)\]?\s*(.+)/i);
|
|
10564
|
-
if (itemMatch) {
|
|
10565
|
-
if (currentItem) items.push(currentItem);
|
|
10566
|
-
const priorityStr = itemMatch[1].toUpperCase();
|
|
10567
|
-
const rest = itemMatch[2];
|
|
10568
|
-
let estimatedMinutes;
|
|
10569
|
-
const timeMatch = rest.match(/\(~?(\d+)\s*(min(?:utes?)?|hr?(?:ours?)?|h)\)/i);
|
|
10570
|
-
if (timeMatch) {
|
|
10571
|
-
const val = parseInt(timeMatch[1], 10);
|
|
10572
|
-
const unit = timeMatch[2].toLowerCase();
|
|
10573
|
-
estimatedMinutes = unit.startsWith("h") ? val * 60 : val;
|
|
10574
|
-
}
|
|
10575
|
-
const title = sanitizeKanbanString(
|
|
10576
|
-
rest.replace(/\(~?\d+\s*(?:min(?:utes?)?|hr?(?:ours?)?|h)\)/i, ""),
|
|
10577
|
-
MAX_KANBAN_TITLE_LENGTH
|
|
10578
|
-
);
|
|
10579
|
-
if (!title) continue;
|
|
10580
|
-
const priorityMap = { HIGH: 1, MEDIUM: 2, MED: 2, LOW: 3 };
|
|
10581
|
-
const priority = priorityMap[priorityStr] ?? 2;
|
|
10582
|
-
if (![1, 2, 3].includes(priority)) continue;
|
|
10583
|
-
currentItem = {
|
|
10584
|
-
title,
|
|
10585
|
-
priority,
|
|
10586
|
-
estimated_minutes: estimatedMinutes,
|
|
10587
|
-
status: "todo"
|
|
10588
|
-
};
|
|
10589
|
-
} else if (currentItem && trimmed && !trimmed.match(/^(?:PLAN|---)/i)) {
|
|
10590
|
-
const descLine = sanitizeKanbanString(trimmed, MAX_KANBAN_NOTES_LENGTH);
|
|
10591
|
-
currentItem.description = currentItem.description ? sanitizeKanbanString(currentItem.description + "\n" + descLine, MAX_KANBAN_NOTES_LENGTH) : descLine;
|
|
10592
|
-
}
|
|
10593
|
-
}
|
|
10594
|
-
if (currentItem) items.push(currentItem);
|
|
10595
|
-
return items;
|
|
10596
|
-
}
|
|
10597
|
-
var VALID_KANBAN_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress", "done"]);
|
|
10598
|
-
var MAX_KANBAN_TITLE_LENGTH = 500;
|
|
10599
|
-
var MAX_KANBAN_NOTES_LENGTH = 2e3;
|
|
10600
|
-
function sanitizeKanbanString(value, maxLen) {
|
|
10601
|
-
if (!value) return "";
|
|
10602
|
-
return value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").replace(/\{\{.*?\}\}/g, "").replace(/^(System|Assistant|Human):\s*/gmi, "").replace(/#{2,}/g, "").replace(/\s+/g, " ").trim().slice(0, maxLen);
|
|
10603
|
-
}
|
|
10604
|
-
function sanitizeBoardItem(item) {
|
|
10605
|
-
return {
|
|
10606
|
-
...item,
|
|
10607
|
-
title: sanitizeKanbanString(item.title, 200),
|
|
10608
|
-
status: sanitizeKanbanString(item.status, 50),
|
|
10609
|
-
...item.deliverable ? { deliverable: sanitizeKanbanString(item.deliverable, 500) } : {}
|
|
10610
|
-
};
|
|
10611
|
-
}
|
|
10612
|
-
var builtInSkillCache = /* @__PURE__ */ new Map();
|
|
10613
|
-
function getBuiltInSkillContent(skillId) {
|
|
10614
|
-
if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId);
|
|
10615
|
-
try {
|
|
10616
|
-
const candidates = [
|
|
10617
|
-
join13(process.cwd(), "skills", skillId, "SKILL.md"),
|
|
10618
|
-
join13(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "skills", skillId, "SKILL.md")
|
|
10619
|
-
];
|
|
10620
|
-
for (const candidate of candidates) {
|
|
10621
|
-
if (existsSync7(candidate)) {
|
|
10622
|
-
const content = readFileSync11(candidate, "utf-8");
|
|
10623
|
-
const files = [{ relativePath: "SKILL.md", content }];
|
|
10624
|
-
builtInSkillCache.set(skillId, files);
|
|
10625
|
-
return files;
|
|
10626
|
-
}
|
|
10627
|
-
}
|
|
10628
|
-
builtInSkillCache.set(skillId, null);
|
|
10629
|
-
return null;
|
|
10630
|
-
} catch {
|
|
10631
|
-
builtInSkillCache.set(skillId, null);
|
|
10632
|
-
return null;
|
|
10633
|
-
}
|
|
10634
|
-
}
|
|
10635
|
-
function parseKanbanUpdates(summary) {
|
|
10636
|
-
const updates = [];
|
|
10637
|
-
const kanbanIdx = summary.indexOf("KANBAN UPDATE:");
|
|
10638
|
-
if (kanbanIdx === -1) return updates;
|
|
10639
|
-
const kanbanSection = summary.slice(kanbanIdx + "KANBAN UPDATE:".length);
|
|
10640
|
-
const lines = kanbanSection.split("\n");
|
|
10641
|
-
for (const line of lines) {
|
|
10642
|
-
const trimmed = line.trim();
|
|
10643
|
-
const match = trimmed.match(/^[-*•]\s*"([^"]+)":\s*(backlog|todo|today|in_progress|done)(?:\s*\((.+)\))?/i);
|
|
10644
|
-
if (match) {
|
|
10645
|
-
const rawStatus = match[2].toLowerCase();
|
|
10646
|
-
const status = rawStatus === "today" ? "todo" : rawStatus;
|
|
10647
|
-
if (!VALID_KANBAN_STATUSES.has(status)) continue;
|
|
10648
|
-
const title = sanitizeKanbanString(match[1], MAX_KANBAN_TITLE_LENGTH);
|
|
10649
|
-
if (!title) continue;
|
|
10650
|
-
const parenthetical = match[3] ?? void 0;
|
|
10651
|
-
let notes;
|
|
10652
|
-
let result;
|
|
10653
|
-
if (parenthetical && status === "done") {
|
|
10654
|
-
const resultMatch = parenthetical.match(/^result:\s*(.+)/i);
|
|
10655
|
-
if (resultMatch) {
|
|
10656
|
-
result = sanitizeKanbanString(resultMatch[1], MAX_KANBAN_NOTES_LENGTH);
|
|
10657
|
-
} else {
|
|
10658
|
-
notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);
|
|
10659
|
-
}
|
|
10660
|
-
} else if (parenthetical) {
|
|
10661
|
-
notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);
|
|
10662
|
-
}
|
|
10663
|
-
updates.push({
|
|
10664
|
-
title,
|
|
10665
|
-
status,
|
|
10666
|
-
notes,
|
|
10667
|
-
result
|
|
10668
|
-
});
|
|
10669
|
-
}
|
|
10670
|
-
}
|
|
10671
|
-
return updates;
|
|
10672
|
-
}
|
|
10673
|
-
function formatBoardForPrompt(items, template) {
|
|
10674
|
-
if (items.length === 0) return "";
|
|
10675
|
-
const priorityLabel = (p) => p === 1 ? "HIGH" : p === 3 ? "LOW" : "MED";
|
|
10676
|
-
const timeLabel = (m) => m ? ` (~${m >= 60 ? `${Math.round(m / 60)}hr` : `${m}min`})` : "";
|
|
10677
|
-
const deliverableLine = (d) => d ? `
|
|
10678
|
-
Deliverable: ${d}` : "";
|
|
10679
|
-
const sourceLine = (channel, thread, url) => {
|
|
10680
|
-
const parts = [];
|
|
10681
|
-
if (channel && thread) parts.push(`${channel} thread ${thread}`);
|
|
10682
|
-
if (url) parts.push(url);
|
|
10683
|
-
return parts.length > 0 ? `
|
|
10684
|
-
source: ${parts.join(" \u2022 ")}` : "";
|
|
10685
|
-
};
|
|
10686
|
-
const grouped = {};
|
|
10687
|
-
for (const item of items) {
|
|
10688
|
-
const key = item.status;
|
|
10689
|
-
if (!grouped[key]) grouped[key] = [];
|
|
10690
|
-
grouped[key].push(item);
|
|
10691
|
-
}
|
|
10692
|
-
const hasSourceThread = items.some(
|
|
10693
|
-
(i) => (i.status === "todo" || i.status === "in_progress") && !!i.source_integration && !!i.source_external_id
|
|
10694
|
-
);
|
|
10695
|
-
const lines = [];
|
|
10696
|
-
if (template === "morning-plan") {
|
|
10697
|
-
lines.push("=== CURRENT BOARD ===");
|
|
10698
|
-
for (const [status, label] of [["backlog", "BACKLOG (carry-over)"], ["todo", "TO DO"], ["in_progress", "IN PROGRESS"]]) {
|
|
10699
|
-
const statusItems = grouped[status];
|
|
10700
|
-
if (statusItems && statusItems.length > 0) {
|
|
10701
|
-
lines.push(`${label}:`);
|
|
10702
|
-
statusItems.forEach((item, i) => {
|
|
10703
|
-
lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}${sourceLine(item.source_integration, item.source_external_id, item.source_url)}`);
|
|
10704
|
-
});
|
|
10705
|
-
}
|
|
10831
|
+
const completed = runs.filter((r) => r.action === "finished" && r.status === "ok" && r.summary).sort((a, b) => b.ts - a.ts);
|
|
10832
|
+
if (completed.length === 0) continue;
|
|
10833
|
+
const latest = completed[0];
|
|
10834
|
+
const lastSeen = lastCronRunTs.get(jobId) ?? 0;
|
|
10835
|
+
if (latest.ts <= lastSeen) continue;
|
|
10836
|
+
lastCronRunTs.set(jobId, latest.ts);
|
|
10837
|
+
const statusUpdate = { agent_code_name: codeName };
|
|
10838
|
+
if (STANDUP_TEMPLATES.has(templateId)) {
|
|
10839
|
+
const summary = latest.summary ?? "";
|
|
10840
|
+
statusUpdate.standup = parseStandupSummary(summary);
|
|
10706
10841
|
}
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
for (const [status, label] of [["todo", "TO DO"], ["in_progress", "IN PROGRESS"], ["backlog", "BACKLOG"]]) {
|
|
10717
|
-
const statusItems = grouped[status];
|
|
10718
|
-
if (statusItems && statusItems.length > 0) {
|
|
10719
|
-
lines.push(`${label}:`);
|
|
10720
|
-
statusItems.forEach((item, i) => {
|
|
10721
|
-
lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}${sourceLine(item.source_integration, item.source_external_id, item.source_url)}`);
|
|
10722
|
-
});
|
|
10842
|
+
if (TASK_UPDATE_TEMPLATES.has(templateId)) {
|
|
10843
|
+
statusUpdate.current_tasks = latest.summary ?? "";
|
|
10844
|
+
}
|
|
10845
|
+
if (statusUpdate.standup || statusUpdate.current_tasks !== void 0) {
|
|
10846
|
+
try {
|
|
10847
|
+
await api.post("/host/agent-status", statusUpdate);
|
|
10848
|
+
log(`Updated ${templateId} for '${codeName}' from cron result`);
|
|
10849
|
+
} catch (err) {
|
|
10850
|
+
log(`Failed to update ${templateId} for '${codeName}': ${err.message}`);
|
|
10723
10851
|
}
|
|
10724
10852
|
}
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10853
|
+
if (PLAN_TEMPLATES.has(templateId)) {
|
|
10854
|
+
const summary = latest.summary ?? "";
|
|
10855
|
+
const planItems = parsePlanItems(summary);
|
|
10856
|
+
if (planItems.length > 0) {
|
|
10857
|
+
try {
|
|
10858
|
+
const agentId = agentState.codeNameToAgentId.get(codeName);
|
|
10859
|
+
if (agentId) {
|
|
10860
|
+
await api.post("/host/kanban", {
|
|
10861
|
+
agent_id: agentId,
|
|
10862
|
+
add: planItems,
|
|
10863
|
+
archive_days: 7
|
|
10864
|
+
});
|
|
10865
|
+
log(`Added ${planItems.length} kanban items for '${codeName}' from morning-plan`);
|
|
10866
|
+
}
|
|
10867
|
+
} catch (err) {
|
|
10868
|
+
log(`Failed to update kanban for '${codeName}': ${err.message}`);
|
|
10869
|
+
}
|
|
10870
|
+
}
|
|
10731
10871
|
}
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10872
|
+
if (TASK_UPDATE_TEMPLATES.has(templateId) || KANBAN_WORK_TEMPLATES.has(templateId)) {
|
|
10873
|
+
const summary = latest.summary ?? "";
|
|
10874
|
+
const kanbanUpdates = parseKanbanUpdates(summary);
|
|
10875
|
+
if (kanbanUpdates.length > 0) {
|
|
10876
|
+
try {
|
|
10877
|
+
const agentId = agentState.codeNameToAgentId.get(codeName);
|
|
10878
|
+
if (agentId) {
|
|
10879
|
+
await api.post("/host/kanban", {
|
|
10880
|
+
agent_id: agentId,
|
|
10881
|
+
update: kanbanUpdates
|
|
10882
|
+
});
|
|
10883
|
+
log(`Updated ${kanbanUpdates.length} kanban items for '${codeName}'`);
|
|
10884
|
+
}
|
|
10885
|
+
} catch (err) {
|
|
10886
|
+
log(`Failed to update kanban for '${codeName}': ${err.message}`);
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10747
10889
|
}
|
|
10748
|
-
lines.push("SELF-MANAGEMENT: When you receive a request from a channel (Slack, Telegram)");
|
|
10749
|
-
lines.push("that takes more than a quick response, create a task first with kanban_add,");
|
|
10750
|
-
lines.push("move to in_progress, do the work, then kanban_done with a result summary.");
|
|
10751
|
-
lines.push("");
|
|
10752
|
-
lines.push("If MCP tools are unavailable, include a KANBAN UPDATE section in your output:");
|
|
10753
|
-
lines.push("KANBAN UPDATE:");
|
|
10754
|
-
lines.push('- "item title": new_status (optional notes)');
|
|
10755
|
-
lines.push('- "item title": done (result: <what you produced>)');
|
|
10756
|
-
lines.push("Statuses: backlog, today, in_progress, done");
|
|
10757
|
-
lines.push("");
|
|
10758
10890
|
}
|
|
10759
|
-
return lines.join("\n");
|
|
10760
10891
|
}
|
|
10761
10892
|
var LATE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
10762
10893
|
async function monitorCronHealth(agentStates) {
|
|
@@ -10823,7 +10954,7 @@ async function monitorCronHealth(agentStates) {
|
|
|
10823
10954
|
for (const alert of alerts) {
|
|
10824
10955
|
log(`ALERT [${alert.type}] ${alert.agentCodeName}/${alert.jobName}: ${alert.detail}`);
|
|
10825
10956
|
}
|
|
10826
|
-
if (
|
|
10957
|
+
if (getAlertSlackWebhook()) {
|
|
10827
10958
|
await sendSlackAlert(alerts);
|
|
10828
10959
|
}
|
|
10829
10960
|
try {
|
|
@@ -10831,122 +10962,6 @@ async function monitorCronHealth(agentStates) {
|
|
|
10831
10962
|
} catch {
|
|
10832
10963
|
}
|
|
10833
10964
|
}
|
|
10834
|
-
function telegramApiCall(botToken, method, body) {
|
|
10835
|
-
return new Promise((resolve, reject) => {
|
|
10836
|
-
const postData = JSON.stringify(body);
|
|
10837
|
-
const req = https.request({
|
|
10838
|
-
hostname: "api.telegram.org",
|
|
10839
|
-
port: 443,
|
|
10840
|
-
path: `/bot${botToken}/${method}`,
|
|
10841
|
-
method: "POST",
|
|
10842
|
-
family: 4,
|
|
10843
|
-
timeout: 1e4,
|
|
10844
|
-
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(postData) }
|
|
10845
|
-
}, (res) => {
|
|
10846
|
-
let data = "";
|
|
10847
|
-
res.on("data", (d) => {
|
|
10848
|
-
data += d;
|
|
10849
|
-
});
|
|
10850
|
-
res.on("end", () => {
|
|
10851
|
-
try {
|
|
10852
|
-
resolve(JSON.parse(data));
|
|
10853
|
-
} catch {
|
|
10854
|
-
reject(new Error("Invalid JSON from Telegram API"));
|
|
10855
|
-
}
|
|
10856
|
-
});
|
|
10857
|
-
});
|
|
10858
|
-
req.on("error", reject);
|
|
10859
|
-
req.on("timeout", () => {
|
|
10860
|
-
req.destroy();
|
|
10861
|
-
reject(new Error("Telegram API timeout"));
|
|
10862
|
-
});
|
|
10863
|
-
req.write(postData);
|
|
10864
|
-
req.end();
|
|
10865
|
-
});
|
|
10866
|
-
}
|
|
10867
|
-
async function sendSlackWebhookMessage(text) {
|
|
10868
|
-
if (!alertSlackWebhook) {
|
|
10869
|
-
log("sendSlackWebhookMessage: no alertSlackWebhook configured \u2014 message dropped");
|
|
10870
|
-
return;
|
|
10871
|
-
}
|
|
10872
|
-
try {
|
|
10873
|
-
const response = await fetch(alertSlackWebhook, {
|
|
10874
|
-
method: "POST",
|
|
10875
|
-
headers: { "Content-Type": "application/json" },
|
|
10876
|
-
body: JSON.stringify({ text })
|
|
10877
|
-
});
|
|
10878
|
-
if (!response.ok) {
|
|
10879
|
-
log(`Slack webhook failed: ${response.status} ${response.statusText}`);
|
|
10880
|
-
}
|
|
10881
|
-
} catch (err) {
|
|
10882
|
-
log(`Slack webhook error: ${err.message}`);
|
|
10883
|
-
}
|
|
10884
|
-
}
|
|
10885
|
-
async function sendSlackChannelMessage(agentCodeName, channelId, text) {
|
|
10886
|
-
const result = await postSlackChannelMessage(agentCodeName, channelId, text);
|
|
10887
|
-
return result.ok;
|
|
10888
|
-
}
|
|
10889
|
-
async function postSlackChannelMessage(agentCodeName, channelId, text, threadTs) {
|
|
10890
|
-
const botToken = agentChannelTokens.get(agentCodeName)?.slack;
|
|
10891
|
-
if (!botToken) {
|
|
10892
|
-
log(`No Slack bot token cached for '${agentCodeName}' \u2014 cannot post to ${channelId}`);
|
|
10893
|
-
return { ok: false };
|
|
10894
|
-
}
|
|
10895
|
-
try {
|
|
10896
|
-
const controller = new AbortController();
|
|
10897
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
10898
|
-
try {
|
|
10899
|
-
const body = { channel: channelId, text };
|
|
10900
|
-
if (threadTs) body.thread_ts = threadTs;
|
|
10901
|
-
const response = await fetch("https://slack.com/api/chat.postMessage", {
|
|
10902
|
-
method: "POST",
|
|
10903
|
-
headers: {
|
|
10904
|
-
"Authorization": `Bearer ${botToken}`,
|
|
10905
|
-
"Content-Type": "application/json"
|
|
10906
|
-
},
|
|
10907
|
-
body: JSON.stringify(body),
|
|
10908
|
-
signal: controller.signal
|
|
10909
|
-
});
|
|
10910
|
-
clearTimeout(timeout);
|
|
10911
|
-
const data = await response.json();
|
|
10912
|
-
if (!data.ok) {
|
|
10913
|
-
log(`Slack chat.postMessage failed for '${agentCodeName}' to ${channelId}: ${data.error}`);
|
|
10914
|
-
return { ok: false, error: data.error };
|
|
10915
|
-
}
|
|
10916
|
-
return { ok: true, ts: data.ts };
|
|
10917
|
-
} finally {
|
|
10918
|
-
clearTimeout(timeout);
|
|
10919
|
-
}
|
|
10920
|
-
} catch (err) {
|
|
10921
|
-
log(`Slack channel message error for '${agentCodeName}': ${err.message}`);
|
|
10922
|
-
return { ok: false };
|
|
10923
|
-
}
|
|
10924
|
-
}
|
|
10925
|
-
async function maybePostSlackThreadHint(agentCodeName, channelId, primaryTs) {
|
|
10926
|
-
if (!shouldIncludeHint(hintProbability(agentCodeName))) return;
|
|
10927
|
-
if (!primaryTs) {
|
|
10928
|
-
return;
|
|
10929
|
-
}
|
|
10930
|
-
const hint = pickHintVariant();
|
|
10931
|
-
const result = await postSlackChannelMessage(agentCodeName, channelId, hint, primaryTs);
|
|
10932
|
-
if (result.ok) {
|
|
10933
|
-
log(`[delivery-hint] Slack thread hint posted for '${agentCodeName}'`);
|
|
10934
|
-
}
|
|
10935
|
-
}
|
|
10936
|
-
async function maybeSendTelegramFollowUpHint(agentCodeName, botToken, chatId) {
|
|
10937
|
-
if (!shouldIncludeHint(hintProbability(agentCodeName))) return;
|
|
10938
|
-
const hint = pickHintVariant();
|
|
10939
|
-
try {
|
|
10940
|
-
const result = await telegramApiCall(botToken, "sendMessage", { chat_id: chatId, text: hint });
|
|
10941
|
-
if (!result.ok) {
|
|
10942
|
-
log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${result.description ?? "unknown"}`);
|
|
10943
|
-
return;
|
|
10944
|
-
}
|
|
10945
|
-
log(`[delivery-hint] Telegram follow-up hint posted for '${agentCodeName}'`);
|
|
10946
|
-
} catch (err) {
|
|
10947
|
-
log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${err.message}`);
|
|
10948
|
-
}
|
|
10949
|
-
}
|
|
10950
10965
|
async function sendSlackAlert(alerts) {
|
|
10951
10966
|
const blocks = alerts.map((a) => {
|
|
10952
10967
|
const emoji = a.type === "late_standup" ? ":warning:" : ":x:";
|
|
@@ -11521,8 +11536,8 @@ function parseMemoryFile(raw, fallbackName) {
|
|
|
11521
11536
|
};
|
|
11522
11537
|
}
|
|
11523
11538
|
async function syncMemories(agent, configDir, log2) {
|
|
11524
|
-
const projectDir =
|
|
11525
|
-
const memoryDir =
|
|
11539
|
+
const projectDir = join14(configDir, agent.code_name, "project");
|
|
11540
|
+
const memoryDir = join14(projectDir, "memory");
|
|
11526
11541
|
const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);
|
|
11527
11542
|
if (isFreshSync) {
|
|
11528
11543
|
log2(`[memory-sync] Fresh-sync requested for '${agent.code_name}' \u2014 pulling DB first`);
|
|
@@ -11533,14 +11548,14 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11533
11548
|
}
|
|
11534
11549
|
pendingFreshMemorySync.delete(agent.agent_id);
|
|
11535
11550
|
}
|
|
11536
|
-
if (
|
|
11551
|
+
if (existsSync8(memoryDir)) {
|
|
11537
11552
|
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
11538
11553
|
const currentHashes = /* @__PURE__ */ new Map();
|
|
11539
11554
|
const changedMemories = [];
|
|
11540
11555
|
for (const file of readdirSync5(memoryDir)) {
|
|
11541
11556
|
if (!file.endsWith(".md")) continue;
|
|
11542
11557
|
try {
|
|
11543
|
-
const raw =
|
|
11558
|
+
const raw = readFileSync12(join14(memoryDir, file), "utf-8");
|
|
11544
11559
|
const fileHash = createHash4("sha256").update(raw).digest("hex").slice(0, 16);
|
|
11545
11560
|
currentHashes.set(file, fileHash);
|
|
11546
11561
|
if (prevHashes.get(file) === fileHash) continue;
|
|
@@ -11565,7 +11580,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11565
11580
|
} catch (err) {
|
|
11566
11581
|
for (const mem of changedMemories) {
|
|
11567
11582
|
for (const [file] of currentHashes) {
|
|
11568
|
-
const parsed = parseMemoryFile(
|
|
11583
|
+
const parsed = parseMemoryFile(readFileSync12(join14(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
11569
11584
|
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
11570
11585
|
}
|
|
11571
11586
|
}
|
|
@@ -11578,7 +11593,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11578
11593
|
}
|
|
11579
11594
|
}
|
|
11580
11595
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
11581
|
-
const localFiles =
|
|
11596
|
+
const localFiles = existsSync8(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
11582
11597
|
const localListHash = createHash4("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
11583
11598
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
11584
11599
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
@@ -11600,7 +11615,7 @@ async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
|
11600
11615
|
const mem = dbMemories.memories[i];
|
|
11601
11616
|
const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
|
|
11602
11617
|
const slug = rawSlug || `memory-${i}`;
|
|
11603
|
-
const filePath =
|
|
11618
|
+
const filePath = join14(memoryDir, `${slug}.md`);
|
|
11604
11619
|
const desired = `---
|
|
11605
11620
|
name: ${JSON.stringify(mem.name)}
|
|
11606
11621
|
type: ${mem.type}
|
|
@@ -11609,10 +11624,10 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
|
11609
11624
|
|
|
11610
11625
|
${mem.content}
|
|
11611
11626
|
`;
|
|
11612
|
-
if (
|
|
11627
|
+
if (existsSync8(filePath)) {
|
|
11613
11628
|
let existing = "";
|
|
11614
11629
|
try {
|
|
11615
|
-
existing =
|
|
11630
|
+
existing = readFileSync12(filePath, "utf-8");
|
|
11616
11631
|
} catch {
|
|
11617
11632
|
}
|
|
11618
11633
|
if (existing === desired) continue;
|
|
@@ -11636,7 +11651,7 @@ ${mem.content}
|
|
|
11636
11651
|
}
|
|
11637
11652
|
}
|
|
11638
11653
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
11639
|
-
if (
|
|
11654
|
+
if (existsSync8(agentDir)) {
|
|
11640
11655
|
try {
|
|
11641
11656
|
rmSync3(agentDir, { recursive: true, force: true });
|
|
11642
11657
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -11886,8 +11901,8 @@ function startManager(opts) {
|
|
|
11886
11901
|
config = opts;
|
|
11887
11902
|
try {
|
|
11888
11903
|
const stateFile = getStateFile();
|
|
11889
|
-
if (
|
|
11890
|
-
const raw =
|
|
11904
|
+
if (existsSync8(stateFile)) {
|
|
11905
|
+
const raw = readFileSync12(stateFile, "utf-8");
|
|
11891
11906
|
const parsed = JSON.parse(raw);
|
|
11892
11907
|
if (Array.isArray(parsed.agents)) {
|
|
11893
11908
|
state6.agents = parsed.agents;
|
|
@@ -11908,7 +11923,7 @@ function startManager(opts) {
|
|
|
11908
11923
|
log(`[startup] state rehydration failed (continuing with empty state): ${err.message}`);
|
|
11909
11924
|
}
|
|
11910
11925
|
log(
|
|
11911
|
-
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${
|
|
11926
|
+
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join14(homedir7(), ".augmented", "manager.log")}`
|
|
11912
11927
|
);
|
|
11913
11928
|
deployMcpAssets();
|
|
11914
11929
|
reapOrphanChannelMcps({ log });
|
|
@@ -11929,7 +11944,7 @@ async function reapOrphanedClaudePids() {
|
|
|
11929
11944
|
const looksLikeClaude = (pid) => {
|
|
11930
11945
|
if (process.platform !== "linux") return true;
|
|
11931
11946
|
try {
|
|
11932
|
-
const comm =
|
|
11947
|
+
const comm = readFileSync12(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
|
|
11933
11948
|
return comm.includes("claude");
|
|
11934
11949
|
} catch {
|
|
11935
11950
|
return false;
|
|
@@ -12039,14 +12054,14 @@ function restartRunningChannelMcps(basenames) {
|
|
|
12039
12054
|
}
|
|
12040
12055
|
}
|
|
12041
12056
|
function deployMcpAssets() {
|
|
12042
|
-
const targetDir =
|
|
12057
|
+
const targetDir = join14(homedir7(), ".augmented", "_mcp");
|
|
12043
12058
|
mkdirSync4(targetDir, { recursive: true });
|
|
12044
12059
|
const moduleDir = dirname3(fileURLToPath(import.meta.url));
|
|
12045
12060
|
let mcpSourceDir = "";
|
|
12046
12061
|
let dir = moduleDir;
|
|
12047
12062
|
for (let i = 0; i < 6; i++) {
|
|
12048
|
-
const candidate =
|
|
12049
|
-
if (
|
|
12063
|
+
const candidate = join14(dir, "dist", "mcp");
|
|
12064
|
+
if (existsSync8(join14(candidate, "index.js"))) {
|
|
12050
12065
|
mcpSourceDir = candidate;
|
|
12051
12066
|
break;
|
|
12052
12067
|
}
|
|
@@ -12061,8 +12076,8 @@ function deployMcpAssets() {
|
|
|
12061
12076
|
const changedBasenames = [];
|
|
12062
12077
|
const fileHash = (p) => {
|
|
12063
12078
|
try {
|
|
12064
|
-
if (!
|
|
12065
|
-
return createHash4("sha256").update(
|
|
12079
|
+
if (!existsSync8(p)) return null;
|
|
12080
|
+
return createHash4("sha256").update(readFileSync12(p)).digest("hex");
|
|
12066
12081
|
} catch {
|
|
12067
12082
|
return null;
|
|
12068
12083
|
}
|
|
@@ -12089,9 +12104,9 @@ function deployMcpAssets() {
|
|
|
12089
12104
|
// natural session restart.
|
|
12090
12105
|
"augmented-admin.js"
|
|
12091
12106
|
]) {
|
|
12092
|
-
const src =
|
|
12093
|
-
const dst =
|
|
12094
|
-
if (!
|
|
12107
|
+
const src = join14(mcpSourceDir, file);
|
|
12108
|
+
const dst = join14(targetDir, file);
|
|
12109
|
+
if (!existsSync8(src)) continue;
|
|
12095
12110
|
const before = fileHash(dst);
|
|
12096
12111
|
try {
|
|
12097
12112
|
copyFileSync(src, dst);
|
|
@@ -12108,16 +12123,16 @@ function deployMcpAssets() {
|
|
|
12108
12123
|
log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
|
|
12109
12124
|
restartRunningChannelMcps(changedBasenames);
|
|
12110
12125
|
}
|
|
12111
|
-
const localMcpPath =
|
|
12126
|
+
const localMcpPath = join14(targetDir, "index.js");
|
|
12112
12127
|
try {
|
|
12113
|
-
const agentsDir =
|
|
12114
|
-
if (
|
|
12128
|
+
const agentsDir = join14(homedir7(), ".augmented", "agents");
|
|
12129
|
+
if (existsSync8(agentsDir)) {
|
|
12115
12130
|
for (const entry of readdirSync5(agentsDir, { withFileTypes: true })) {
|
|
12116
12131
|
if (!entry.isDirectory()) continue;
|
|
12117
12132
|
for (const subdir of ["provision", "project"]) {
|
|
12118
|
-
const mcpJsonPath =
|
|
12133
|
+
const mcpJsonPath = join14(agentsDir, entry.name, subdir, ".mcp.json");
|
|
12119
12134
|
try {
|
|
12120
|
-
const raw =
|
|
12135
|
+
const raw = readFileSync12(mcpJsonPath, "utf-8");
|
|
12121
12136
|
if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
|
|
12122
12137
|
const mcpConfig = JSON.parse(raw);
|
|
12123
12138
|
const augServer = mcpConfig.mcpServers?.["augmented"];
|
|
@@ -12162,7 +12177,6 @@ export {
|
|
|
12162
12177
|
BACK_ONLINE_GREETING_GUIDANCE,
|
|
12163
12178
|
SCHEDULED_CARD_DELIVERY_CONTRACT,
|
|
12164
12179
|
__resetScheduledDeliveryDedupeForTest,
|
|
12165
|
-
__setAgentChannelTokensForTest,
|
|
12166
12180
|
applyRestartAcks,
|
|
12167
12181
|
buildDoneCardNotification,
|
|
12168
12182
|
buildSchedulerTaskInput,
|
|
@@ -12177,15 +12191,8 @@ export {
|
|
|
12177
12191
|
extractCharterSlackPeers,
|
|
12178
12192
|
extractCharterTelegramPeers,
|
|
12179
12193
|
fireScheduledTaskViaKanban,
|
|
12180
|
-
formatBoardForPrompt,
|
|
12181
|
-
hasActionableItems,
|
|
12182
|
-
hasHybridActionableItems,
|
|
12183
12194
|
hasRevokedResiduals,
|
|
12184
|
-
isHybridActionable,
|
|
12185
|
-
isKanbanHybridDryRun,
|
|
12186
|
-
isKanbanHybridEnabled,
|
|
12187
12195
|
isOlderSemverTuple,
|
|
12188
|
-
isPlainScheduledTemplate,
|
|
12189
12196
|
isPrereleaseVersion,
|
|
12190
12197
|
isScheduledCardTracked,
|
|
12191
12198
|
isScheduledViaKanbanEnabled,
|