@integrity-labs/agt-cli 0.14.12 → 0.14.15
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 +51 -29
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-NSHSUWZQ.js → chunk-LGEQOVFU.js} +101 -15
- package/dist/chunk-LGEQOVFU.js.map +1 -0
- package/dist/lib/manager-worker.js +246 -34
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/index.js +113 -0
- package/mcp/slack-channel.js +508 -41
- package/mcp/telegram-channel.js +439 -27
- package/package.json +1 -1
- package/dist/chunk-NSHSUWZQ.js.map +0 -1
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveChannels,
|
|
20
20
|
resolveDmTarget,
|
|
21
21
|
wrapScheduledTaskPrompt
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-LGEQOVFU.js";
|
|
23
23
|
import {
|
|
24
24
|
findTaskByTemplate,
|
|
25
25
|
getProjectDir,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
import { createHash } from "crypto";
|
|
46
46
|
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, chmodSync, existsSync, rmSync, readdirSync, statSync, unlinkSync, copyFileSync } from "fs";
|
|
47
47
|
import https from "https";
|
|
48
|
+
import { execFileSync as syncExecFile } from "child_process";
|
|
48
49
|
import { join as join2, dirname } from "path";
|
|
49
50
|
import { homedir as homedir2 } from "os";
|
|
50
51
|
import { fileURLToPath } from "url";
|
|
@@ -81,6 +82,116 @@ ${trimmedOverrides}
|
|
|
81
82
|
`;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// src/lib/plugin-skill-layout.ts
|
|
86
|
+
function extractDescription(content) {
|
|
87
|
+
const match = content.match(/^---\s*([\s\S]*?)---/);
|
|
88
|
+
if (!match) return null;
|
|
89
|
+
const frontmatter = match[1] ?? "";
|
|
90
|
+
const descMatch = frontmatter.match(/^\s*description:\s*(?:"((?:\\.|[^"\\])*)"|([^\n]+))\s*$/m);
|
|
91
|
+
if (!descMatch) return null;
|
|
92
|
+
const quoted = descMatch[1];
|
|
93
|
+
if (quoted !== void 0) {
|
|
94
|
+
return quoted.replace(/\\(["\\])/g, "$1").trim();
|
|
95
|
+
}
|
|
96
|
+
return descMatch[2]?.trim() ?? null;
|
|
97
|
+
}
|
|
98
|
+
function sanitizeScopeSlug(skillId, pluginSlug) {
|
|
99
|
+
const normalized = skillId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
100
|
+
const prefix = pluginSlug.replace(/-/g, "");
|
|
101
|
+
const stripped = normalized.startsWith(`${prefix}-`) ? normalized.slice(prefix.length + 1) : normalized;
|
|
102
|
+
return stripped.replace(/^-|-$/g, "") || normalized || "scope";
|
|
103
|
+
}
|
|
104
|
+
function buildPluginBundle(skills) {
|
|
105
|
+
if (skills.length === 0) {
|
|
106
|
+
throw new Error("buildPluginBundle: empty skills list");
|
|
107
|
+
}
|
|
108
|
+
const pluginSlug = skills[0].plugin_slug;
|
|
109
|
+
const ordered = [...skills].sort((a, b) => a.skill_id.localeCompare(b.skill_id));
|
|
110
|
+
const entries = ordered.map((s) => {
|
|
111
|
+
const scopeSlug = sanitizeScopeSlug(s.skill_id, pluginSlug);
|
|
112
|
+
const description = extractDescription(s.content);
|
|
113
|
+
return {
|
|
114
|
+
skillId: s.skill_id,
|
|
115
|
+
skillName: s.skill_name,
|
|
116
|
+
description,
|
|
117
|
+
scopeSlug,
|
|
118
|
+
scopePath: `scopes/${scopeSlug}.md`,
|
|
119
|
+
content: s.content
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const scopePathGroups = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
const bucket = scopePathGroups.get(entry.scopePath);
|
|
125
|
+
if (bucket) bucket.push(entry);
|
|
126
|
+
else scopePathGroups.set(entry.scopePath, [entry]);
|
|
127
|
+
}
|
|
128
|
+
for (const [scopePath, group] of scopePathGroups) {
|
|
129
|
+
if (group.length > 1) {
|
|
130
|
+
const conflicts = group.map((e) => `${e.skillId} (${e.skillName})`).join(", ");
|
|
131
|
+
throw new Error(
|
|
132
|
+
`buildPluginBundle: duplicate scope path '${scopePath}' for plugin '${pluginSlug}' \u2014 conflicting skills: ${conflicts}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const pluginName = slugToTitle(pluginSlug);
|
|
137
|
+
const umbrellaDescription = entries.map((e) => e.description ?? `Use for ${e.skillName}.`).join(" ");
|
|
138
|
+
const scopeList = entries.map((e) => {
|
|
139
|
+
const label = e.skillName || slugToTitle(e.scopeSlug);
|
|
140
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
141
|
+
return `- **${label}** ([${e.scopePath}](${e.scopePath}))${desc}`;
|
|
142
|
+
}).join("\n");
|
|
143
|
+
const umbrella = [
|
|
144
|
+
"---",
|
|
145
|
+
`name: "${pluginName}"`,
|
|
146
|
+
`description: "${escapeYamlDouble(umbrellaDescription)}"`,
|
|
147
|
+
"---",
|
|
148
|
+
"",
|
|
149
|
+
`# ${pluginName}`,
|
|
150
|
+
"",
|
|
151
|
+
`This skill bundles ${entries.length === 1 ? "one scope" : `${entries.length} scopes`} for the ${pluginSlug} plugin. Each scope has a dedicated reference under \`scopes/\` that you should load on demand when the user's intent maps to it.`,
|
|
152
|
+
"",
|
|
153
|
+
"## Scopes",
|
|
154
|
+
"",
|
|
155
|
+
scopeList,
|
|
156
|
+
"",
|
|
157
|
+
"## How to use",
|
|
158
|
+
"",
|
|
159
|
+
`Identify which scope matches the user's request, then read the matching file under \`scopes/\` for the exact tool names and usage details. Do not guess tool names from this umbrella file alone.`,
|
|
160
|
+
""
|
|
161
|
+
].join("\n");
|
|
162
|
+
const files = [
|
|
163
|
+
{ relativePath: "SKILL.md", content: umbrella },
|
|
164
|
+
...entries.map((e) => ({
|
|
165
|
+
relativePath: e.scopePath,
|
|
166
|
+
content: e.content
|
|
167
|
+
}))
|
|
168
|
+
];
|
|
169
|
+
return {
|
|
170
|
+
pluginSlug,
|
|
171
|
+
files,
|
|
172
|
+
scopePaths: entries.map((e) => e.scopePath)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function slugToTitle(slug) {
|
|
176
|
+
return slug.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
177
|
+
}
|
|
178
|
+
function escapeYamlDouble(value) {
|
|
179
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
180
|
+
}
|
|
181
|
+
function groupSkillsByPlugin(skills) {
|
|
182
|
+
const map = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const s of skills) {
|
|
184
|
+
const bucket = map.get(s.plugin_slug);
|
|
185
|
+
if (bucket) bucket.push(s);
|
|
186
|
+
else map.set(s.plugin_slug, [s]);
|
|
187
|
+
}
|
|
188
|
+
return map;
|
|
189
|
+
}
|
|
190
|
+
function bundleFingerprint(files) {
|
|
191
|
+
const sorted = [...files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
192
|
+
return sorted.map((f) => `${f.relativePath}\0${f.content}`).join("");
|
|
193
|
+
}
|
|
194
|
+
|
|
84
195
|
// src/lib/gateway-client.ts
|
|
85
196
|
import { EventEmitter } from "events";
|
|
86
197
|
import WebSocket from "ws";
|
|
@@ -691,11 +802,23 @@ function appendScheduleLinkFooter(body, url, medium) {
|
|
|
691
802
|
|
|
692
803
|
${footer}`;
|
|
693
804
|
}
|
|
805
|
+
var warnedNullConsoleUrl = false;
|
|
694
806
|
function withScheduleLinkFooter(opts) {
|
|
695
807
|
if (!opts.taskId) return opts.body;
|
|
696
808
|
if (!scheduleLinkFooterEnabled(opts.codeName, opts.env)) return opts.body;
|
|
697
809
|
const consoleUrl = getConsoleUrl(opts.env);
|
|
698
|
-
if (!consoleUrl)
|
|
810
|
+
if (!consoleUrl) {
|
|
811
|
+
if (!warnedNullConsoleUrl && opts.log) {
|
|
812
|
+
warnedNullConsoleUrl = true;
|
|
813
|
+
try {
|
|
814
|
+
opts.log(
|
|
815
|
+
"[schedule-link] AGT_CONSOLE_URL unset and NEXT_PUBLIC_APP_URL unset \u2014 schedule-edit deep-link footer disabled. Run `agt setup` again or export AGT_CONSOLE_URL (e.g. https://app.augmented.team) to restore it."
|
|
816
|
+
);
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return opts.body;
|
|
821
|
+
}
|
|
699
822
|
const url = buildScheduleEditLink(consoleUrl, opts.agentId, opts.taskId);
|
|
700
823
|
return appendScheduleLinkFooter(opts.body, url, opts.medium);
|
|
701
824
|
}
|
|
@@ -2608,44 +2731,74 @@ async function processAgent(agent, agentStates) {
|
|
|
2608
2731
|
for (const ctx of refreshData.plugin_contexts ?? []) {
|
|
2609
2732
|
contextBySlug.set(ctx.plugin_slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? "").trim() });
|
|
2610
2733
|
}
|
|
2611
|
-
|
|
2734
|
+
const pluginGroups = groupSkillsByPlugin(
|
|
2735
|
+
refreshData.plugin_skills ?? []
|
|
2736
|
+
);
|
|
2737
|
+
for (const [pluginSlug, scopes] of pluginGroups) {
|
|
2612
2738
|
try {
|
|
2613
|
-
const
|
|
2614
|
-
currentPluginSkillIds.add(
|
|
2615
|
-
const ctx = contextBySlug.get(
|
|
2616
|
-
const
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2739
|
+
const pluginSkillId = `plugin-${pluginSlug}`.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2740
|
+
currentPluginSkillIds.add(pluginSkillId);
|
|
2741
|
+
const ctx = contextBySlug.get(pluginSlug);
|
|
2742
|
+
const renderedScopes = scopes.map((s) => ({
|
|
2743
|
+
plugin_slug: s.plugin_slug,
|
|
2744
|
+
skill_id: s.skill_id,
|
|
2745
|
+
skill_name: s.skill_name,
|
|
2746
|
+
content: renderPluginSkillContent(
|
|
2747
|
+
s.content,
|
|
2748
|
+
ctx?.values ?? {},
|
|
2749
|
+
ctx?.overrides ?? "",
|
|
2750
|
+
(warning) => log(`[plugin-context] ${s.plugin_slug}/${s.skill_id}: ${warning}`)
|
|
2751
|
+
)
|
|
2752
|
+
}));
|
|
2753
|
+
const bundle = buildPluginBundle(renderedScopes);
|
|
2754
|
+
const contentHash = createHash2("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
2755
|
+
const hashKey = `plugin-skill:${agent.agent_id}:${pluginSkillId}`;
|
|
2624
2756
|
if (knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
2625
|
-
frameworkAdapter.installSkillFiles(agent.code_name,
|
|
2626
|
-
{ relativePath: "SKILL.md", content: renderedContent }
|
|
2627
|
-
]);
|
|
2757
|
+
frameworkAdapter.installSkillFiles(agent.code_name, pluginSkillId, bundle.files);
|
|
2628
2758
|
knownSkillHashes.set(hashKey, contentHash);
|
|
2629
|
-
installedPluginSkills.push(
|
|
2630
|
-
log(`Installed plugin
|
|
2759
|
+
for (const s of scopes) installedPluginSkills.push(s.skill_name);
|
|
2760
|
+
log(`Installed plugin '${pluginSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);
|
|
2631
2761
|
} catch (err) {
|
|
2632
|
-
log(`Plugin
|
|
2762
|
+
log(`Plugin install failed for '${agent.code_name}' / '${pluginSlug}': ${err.message}`);
|
|
2633
2763
|
}
|
|
2634
2764
|
}
|
|
2635
2765
|
try {
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2766
|
+
const { readdirSync: readdirSync2, rmSync: rmSync2 } = await import("fs");
|
|
2767
|
+
const { homedir: homedir3 } = await import("os");
|
|
2768
|
+
const frameworkId2 = frameworkAdapter.id;
|
|
2769
|
+
const candidateSkillDirs = [
|
|
2770
|
+
// Claude Code — framework runtime tree
|
|
2771
|
+
join2(homedir3(), ".augmented", agent.code_name, "skills"),
|
|
2772
|
+
// Claude Code — project tree
|
|
2773
|
+
join2(homedir3(), ".augmented", agent.code_name, "project", ".claude", "skills"),
|
|
2774
|
+
// OpenClaw — framework runtime tree
|
|
2775
|
+
join2(homedir3(), `.openclaw-${agent.code_name}`, "skills"),
|
|
2776
|
+
// Defensive: legacy provision-side path, not currently an
|
|
2777
|
+
// install target but cheap to sweep.
|
|
2778
|
+
join2(agentDir, ".claude", "skills")
|
|
2779
|
+
];
|
|
2780
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync(d));
|
|
2781
|
+
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
2782
|
+
for (const dir of existingDirs) {
|
|
2783
|
+
try {
|
|
2784
|
+
for (const entry of readdirSync2(dir)) {
|
|
2785
|
+
if (entry.startsWith("plugin-")) discoveredEntries.add(entry);
|
|
2648
2786
|
}
|
|
2787
|
+
} catch {
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
const removeSkillFolder = (entry, reason) => {
|
|
2791
|
+
for (const dir of existingDirs) {
|
|
2792
|
+
const p = join2(dir, entry);
|
|
2793
|
+
if (existsSync(p)) {
|
|
2794
|
+
rmSync2(p, { recursive: true, force: true });
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
log(`Removed ${reason} '${entry}' for '${agent.code_name}' (framework=${frameworkId2})`);
|
|
2798
|
+
};
|
|
2799
|
+
for (const entry of discoveredEntries) {
|
|
2800
|
+
if (!currentPluginSkillIds.has(entry)) {
|
|
2801
|
+
removeSkillFolder(entry, "orphaned plugin skill");
|
|
2649
2802
|
}
|
|
2650
2803
|
}
|
|
2651
2804
|
} catch (err) {
|
|
@@ -3739,7 +3892,7 @@ function ensureRealtimePluginContextStarted(agentStates) {
|
|
|
3739
3892
|
const agent = agentStates.find((a) => a.agentId === payload.agent_id);
|
|
3740
3893
|
if (agent) {
|
|
3741
3894
|
for (const key of knownSkillHashes.keys()) {
|
|
3742
|
-
if (key.startsWith(`plugin-skill:${agent.
|
|
3895
|
+
if (key.startsWith(`plugin-skill:${agent.agentId}:`)) {
|
|
3743
3896
|
knownSkillHashes.delete(key);
|
|
3744
3897
|
}
|
|
3745
3898
|
}
|
|
@@ -4487,7 +4640,7 @@ ${a.detail}`;
|
|
|
4487
4640
|
${blocks.join("\n\n")}`);
|
|
4488
4641
|
}
|
|
4489
4642
|
async function deliverScheduledTaskOutput(agentCodeName, agentId, rawTarget, body, taskId) {
|
|
4490
|
-
const withLink = (b, medium) => withScheduleLinkFooter({ body: b, medium, codeName: agentCodeName, agentId, taskId });
|
|
4643
|
+
const withLink = (b, medium) => withScheduleLinkFooter({ body: b, medium, codeName: agentCodeName, agentId, taskId, log });
|
|
4491
4644
|
if (typeof rawTarget === "string") {
|
|
4492
4645
|
if (rawTarget.startsWith("channel:")) {
|
|
4493
4646
|
const result = await sendTaskNotification(agentCodeName, "slack", rawTarget, withLink(body, "slack"));
|
|
@@ -5124,6 +5277,42 @@ async function ensureHostFrameworkBinaries() {
|
|
|
5124
5277
|
log(`[framework-install] failed: ${err.message}`);
|
|
5125
5278
|
}
|
|
5126
5279
|
}
|
|
5280
|
+
function restartRunningChannelMcps(basenames) {
|
|
5281
|
+
try {
|
|
5282
|
+
const psOutput = syncExecFile(
|
|
5283
|
+
"ps",
|
|
5284
|
+
["-eo", "pid=,command="],
|
|
5285
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
5286
|
+
);
|
|
5287
|
+
const lines = psOutput.split("\n").filter((l) => l.trim());
|
|
5288
|
+
const selfPid = process.pid;
|
|
5289
|
+
let signalled = 0;
|
|
5290
|
+
for (const line of lines) {
|
|
5291
|
+
const match = line.match(/^\s*(\d+)\s+(.*)$/);
|
|
5292
|
+
if (!match) continue;
|
|
5293
|
+
const pid = Number(match[1]);
|
|
5294
|
+
const command = match[2];
|
|
5295
|
+
if (pid === selfPid) continue;
|
|
5296
|
+
const hit = basenames.find((b) => new RegExp(`/${b}\\.js(\\s|$)`).test(command));
|
|
5297
|
+
if (!hit) continue;
|
|
5298
|
+
try {
|
|
5299
|
+
process.kill(pid, "SIGTERM");
|
|
5300
|
+
signalled++;
|
|
5301
|
+
log(`[manager] sent SIGTERM to pid=${pid} (${hit}.js) \u2014 will respawn on next message`);
|
|
5302
|
+
} catch (err) {
|
|
5303
|
+
const code = err.code;
|
|
5304
|
+
if (code && code !== "ESRCH") {
|
|
5305
|
+
log(`[manager] failed to signal pid=${pid}: ${code}`);
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
if (signalled === 0) {
|
|
5310
|
+
log(`[manager] no running instances to restart (deploy still applies to future spawns)`);
|
|
5311
|
+
}
|
|
5312
|
+
} catch (err) {
|
|
5313
|
+
log(`[manager] restartRunningChannelMcps failed: ${err.message}`);
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5127
5316
|
function deployMcpAssets() {
|
|
5128
5317
|
const targetDir = join2(homedir2(), ".augmented", "_mcp");
|
|
5129
5318
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -5144,17 +5333,40 @@ function deployMcpAssets() {
|
|
|
5144
5333
|
log("[manager] MCP assets not found in CLI package \u2014 skipping deployment");
|
|
5145
5334
|
return;
|
|
5146
5335
|
}
|
|
5336
|
+
const changedBasenames = [];
|
|
5337
|
+
const fileHash = (p) => {
|
|
5338
|
+
try {
|
|
5339
|
+
if (!existsSync(p)) return null;
|
|
5340
|
+
return createHash("sha256").update(readFileSync(p)).digest("hex");
|
|
5341
|
+
} catch {
|
|
5342
|
+
return null;
|
|
5343
|
+
}
|
|
5344
|
+
};
|
|
5345
|
+
const RESTARTABLE_CHANNEL_FILES = /* @__PURE__ */ new Set([
|
|
5346
|
+
"slack-channel.js",
|
|
5347
|
+
"direct-chat-channel.js",
|
|
5348
|
+
"telegram-channel.js"
|
|
5349
|
+
]);
|
|
5147
5350
|
for (const file of ["index.js", "slack-channel.js", "direct-chat-channel.js", "telegram-channel.js"]) {
|
|
5148
5351
|
const src = join2(mcpSourceDir, file);
|
|
5149
5352
|
const dst = join2(targetDir, file);
|
|
5150
5353
|
if (!existsSync(src)) continue;
|
|
5354
|
+
const before = fileHash(dst);
|
|
5151
5355
|
try {
|
|
5152
5356
|
copyFileSync(src, dst);
|
|
5357
|
+
const after = fileHash(dst);
|
|
5358
|
+
if (before !== after && RESTARTABLE_CHANNEL_FILES.has(file)) {
|
|
5359
|
+
changedBasenames.push(file.replace(/\.js$/, ""));
|
|
5360
|
+
}
|
|
5153
5361
|
} catch (err) {
|
|
5154
5362
|
log(`[manager] Failed to deploy ${file}: ${err.message}`);
|
|
5155
5363
|
}
|
|
5156
5364
|
}
|
|
5157
5365
|
log(`[manager] MCP assets deployed to ${targetDir}`);
|
|
5366
|
+
if (changedBasenames.length > 0) {
|
|
5367
|
+
log(`[manager] Bundle(s) updated: ${changedBasenames.join(", ")} \u2014 signalling running instances to restart`);
|
|
5368
|
+
restartRunningChannelMcps(changedBasenames);
|
|
5369
|
+
}
|
|
5158
5370
|
const localMcpPath = join2(targetDir, "index.js");
|
|
5159
5371
|
try {
|
|
5160
5372
|
const agentsDir = join2(homedir2(), ".augmented", "agents");
|