@integrity-labs/agt-cli 0.15.38 → 0.16.1
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 +60 -51
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-XJSQKRFV.js → chunk-ITLXAEXI.js} +117 -11
- package/dist/chunk-ITLXAEXI.js.map +1 -0
- package/dist/{chunk-AFUG4KD3.js → chunk-QYG5LUTP.js} +204 -39
- package/dist/chunk-QYG5LUTP.js.map +1 -0
- package/dist/{claude-pair-runtime-GLO2D7WP.js → claude-pair-runtime-FEUHTZXZ.js} +42 -3
- package/dist/claude-pair-runtime-FEUHTZXZ.js.map +1 -0
- package/dist/lib/manager-worker.js +205 -110
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-VRS3MFQ3.js → persistent-session-RZWMTEZY.js} +4 -2
- package/mcp/slack-channel.js +99 -22
- package/package.json +1 -1
- package/dist/chunk-AFUG4KD3.js.map +0 -1
- package/dist/chunk-XJSQKRFV.js.map +0 -1
- package/dist/claude-pair-runtime-GLO2D7WP.js.map +0 -1
- /package/dist/{persistent-session-VRS3MFQ3.js.map → persistent-session-RZWMTEZY.js.map} +0 -0
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveChannels,
|
|
23
23
|
resolveDmTarget,
|
|
24
24
|
wrapScheduledTaskPrompt
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-ITLXAEXI.js";
|
|
26
26
|
import {
|
|
27
27
|
findTaskByTemplate,
|
|
28
28
|
getProjectDir,
|
|
@@ -35,14 +35,17 @@ import {
|
|
|
35
35
|
buildAllowedTools,
|
|
36
36
|
getProjectDir as getProjectDir2,
|
|
37
37
|
injectMessage,
|
|
38
|
+
isAgentIdle,
|
|
38
39
|
isSessionHealthy,
|
|
40
|
+
isStaleForToday,
|
|
41
|
+
peekCurrentSession,
|
|
39
42
|
resetRestartCount,
|
|
40
43
|
resolveClaudeBinary,
|
|
41
44
|
sanitizeMcpJson,
|
|
42
45
|
startPersistentSession,
|
|
43
46
|
stopAllSessionsAndWait,
|
|
44
47
|
stopPersistentSession
|
|
45
|
-
} from "../chunk-
|
|
48
|
+
} from "../chunk-QYG5LUTP.js";
|
|
46
49
|
|
|
47
50
|
// src/lib/manager-worker.ts
|
|
48
51
|
import { createHash } from "crypto";
|
|
@@ -53,7 +56,7 @@ import { join as join3, dirname } from "path";
|
|
|
53
56
|
import { homedir as homedir3 } from "os";
|
|
54
57
|
import { fileURLToPath } from "url";
|
|
55
58
|
|
|
56
|
-
// src/lib/
|
|
59
|
+
// src/lib/integration-context-render.ts
|
|
57
60
|
var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
58
61
|
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";
|
|
59
62
|
function formatContextValue(value) {
|
|
@@ -66,7 +69,7 @@ function formatContextValue(value) {
|
|
|
66
69
|
return "";
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
|
-
function
|
|
72
|
+
function renderIntegrationSkillContent(raw, values, overrides, warn = () => {
|
|
70
73
|
}) {
|
|
71
74
|
const substituted = raw.replace(PLUGIN_CONTEXT_PLACEHOLDER_RE, (_match, fieldName) => {
|
|
72
75
|
if (!(fieldName in values)) {
|
|
@@ -85,7 +88,7 @@ ${trimmedOverrides}
|
|
|
85
88
|
`;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
// src/lib/
|
|
91
|
+
// src/lib/integration-skill-layout.ts
|
|
89
92
|
function extractDescription(content) {
|
|
90
93
|
const match = content.match(/^---\s*([\s\S]*?)---/);
|
|
91
94
|
if (!match) return null;
|
|
@@ -98,20 +101,20 @@ function extractDescription(content) {
|
|
|
98
101
|
}
|
|
99
102
|
return descMatch[2]?.trim() ?? null;
|
|
100
103
|
}
|
|
101
|
-
function sanitizeScopeSlug(skillId,
|
|
104
|
+
function sanitizeScopeSlug(skillId, integrationSlug) {
|
|
102
105
|
const normalized = skillId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
103
|
-
const prefix =
|
|
106
|
+
const prefix = integrationSlug.replace(/-/g, "");
|
|
104
107
|
const stripped = normalized.startsWith(`${prefix}-`) ? normalized.slice(prefix.length + 1) : normalized;
|
|
105
108
|
return stripped.replace(/^-|-$/g, "") || normalized || "scope";
|
|
106
109
|
}
|
|
107
|
-
function
|
|
110
|
+
function buildIntegrationBundle(skills) {
|
|
108
111
|
if (skills.length === 0) {
|
|
109
|
-
throw new Error("
|
|
112
|
+
throw new Error("buildIntegrationBundle: empty skills list");
|
|
110
113
|
}
|
|
111
|
-
const
|
|
114
|
+
const integrationSlug = skills[0].plugin_slug;
|
|
112
115
|
const ordered = [...skills].sort((a, b) => a.skill_id.localeCompare(b.skill_id));
|
|
113
116
|
const entries = ordered.map((s) => {
|
|
114
|
-
const scopeSlug = sanitizeScopeSlug(s.skill_id,
|
|
117
|
+
const scopeSlug = sanitizeScopeSlug(s.skill_id, integrationSlug);
|
|
115
118
|
const description = extractDescription(s.content);
|
|
116
119
|
return {
|
|
117
120
|
skillId: s.skill_id,
|
|
@@ -132,11 +135,11 @@ function buildPluginBundle(skills) {
|
|
|
132
135
|
if (group.length > 1) {
|
|
133
136
|
const conflicts = group.map((e) => `${e.skillId} (${e.skillName})`).join(", ");
|
|
134
137
|
throw new Error(
|
|
135
|
-
`
|
|
138
|
+
`buildIntegrationBundle: duplicate scope path '${scopePath}' for integration '${integrationSlug}' \u2014 conflicting skills: ${conflicts}`
|
|
136
139
|
);
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
|
-
const
|
|
142
|
+
const integrationName = slugToTitle(integrationSlug);
|
|
140
143
|
const umbrellaDescription = entries.map((e) => e.description ?? `Use for ${e.skillName}.`).join(" ");
|
|
141
144
|
const scopeList = entries.map((e) => {
|
|
142
145
|
const label = e.skillName || slugToTitle(e.scopeSlug);
|
|
@@ -145,13 +148,13 @@ function buildPluginBundle(skills) {
|
|
|
145
148
|
}).join("\n");
|
|
146
149
|
const umbrella = [
|
|
147
150
|
"---",
|
|
148
|
-
`name: "${
|
|
151
|
+
`name: "${integrationName}"`,
|
|
149
152
|
`description: "${escapeYamlDouble(umbrellaDescription)}"`,
|
|
150
153
|
"---",
|
|
151
154
|
"",
|
|
152
|
-
`# ${
|
|
155
|
+
`# ${integrationName}`,
|
|
153
156
|
"",
|
|
154
|
-
`This skill bundles ${entries.length === 1 ? "one scope" : `${entries.length} scopes`} for the ${
|
|
157
|
+
`This skill bundles ${entries.length === 1 ? "one scope" : `${entries.length} scopes`} for the ${integrationSlug} integration. Each scope has a dedicated reference under \`scopes/\` that you should load on demand when the user's intent maps to it.`,
|
|
155
158
|
"",
|
|
156
159
|
"## Scopes",
|
|
157
160
|
"",
|
|
@@ -170,7 +173,7 @@ function buildPluginBundle(skills) {
|
|
|
170
173
|
}))
|
|
171
174
|
];
|
|
172
175
|
return {
|
|
173
|
-
|
|
176
|
+
integrationSlug,
|
|
174
177
|
files,
|
|
175
178
|
scopePaths: entries.map((e) => e.scopePath)
|
|
176
179
|
};
|
|
@@ -181,7 +184,7 @@ function slugToTitle(slug) {
|
|
|
181
184
|
function escapeYamlDouble(value) {
|
|
182
185
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
183
186
|
}
|
|
184
|
-
function
|
|
187
|
+
function groupSkillsByIntegration(skills) {
|
|
185
188
|
const map = /* @__PURE__ */ new Map();
|
|
186
189
|
for (const s of skills) {
|
|
187
190
|
const bucket = map.get(s.plugin_slug);
|
|
@@ -794,6 +797,44 @@ async function sweepChannelProcesses(opts) {
|
|
|
794
797
|
dryRun
|
|
795
798
|
};
|
|
796
799
|
}
|
|
800
|
+
function pickTeardownTargets(processes, codeName) {
|
|
801
|
+
return processes.filter((p) => p.codeName === codeName);
|
|
802
|
+
}
|
|
803
|
+
function defaultKillSignal(pid, signal) {
|
|
804
|
+
try {
|
|
805
|
+
process.kill(pid, signal);
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function defaultPs() {
|
|
810
|
+
return execFileSync("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
|
|
811
|
+
encoding: "utf-8",
|
|
812
|
+
timeout: 5e3,
|
|
813
|
+
maxBuffer: 10 * 1024 * 1024
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
function killAgentChannelProcesses(codeName, opts) {
|
|
817
|
+
const { log: log2, dryRun = false } = opts;
|
|
818
|
+
const kill = opts.killFn ?? defaultKillSignal;
|
|
819
|
+
const ps = opts.psFn ?? defaultPs;
|
|
820
|
+
let psOutput = "";
|
|
821
|
+
try {
|
|
822
|
+
psOutput = ps();
|
|
823
|
+
} catch (err) {
|
|
824
|
+
log2(`[channel-teardown] ps failed for '${codeName}': ${err.message}`);
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
const targets = pickTeardownTargets(parsePsOutput(psOutput), codeName);
|
|
828
|
+
if (targets.length === 0) return [];
|
|
829
|
+
const pids = targets.map((t) => t.pid);
|
|
830
|
+
log2(
|
|
831
|
+
`[channel-teardown]${dryRun ? "[dry-run]" : ""} de-provision '${codeName}': killing ${targets.length} channel MCP(s) \u2014 ` + targets.map((t) => `${t.channelType}#${t.pid}`).join(", ")
|
|
832
|
+
);
|
|
833
|
+
if (!dryRun) {
|
|
834
|
+
for (const pid of pids) kill(pid, "SIGTERM");
|
|
835
|
+
}
|
|
836
|
+
return pids;
|
|
837
|
+
}
|
|
797
838
|
|
|
798
839
|
// src/lib/delivery-hint.ts
|
|
799
840
|
var DEFAULT_PROBABILITY = 0.1;
|
|
@@ -1033,7 +1074,7 @@ var driftChannel = null;
|
|
|
1033
1074
|
var assignChannel = null;
|
|
1034
1075
|
var configChannel = null;
|
|
1035
1076
|
var kanbanChannel = null;
|
|
1036
|
-
var
|
|
1077
|
+
var integrationContextChannel = null;
|
|
1037
1078
|
var connected = false;
|
|
1038
1079
|
var tearingDown = false;
|
|
1039
1080
|
function ensureClient(config2) {
|
|
@@ -1078,7 +1119,7 @@ function startRealtimeChat(config2) {
|
|
|
1078
1119
|
assignChannel = null;
|
|
1079
1120
|
configChannel = null;
|
|
1080
1121
|
kanbanChannel = null;
|
|
1081
|
-
|
|
1122
|
+
integrationContextChannel = null;
|
|
1082
1123
|
if (client) {
|
|
1083
1124
|
try {
|
|
1084
1125
|
client.removeAllChannels();
|
|
@@ -1251,12 +1292,12 @@ function startRealtimeKanban(config2) {
|
|
|
1251
1292
|
log2(`[realtime] Subscribing to agent_kanban_items for ${agentIds.length} agent(s)`);
|
|
1252
1293
|
void formatActorId;
|
|
1253
1294
|
}
|
|
1254
|
-
function
|
|
1295
|
+
function startRealtimeIntegrationContext(config2) {
|
|
1255
1296
|
const { agentIds, onContextChange, log: log2 } = config2;
|
|
1256
1297
|
if (agentIds.length === 0) return;
|
|
1257
1298
|
const sb = ensureClient(config2);
|
|
1258
1299
|
const filterStr = agentIds.length === 1 ? `agent_id=eq.${agentIds[0]}` : `agent_id=in.(${agentIds.join(",")})`;
|
|
1259
|
-
|
|
1300
|
+
integrationContextChannel = sb.channel("plugin-context-realtime").on("postgres_changes", {
|
|
1260
1301
|
event: "INSERT",
|
|
1261
1302
|
schema: "public",
|
|
1262
1303
|
table: "plugin_context",
|
|
@@ -1276,9 +1317,9 @@ function startRealtimePluginContext(config2) {
|
|
|
1276
1317
|
onContextChange(row);
|
|
1277
1318
|
}).subscribe((status) => {
|
|
1278
1319
|
if (status === "SUBSCRIBED") {
|
|
1279
|
-
log2("[realtime]
|
|
1320
|
+
log2("[realtime] Integration context channel connected");
|
|
1280
1321
|
} else if (status === "CLOSED" || status === "CHANNEL_ERROR") {
|
|
1281
|
-
log2(`[realtime]
|
|
1322
|
+
log2(`[realtime] Integration context channel: ${status}`);
|
|
1282
1323
|
}
|
|
1283
1324
|
});
|
|
1284
1325
|
log2(`[realtime] Subscribing to plugin_context for ${agentIds.length} agent(s)`);
|
|
@@ -1325,12 +1366,12 @@ function stopRealtimeChat() {
|
|
|
1325
1366
|
}
|
|
1326
1367
|
kanbanChannel = null;
|
|
1327
1368
|
}
|
|
1328
|
-
if (
|
|
1369
|
+
if (integrationContextChannel) {
|
|
1329
1370
|
try {
|
|
1330
|
-
|
|
1371
|
+
integrationContextChannel.unsubscribe();
|
|
1331
1372
|
} catch {
|
|
1332
1373
|
}
|
|
1333
|
-
|
|
1374
|
+
integrationContextChannel = null;
|
|
1334
1375
|
}
|
|
1335
1376
|
if (client) {
|
|
1336
1377
|
try {
|
|
@@ -1435,15 +1476,21 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
1435
1476
|
var cachedFrameworkVersion = null;
|
|
1436
1477
|
var lastVersionCheckAt = 0;
|
|
1437
1478
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1438
|
-
var agtCliVersion = true ? "0.
|
|
1479
|
+
var agtCliVersion = true ? "0.16.1" : "dev";
|
|
1439
1480
|
function resolveBrewPath(execFileSync2) {
|
|
1440
1481
|
try {
|
|
1441
1482
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1442
1483
|
if (out) return out;
|
|
1443
1484
|
} catch {
|
|
1444
1485
|
}
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1486
|
+
const fallbacks = [
|
|
1487
|
+
"/home/linuxbrew/.linuxbrew/bin/brew",
|
|
1488
|
+
"/opt/homebrew/bin/brew",
|
|
1489
|
+
"/usr/local/bin/brew"
|
|
1490
|
+
];
|
|
1491
|
+
for (const path of fallbacks) {
|
|
1492
|
+
if (existsSync2(path)) return path;
|
|
1493
|
+
}
|
|
1447
1494
|
return null;
|
|
1448
1495
|
}
|
|
1449
1496
|
var toolkitCliEnsured = /* @__PURE__ */ new Set();
|
|
@@ -1656,44 +1703,51 @@ var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
|
1656
1703
|
var selfUpdateUpToDateLogged = false;
|
|
1657
1704
|
var restartAfterUpgrade = false;
|
|
1658
1705
|
var pendingUpgradeVersion = null;
|
|
1706
|
+
var selfUpdateInFlight = false;
|
|
1659
1707
|
async function checkAndUpdateCli() {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
if (isDevMode) return;
|
|
1663
|
-
let resolvedPath = cliPath;
|
|
1708
|
+
if (selfUpdateInFlight) return;
|
|
1709
|
+
selfUpdateInFlight = true;
|
|
1664
1710
|
try {
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const
|
|
1676
|
-
if (
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1711
|
+
const cliPath = process.argv[1] ?? "";
|
|
1712
|
+
const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
|
|
1713
|
+
if (isDevMode) return;
|
|
1714
|
+
let resolvedPath = cliPath;
|
|
1715
|
+
try {
|
|
1716
|
+
const { realpathSync } = await import("fs");
|
|
1717
|
+
resolvedPath = realpathSync(cliPath);
|
|
1718
|
+
} catch {
|
|
1719
|
+
}
|
|
1720
|
+
const isBrewFormula = /\/Cellar\/[^/]+\//.test(resolvedPath);
|
|
1721
|
+
const isNpmGlobal = !isBrewFormula && resolvedPath.includes("node_modules");
|
|
1722
|
+
if (!isBrewFormula && !isNpmGlobal) return;
|
|
1723
|
+
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
1724
|
+
const markerPath = join3(homedir3(), ".augmented", ".last-update-check");
|
|
1725
|
+
try {
|
|
1726
|
+
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
1727
|
+
if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
|
|
1728
|
+
} catch {
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
writeF(markerPath, String(Date.now()));
|
|
1732
|
+
} catch {
|
|
1733
|
+
}
|
|
1734
|
+
if (isBrewFormula) {
|
|
1735
|
+
await checkAndUpdateCliViaBrew();
|
|
1736
|
+
} else {
|
|
1737
|
+
await checkAndUpdateCliViaNpm();
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
writeF(markerPath, String(Date.now()));
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
} finally {
|
|
1744
|
+
selfUpdateInFlight = false;
|
|
1687
1745
|
}
|
|
1688
1746
|
}
|
|
1689
1747
|
async function checkAndUpdateCliViaBrew() {
|
|
1690
1748
|
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1694
|
-
} catch {
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1749
|
+
const brewPath = resolveBrewPath(execFileSync2);
|
|
1750
|
+
if (!brewPath) return;
|
|
1697
1751
|
try {
|
|
1698
1752
|
execFileSync2(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
|
|
1699
1753
|
} catch (err) {
|
|
@@ -2299,7 +2353,7 @@ async function pollCycle() {
|
|
|
2299
2353
|
}
|
|
2300
2354
|
try {
|
|
2301
2355
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
2302
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
2356
|
+
const { collectDiagnostics } = await import("../persistent-session-RZWMTEZY.js");
|
|
2303
2357
|
const diagCodeNames = [...persistentSessionAgents];
|
|
2304
2358
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
2305
2359
|
let tailscaleHostname;
|
|
@@ -2399,6 +2453,13 @@ async function pollCycle() {
|
|
|
2399
2453
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
2400
2454
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
2401
2455
|
await stopGatewayIfRunning(prev.codeName, adapter);
|
|
2456
|
+
stopPersistentSession(prev.codeName, log);
|
|
2457
|
+
try {
|
|
2458
|
+
const { execSync: es } = await import("child_process");
|
|
2459
|
+
es(`tmux kill-session -t agt-${prev.codeName} 2>/dev/null`, { stdio: "ignore" });
|
|
2460
|
+
} catch {
|
|
2461
|
+
}
|
|
2462
|
+
killAgentChannelProcesses(prev.codeName, { log });
|
|
2402
2463
|
freePort(prev.codeName);
|
|
2403
2464
|
const agentDir = join3(adapter.getAgentDir(prev.codeName), "provision");
|
|
2404
2465
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
@@ -2442,7 +2503,7 @@ async function pollCycle() {
|
|
|
2442
2503
|
ensureRealtimeAssignStarted(agentStates);
|
|
2443
2504
|
ensureRealtimeConfigStarted(agentStates);
|
|
2444
2505
|
ensureRealtimeKanbanStarted(agentStates);
|
|
2445
|
-
|
|
2506
|
+
ensureRealtimeIntegrationContextStarted(agentStates);
|
|
2446
2507
|
try {
|
|
2447
2508
|
const spawnData = await api.post("/host/kanban/recurring/spawn");
|
|
2448
2509
|
if (spawnData.spawned > 0) {
|
|
@@ -2526,6 +2587,7 @@ async function processAgent(agent, agentStates) {
|
|
|
2526
2587
|
es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: "ignore" });
|
|
2527
2588
|
} catch {
|
|
2528
2589
|
}
|
|
2590
|
+
killAgentChannelProcesses(agent.code_name, { log });
|
|
2529
2591
|
freePort(agent.code_name);
|
|
2530
2592
|
await cleanupAgentFiles(agent.code_name, agentDir);
|
|
2531
2593
|
clearAgentCaches(agent.agent_id, agent.code_name);
|
|
@@ -3123,42 +3185,46 @@ async function processAgent(agent, agentStates) {
|
|
|
3123
3185
|
}
|
|
3124
3186
|
}
|
|
3125
3187
|
if (frameworkAdapter.installSkillFiles) {
|
|
3126
|
-
const
|
|
3127
|
-
const
|
|
3188
|
+
const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
|
|
3189
|
+
const installedIntegrationSkills = [];
|
|
3128
3190
|
const { createHash: createHash2 } = await import("crypto");
|
|
3191
|
+
const refreshAny = refreshData;
|
|
3192
|
+
const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
|
|
3129
3193
|
const contextBySlug = /* @__PURE__ */ new Map();
|
|
3130
|
-
for (const ctx of
|
|
3131
|
-
|
|
3194
|
+
for (const ctx of contexts) {
|
|
3195
|
+
const slug = ctx.integration_slug ?? ctx.plugin_slug;
|
|
3196
|
+
if (!slug) continue;
|
|
3197
|
+
contextBySlug.set(slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? "").trim() });
|
|
3132
3198
|
}
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3199
|
+
const integrationGroups = groupSkillsByIntegration(
|
|
3200
|
+
refreshAny.integration_skills ?? refreshAny.plugin_skills ?? []
|
|
3135
3201
|
);
|
|
3136
|
-
for (const [
|
|
3202
|
+
for (const [integrationSlug, scopes] of integrationGroups) {
|
|
3137
3203
|
try {
|
|
3138
|
-
const
|
|
3139
|
-
|
|
3140
|
-
const ctx = contextBySlug.get(
|
|
3204
|
+
const integrationSkillId = `integration-${integrationSlug}`.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3205
|
+
currentIntegrationSkillIds.add(integrationSkillId);
|
|
3206
|
+
const ctx = contextBySlug.get(integrationSlug);
|
|
3141
3207
|
const renderedScopes = scopes.map((s) => ({
|
|
3142
3208
|
plugin_slug: s.plugin_slug,
|
|
3143
3209
|
skill_id: s.skill_id,
|
|
3144
3210
|
skill_name: s.skill_name,
|
|
3145
|
-
content:
|
|
3211
|
+
content: renderIntegrationSkillContent(
|
|
3146
3212
|
s.content,
|
|
3147
3213
|
ctx?.values ?? {},
|
|
3148
3214
|
ctx?.overrides ?? "",
|
|
3149
3215
|
(warning) => log(`[plugin-context] ${s.plugin_slug}/${s.skill_id}: ${warning}`)
|
|
3150
3216
|
)
|
|
3151
3217
|
}));
|
|
3152
|
-
const bundle =
|
|
3218
|
+
const bundle = buildIntegrationBundle(renderedScopes);
|
|
3153
3219
|
const contentHash = createHash2("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
3154
|
-
const hashKey = `plugin-skill:${agent.agent_id}:${
|
|
3220
|
+
const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
|
|
3155
3221
|
if (knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
3156
|
-
frameworkAdapter.installSkillFiles(agent.code_name,
|
|
3222
|
+
frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
|
|
3157
3223
|
knownSkillHashes.set(hashKey, contentHash);
|
|
3158
|
-
for (const s of scopes)
|
|
3159
|
-
log(`Installed
|
|
3224
|
+
for (const s of scopes) installedIntegrationSkills.push(s.skill_name);
|
|
3225
|
+
log(`Installed integration skill bundle '${integrationSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);
|
|
3160
3226
|
} catch (err) {
|
|
3161
|
-
log(`
|
|
3227
|
+
log(`Integration skill install failed for '${agent.code_name}' / '${integrationSlug}': ${err.message}`);
|
|
3162
3228
|
}
|
|
3163
3229
|
}
|
|
3164
3230
|
try {
|
|
@@ -3198,12 +3264,12 @@ async function processAgent(agent, agentStates) {
|
|
|
3198
3264
|
log(`Removed ${reason} '${entry}' for '${agent.code_name}' (framework=${frameworkId2})`);
|
|
3199
3265
|
};
|
|
3200
3266
|
for (const entry of discoveredEntries) {
|
|
3201
|
-
if (!
|
|
3267
|
+
if (!currentIntegrationSkillIds.has(entry)) {
|
|
3202
3268
|
removeSkillFolder(entry, "orphaned skill folder");
|
|
3203
3269
|
}
|
|
3204
3270
|
}
|
|
3205
3271
|
} catch (err) {
|
|
3206
|
-
log(`
|
|
3272
|
+
log(`Integration skill cleanup failed for '${agent.code_name}': ${err.message}`);
|
|
3207
3273
|
}
|
|
3208
3274
|
try {
|
|
3209
3275
|
const agentFwForIndex = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
@@ -3213,40 +3279,44 @@ async function processAgent(agent, agentStates) {
|
|
|
3213
3279
|
} catch (err) {
|
|
3214
3280
|
log(`Skills index refresh failed for '${agent.code_name}': ${err.message}`);
|
|
3215
3281
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3282
|
+
const installHooks = refreshAny.integration_install_hooks ?? refreshAny.plugin_install_hooks ?? [];
|
|
3283
|
+
if (frameworkAdapter.executePluginHook && installHooks.length) {
|
|
3284
|
+
for (const hook of installHooks) {
|
|
3285
|
+
const slug = hook.integration_slug ?? hook.plugin_slug;
|
|
3286
|
+
if (!slug) continue;
|
|
3218
3287
|
try {
|
|
3219
3288
|
const scriptHash = createHash2("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
3220
|
-
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${
|
|
3289
|
+
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
|
|
3221
3290
|
if (knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
3222
3291
|
const result = await frameworkAdapter.executePluginHook({
|
|
3223
3292
|
codeName: agent.code_name,
|
|
3224
|
-
pluginSlug:
|
|
3293
|
+
pluginSlug: slug,
|
|
3225
3294
|
hookName: "on_install",
|
|
3226
3295
|
script: hook.script
|
|
3227
3296
|
});
|
|
3228
3297
|
if (result.exitCode === 0) {
|
|
3229
3298
|
knownSkillHashes.set(hookKey, scriptHash);
|
|
3230
|
-
log(`
|
|
3299
|
+
log(`Integration hook on_install '${slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);
|
|
3231
3300
|
} else if (result.timedOut) {
|
|
3232
|
-
log(`
|
|
3301
|
+
log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
3233
3302
|
} else {
|
|
3234
3303
|
const stderrHash = createHash2("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
3235
3304
|
const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
|
|
3236
3305
|
const missingCmdHash = missingCmd ? createHash2("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
|
|
3237
3306
|
log(
|
|
3238
|
-
`
|
|
3307
|
+
`Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
|
|
3239
3308
|
);
|
|
3240
3309
|
}
|
|
3241
3310
|
} catch (err) {
|
|
3242
|
-
log(`
|
|
3311
|
+
log(`Integration hook on_install failed for '${agent.code_name}' / '${slug}': ${err.message}`);
|
|
3243
3312
|
}
|
|
3244
3313
|
}
|
|
3245
3314
|
}
|
|
3246
3315
|
const agentFwForToolkits = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
3247
|
-
|
|
3316
|
+
const toolkitsList = refreshAny.integration_toolkits ?? refreshAny.plugin_toolkits ?? [];
|
|
3317
|
+
if (agentFwForToolkits === "claude-code" && toolkitsList.length) {
|
|
3248
3318
|
const toolkitUnion = /* @__PURE__ */ new Set();
|
|
3249
|
-
for (const { toolkits } of
|
|
3319
|
+
for (const { toolkits } of toolkitsList) {
|
|
3250
3320
|
for (const t of toolkits) toolkitUnion.add(t);
|
|
3251
3321
|
}
|
|
3252
3322
|
for (const toolkitSlug of toolkitUnion) {
|
|
@@ -3254,17 +3324,17 @@ async function processAgent(agent, agentStates) {
|
|
|
3254
3324
|
}
|
|
3255
3325
|
}
|
|
3256
3326
|
const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
3257
|
-
if (agentFw2 === "claude-code" &&
|
|
3258
|
-
const names =
|
|
3327
|
+
if (agentFw2 === "claude-code" && installedIntegrationSkills.length > 0 && isSessionHealthy(agent.code_name)) {
|
|
3328
|
+
const names = installedIntegrationSkills.join(", ");
|
|
3259
3329
|
injectMessage(
|
|
3260
3330
|
agent.code_name,
|
|
3261
3331
|
"system",
|
|
3262
|
-
`New
|
|
3332
|
+
`New integration skills installed: ${names}. These are available immediately \u2014 Claude Code loads skills on demand from .claude/skills/.`,
|
|
3263
3333
|
{ task_name: "plugin-skill-update" },
|
|
3264
3334
|
log
|
|
3265
3335
|
).catch(() => {
|
|
3266
3336
|
});
|
|
3267
|
-
log(`[hot-reload] Notified '${agent.code_name}' about new
|
|
3337
|
+
log(`[hot-reload] Notified '${agent.code_name}' about new integration skills: ${names}`);
|
|
3268
3338
|
}
|
|
3269
3339
|
}
|
|
3270
3340
|
}
|
|
@@ -4040,6 +4110,23 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4040
4110
|
stopPersistentSession(codeName, log);
|
|
4041
4111
|
persistentSessionAgents.delete(codeName);
|
|
4042
4112
|
}
|
|
4113
|
+
if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {
|
|
4114
|
+
const current = peekCurrentSession(codeName);
|
|
4115
|
+
if (current) {
|
|
4116
|
+
const idle = isAgentIdle(projectDir, current.sessionId);
|
|
4117
|
+
if (idle) {
|
|
4118
|
+
log(
|
|
4119
|
+
`[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
|
|
4120
|
+
);
|
|
4121
|
+
stopPersistentSession(codeName, log);
|
|
4122
|
+
persistentSessionAgents.delete(codeName);
|
|
4123
|
+
} else {
|
|
4124
|
+
log(
|
|
4125
|
+
`[persistent-session] Day rollover for '${codeName}' deferred \u2014 agent still active on session ${current.sessionId} (will retry next tick)`
|
|
4126
|
+
);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4043
4130
|
if (!isSessionHealthy(codeName)) {
|
|
4044
4131
|
if (persistentSessionAgents.has(codeName)) {
|
|
4045
4132
|
log(`[persistent-session] Session for '${codeName}' is unhealthy, will restart`);
|
|
@@ -4155,7 +4242,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4155
4242
|
var realtimeStarted = false;
|
|
4156
4243
|
var realtimeDriftStarted = false;
|
|
4157
4244
|
var realtimeKanbanStarted = false;
|
|
4158
|
-
var
|
|
4245
|
+
var realtimeIntegrationContextStarted = false;
|
|
4159
4246
|
var realtimeAssignStarted = false;
|
|
4160
4247
|
var realtimeConfigStarted = false;
|
|
4161
4248
|
var realtimeSubscribedAgentIds = /* @__PURE__ */ new Set();
|
|
@@ -4174,7 +4261,7 @@ function ensureRealtimeStarted(agentStates) {
|
|
|
4174
4261
|
realtimeAssignStarted = false;
|
|
4175
4262
|
realtimeConfigStarted = false;
|
|
4176
4263
|
realtimeKanbanStarted = false;
|
|
4177
|
-
|
|
4264
|
+
realtimeIntegrationContextStarted = false;
|
|
4178
4265
|
}
|
|
4179
4266
|
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|
|
4180
4267
|
if (activeAgentIds.length === 0) return;
|
|
@@ -4214,6 +4301,7 @@ function ensureRealtimeStarted(agentStates) {
|
|
|
4214
4301
|
realtimeAssignStarted = false;
|
|
4215
4302
|
realtimeConfigStarted = false;
|
|
4216
4303
|
realtimeKanbanStarted = false;
|
|
4304
|
+
realtimeIntegrationContextStarted = false;
|
|
4217
4305
|
}
|
|
4218
4306
|
},
|
|
4219
4307
|
log
|
|
@@ -4353,15 +4441,15 @@ function ensureRealtimeKanbanStarted(agentStates) {
|
|
|
4353
4441
|
log(`[realtime] Kanban subscription failed: ${err.message}`);
|
|
4354
4442
|
});
|
|
4355
4443
|
}
|
|
4356
|
-
function
|
|
4357
|
-
if (
|
|
4444
|
+
function ensureRealtimeIntegrationContextStarted(agentStates) {
|
|
4445
|
+
if (realtimeIntegrationContextStarted) return;
|
|
4358
4446
|
const activeAgentIds = agentStates.filter((a) => a.status === "active").map((a) => a.agentId);
|
|
4359
4447
|
if (activeAgentIds.length === 0) return;
|
|
4360
4448
|
const apiKey = process.env["AGT_API_KEY"];
|
|
4361
4449
|
if (!apiKey) return;
|
|
4362
4450
|
void exchangeApiKey(apiKey).then((exchange) => {
|
|
4363
4451
|
if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;
|
|
4364
|
-
|
|
4452
|
+
startRealtimeIntegrationContext({
|
|
4365
4453
|
supabaseUrl: exchange.supabaseUrl,
|
|
4366
4454
|
supabaseAnonKey: exchange.supabaseAnonKey,
|
|
4367
4455
|
token: exchange.token,
|
|
@@ -4375,14 +4463,14 @@ function ensureRealtimePluginContextStarted(agentStates) {
|
|
|
4375
4463
|
}
|
|
4376
4464
|
}
|
|
4377
4465
|
}
|
|
4378
|
-
triggerEarlyPoll(`
|
|
4466
|
+
triggerEarlyPoll(`integration context changed for agent ${payload.agent_id}`);
|
|
4379
4467
|
},
|
|
4380
4468
|
log
|
|
4381
4469
|
});
|
|
4382
|
-
|
|
4383
|
-
log(`[realtime]
|
|
4470
|
+
realtimeIntegrationContextStarted = true;
|
|
4471
|
+
log(`[realtime] Integration context subscription started for ${activeAgentIds.length} agent(s)`);
|
|
4384
4472
|
}).catch((err) => {
|
|
4385
|
-
log(`[realtime]
|
|
4473
|
+
log(`[realtime] Integration context subscription failed: ${err.message}`);
|
|
4386
4474
|
});
|
|
4387
4475
|
}
|
|
4388
4476
|
function triggerEarlyPoll(reason) {
|
|
@@ -5352,8 +5440,9 @@ async function processClaudePairSessions(agents) {
|
|
|
5352
5440
|
submitClaudePairCode,
|
|
5353
5441
|
spawnPairSession,
|
|
5354
5442
|
killPairSession,
|
|
5355
|
-
pairTmuxSession
|
|
5356
|
-
|
|
5443
|
+
pairTmuxSession,
|
|
5444
|
+
finalizeClaudePairOnboarding
|
|
5445
|
+
} = await import("../claude-pair-runtime-FEUHTZXZ.js");
|
|
5357
5446
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
5358
5447
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
5359
5448
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -5414,10 +5503,11 @@ async function processClaudePairSessions(agents) {
|
|
|
5414
5503
|
});
|
|
5415
5504
|
} else {
|
|
5416
5505
|
const errKind = result.error.kind;
|
|
5506
|
+
const errMessage = "message" in result.error ? result.error.message : void 0;
|
|
5417
5507
|
await reportAndCleanup(session.pair_id, {
|
|
5418
5508
|
status: errKind === "no-session" ? "session_missing" : "failure",
|
|
5419
5509
|
error_code: errKind,
|
|
5420
|
-
error_message:
|
|
5510
|
+
error_message: errMessage
|
|
5421
5511
|
});
|
|
5422
5512
|
}
|
|
5423
5513
|
} else if (session.status === "code_submitted" && session.code) {
|
|
@@ -5427,6 +5517,10 @@ async function processClaudePairSessions(agents) {
|
|
|
5427
5517
|
code: session.code
|
|
5428
5518
|
});
|
|
5429
5519
|
if (result.kind === "success") {
|
|
5520
|
+
const finalize = await finalizeClaudePairOnboarding(pairSession, log);
|
|
5521
|
+
if (!finalize.finalized) {
|
|
5522
|
+
log(`[claude-pair] WARN: ~/.claude.json was not updated during onboarding (pair ${session.pair_id.slice(0, 8)}) \u2014 first agent launch may show the login picker`);
|
|
5523
|
+
}
|
|
5430
5524
|
await reportAndCleanup(session.pair_id, { status: "success" });
|
|
5431
5525
|
} else if (result.kind === "failure") {
|
|
5432
5526
|
await reportAndCleanup(session.pair_id, {
|
|
@@ -5442,10 +5536,11 @@ async function processClaudePairSessions(agents) {
|
|
|
5442
5536
|
});
|
|
5443
5537
|
} else {
|
|
5444
5538
|
const errKind = result.error.kind;
|
|
5539
|
+
const errMessage = "message" in result.error ? result.error.message : void 0;
|
|
5445
5540
|
await reportAndCleanup(session.pair_id, {
|
|
5446
5541
|
status: errKind === "no-session" ? "session_missing" : "failure",
|
|
5447
5542
|
error_code: errKind,
|
|
5448
|
-
error_message:
|
|
5543
|
+
error_message: errMessage
|
|
5449
5544
|
});
|
|
5450
5545
|
}
|
|
5451
5546
|
}
|