@integrity-labs/agt-cli 0.11.0 → 0.12.2

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.
@@ -7,7 +7,7 @@ import {
7
7
  markTaskFired,
8
8
  saveSchedulerState,
9
9
  syncTasksToScheduler
10
- } from "./chunk-QU7FBXH3.js";
10
+ } from "./chunk-M6FSTVGG.js";
11
11
  export {
12
12
  computeNextFire,
13
13
  findTaskByTemplate,
@@ -18,4 +18,4 @@ export {
18
18
  saveSchedulerState,
19
19
  syncTasksToScheduler
20
20
  };
21
- //# sourceMappingURL=claude-scheduler-7PVWQHWU.js.map
21
+ //# sourceMappingURL=claude-scheduler-CLSMSF5W.js.map
@@ -1,16 +1,22 @@
1
1
  import {
2
2
  api,
3
+ appendDmFooter,
3
4
  exchangeApiKey,
4
5
  extractFrontmatter,
5
6
  getApiKey,
6
7
  getFramework,
7
8
  getHostId,
9
+ getIntegration,
10
+ isParseError,
11
+ isResolveError,
12
+ parseDeliveryTarget,
8
13
  provision,
9
14
  provisionIsolationHook,
10
15
  provisionStopHook,
11
16
  requireHost,
12
- resolveChannels
13
- } from "../chunk-WTMVQND4.js";
17
+ resolveChannels,
18
+ resolveDmTarget
19
+ } from "../chunk-S5VHDDAJ.js";
14
20
  import {
15
21
  findTaskByTemplate,
16
22
  getProjectDir,
@@ -18,7 +24,7 @@ import {
18
24
  loadSchedulerState,
19
25
  markTaskFired,
20
26
  syncTasksToScheduler
21
- } from "../chunk-QU7FBXH3.js";
27
+ } from "../chunk-M6FSTVGG.js";
22
28
  import {
23
29
  getProjectDir as getProjectDir2,
24
30
  injectMessage,
@@ -814,6 +820,91 @@ function resolveBrewPath(execFileSync) {
814
820
  if (existsSync(fallback)) return fallback;
815
821
  return null;
816
822
  }
823
+ var toolkitCliEnsured = /* @__PURE__ */ new Set();
824
+ var toolkitCliRetryAfter = /* @__PURE__ */ new Map();
825
+ var TOOLKIT_INSTALL_RETRY_MS = 5 * 6e4;
826
+ async function ensureToolkitCli(toolkitSlug) {
827
+ if (toolkitCliEnsured.has(toolkitSlug)) return;
828
+ const retryAfter = toolkitCliRetryAfter.get(toolkitSlug) ?? 0;
829
+ if (retryAfter > Date.now()) return;
830
+ const integration = getIntegration(toolkitSlug);
831
+ if (!integration?.cli_tool) {
832
+ toolkitCliEnsured.add(toolkitSlug);
833
+ return;
834
+ }
835
+ const { binary, installer, package: pkg, script } = integration.cli_tool;
836
+ const resolvedInstaller = installer ?? "manual";
837
+ const { execFileSync, execSync } = await import("child_process");
838
+ try {
839
+ execFileSync("which", [binary], { timeout: 5e3, stdio: "pipe" });
840
+ toolkitCliEnsured.add(toolkitSlug);
841
+ toolkitCliRetryAfter.delete(toolkitSlug);
842
+ return;
843
+ } catch {
844
+ }
845
+ if (resolvedInstaller === "manual") {
846
+ log(`[toolkit-install] ${toolkitSlug}: binary '${binary}' missing, installer=manual \u2014 operator must install`);
847
+ toolkitCliEnsured.add(toolkitSlug);
848
+ return;
849
+ }
850
+ let brewBinDir = null;
851
+ try {
852
+ if (resolvedInstaller === "npm") {
853
+ if (!pkg) {
854
+ log(`[toolkit-install] ${toolkitSlug}: installer=npm but no package declared`);
855
+ toolkitCliEnsured.add(toolkitSlug);
856
+ return;
857
+ }
858
+ log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
859
+ execFileSync("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
860
+ } else if (resolvedInstaller === "brew") {
861
+ if (!pkg) {
862
+ log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
863
+ toolkitCliEnsured.add(toolkitSlug);
864
+ return;
865
+ }
866
+ const brewPath = resolveBrewPath(execFileSync);
867
+ if (!brewPath) {
868
+ log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
869
+ toolkitCliEnsured.add(toolkitSlug);
870
+ return;
871
+ }
872
+ brewBinDir = dirname(brewPath);
873
+ const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
874
+ log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
875
+ if (isRoot) {
876
+ execFileSync("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe" });
877
+ } else {
878
+ execFileSync(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
879
+ }
880
+ } else if (resolvedInstaller === "script") {
881
+ if (!script) {
882
+ log(`[toolkit-install] ${toolkitSlug}: installer=script but no script declared`);
883
+ toolkitCliEnsured.add(toolkitSlug);
884
+ return;
885
+ }
886
+ log(`[toolkit-install] ${toolkitSlug}: running declared install script\u2026`);
887
+ execSync(script, { timeout: 18e4, stdio: "pipe" });
888
+ }
889
+ } catch (err) {
890
+ const msg = err.message.slice(0, 200);
891
+ log(`[toolkit-install] ${toolkitSlug}: installer=${resolvedInstaller} failed \u2014 ${msg} (retrying in ${TOOLKIT_INSTALL_RETRY_MS / 6e4}m)`);
892
+ toolkitCliRetryAfter.set(toolkitSlug, Date.now() + TOOLKIT_INSTALL_RETRY_MS);
893
+ return;
894
+ }
895
+ if (brewBinDir && !process.env.PATH?.split(":").includes(brewBinDir)) {
896
+ process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
897
+ }
898
+ try {
899
+ execFileSync("which", [binary], { timeout: 5e3, stdio: "pipe" });
900
+ log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
901
+ toolkitCliEnsured.add(toolkitSlug);
902
+ toolkitCliRetryAfter.delete(toolkitSlug);
903
+ } catch {
904
+ log(`[toolkit-install] ${toolkitSlug}: installer=${resolvedInstaller} completed but ${binary} still not on PATH (retrying in ${TOOLKIT_INSTALL_RETRY_MS / 6e4}m)`);
905
+ toolkitCliRetryAfter.set(toolkitSlug, Date.now() + TOOLKIT_INSTALL_RETRY_MS);
906
+ }
907
+ }
817
908
  async function ensureFrameworkBinary(frameworkId) {
818
909
  if (frameworkId !== "claude-code") return;
819
910
  if (frameworkBinaryChecked.has(frameworkId)) return;
@@ -1459,7 +1550,7 @@ async function pollCycle() {
1459
1550
  const adapter = resolveAgentFramework(prev.codeName);
1460
1551
  await stopGatewayIfRunning(prev.codeName, adapter);
1461
1552
  freePort(prev.codeName);
1462
- const agentDir = join2(config.configDir, prev.codeName, "provision");
1553
+ const agentDir = join2(adapter.getAgentDir(prev.codeName), "provision");
1463
1554
  await cleanupAgentFiles(prev.codeName, agentDir);
1464
1555
  clearAgentCaches(prev.agentId, prev.codeName);
1465
1556
  }
@@ -1527,8 +1618,8 @@ async function processAgent(agent, agentStates) {
1527
1618
  agentFrameworkCache.set(agent.code_name, agent.framework);
1528
1619
  }
1529
1620
  const now = (/* @__PURE__ */ new Date()).toISOString();
1530
- const agentDir = join2(config.configDir, agent.code_name, "provision");
1531
1621
  const adapter = resolveAgentFramework(agent.code_name);
1622
+ let agentDir = join2(adapter.getAgentDir(agent.code_name), "provision");
1532
1623
  if (agent.status === "draft" || agent.status === "paused") {
1533
1624
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
1534
1625
  await stopGatewayIfRunning(agent.code_name, adapter);
@@ -1652,6 +1743,8 @@ async function processAgent(agent, agentStates) {
1652
1743
  const frameworkId = refreshData.agent.framework ?? "openclaw";
1653
1744
  agentFrameworkCache.set(agent.code_name, frameworkId);
1654
1745
  const frameworkAdapter = getFramework(frameworkId);
1746
+ agentDir = join2(frameworkAdapter.getAgentDir(agent.code_name), "provision");
1747
+ cacheAgentDeliveryMetadata(agent.code_name, refreshData);
1655
1748
  if (frameworkAdapter.seedProfileConfig) {
1656
1749
  frameworkAdapter.seedProfileConfig(agent.code_name);
1657
1750
  }
@@ -1896,7 +1989,7 @@ async function processAgent(agent, agentStates) {
1896
1989
  const agentSessionMode = refreshData.agent.session_mode;
1897
1990
  if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? "openclaw") === "claude-code") {
1898
1991
  try {
1899
- const agentProvisionDir = join2(homedir2(), ".augmented", agent.code_name, "claudecode", "provision");
1992
+ const agentProvisionDir = agentDir;
1900
1993
  const projectDir = join2(homedir2(), ".augmented", agent.code_name, "project");
1901
1994
  mkdirSync(agentProvisionDir, { recursive: true });
1902
1995
  mkdirSync(projectDir, { recursive: true });
@@ -2025,8 +2118,7 @@ async function processAgent(agent, agentStates) {
2025
2118
  try {
2026
2119
  const { readFileSync: readFileSync2 } = await import("fs");
2027
2120
  const { join: join3 } = await import("path");
2028
- const { homedir: homedir3 } = await import("os");
2029
- const mcpPath = join3(homedir3(), ".augmented", "agents", agent.code_name, "provision", ".mcp.json");
2121
+ const mcpPath = join3(frameworkAdapter.getAgentDir(agent.code_name), "provision", ".mcp.json");
2030
2122
  const mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf-8"));
2031
2123
  if (mcpConfig.mcpServers) {
2032
2124
  const managedPrefixes = [
@@ -2228,6 +2320,16 @@ async function processAgent(agent, agentStates) {
2228
2320
  }
2229
2321
  }
2230
2322
  }
2323
+ const agentFwForToolkits = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
2324
+ if (agentFwForToolkits === "claude-code" && refreshData.plugin_toolkits?.length) {
2325
+ const toolkitUnion = /* @__PURE__ */ new Set();
2326
+ for (const { toolkits } of refreshData.plugin_toolkits) {
2327
+ for (const t of toolkits) toolkitUnion.add(t);
2328
+ }
2329
+ for (const toolkitSlug of toolkitUnion) {
2330
+ await ensureToolkitCli(toolkitSlug);
2331
+ }
2332
+ }
2231
2333
  const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
2232
2334
  if (agentFw2 === "claude-code" && installedPluginSkills.length > 0 && isSessionHealthy(agent.code_name)) {
2233
2335
  const names = installedPluginSkills.join(", ");
@@ -2754,7 +2856,8 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
2754
2856
  await processClaudeTaskResult(codeName, agentId, task.templateId, output, {
2755
2857
  mode: task.deliveryMode,
2756
2858
  channel: task.deliveryChannel,
2757
- to: task.deliveryTo
2859
+ to: task.deliveryTo,
2860
+ taskId: task.taskId
2758
2861
  });
2759
2862
  const updated = markTaskFired(codeName, task.taskId, "ok");
2760
2863
  claudeSchedulerStates.set(codeName, updated);
@@ -2814,8 +2917,14 @@ async function processClaudeTaskResult(codeName, agentId, templateId, output, de
2814
2917
  log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);
2815
2918
  }
2816
2919
  }
2817
- if (delivery?.mode === "announce" && delivery.channel && delivery.to && output) {
2818
- await sendTaskNotification(codeName, delivery.channel, delivery.to, output.slice(0, 4e3));
2920
+ if (delivery?.mode === "announce" && delivery.to && output) {
2921
+ await deliverScheduledTaskOutput(
2922
+ codeName,
2923
+ agentId,
2924
+ delivery.to,
2925
+ output.slice(0, 4e3),
2926
+ delivery.taskId
2927
+ );
2819
2928
  }
2820
2929
  } catch (err) {
2821
2930
  log(`[claude-scheduler] Failed to post result for '${codeName}': ${err.message}`);
@@ -3281,7 +3390,7 @@ async function processDirectChatMessage(agent, msg) {
3281
3390
  try {
3282
3391
  let reply;
3283
3392
  if (fw === "claude-code") {
3284
- const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-7PVWQHWU.js");
3393
+ const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-CLSMSF5W.js");
3285
3394
  const projDir = ccProjectDir(agent.codeName);
3286
3395
  const mcpConfigPath = join2(projDir, ".mcp.json");
3287
3396
  const serverNames = [];
@@ -3944,41 +4053,249 @@ ${a.detail}`;
3944
4053
 
3945
4054
  ${blocks.join("\n\n")}`);
3946
4055
  }
4056
+ async function deliverScheduledTaskOutput(agentCodeName, agentId, rawTarget, body, taskId) {
4057
+ if (typeof rawTarget === "string") {
4058
+ if (rawTarget.startsWith("channel:")) {
4059
+ const result = await sendTaskNotification(agentCodeName, "slack", rawTarget, body);
4060
+ await reportDeliveryStatus(agentId, taskId, {
4061
+ status: result.ok ? "ok" : "failed",
4062
+ medium: "slack",
4063
+ error_code: result.ok ? null : result.error_code ?? "SLACK_SEND_FAILED"
4064
+ });
4065
+ return;
4066
+ }
4067
+ if (rawTarget.startsWith("chat:")) {
4068
+ const result = await sendTaskNotification(agentCodeName, "telegram", rawTarget, body);
4069
+ await reportDeliveryStatus(agentId, taskId, {
4070
+ status: result.ok ? "ok" : "failed",
4071
+ medium: "telegram",
4072
+ error_code: result.ok ? null : result.error_code ?? "TELEGRAM_SEND_FAILED"
4073
+ });
4074
+ return;
4075
+ }
4076
+ log(`[delivery] Unrecognised legacy delivery_to string for '${agentCodeName}': ${rawTarget.slice(0, 60)}`);
4077
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: "LEGACY_DELIVERY_TO_UNRECOGNISED" });
4078
+ return;
4079
+ }
4080
+ const parsed = parseDeliveryTarget(rawTarget);
4081
+ if (isParseError(parsed)) {
4082
+ log(`[delivery] Malformed delivery_to for '${agentCodeName}': ${parsed.code} \u2014 ${parsed.detail}`);
4083
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: parsed.code });
4084
+ return;
4085
+ }
4086
+ if (parsed.kind === "channel") {
4087
+ if (parsed.provider === "slack") {
4088
+ const sent = await sendSlackChannelMessage(agentCodeName, parsed.channel_id ?? "", body);
4089
+ await reportDeliveryStatus(agentId, taskId, {
4090
+ status: sent ? "ok" : "failed",
4091
+ medium: "slack",
4092
+ error_code: sent ? null : "SLACK_SEND_FAILED"
4093
+ });
4094
+ return;
4095
+ }
4096
+ const toStr = `chat:${parsed.chat_id ?? ""}`;
4097
+ const result = await sendTaskNotification(agentCodeName, "telegram", toStr, body);
4098
+ await reportDeliveryStatus(agentId, taskId, {
4099
+ status: result.ok ? "ok" : "failed",
4100
+ medium: "telegram",
4101
+ error_code: result.ok ? null : result.error_code ?? "TELEGRAM_SEND_FAILED"
4102
+ });
4103
+ return;
4104
+ }
4105
+ const agentRow = agentInfoForDelivery.get(agentCodeName);
4106
+ if (!agentRow) {
4107
+ log(`[delivery] No agent metadata cached for '${agentCodeName}' \u2014 dropping DM`);
4108
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: "AGENT_METADATA_UNAVAILABLE" });
4109
+ return;
4110
+ }
4111
+ const resolved = resolveDmTarget(parsed, agentRow.resolverAgent, agentRow.peopleByPersonId);
4112
+ if (isResolveError(resolved)) {
4113
+ log(`[delivery] Cannot resolve DM target for '${agentCodeName}': ${resolved.code} \u2014 ${resolved.detail}`);
4114
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: resolved.code });
4115
+ return;
4116
+ }
4117
+ const footeredBody = appendDmFooter(
4118
+ body,
4119
+ agentRow.ownerTeamName,
4120
+ agentRow.agentDisplayName
4121
+ );
4122
+ if (resolved.kind === "dm" && resolved.medium === "slack") {
4123
+ const tokens = agentChannelTokens.get(agentCodeName);
4124
+ const botToken = tokens?.slack;
4125
+ if (!botToken) {
4126
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: "SLACK_MISSING_SCOPE", medium: "slack" });
4127
+ log(`[delivery] No Slack bot token for '${agentCodeName}' \u2014 DM dropped`);
4128
+ return;
4129
+ }
4130
+ try {
4131
+ const controller = new AbortController();
4132
+ const timeoutHandle = setTimeout(() => controller.abort(), 5e3);
4133
+ let openJson;
4134
+ try {
4135
+ const openResp = await fetch("https://slack.com/api/conversations.open", {
4136
+ method: "POST",
4137
+ headers: {
4138
+ Authorization: `Bearer ${botToken}`,
4139
+ "Content-Type": "application/json; charset=utf-8"
4140
+ },
4141
+ body: JSON.stringify({ users: resolved.slack_user_id }),
4142
+ signal: controller.signal
4143
+ });
4144
+ openJson = await openResp.json();
4145
+ } finally {
4146
+ clearTimeout(timeoutHandle);
4147
+ }
4148
+ if (!openJson.ok || !openJson.channel?.id) {
4149
+ const errCode = openJson.error === "missing_scope" ? "SLACK_MISSING_SCOPE" : `SLACK_OPEN_FAILED:${openJson.error ?? "unknown"}`;
4150
+ log(`[delivery] conversations.open failed for '${agentCodeName}': ${openJson.error}`);
4151
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: errCode, medium: "slack" });
4152
+ return;
4153
+ }
4154
+ const sent = await sendSlackChannelMessage(agentCodeName, openJson.channel.id, footeredBody);
4155
+ await reportDeliveryStatus(agentId, taskId, {
4156
+ status: sent ? "ok" : "failed",
4157
+ medium: "slack",
4158
+ error_code: sent ? null : "SLACK_SEND_FAILED"
4159
+ });
4160
+ } catch (err) {
4161
+ const isAbort = err.name === "AbortError";
4162
+ const errCode = isAbort ? "SLACK_OPEN_TIMEOUT" : "SLACK_EXCEPTION";
4163
+ log(`[delivery] Slack DM ${isAbort ? "timeout" : "failure"} for '${agentCodeName}': ${err.message}`);
4164
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: errCode, medium: "slack" });
4165
+ }
4166
+ return;
4167
+ }
4168
+ if (resolved.kind === "dm" && resolved.medium === "telegram") {
4169
+ const tokens = agentChannelTokens.get(agentCodeName);
4170
+ const botToken = tokens?.telegram;
4171
+ if (!botToken) {
4172
+ log(`[delivery] No Telegram bot token for '${agentCodeName}' \u2014 DM dropped`);
4173
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: "TELEGRAM_NO_TOKEN", medium: "telegram" });
4174
+ return;
4175
+ }
4176
+ try {
4177
+ const result = await telegramApiCall(botToken, "sendMessage", {
4178
+ chat_id: resolved.telegram_chat_id,
4179
+ text: footeredBody
4180
+ });
4181
+ if (!result.ok) {
4182
+ log(`[delivery] Telegram DM failed for '${agentCodeName}': ${result.description}`);
4183
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: `TELEGRAM_SEND_FAILED:${result.description ?? "unknown"}`, medium: "telegram" });
4184
+ return;
4185
+ }
4186
+ await reportDeliveryStatus(agentId, taskId, { status: "ok", medium: "telegram" });
4187
+ } catch (err) {
4188
+ log(`[delivery] Telegram DM exception for '${agentCodeName}': ${err.message}`);
4189
+ await reportDeliveryStatus(agentId, taskId, { status: "failed", error_code: "TELEGRAM_EXCEPTION", medium: "telegram" });
4190
+ }
4191
+ }
4192
+ }
4193
+ var agentInfoForDelivery = /* @__PURE__ */ new Map();
4194
+ function cacheAgentDeliveryMetadata(codeName, refreshData) {
4195
+ const agentRow = refreshData["agent"] ?? {};
4196
+ const displayName = agentRow["display_name"] ?? codeName;
4197
+ const ownerTeamName = agentRow["owner_team_name"] ?? null;
4198
+ const framework = agentRow["framework"] ?? "openclaw";
4199
+ const reportsToPersonId = agentRow["reports_to"] ?? null;
4200
+ const reportsToType = agentRow["reports_to_type"] ?? null;
4201
+ const channelConfigs = refreshData["channel_configs"] ?? {};
4202
+ const dmCapable = [];
4203
+ const slackBotToken = channelConfigs["slack"]?.config?.["bot_token"];
4204
+ if (typeof slackBotToken === "string" && slackBotToken.length > 0) {
4205
+ dmCapable.push("slack");
4206
+ }
4207
+ const telegramBotToken = channelConfigs["telegram"]?.config?.["bot_token"];
4208
+ if (typeof telegramBotToken === "string" && telegramBotToken.length > 0) {
4209
+ dmCapable.push("telegram");
4210
+ }
4211
+ const resolverAgent = {
4212
+ agent_id: agentRow["agent_id"] ?? "",
4213
+ framework,
4214
+ dm_capable_mediums: dmCapable,
4215
+ reports_to_person_id: reportsToType === "person" ? reportsToPersonId : null,
4216
+ reports_to_type: reportsToType
4217
+ };
4218
+ const peopleByPersonId = /* @__PURE__ */ new Map();
4219
+ if (reportsToPersonId && reportsToType === "person") {
4220
+ peopleByPersonId.set(reportsToPersonId, {
4221
+ person_id: reportsToPersonId,
4222
+ display_name: agentRow["reports_to_name"] ?? "Reports-To",
4223
+ slack_user_id: agentRow["reports_to_slack_user_id"] ?? null,
4224
+ telegram_chat_id: agentRow["reports_to_telegram_chat_id"] ?? null
4225
+ });
4226
+ }
4227
+ const people = refreshData["people"] ?? [];
4228
+ for (const p of people) {
4229
+ const personId = p["person_id"];
4230
+ if (!personId) continue;
4231
+ const contactPrefs = p["contact_preferences"] ?? {};
4232
+ peopleByPersonId.set(personId, {
4233
+ person_id: personId,
4234
+ display_name: p["display_name"] ?? "person",
4235
+ slack_user_id: contactPrefs["slack_user_id"] ?? null,
4236
+ telegram_chat_id: contactPrefs["telegram_chat_id"] ?? null
4237
+ });
4238
+ }
4239
+ agentInfoForDelivery.set(codeName, {
4240
+ agentDisplayName: displayName,
4241
+ ownerTeamName,
4242
+ resolverAgent,
4243
+ peopleByPersonId
4244
+ });
4245
+ }
4246
+ async function reportDeliveryStatus(agentId, taskId, payload) {
4247
+ if (!taskId) return;
4248
+ try {
4249
+ await api.post("/host/schedules/delivery-status", {
4250
+ agent_id: agentId,
4251
+ task_id: taskId,
4252
+ status: payload.status,
4253
+ medium: payload.medium ?? null,
4254
+ error_code: payload.error_code ?? null
4255
+ });
4256
+ } catch (err) {
4257
+ log(`[delivery] Failed to report delivery status for ${agentId}/${taskId}: ${err.message}`);
4258
+ }
4259
+ }
3947
4260
  async function sendTaskNotification(agentCodeName, channel, to, text) {
3948
4261
  const tokens = agentChannelTokens.get(agentCodeName);
3949
4262
  if (channel === "slack") {
3950
4263
  const botToken = tokens?.slack;
3951
4264
  const channelId = to.replace(/^channel:/, "");
3952
- if (botToken) {
3953
- const sent = await sendSlackChannelMessage(agentCodeName, channelId, text);
3954
- if (sent) return;
4265
+ if (!botToken) {
4266
+ log(`No Slack bot token for '${agentCodeName}' \u2014 targeted notification dropped`);
4267
+ return { ok: false, error_code: "SLACK_NO_TOKEN" };
3955
4268
  }
3956
- log(`No Slack bot token for '${agentCodeName}' \u2014 targeted notification dropped`);
3957
- } else if (channel === "telegram") {
4269
+ const sent = await sendSlackChannelMessage(agentCodeName, channelId, text);
4270
+ return sent ? { ok: true } : { ok: false, error_code: "SLACK_SEND_FAILED" };
4271
+ }
4272
+ if (channel === "telegram") {
3958
4273
  const botToken = tokens?.telegram;
3959
4274
  const chatId = to.replace(/^chat:/, "");
3960
4275
  if (!botToken) {
3961
4276
  log(`No Telegram bot token for '${agentCodeName}' \u2014 notification dropped`);
3962
- return;
4277
+ return { ok: false, error_code: "TELEGRAM_NO_TOKEN" };
3963
4278
  }
3964
4279
  const allowedChats = tokens?.telegramAllowedChats;
3965
4280
  if (allowedChats && allowedChats.length > 0 && !allowedChats.includes(chatId)) {
3966
4281
  log(`Telegram chat ${chatId} not in allowed_chat_ids for '${agentCodeName}'`);
3967
- return;
4282
+ return { ok: false, error_code: "TELEGRAM_CHAT_NOT_ALLOWED" };
3968
4283
  }
3969
4284
  try {
3970
4285
  const result = await telegramApiCall(botToken, "sendMessage", { chat_id: chatId, text });
3971
4286
  if (!result.ok) {
3972
4287
  log(`Telegram sendMessage failed for '${agentCodeName}': ${result.description}`);
3973
- } else {
3974
- log(`Telegram notification sent for '${agentCodeName}' to chat ${chatId}`);
4288
+ return { ok: false, error_code: `TELEGRAM_SEND_FAILED:${result.description ?? "unknown"}` };
3975
4289
  }
4290
+ log(`Telegram notification sent for '${agentCodeName}' to chat ${chatId}`);
4291
+ return { ok: true };
3976
4292
  } catch (err) {
3977
4293
  log(`Telegram API error for '${agentCodeName}': ${err.message}`);
4294
+ return { ok: false, error_code: "TELEGRAM_EXCEPTION" };
3978
4295
  }
3979
- } else {
3980
- log(`Unknown notify_channel '${channel}' for '${agentCodeName}'`);
3981
4296
  }
4297
+ log(`Unknown notify_channel '${channel}' for '${agentCodeName}'`);
4298
+ return { ok: false, error_code: `UNKNOWN_CHANNEL:${channel}` };
3982
4299
  }
3983
4300
  function generateArtifacts(agent, refreshData, adapter) {
3984
4301
  if (!refreshData.charter || !refreshData.tools) {