@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-6MF3QAKQ.js";
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 readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, rmSync as rmSync3, readdirSync as readdirSync5, statSync as statSync4, unlinkSync, copyFileSync } from "fs";
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 join13, dirname as dirname3 } from "path";
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 envSuffixFor2(codeName) {
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__${envSuffixFor2(codeName)}`];
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/wedge-detection.ts
3835
- var DEFAULTS = {
3836
- inboundWaitSeconds: 120,
3837
- // ENG-6264: DISABLED by default (0). A session that's actively producing
3838
- // tokens is never force-respawned a working agent must not be killed just
3839
- // because a message has been queued behind its turn, no matter how long.
3840
- // ENG-6238 made this an absolute backstop (1200s) to still catch a
3841
- // producing-but-never-draining runaway loop, but that re-introduced the exact
3842
- // failure we set out to kill: cutting off real work on a long turn. Runaway
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 isWedgeCandidateCycle(signals, config2) {
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 decideWedgeRestart(input, config2) {
3911
- if (!isWedgeCandidateCycle(input, config2)) return "none";
3912
- return input.consecutiveWedgeCycles >= config2.minCycles ? "wedged" : "none";
3802
+ function isKanbanHybridDryRun() {
3803
+ const v = process.env["AGT_KANBAN_HYBRID_DRY_RUN"];
3804
+ return v === "1" || v?.toLowerCase() === "true";
3913
3805
  }
3914
-
3915
- // src/lib/wedge-poison-card.ts
3916
- var DEFAULTS2 = {
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 resolvePoisonCardConfig(env = process.env) {
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
- function recordWedgeForCards(states, inProgressCardIds, nowMs, config2) {
3936
- if (inProgressCardIds.length === 0) {
3937
- return { next: new Map(states), newlyPoisoned: [] };
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
- const next = /* @__PURE__ */ new Map();
3940
- const newlyPoisoned = [];
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 { next, newlyPoisoned };
3849
+ return { yesterday, today, blockers };
3948
3850
  }
3949
- function pruneCardStates(states, liveInProgressCardIds, nowMs, config2) {
3950
- const live = liveInProgressCardIds instanceof Set ? liveInProgressCardIds : new Set(liveInProgressCardIds);
3951
- const next = /* @__PURE__ */ new Map();
3952
- for (const [id, state7] of states) {
3953
- if (!live.has(id)) continue;
3954
- if (nowMs - state7.lastWedgeAtMs > config2.cooldownMs) continue;
3955
- next.set(id, state7);
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
- return next;
3888
+ if (currentItem) items.push(currentItem);
3889
+ return items;
3958
3890
  }
3959
- function isCardPoisoned(states, cardId, config2) {
3960
- return (states.get(cardId)?.count ?? 0) >= config2.threshold;
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 existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync10, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
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 join12 } from "path";
4380
+ import { join as join13 } from "path";
3976
4381
  import { randomUUID } from "crypto";
3977
4382
  function restartFlagsDir() {
3978
- return join12(homedir6(), ".augmented", "restart-flags");
4383
+ return join13(homedir6(), ".augmented", "restart-flags");
3979
4384
  }
3980
4385
  function flagPath(codeName) {
3981
- return join12(restartFlagsDir(), `${codeName}.flag`);
4386
+ return join13(restartFlagsDir(), `${codeName}.flag`);
3982
4387
  }
3983
4388
  function readRestartFlags() {
3984
4389
  const dir = restartFlagsDir();
3985
- if (!existsSync6(dir)) return [];
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 = readFileSync10(join12(dir, entry), "utf8");
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 (existsSync6(path)) {
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 = join13(process.env["HOME"] ?? "/tmp", ".augmented");
4544
- var GATEWAY_PORTS_FILE = join13(AUGMENTED_DIR, "gateway-ports.json");
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 = readFileSync11(join13(projectDir, ".mcp.json"), "utf-8");
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 = readFileSync11(join13(projectDir, ".mcp.json"), "utf-8");
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 = readFileSync11(join13(projectDir, ".mcp.json"), "utf-8");
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 = join13(projectDir, ".env.integrations");
5067
- if (existsSync7(envIntPath)) {
5068
- Object.assign(probeEnv, parseEnvIntegrations(readFileSync11(envIntPath, "utf-8")));
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
- readFileSync11(join13(projectDir, ".env.integrations"), "utf-8")
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.25" : "dev";
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 (existsSync7(path)) return path;
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) => existsSync7(path))) return true;
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 (existsSync7(path)) {
5542
- const raw = readFileSync11(path, "utf-8").trim();
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 (existsSync7("/home/linuxbrew/.linuxbrew/bin/claude")) {
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 join13(homedir7(), ".augmented", ".last-claude-code-upgrade-check");
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(readFileSync11(claudeCodeUpgradeMarkerPath(), "utf-8").trim(), 10);
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 join13(homedir7(), ".augmented", ".last-self-update-applied");
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 = join13(homedir7(), ".augmented", ".last-update-check");
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 && existsSync7(evalEmptyMcpConfigPath)) return evalEmptyMcpConfigPath;
5960
- const dir = join13(homedir7(), ".augmented");
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 = join13(dir, ".eval-empty-mcp.json");
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(readFileSync11(GATEWAY_PORTS_FILE, "utf-8"));
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 join13(config?.configDir ?? join13(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
6471
+ return join14(config?.configDir ?? join14(process.env["HOME"] ?? "/tmp", ".augmented"), "manager-state.json");
6071
6472
  }
6072
6473
  function channelHashCacheDir() {
6073
- return config?.configDir ?? join13(process.env["HOME"] ?? "/tmp", ".augmented");
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 ?? join13(process.env["HOME"] ?? "/tmp", ".augmented");
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 ?? join13(process.env["HOME"] ?? "/tmp", ".augmented");
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 = join13(configDir, codeName, "project", ".claude", "skills");
6145
- const claudeMdPath = join13(configDir, codeName, "project", "CLAUDE.md");
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 = join13(skillsDir, dir, "SKILL.md");
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 = join13(homeDir, ".openclaw", "openclaw.json");
6598
+ const sharedConfigPath = join14(homeDir, ".openclaw", "openclaw.json");
6198
6599
  let sharedConfig;
6199
6600
  try {
6200
- sharedConfig = JSON.parse(readFileSync11(sharedConfigPath, "utf-8"));
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 = join13(homeDir, `.openclaw-${codeName}`);
6214
- if (existsSync7(join13(profileDir, "openclaw.json"))) continue;
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 = join13(homeDir, ".openclaw", "agents", codeName, "agent");
6220
- const profileAuthDir = join13(profileDir, "agents", codeName, "agent");
6221
- const authFile = join13(sharedAuthDir, "auth-profiles.json");
6222
- if (existsSync7(authFile)) {
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 = readFileSync11(authFile, "utf-8");
6225
- writeFileSync4(join13(profileAuthDir, "auth-profiles.json"), authContent);
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(readFileSync11(join13(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
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 = join13(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
6251
- if (!existsSync7(jobsPath)) return false;
6651
+ const jobsPath = join14(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
6652
+ if (!existsSync8(jobsPath)) return false;
6252
6653
  try {
6253
- const data = JSON.parse(readFileSync11(jobsPath, "utf-8"));
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 = join13(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
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 = join13(homeDir, `.openclaw-${codeName}`, "openclaw.json");
6293
- if (existsSync7(configPath)) {
6294
- const cfg = JSON.parse(readFileSync11(configPath, "utf-8"));
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 = join13(homeDir, `.openclaw-${codeName}`, "openclaw.json");
6319
- if (existsSync7(configPath)) {
6320
- const cfg = JSON.parse(readFileSync11(configPath, "utf-8"));
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 = join13(adapter.getAgentDir(prev.codeName), "provision");
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 = join13(homedir7(), ".augmented", codeName);
6963
- if (!existsSync7(dir)) return;
7363
+ const dir = join14(homedir7(), ".augmented", codeName);
7364
+ if (!existsSync8(dir)) return;
6964
7365
  atomicWriteFileSync(
6965
- join13(dir, "watchdog-give-up.json"),
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 = join13(adapter.getAgentDir(agent.code_name), "provision");
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: existsSync7(agentDir)
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 (!alertSlackWebhook && refreshData.team?.settings) {
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
- alertSlackWebhook = webhook;
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 = join13(frameworkAdapter.getAgentDir(agent.code_name), "provision");
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 = join13(agentDir, artifact.relativePath);
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 = join13(config.configDir, agent.code_name, "project", "CLAUDE.md");
7359
- const existing = readFileSync11(projectClaudeMd, "utf-8");
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 = readFileSync11(filePath, "utf-8");
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 = !existsSync7(join13(agentDir, "CHARTER.md"));
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 = join13(agentDir, file.relativePath);
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 = join13(agentDir, ".claude", "skills");
7414
- if (existsSync7(provSkillsDir)) {
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(join13(provSkillsDir, folder), { recursive: true });
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(join13(agentDir, file));
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 = join13(agentDir, ".claude", "workflows");
7450
- if (existsSync7(provWorkflowsDir)) {
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(join13(provWorkflowsDir, file));
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 && existsSync7(agentDir)) {
7914
+ if (written && existsSync8(agentDir)) {
7514
7915
  const driftedFiles = [];
7515
7916
  for (const [file, expectedHash] of written) {
7516
- const localHash = hashFile(join13(agentDir, file));
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(join13(agentDir, file));
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 = join13(homedir7(), ".augmented", agent.code_name, "project");
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 = join13(agentProvisionDir, ".mcp.json");
7855
- const projectMcpPath = join13(projectDir, ".mcp.json");
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(readFileSync11(provisionMcpPath, "utf-8"));
8259
+ mcpConfig = JSON.parse(readFileSync12(provisionMcpPath, "utf-8"));
7859
8260
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
7860
8261
  } catch {
7861
8262
  }
7862
- const localDirectChatChannel = join13(homedir7(), ".augmented", "_mcp", "direct-chat-channel.js");
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 (existsSync7(localDirectChatChannel)) {
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 = join13(projectDir, ".mcp-channels.json");
7893
- if (existsSync7(staleChannelsPath)) {
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 = join13(homedir7(), ".augmented", agent.code_name, "project");
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 = join13(homedir7(), ".augmented", agent.code_name, "project");
7976
- const envIntPath = join13(projectDir, ".env.integrations");
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 = readFileSync11(envIntPath, "utf-8");
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 = join13(projectDir, ".mcp.json");
7992
- const postWriteEnv = readFileSync11(envIntPath, "utf-8");
7993
- const mcpContent = readFileSync11(projectMcpPath, "utf-8");
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: readFileSync12 } = await import("fs");
8074
- const mcpConfig = JSON.parse(readFileSync12(mcpPath, "utf-8"));
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 = join13(process.cwd(), "packages", "openclaw-plugin-augmented", "src", "index.ts");
8176
- if (existsSync7(pluginPath)) {
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
- join13(homedir8(), ".augmented", agent.code_name, "skills"),
8647
+ join14(homedir8(), ".augmented", agent.code_name, "skills"),
8247
8648
  // Claude Code — project tree
8248
- join13(homedir8(), ".augmented", agent.code_name, "project", ".claude", "skills"),
8649
+ join14(homedir8(), ".augmented", agent.code_name, "project", ".claude", "skills"),
8249
8650
  // OpenClaw — framework runtime tree
8250
- join13(homedir8(), `.openclaw-${agent.code_name}`, "skills"),
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
- join13(agentDir, ".claude", "skills")
8654
+ join14(agentDir, ".claude", "skills")
8254
8655
  ];
8255
- const existingDirs = candidateSkillDirs.filter((d) => existsSync7(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 = join13(dir, entry);
8270
- if (existsSync7(p)) {
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
- join13(homedir8(), ".augmented", agent.code_name, "skills"),
8301
- join13(homedir8(), ".augmented", agent.code_name, "project", ".claude", "skills"),
8302
- join13(homedir8(), `.openclaw-${agent.code_name}`, "skills"),
8303
- join13(agentDir, ".claude", "skills")
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 = join13(dir, id);
8308
- if (existsSync7(p)) rmSync4(p, { recursive: true, force: true });
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 = join13(getProjectDir(agent.code_name), ".mcp.json");
8484
- mcpJsonParsed = JSON.parse(readFileSync11(mcpPath, "utf-8"));
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 = join13(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
8694
- if (existsSync7(jobsPath)) {
9094
+ const jobsPath = join14(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
9095
+ if (existsSync8(jobsPath)) {
8695
9096
  try {
8696
- const jobsData = JSON.parse(readFileSync11(jobsPath, "utf-8"));
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 && existsSync7(agentDir)) {
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(join13(agentDir, file));
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 = join13(homeDir, `.openclaw-${codeName}`, "agents", agentDir, "sessions");
9268
+ const sessionsDir = join14(homeDir, `.openclaw-${codeName}`, "agents", agentDir, "sessions");
8868
9269
  cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);
8869
9270
  }
8870
- const cronRunsDir = join13(homeDir, `.openclaw-${codeName}`, "cron", "runs");
9271
+ const cronRunsDir = join14(homeDir, `.openclaw-${codeName}`, "cron", "runs");
8871
9272
  cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, ".jsonl");
8872
- const cronJobsPath = join13(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
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 = join13(sessionsDir, "sessions.json");
8877
- if (!existsSync7(indexPath)) return;
9277
+ const indexPath = join14(sessionsDir, "sessions.json");
9278
+ if (!existsSync8(indexPath)) return;
8878
9279
  try {
8879
- const raw = readFileSync11(indexPath, "utf-8");
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 = join13(sessionsDir, `${entry.sessionId}.jsonl`);
9293
+ const sessionFile = join14(sessionsDir, `${entry.sessionId}.jsonl`);
8893
9294
  try {
8894
- if (existsSync7(sessionFile)) {
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 = join13(sessionsDir, `${parentSessionId}.jsonl`);
8915
- if (existsSync7(f)) {
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 (!existsSync7(jobsPath)) return;
9334
+ if (!existsSync8(jobsPath)) return;
8934
9335
  try {
8935
- const raw = readFileSync11(jobsPath, "utf-8");
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 (!existsSync7(dir)) return;
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 = join13(dir, f);
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 join13(homedir7(), ".augmented", "manager-claude-pids.json");
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 = join13(projectDir, ".mcp.json");
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 = join13(projectDir, "CLAUDE.md");
9761
+ const claudeMdPath = join14(projectDir, "CLAUDE.md");
9361
9762
  const serverNames = [];
9362
- if (existsSync7(mcpConfigPath)) {
9763
+ if (existsSync8(mcpConfigPath)) {
9363
9764
  try {
9364
- const d = JSON.parse(readFileSync11(mcpConfigPath, "utf-8"));
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 (existsSync7(claudeMdPath)) {
9784
+ if (existsSync8(claudeMdPath)) {
9384
9785
  claudeArgs.push("--system-prompt-file", claudeMdPath);
9385
9786
  }
9386
9787
  const childEnv = { ...process.env };
9387
- const envIntPath = join13(projectDir, ".env.integrations");
9388
- if (existsSync7(envIntPath)) {
9788
+ const envIntPath = join14(projectDir, ".env.integrations");
9789
+ if (existsSync8(envIntPath)) {
9389
9790
  try {
9390
- Object.assign(childEnv, parseEnvIntegrations(readFileSync11(envIntPath, "utf-8")));
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 = join13(projectDir, ".mcp.json");
9563
- const claudeMdPath = join13(projectDir, "CLAUDE.md");
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 = join13(projDir, ".mcp.json");
10609
+ const mcpConfigPath = join14(projDir, ".mcp.json");
10209
10610
  const serverNames = [];
10210
- if (existsSync7(mcpConfigPath)) {
10611
+ if (existsSync8(mcpConfigPath)) {
10211
10612
  try {
10212
- const d = JSON.parse(readFileSync11(mcpConfigPath, "utf-8"));
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 = join13(projDir, "CLAUDE.md");
10232
- if (existsSync7(chatClaudeMd)) {
10632
+ const chatClaudeMd = join14(projDir, "CLAUDE.md");
10633
+ if (existsSync8(chatClaudeMd)) {
10233
10634
  chatArgs.push("--system-prompt-file", chatClaudeMd);
10234
10635
  }
10235
- const envIntPath = join13(projDir, ".env.integrations");
10636
+ const envIntPath = join14(projDir, ".env.integrations");
10236
10637
  const childEnv = { ...process.env };
10237
- if (existsSync7(envIntPath)) {
10638
+ if (existsSync8(envIntPath)) {
10238
10639
  try {
10239
- Object.assign(childEnv, parseEnvIntegrations(readFileSync11(envIntPath, "utf-8")));
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
- lines.push("=====================");
10708
- lines.push("");
10709
- lines.push("Create today's plan. You may:");
10710
- lines.push('- Move backlog items to "todo"');
10711
- lines.push("- Add new items you've identified");
10712
- lines.push("- Reprioritise existing items");
10713
- lines.push("");
10714
- } else {
10715
- lines.push("=== YOUR KANBAN BOARD ===");
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
- const doneItems = grouped["done"];
10726
- if (doneItems && doneItems.length > 0) {
10727
- lines.push("DONE TODAY:");
10728
- doneItems.forEach((item, i) => {
10729
- lines.push(` ${i + 1}. ${item.title}`);
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
- lines.push("=========================");
10733
- lines.push("");
10734
- lines.push("IMPORTANT: Use kanban MCP tools to update the board IN REAL TIME:");
10735
- lines.push("1. FIRST call kanban_move to move your chosen item to in_progress BEFORE starting work");
10736
- lines.push("2. Do the work");
10737
- lines.push("3. Call kanban_done with a result summary when finished");
10738
- lines.push("4. If blocked, call kanban_update with notes, then pick the next item");
10739
- lines.push("");
10740
- if (hasSourceThread) {
10741
- lines.push("THREAD CONTINUITY: If a card shows a `source:` line, the request originated");
10742
- lines.push("in that Slack/Telegram thread. When you deliver the result, reply in that");
10743
- lines.push("thread (not the default channel) so the user sees the answer where they");
10744
- lines.push("asked. The card's `source_channel` + `source_thread_id` are the thread");
10745
- lines.push("coordinates to use.");
10746
- lines.push("");
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 (alertSlackWebhook) {
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 = join13(configDir, agent.code_name, "project");
11525
- const memoryDir = join13(projectDir, "memory");
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 (existsSync7(memoryDir)) {
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 = readFileSync11(join13(memoryDir, file), "utf-8");
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(readFileSync11(join13(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
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 = existsSync7(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
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 = join13(memoryDir, `${slug}.md`);
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 (existsSync7(filePath)) {
11627
+ if (existsSync8(filePath)) {
11613
11628
  let existing = "";
11614
11629
  try {
11615
- existing = readFileSync11(filePath, "utf-8");
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 (existsSync7(agentDir)) {
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 (existsSync7(stateFile)) {
11890
- const raw = readFileSync11(stateFile, "utf-8");
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=${join13(homedir7(), ".augmented", "manager.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 = readFileSync11(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
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 = join13(homedir7(), ".augmented", "_mcp");
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 = join13(dir, "dist", "mcp");
12049
- if (existsSync7(join13(candidate, "index.js"))) {
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 (!existsSync7(p)) return null;
12065
- return createHash4("sha256").update(readFileSync11(p)).digest("hex");
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 = join13(mcpSourceDir, file);
12093
- const dst = join13(targetDir, file);
12094
- if (!existsSync7(src)) continue;
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 = join13(targetDir, "index.js");
12126
+ const localMcpPath = join14(targetDir, "index.js");
12112
12127
  try {
12113
- const agentsDir = join13(homedir7(), ".augmented", "agents");
12114
- if (existsSync7(agentsDir)) {
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 = join13(agentsDir, entry.name, subdir, ".mcp.json");
12133
+ const mcpJsonPath = join14(agentsDir, entry.name, subdir, ".mcp.json");
12119
12134
  try {
12120
- const raw = readFileSync11(mcpJsonPath, "utf-8");
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,