@integrity-labs/agt-cli 0.10.1 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +237 -3
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-N7TRKQMT.js → chunk-6YGOQRAR.js} +658 -25
- package/dist/chunk-6YGOQRAR.js.map +1 -0
- package/dist/lib/manager-worker.js +406 -100
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/direct-chat-channel.js +13985 -0
- package/mcp/index.js +181 -0
- package/mcp/slack-channel.js +30 -3
- package/package.json +2 -2
- package/dist/chunk-N7TRKQMT.js.map +0 -1
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
getFramework,
|
|
7
7
|
getHostId,
|
|
8
8
|
provision,
|
|
9
|
+
provisionIsolationHook,
|
|
9
10
|
provisionStopHook,
|
|
10
11
|
requireHost,
|
|
11
12
|
resolveChannels
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-6YGOQRAR.js";
|
|
13
14
|
import {
|
|
14
15
|
findTaskByTemplate,
|
|
15
16
|
getProjectDir,
|
|
@@ -37,6 +38,38 @@ import { join, dirname } from "path";
|
|
|
37
38
|
import { homedir } from "os";
|
|
38
39
|
import { fileURLToPath } from "url";
|
|
39
40
|
|
|
41
|
+
// src/lib/plugin-context-render.ts
|
|
42
|
+
var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
43
|
+
var TEAM_OVERRIDES_HEADER = "## Team Overrides\n\n> **The following overrides anything you've read above.** If any rule here conflicts with earlier instructions in this skill, follow what is written here. These are user-supplied directives that take precedence over the plugin's default guidance.\n";
|
|
44
|
+
function formatContextValue(value) {
|
|
45
|
+
if (value === null || value === void 0) return "";
|
|
46
|
+
if (typeof value === "string") return value;
|
|
47
|
+
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
} catch {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function renderPluginSkillContent(raw, values, overrides, warn = () => {
|
|
55
|
+
}) {
|
|
56
|
+
const substituted = raw.replace(PLUGIN_CONTEXT_PLACEHOLDER_RE, (_match, fieldName) => {
|
|
57
|
+
if (!(fieldName in values)) {
|
|
58
|
+
warn(`unresolved placeholder {{context.${fieldName}}} \u2014 substituting empty string`);
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
return formatContextValue(values[fieldName]);
|
|
62
|
+
});
|
|
63
|
+
const trimmedOverrides = overrides.trim();
|
|
64
|
+
if (!trimmedOverrides) return substituted;
|
|
65
|
+
const separator = substituted.endsWith("\n") ? "\n" : "\n\n";
|
|
66
|
+
return `${substituted}${separator}---
|
|
67
|
+
|
|
68
|
+
${TEAM_OVERRIDES_HEADER}
|
|
69
|
+
${trimmedOverrides}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
40
73
|
// src/lib/gateway-client.ts
|
|
41
74
|
import { EventEmitter } from "events";
|
|
42
75
|
import WebSocket from "ws";
|
|
@@ -314,6 +347,7 @@ var driftChannel = null;
|
|
|
314
347
|
var assignChannel = null;
|
|
315
348
|
var configChannel = null;
|
|
316
349
|
var kanbanChannel = null;
|
|
350
|
+
var pluginContextChannel = null;
|
|
317
351
|
var connected = false;
|
|
318
352
|
var tearingDown = false;
|
|
319
353
|
function ensureClient(config2) {
|
|
@@ -358,6 +392,7 @@ function startRealtimeChat(config2) {
|
|
|
358
392
|
assignChannel = null;
|
|
359
393
|
configChannel = null;
|
|
360
394
|
kanbanChannel = null;
|
|
395
|
+
pluginContextChannel = null;
|
|
361
396
|
if (client) {
|
|
362
397
|
try {
|
|
363
398
|
client.removeAllChannels();
|
|
@@ -489,6 +524,38 @@ function startRealtimeKanban(config2) {
|
|
|
489
524
|
});
|
|
490
525
|
log2(`[realtime] Subscribing to agent_kanban_items for ${agentIds.length} agent(s)`);
|
|
491
526
|
}
|
|
527
|
+
function startRealtimePluginContext(config2) {
|
|
528
|
+
const { agentIds, onContextChange, log: log2 } = config2;
|
|
529
|
+
if (agentIds.length === 0) return;
|
|
530
|
+
const sb = ensureClient(config2);
|
|
531
|
+
const filterStr = agentIds.length === 1 ? `agent_id=eq.${agentIds[0]}` : `agent_id=in.(${agentIds.join(",")})`;
|
|
532
|
+
pluginContextChannel = sb.channel("plugin-context-realtime").on("postgres_changes", {
|
|
533
|
+
event: "INSERT",
|
|
534
|
+
schema: "public",
|
|
535
|
+
table: "plugin_context",
|
|
536
|
+
filter: filterStr
|
|
537
|
+
}, (payload) => {
|
|
538
|
+
const row = payload.new;
|
|
539
|
+
log2(`[realtime] plugin_context INSERT for agent ${row.agent_id} (plugin ${row.plugin_id})`);
|
|
540
|
+
onContextChange(row);
|
|
541
|
+
}).on("postgres_changes", {
|
|
542
|
+
event: "UPDATE",
|
|
543
|
+
schema: "public",
|
|
544
|
+
table: "plugin_context",
|
|
545
|
+
filter: filterStr
|
|
546
|
+
}, (payload) => {
|
|
547
|
+
const row = payload.new;
|
|
548
|
+
log2(`[realtime] plugin_context UPDATE for agent ${row.agent_id} (plugin ${row.plugin_id})`);
|
|
549
|
+
onContextChange(row);
|
|
550
|
+
}).subscribe((status) => {
|
|
551
|
+
if (status === "SUBSCRIBED") {
|
|
552
|
+
log2("[realtime] Plugin context channel connected");
|
|
553
|
+
} else if (status === "CLOSED" || status === "CHANNEL_ERROR") {
|
|
554
|
+
log2(`[realtime] Plugin context channel: ${status}`);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
log2(`[realtime] Subscribing to plugin_context for ${agentIds.length} agent(s)`);
|
|
558
|
+
}
|
|
492
559
|
function isRealtimeConnected() {
|
|
493
560
|
return connected;
|
|
494
561
|
}
|
|
@@ -531,6 +598,13 @@ function stopRealtimeChat() {
|
|
|
531
598
|
}
|
|
532
599
|
kanbanChannel = null;
|
|
533
600
|
}
|
|
601
|
+
if (pluginContextChannel) {
|
|
602
|
+
try {
|
|
603
|
+
pluginContextChannel.unsubscribe();
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
pluginContextChannel = null;
|
|
607
|
+
}
|
|
534
608
|
if (client) {
|
|
535
609
|
try {
|
|
536
610
|
client.removeAllChannels();
|
|
@@ -818,6 +892,76 @@ function hashFile(filePath) {
|
|
|
818
892
|
return null;
|
|
819
893
|
}
|
|
820
894
|
}
|
|
895
|
+
var SKILLS_INDEX_START = "<!-- AGT:SKILLS_INDEX_START -->";
|
|
896
|
+
var SKILLS_INDEX_END = "<!-- AGT:SKILLS_INDEX_END -->";
|
|
897
|
+
function sanitizeSkillsIndexText(value) {
|
|
898
|
+
return value.replaceAll(SKILLS_INDEX_START, "").replaceAll(SKILLS_INDEX_END, "").replace(/<!--[\s\S]*?-->/g, "").replace(/\r?\n+/g, " ").trim();
|
|
899
|
+
}
|
|
900
|
+
function parseSkillFrontmatter(content) {
|
|
901
|
+
if (!content.startsWith("---")) return {};
|
|
902
|
+
const end = content.indexOf("\n---", 3);
|
|
903
|
+
if (end === -1) return {};
|
|
904
|
+
const block = content.slice(3, end);
|
|
905
|
+
const out = {};
|
|
906
|
+
for (const line of block.split("\n")) {
|
|
907
|
+
const m = line.match(/^(name|description)\s*:\s*(.*)$/);
|
|
908
|
+
if (m && m[1] && m[2] !== void 0) out[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
909
|
+
}
|
|
910
|
+
return out;
|
|
911
|
+
}
|
|
912
|
+
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
913
|
+
const { readdirSync: readdirSync2, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync2 } = await import("fs");
|
|
914
|
+
const skillsDir = join(configDir, codeName, "project", ".claude", "skills");
|
|
915
|
+
const claudeMdPath = join(configDir, codeName, "project", "CLAUDE.md");
|
|
916
|
+
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
917
|
+
const entries = [];
|
|
918
|
+
for (const dir of readdirSync2(skillsDir).sort()) {
|
|
919
|
+
const skillFile = join(skillsDir, dir, "SKILL.md");
|
|
920
|
+
if (!ex(skillFile)) continue;
|
|
921
|
+
try {
|
|
922
|
+
const { name, description } = parseSkillFrontmatter(rfs(skillFile, "utf-8"));
|
|
923
|
+
entries.push({
|
|
924
|
+
id: dir,
|
|
925
|
+
name: sanitizeSkillsIndexText(name ?? dir),
|
|
926
|
+
description: sanitizeSkillsIndexText(description ?? "(no description)")
|
|
927
|
+
});
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const body = entries.length ? entries.map((e) => `- **${e.name}** \u2014 ${e.description}`).join("\n") : "_(no skills installed)_";
|
|
932
|
+
const section = `${SKILLS_INDEX_START}
|
|
933
|
+
## Available Skills
|
|
934
|
+
|
|
935
|
+
The following skills are installed in \`.claude/skills/\`. Claude Code auto-activates them when relevant \u2014 you don't need to read them manually, but you should know they exist.
|
|
936
|
+
|
|
937
|
+
${body}
|
|
938
|
+
|
|
939
|
+
## Updating Plugins (ENG-4341)
|
|
940
|
+
|
|
941
|
+
Plugin skills under \`.claude/skills/plugin-*/SKILL.md\` are **read-only** and managed by the platform. They are derived from the plugin's database row plus any per-agent context overrides, and are re-rendered every time the manager polls or a context change is broadcast over Supabase Realtime.
|
|
942
|
+
|
|
943
|
+
**Never edit \`.claude/skills/plugin-*/SKILL.md\` files directly.** If you do, your edit will be silently overwritten on the next manager refresh, AND it won't propagate to other agents using the same plugin.
|
|
944
|
+
|
|
945
|
+
To change a plugin's behavior (add a rule, update a default, tell the plugin not to do something), call the **\`plugin.improve\`** MCP tool with the user's request. The tool calls the platform API, which uses an LLM to translate the request into a structured update of the plugin's typed context fields and/or freeform overrides text. You'll get a diff back to show the user; on confirmation, call the tool again with \`auto_apply: true\` to apply.
|
|
946
|
+
|
|
947
|
+
Examples of when to use \`plugin.improve\`:
|
|
948
|
+
- *"Update the Coding plugin so we always use trunk instead of main"*
|
|
949
|
+
- *"Add a rule to the Coding plugin: never merge directly to main, always open a PR"*
|
|
950
|
+
- *"Tell the Knowledge Base plugin not to return results below 70% relevance"*
|
|
951
|
+
- *"The Slack plugin should post incidents to #oncall, not #general"*
|
|
952
|
+
${SKILLS_INDEX_END}`;
|
|
953
|
+
const current = rfs(claudeMdPath, "utf-8");
|
|
954
|
+
let next;
|
|
955
|
+
if (current.includes(SKILLS_INDEX_START) && current.includes(SKILLS_INDEX_END)) {
|
|
956
|
+
next = current.replace(new RegExp(`${SKILLS_INDEX_START}[\\s\\S]*?${SKILLS_INDEX_END}`), section);
|
|
957
|
+
} else {
|
|
958
|
+
next = current.trimEnd() + "\n\n" + section + "\n";
|
|
959
|
+
}
|
|
960
|
+
if (next !== current) {
|
|
961
|
+
writeFileSync2(claudeMdPath, next, "utf-8");
|
|
962
|
+
log2(`Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
821
965
|
async function migrateToProfiles() {
|
|
822
966
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
823
967
|
const sharedConfigPath = join(homeDir, ".openclaw", "openclaw.json");
|
|
@@ -1186,6 +1330,7 @@ async function pollCycle() {
|
|
|
1186
1330
|
ensureRealtimeAssignStarted(agentStates);
|
|
1187
1331
|
ensureRealtimeConfigStarted(agentStates);
|
|
1188
1332
|
ensureRealtimeKanbanStarted(agentStates);
|
|
1333
|
+
ensureRealtimePluginContextStarted(agentStates);
|
|
1189
1334
|
try {
|
|
1190
1335
|
const spawnData = await api.post("/host/kanban/recurring/spawn");
|
|
1191
1336
|
if (spawnData.spawned > 0) {
|
|
@@ -1396,7 +1541,23 @@ async function processAgent(agent, agentStates) {
|
|
|
1396
1541
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
1397
1542
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
1398
1543
|
for (const file of changedFiles) {
|
|
1399
|
-
|
|
1544
|
+
const filePath = join(agentDir, file.relativePath);
|
|
1545
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
1546
|
+
writeFileSync(filePath, file.content);
|
|
1547
|
+
}
|
|
1548
|
+
try {
|
|
1549
|
+
const provSkillsDir = join(agentDir, ".claude", "skills");
|
|
1550
|
+
if (existsSync(provSkillsDir)) {
|
|
1551
|
+
for (const folder of readdirSync(provSkillsDir)) {
|
|
1552
|
+
if (folder.startsWith("knowledge-")) {
|
|
1553
|
+
try {
|
|
1554
|
+
rmSync(join(provSkillsDir, folder), { recursive: true });
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} catch {
|
|
1400
1561
|
}
|
|
1401
1562
|
lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1402
1563
|
knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
|
|
@@ -1527,6 +1688,36 @@ async function processAgent(agent, agentStates) {
|
|
|
1527
1688
|
}
|
|
1528
1689
|
}
|
|
1529
1690
|
}
|
|
1691
|
+
const agentSessionMode = refreshData.agent.session_mode;
|
|
1692
|
+
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? "openclaw") === "claude-code") {
|
|
1693
|
+
try {
|
|
1694
|
+
const projectDir = join(homedir(), ".augmented", agent.code_name, "project");
|
|
1695
|
+
mkdirSync(projectDir, { recursive: true });
|
|
1696
|
+
const channelsPath = join(projectDir, ".mcp-channels.json");
|
|
1697
|
+
let channelsMcp = { mcpServers: {} };
|
|
1698
|
+
try {
|
|
1699
|
+
channelsMcp = JSON.parse(readFileSync(channelsPath, "utf-8"));
|
|
1700
|
+
if (!channelsMcp.mcpServers) channelsMcp.mcpServers = {};
|
|
1701
|
+
} catch {
|
|
1702
|
+
}
|
|
1703
|
+
const localDirectChatChannel = join(homedir(), ".augmented", "_mcp", "direct-chat-channel.js");
|
|
1704
|
+
if (existsSync(localDirectChatChannel) && !channelsMcp.mcpServers["direct-chat"]) {
|
|
1705
|
+
channelsMcp.mcpServers["direct-chat"] = {
|
|
1706
|
+
command: "node",
|
|
1707
|
+
args: [localDirectChatChannel],
|
|
1708
|
+
env: {
|
|
1709
|
+
AGT_HOST: requireHost(),
|
|
1710
|
+
AGT_API_KEY: getApiKey() ?? "",
|
|
1711
|
+
AGT_AGENT_ID: agent.agent_id
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
writeFileSync(channelsPath, JSON.stringify(channelsMcp, null, 2));
|
|
1715
|
+
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
1716
|
+
}
|
|
1717
|
+
} catch (err) {
|
|
1718
|
+
log(`Failed to provision direct-chat channel for '${agent.code_name}': ${err.message}`);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1530
1721
|
let lastSecretsProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
|
|
1531
1722
|
let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;
|
|
1532
1723
|
try {
|
|
@@ -1604,7 +1795,7 @@ async function processAgent(agent, agentStates) {
|
|
|
1604
1795
|
const expectedServerIds = /* @__PURE__ */ new Set();
|
|
1605
1796
|
for (const tk of toolkitData.toolkits) {
|
|
1606
1797
|
if (tk.agent_id !== agent.agent_id) continue;
|
|
1607
|
-
const serverId = tk.toolkit_id.replace(
|
|
1798
|
+
const serverId = tk.toolkit_id.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
1608
1799
|
expectedServerIds.add(serverId);
|
|
1609
1800
|
const mcpUrl = tk.mcp_url;
|
|
1610
1801
|
const mcpHeaders = tk.mcp_headers;
|
|
@@ -1621,7 +1812,19 @@ async function processAgent(agent, agentStates) {
|
|
|
1621
1812
|
const mcpPath = join2(homedir2(), ".augmented", "agents", agent.code_name, "provision", ".mcp.json");
|
|
1622
1813
|
const mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf-8"));
|
|
1623
1814
|
if (mcpConfig.mcpServers) {
|
|
1624
|
-
const managedPrefixes = [
|
|
1815
|
+
const managedPrefixes = [
|
|
1816
|
+
"composio_",
|
|
1817
|
+
"one_",
|
|
1818
|
+
"pipedream_",
|
|
1819
|
+
"nango_",
|
|
1820
|
+
"paragon_",
|
|
1821
|
+
// Legacy hyphenated format
|
|
1822
|
+
"composio-",
|
|
1823
|
+
"one-",
|
|
1824
|
+
"pipedream-",
|
|
1825
|
+
"nango-",
|
|
1826
|
+
"paragon-"
|
|
1827
|
+
];
|
|
1625
1828
|
for (const key of Object.keys(mcpConfig.mcpServers)) {
|
|
1626
1829
|
if (managedPrefixes.some((p) => key.startsWith(p)) && !expectedServerIds.has(key)) {
|
|
1627
1830
|
frameworkAdapter.removeMcpServer(agent.code_name, key);
|
|
@@ -1657,93 +1860,6 @@ async function processAgent(agent, agentStates) {
|
|
|
1657
1860
|
}, delay);
|
|
1658
1861
|
}
|
|
1659
1862
|
}
|
|
1660
|
-
const resolvedDefIds = new Set(integrations.map((i) => i.definition_id));
|
|
1661
|
-
for (const capId of resolvedDefIds) {
|
|
1662
|
-
try {
|
|
1663
|
-
const capData = await api.post("/host/capability-skill", { definition_id: capId });
|
|
1664
|
-
const allSatisfied = capData.requiredIntegrations.every((reqId) => resolvedDefIds.has(reqId));
|
|
1665
|
-
if (!allSatisfied) {
|
|
1666
|
-
const missing = capData.requiredIntegrations.filter((reqId) => !resolvedDefIds.has(reqId));
|
|
1667
|
-
log(`Capability '${capId}' skipped \u2014 missing integration(s): ${missing.join(", ")}`);
|
|
1668
|
-
continue;
|
|
1669
|
-
}
|
|
1670
|
-
const skillContent = JSON.stringify(capData.skills.map((s) => s.files.map((f) => `${f.relativePath}:${f.content}`)));
|
|
1671
|
-
const skillHash = createHash("sha256").update(skillContent).digest("hex").slice(0, 16);
|
|
1672
|
-
const skillKey = `${agent.agent_id}:${capId}`;
|
|
1673
|
-
const prevSkillHash = knownSkillHashes.get(skillKey);
|
|
1674
|
-
if (skillHash !== prevSkillHash) {
|
|
1675
|
-
const installedSkillNames = [];
|
|
1676
|
-
if (frameworkAdapter.installSkillFiles && capData.skills) {
|
|
1677
|
-
for (const skill of capData.skills) {
|
|
1678
|
-
if (skill.files.length > 0) {
|
|
1679
|
-
frameworkAdapter.installSkillFiles(agent.code_name, skill.id, skill.files);
|
|
1680
|
-
installedSkillNames.push(skill.name || skill.id);
|
|
1681
|
-
log(`Installed skill '${skill.id}' for '${agent.code_name}' (${skill.files.length} file(s))`);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
knownSkillHashes.set(skillKey, skillHash);
|
|
1686
|
-
const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
1687
|
-
if (agentFw2 === "claude-code" && installedSkillNames.length > 0 && isSessionHealthy(agent.code_name)) {
|
|
1688
|
-
const names = installedSkillNames.join(", ");
|
|
1689
|
-
injectMessage(
|
|
1690
|
-
agent.code_name,
|
|
1691
|
-
"system",
|
|
1692
|
-
`New skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
|
|
1693
|
-
{ task_name: "skill-update" },
|
|
1694
|
-
log
|
|
1695
|
-
).catch(() => {
|
|
1696
|
-
});
|
|
1697
|
-
log(`[hot-reload] Notified '${agent.code_name}' about new skills: ${names}`);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
const ALLOWED_CLI_PACKAGES = /* @__PURE__ */ new Set([
|
|
1701
|
-
"xero-cli",
|
|
1702
|
-
"@openapitools/openapi-generator-cli",
|
|
1703
|
-
"gh",
|
|
1704
|
-
"@schpet/linear-cli",
|
|
1705
|
-
"@googleworkspace/cli",
|
|
1706
|
-
"@tobilu/qmd"
|
|
1707
|
-
]);
|
|
1708
|
-
if (intHash !== prevIntHash) {
|
|
1709
|
-
const { execFileSync } = await import("child_process");
|
|
1710
|
-
for (const tool of capData.cliTools) {
|
|
1711
|
-
if (!ALLOWED_CLI_PACKAGES.has(tool.package)) {
|
|
1712
|
-
log(`Skipping CLI tool '${tool.package}' for '${agent.code_name}' \u2014 not on the allowed packages list`);
|
|
1713
|
-
continue;
|
|
1714
|
-
}
|
|
1715
|
-
try {
|
|
1716
|
-
execFileSync("which", [tool.binary], { stdio: "ignore" });
|
|
1717
|
-
} catch {
|
|
1718
|
-
log(`Installing CLI tool '${tool.package}' for '${agent.code_name}'...`);
|
|
1719
|
-
try {
|
|
1720
|
-
execFileSync("npm", ["install", "-g", tool.package], { stdio: "ignore", timeout: 6e4 });
|
|
1721
|
-
log(`CLI tool '${tool.binary}' installed successfully`);
|
|
1722
|
-
} catch (installErr) {
|
|
1723
|
-
log(`Failed to install CLI tool '${tool.package}': ${installErr.message}`);
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
if (tool.binary === "qmd") {
|
|
1727
|
-
try {
|
|
1728
|
-
const agentDir2 = join(process.env["HOME"] ?? "/tmp", ".augmented", agent.code_name);
|
|
1729
|
-
execFileSync("qmd", ["collection", "add", agent.code_name, "project"], {
|
|
1730
|
-
stdio: "ignore",
|
|
1731
|
-
timeout: 3e4,
|
|
1732
|
-
cwd: agentDir2
|
|
1733
|
-
});
|
|
1734
|
-
log(`QMD collection '${agent.code_name}' configured`);
|
|
1735
|
-
} catch {
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
} catch (skillErr) {
|
|
1741
|
-
const msg = skillErr.message ?? "";
|
|
1742
|
-
if (!msg.includes("404") && !msg.includes("Unknown capability")) {
|
|
1743
|
-
log(`Capability fetch failed for '${capId}': ${msg}`);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
1863
|
}
|
|
1748
1864
|
} catch (err) {
|
|
1749
1865
|
log(`Integration provisioning failed for '${agent.code_name}': ${err.message}`);
|
|
@@ -1808,6 +1924,107 @@ async function processAgent(agent, agentStates) {
|
|
|
1808
1924
|
log(`Kanban skill install failed for '${agent.code_name}': ${err.message}`);
|
|
1809
1925
|
}
|
|
1810
1926
|
}
|
|
1927
|
+
if (frameworkAdapter.installSkillFiles) {
|
|
1928
|
+
const currentPluginSkillIds = /* @__PURE__ */ new Set();
|
|
1929
|
+
const installedPluginSkills = [];
|
|
1930
|
+
const { createHash: createHash2 } = await import("crypto");
|
|
1931
|
+
const contextBySlug = /* @__PURE__ */ new Map();
|
|
1932
|
+
for (const ctx of refreshData.plugin_contexts ?? []) {
|
|
1933
|
+
contextBySlug.set(ctx.plugin_slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? "").trim() });
|
|
1934
|
+
}
|
|
1935
|
+
for (const ps of refreshData.plugin_skills ?? []) {
|
|
1936
|
+
try {
|
|
1937
|
+
const skillId = `plugin-${ps.plugin_slug}-${ps.skill_id}`.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1938
|
+
currentPluginSkillIds.add(skillId);
|
|
1939
|
+
const ctx = contextBySlug.get(ps.plugin_slug);
|
|
1940
|
+
const renderedContent = renderPluginSkillContent(
|
|
1941
|
+
ps.content,
|
|
1942
|
+
ctx?.values ?? {},
|
|
1943
|
+
ctx?.overrides ?? "",
|
|
1944
|
+
(warning) => log(`[plugin-context] ${ps.plugin_slug}/${ps.skill_id}: ${warning}`)
|
|
1945
|
+
);
|
|
1946
|
+
const contentHash = createHash2("sha256").update(renderedContent).digest("hex").slice(0, 12);
|
|
1947
|
+
const hashKey = `plugin-skill:${agent.code_name}:${skillId}`;
|
|
1948
|
+
if (knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
1949
|
+
frameworkAdapter.installSkillFiles(agent.code_name, skillId, [
|
|
1950
|
+
{ relativePath: "SKILL.md", content: renderedContent }
|
|
1951
|
+
]);
|
|
1952
|
+
knownSkillHashes.set(hashKey, contentHash);
|
|
1953
|
+
installedPluginSkills.push(ps.skill_name);
|
|
1954
|
+
log(`Installed plugin skill '${skillId}' for '${agent.code_name}'`);
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
log(`Plugin skill install failed for '${agent.code_name}' / '${ps.skill_id}': ${err.message}`);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
try {
|
|
1960
|
+
const agentSkillsDir = join(config.configDir, agent.code_name, "project", ".claude", "skills");
|
|
1961
|
+
if (existsSync(agentSkillsDir)) {
|
|
1962
|
+
const { readdirSync: readdirSync2, rmSync: rmSync2 } = await import("fs");
|
|
1963
|
+
for (const entry of readdirSync2(agentSkillsDir)) {
|
|
1964
|
+
if (entry.startsWith("plugin-") && !currentPluginSkillIds.has(entry)) {
|
|
1965
|
+
const orphanPath = join(agentSkillsDir, entry);
|
|
1966
|
+
rmSync2(orphanPath, { recursive: true, force: true });
|
|
1967
|
+
log(`Removed orphaned plugin skill '${entry}' for '${agent.code_name}'`);
|
|
1968
|
+
const provisionSkillPath = join(config.configDir, agent.code_name, "skills", entry);
|
|
1969
|
+
if (existsSync(provisionSkillPath)) {
|
|
1970
|
+
rmSync2(provisionSkillPath, { recursive: true, force: true });
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
log(`Plugin skill cleanup failed for '${agent.code_name}': ${err.message}`);
|
|
1977
|
+
}
|
|
1978
|
+
try {
|
|
1979
|
+
const agentFwForIndex = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
1980
|
+
if (agentFwForIndex === "claude-code") {
|
|
1981
|
+
await refreshSkillsIndexInClaudeMd(config.configDir, agent.code_name, log);
|
|
1982
|
+
}
|
|
1983
|
+
} catch (err) {
|
|
1984
|
+
log(`Skills index refresh failed for '${agent.code_name}': ${err.message}`);
|
|
1985
|
+
}
|
|
1986
|
+
if (frameworkAdapter.executePluginHook && refreshData.plugin_install_hooks?.length) {
|
|
1987
|
+
for (const hook of refreshData.plugin_install_hooks) {
|
|
1988
|
+
try {
|
|
1989
|
+
const scriptHash = createHash2("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
1990
|
+
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${hook.plugin_slug}:on_install`;
|
|
1991
|
+
if (knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
1992
|
+
const result = await frameworkAdapter.executePluginHook({
|
|
1993
|
+
codeName: agent.code_name,
|
|
1994
|
+
pluginSlug: hook.plugin_slug,
|
|
1995
|
+
hookName: "on_install",
|
|
1996
|
+
script: hook.script
|
|
1997
|
+
});
|
|
1998
|
+
if (result.exitCode === 0) {
|
|
1999
|
+
knownSkillHashes.set(hookKey, scriptHash);
|
|
2000
|
+
log(`Plugin hook on_install '${hook.plugin_slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);
|
|
2001
|
+
} else if (result.timedOut) {
|
|
2002
|
+
log(`Plugin hook on_install '${hook.plugin_slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
2003
|
+
} else {
|
|
2004
|
+
const stderrHash = createHash2("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
2005
|
+
log(
|
|
2006
|
+
`Plugin hook on_install '${hook.plugin_slug}' exited ${result.exitCode} for '${agent.code_name}' [stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
log(`Plugin hook on_install failed for '${agent.code_name}' / '${hook.plugin_slug}': ${err.message}`);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
2015
|
+
if (agentFw2 === "claude-code" && installedPluginSkills.length > 0 && isSessionHealthy(agent.code_name)) {
|
|
2016
|
+
const names = installedPluginSkills.join(", ");
|
|
2017
|
+
injectMessage(
|
|
2018
|
+
agent.code_name,
|
|
2019
|
+
"system",
|
|
2020
|
+
`New plugin skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
|
|
2021
|
+
{ task_name: "plugin-skill-update" },
|
|
2022
|
+
log
|
|
2023
|
+
).catch(() => {
|
|
2024
|
+
});
|
|
2025
|
+
log(`[hot-reload] Notified '${agent.code_name}' about new plugin skills: ${names}`);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1811
2028
|
}
|
|
1812
2029
|
let boardItems = [];
|
|
1813
2030
|
const hasBoardTemplates = tasks.some((t) => BOARD_INJECT_TEMPLATES.has(t.template_id));
|
|
@@ -2291,6 +2508,7 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
2291
2508
|
} catch {
|
|
2292
2509
|
}
|
|
2293
2510
|
}
|
|
2511
|
+
delete childEnv.ANTHROPIC_API_KEY;
|
|
2294
2512
|
const { stdout, stderr } = await execFilePromiseLong("claude", claudeArgs, {
|
|
2295
2513
|
cwd: projectDir,
|
|
2296
2514
|
timeout: 3e5,
|
|
@@ -2318,6 +2536,14 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
2318
2536
|
} catch (err) {
|
|
2319
2537
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2320
2538
|
log(`[claude-scheduler] Task '${task.name}' failed for '${codeName}': ${errMsg}`);
|
|
2539
|
+
if (err instanceof ChildProcessError) {
|
|
2540
|
+
if (err.stdout.trim()) {
|
|
2541
|
+
log(`[claude-scheduler] Task '${task.name}' stdout for '${codeName}': ${err.stdout.trim().slice(0, 1e3)}`);
|
|
2542
|
+
}
|
|
2543
|
+
if (err.stderr.trim()) {
|
|
2544
|
+
log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${err.stderr.trim().slice(0, 1e3)}`);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2321
2547
|
const updated = markTaskFired(codeName, task.taskId, "error");
|
|
2322
2548
|
claudeSchedulerStates.set(codeName, updated);
|
|
2323
2549
|
}
|
|
@@ -2408,6 +2634,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
2408
2634
|
devChannels.push("server:slack");
|
|
2409
2635
|
}
|
|
2410
2636
|
}
|
|
2637
|
+
devChannels.push("server:direct-chat");
|
|
2411
2638
|
if (!agentRuntimeAuthenticated) {
|
|
2412
2639
|
const { execFileSync } = await import("child_process");
|
|
2413
2640
|
agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
|
|
@@ -2425,6 +2652,11 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
2425
2652
|
} catch (err) {
|
|
2426
2653
|
log(`[persistent-session] Failed to provision Stop hook for '${codeName}': ${err.message}`);
|
|
2427
2654
|
}
|
|
2655
|
+
try {
|
|
2656
|
+
provisionIsolationHook(codeName);
|
|
2657
|
+
} catch (err) {
|
|
2658
|
+
log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${err.message}`);
|
|
2659
|
+
}
|
|
2428
2660
|
startPersistentSession({
|
|
2429
2661
|
codeName,
|
|
2430
2662
|
agentId: agent.agent_id,
|
|
@@ -2514,6 +2746,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
2514
2746
|
var realtimeStarted = false;
|
|
2515
2747
|
var realtimeDriftStarted = false;
|
|
2516
2748
|
var realtimeKanbanStarted = false;
|
|
2749
|
+
var realtimePluginContextStarted = false;
|
|
2517
2750
|
var realtimeAssignStarted = false;
|
|
2518
2751
|
var realtimeConfigStarted = false;
|
|
2519
2752
|
var realtimeSubscribedAgentIds = /* @__PURE__ */ new Set();
|
|
@@ -2532,6 +2765,7 @@ function ensureRealtimeStarted(agentStates) {
|
|
|
2532
2765
|
realtimeAssignStarted = false;
|
|
2533
2766
|
realtimeConfigStarted = false;
|
|
2534
2767
|
realtimeKanbanStarted = false;
|
|
2768
|
+
realtimePluginContextStarted = false;
|
|
2535
2769
|
}
|
|
2536
2770
|
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|
|
2537
2771
|
if (activeAgentIds.length === 0) return;
|
|
@@ -2698,6 +2932,51 @@ function ensureRealtimeKanbanStarted(agentStates) {
|
|
|
2698
2932
|
log(`[realtime] Kanban subscription failed: ${err.message}`);
|
|
2699
2933
|
});
|
|
2700
2934
|
}
|
|
2935
|
+
function ensureRealtimePluginContextStarted(agentStates) {
|
|
2936
|
+
if (realtimePluginContextStarted) return;
|
|
2937
|
+
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|
|
2938
|
+
if (activeAgentIds.length === 0) return;
|
|
2939
|
+
const apiKey = process.env["AGT_API_KEY"];
|
|
2940
|
+
if (!apiKey) return;
|
|
2941
|
+
void exchangeApiKey(apiKey).then((exchange) => {
|
|
2942
|
+
if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;
|
|
2943
|
+
startRealtimePluginContext({
|
|
2944
|
+
supabaseUrl: exchange.supabaseUrl,
|
|
2945
|
+
supabaseAnonKey: exchange.supabaseAnonKey,
|
|
2946
|
+
token: exchange.token,
|
|
2947
|
+
agentIds: activeAgentIds,
|
|
2948
|
+
onContextChange: (payload) => {
|
|
2949
|
+
const agent = agentStates.find((a) => a.agentId === payload.agent_id);
|
|
2950
|
+
if (agent) {
|
|
2951
|
+
for (const key of knownSkillHashes.keys()) {
|
|
2952
|
+
if (key.startsWith(`plugin-skill:${agent.codeName}:`)) {
|
|
2953
|
+
knownSkillHashes.delete(key);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
triggerEarlyPoll(`plugin context changed for agent ${payload.agent_id}`);
|
|
2958
|
+
},
|
|
2959
|
+
log
|
|
2960
|
+
});
|
|
2961
|
+
realtimePluginContextStarted = true;
|
|
2962
|
+
log(`[realtime] Plugin context subscription started for ${activeAgentIds.length} agent(s)`);
|
|
2963
|
+
}).catch((err) => {
|
|
2964
|
+
log(`[realtime] Plugin context subscription failed: ${err.message}`);
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
function triggerEarlyPoll(reason) {
|
|
2968
|
+
if (!running) return;
|
|
2969
|
+
if (pollTimer) {
|
|
2970
|
+
clearTimeout(pollTimer);
|
|
2971
|
+
pollTimer = null;
|
|
2972
|
+
}
|
|
2973
|
+
log(`[realtime] Triggering early poll: ${reason}`);
|
|
2974
|
+
pollTimer = setTimeout(() => {
|
|
2975
|
+
void pollCycle().then(() => {
|
|
2976
|
+
scheduleNext();
|
|
2977
|
+
});
|
|
2978
|
+
}, 0);
|
|
2979
|
+
}
|
|
2701
2980
|
var directChatInFlight = /* @__PURE__ */ new Set();
|
|
2702
2981
|
async function pollDirectChatMessages(agentStates) {
|
|
2703
2982
|
for (const agent of agentStates) {
|
|
@@ -2768,6 +3047,7 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
2768
3047
|
} catch {
|
|
2769
3048
|
}
|
|
2770
3049
|
}
|
|
3050
|
+
delete childEnv.ANTHROPIC_API_KEY;
|
|
2771
3051
|
const { stdout } = await execFilePromiseLong("claude", chatArgs, { cwd: projDir, stdin: "ignore", env: childEnv });
|
|
2772
3052
|
reply = stdout.trim() || "[No response from agent]";
|
|
2773
3053
|
} else {
|
|
@@ -2801,7 +3081,7 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
2801
3081
|
} catch (err) {
|
|
2802
3082
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2803
3083
|
const errorId = createHash("sha256").update(errMsg).digest("hex").slice(0, 12);
|
|
2804
|
-
log(`[direct-chat] Failed to process message for '${agent.codeName}': error_id=${errorId}`);
|
|
3084
|
+
log(`[direct-chat] Failed to process message for '${agent.codeName}': error_id=${errorId} error=${errMsg.slice(0, 500)}`);
|
|
2805
3085
|
try {
|
|
2806
3086
|
await api.post("/host/direct-chat/reply", {
|
|
2807
3087
|
agent_id: agent.agentId,
|
|
@@ -3166,6 +3446,21 @@ async function execFilePromise(cmd, args) {
|
|
|
3166
3446
|
});
|
|
3167
3447
|
});
|
|
3168
3448
|
}
|
|
3449
|
+
var ChildProcessError = class extends Error {
|
|
3450
|
+
code;
|
|
3451
|
+
stdout;
|
|
3452
|
+
stderr;
|
|
3453
|
+
constructor(code, stdout, stderr) {
|
|
3454
|
+
const stderrSnippet = stderr.trim().slice(0, 500);
|
|
3455
|
+
const stdoutSnippet = stdout.trim().slice(0, 500);
|
|
3456
|
+
const detail = stderrSnippet || stdoutSnippet || "(no output)";
|
|
3457
|
+
super(`Exit code ${code}: ${detail}`);
|
|
3458
|
+
this.name = "ChildProcessError";
|
|
3459
|
+
this.code = code;
|
|
3460
|
+
this.stdout = stdout;
|
|
3461
|
+
this.stderr = stderr;
|
|
3462
|
+
}
|
|
3463
|
+
};
|
|
3169
3464
|
async function execFilePromiseLong(cmd, args, opts) {
|
|
3170
3465
|
const { spawn: sp } = await import("child_process");
|
|
3171
3466
|
return new Promise((resolve, reject) => {
|
|
@@ -3188,7 +3483,7 @@ async function execFilePromiseLong(cmd, args, opts) {
|
|
|
3188
3483
|
}, opts?.timeout ?? 12e4);
|
|
3189
3484
|
child.on("close", (code) => {
|
|
3190
3485
|
clearTimeout(timer);
|
|
3191
|
-
if (code !== 0) reject(new
|
|
3486
|
+
if (code !== 0) reject(new ChildProcessError(code, stdout, stderr));
|
|
3192
3487
|
else resolve({ stdout, stderr });
|
|
3193
3488
|
});
|
|
3194
3489
|
child.on("error", (err) => {
|
|
@@ -3445,8 +3740,9 @@ function generateArtifacts(agent, refreshData, adapter) {
|
|
|
3445
3740
|
const teamTimezoneRaw = refreshData.team?.timezone;
|
|
3446
3741
|
const teamTimezone = typeof teamTimezoneRaw === "string" && teamTimezoneRaw.trim().length > 0 ? teamTimezoneRaw.trim() : void 0;
|
|
3447
3742
|
const agentTimezone = (taskTimezones.length === 1 ? taskTimezones[0] : void 0) ?? teamTimezone ?? "UTC";
|
|
3743
|
+
const agentPersonalitySeed = refreshData.agent.personality_seed;
|
|
3448
3744
|
const orgDefaults = refreshData.model_defaults;
|
|
3449
|
-
const personalitySeed = orgDefaults?.org?.settings?.personality_seed;
|
|
3745
|
+
const personalitySeed = agentPersonalitySeed || orgDefaults?.org?.settings?.personality_seed;
|
|
3450
3746
|
let reportsTo;
|
|
3451
3747
|
const agentData = refreshData.agent;
|
|
3452
3748
|
const reportsToId = agentData.reports_to;
|
|
@@ -3476,7 +3772,8 @@ function generateArtifacts(agent, refreshData, adapter) {
|
|
|
3476
3772
|
team: refreshData.team ?? void 0,
|
|
3477
3773
|
timezone: agentTimezone,
|
|
3478
3774
|
reportsTo,
|
|
3479
|
-
personalitySeed
|
|
3775
|
+
personalitySeed,
|
|
3776
|
+
knowledge: (refreshData.knowledge ?? []).filter((k) => !!k.content)
|
|
3480
3777
|
};
|
|
3481
3778
|
const provisionOutput = provision(provisionInput, adapter.id);
|
|
3482
3779
|
return provisionOutput.artifacts;
|
|
@@ -3573,7 +3870,8 @@ function startPolling() {
|
|
|
3573
3870
|
void startCaffeinate();
|
|
3574
3871
|
log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);
|
|
3575
3872
|
checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${err.message}`));
|
|
3576
|
-
void
|
|
3873
|
+
void killAllAgtTmuxSessions().catch(() => {
|
|
3874
|
+
}).then(() => migrateToProfiles()).then(() => {
|
|
3577
3875
|
startGatewayPool();
|
|
3578
3876
|
return pollCycle();
|
|
3579
3877
|
}).then(() => {
|
|
@@ -3617,18 +3915,19 @@ async function stopPolling() {
|
|
|
3617
3915
|
pollTimer = null;
|
|
3618
3916
|
}
|
|
3619
3917
|
const shutdownTimer = setTimeout(() => {
|
|
3620
|
-
log("Shutdown timeout exceeded (
|
|
3918
|
+
log("Shutdown timeout exceeded (15s), forcing exit");
|
|
3621
3919
|
process.exit(1);
|
|
3622
|
-
},
|
|
3920
|
+
}, 15e3);
|
|
3623
3921
|
shutdownTimer.unref();
|
|
3624
3922
|
stopCaffeinate();
|
|
3625
3923
|
stopRealtimeChat();
|
|
3626
3924
|
stopGatewayPool();
|
|
3925
|
+
log("Killing tmux sessions...");
|
|
3926
|
+
await killAllAgtTmuxSessions();
|
|
3627
3927
|
log("Stopping persistent sessions...");
|
|
3628
3928
|
await stopAllSessionsAndWait(log, { timeoutMs: 4e3 });
|
|
3629
3929
|
log("Stopping gateway processes...");
|
|
3630
3930
|
await stopAllGateways();
|
|
3631
|
-
await killAllAgtTmuxSessions();
|
|
3632
3931
|
clearTimeout(shutdownTimer);
|
|
3633
3932
|
}
|
|
3634
3933
|
function startManager(opts) {
|
|
@@ -3656,7 +3955,7 @@ function deployMcpAssets() {
|
|
|
3656
3955
|
log("[manager] MCP assets not found in CLI package \u2014 skipping deployment");
|
|
3657
3956
|
return;
|
|
3658
3957
|
}
|
|
3659
|
-
for (const file of ["index.js", "slack-channel.js"]) {
|
|
3958
|
+
for (const file of ["index.js", "slack-channel.js", "direct-chat-channel.js"]) {
|
|
3660
3959
|
const src = join(mcpSourceDir, file);
|
|
3661
3960
|
const dst = join(targetDir, file);
|
|
3662
3961
|
if (!existsSync(src)) continue;
|
|
@@ -3697,8 +3996,14 @@ function deployMcpAssets() {
|
|
|
3697
3996
|
async function stopManager() {
|
|
3698
3997
|
await stopPolling();
|
|
3699
3998
|
}
|
|
3999
|
+
var shuttingDown = false;
|
|
3700
4000
|
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
3701
4001
|
process.on(sig, () => {
|
|
4002
|
+
if (shuttingDown) {
|
|
4003
|
+
log(`Received ${sig} again during shutdown \u2014 ignoring (cleanup in progress)`);
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
shuttingDown = true;
|
|
3702
4007
|
log(`Received ${sig}, shutting down`);
|
|
3703
4008
|
void stopPolling().then(() => {
|
|
3704
4009
|
process.exit(0);
|
|
@@ -3712,6 +4017,7 @@ process.on("disconnect", () => {
|
|
|
3712
4017
|
});
|
|
3713
4018
|
});
|
|
3714
4019
|
export {
|
|
4020
|
+
ChildProcessError,
|
|
3715
4021
|
startManager,
|
|
3716
4022
|
stopManager
|
|
3717
4023
|
};
|