@integrity-labs/agt-cli 0.27.8 → 0.27.9-test.11
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/assets/impersonate-statusline.sh +7 -2
- package/dist/bin/agt.js +946 -129
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-YSBGIXJG.js → chunk-45I2X4M7.js} +361 -26
- package/dist/chunk-45I2X4M7.js.map +1 -0
- package/dist/{chunk-IB655E5U.js → chunk-N2EBL4PZ.js} +1086 -285
- package/dist/chunk-N2EBL4PZ.js.map +1 -0
- package/dist/{chunk-GN4XPQWJ.js → chunk-SJPAXXTW.js} +535 -94
- package/dist/chunk-SJPAXXTW.js.map +1 -0
- package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-OUGV74LY.js} +2 -2
- package/dist/lib/manager-worker.js +2071 -517
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +49 -455
- package/dist/mcp/index.js +232 -137
- package/dist/mcp/slack-channel.js +1379 -856
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +1264 -618
- package/dist/{persistent-session-ICYFLUAM.js → persistent-session-G3MR7JVI.js} +5 -3
- package/dist/responsiveness-probe-UHSBETBR.js +74 -0
- package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-GN4XPQWJ.js.map +0 -1
- package/dist/chunk-IB655E5U.js.map +0 -1
- package/dist/chunk-YSBGIXJG.js.map +0 -1
- package/dist/responsiveness-probe-WZNQ2762.js +0 -33
- package/dist/responsiveness-probe-WZNQ2762.js.map +0 -1
- /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
- /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-G3MR7JVI.js.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseDeliveryTarget,
|
|
10
10
|
registerFramework,
|
|
11
11
|
wrapScheduledTaskPrompt
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-45I2X4M7.js";
|
|
13
13
|
|
|
14
14
|
// ../../packages/core/dist/integrations/registry.js
|
|
15
15
|
var INTEGRATION_REGISTRY = [
|
|
@@ -136,6 +136,40 @@ var INTEGRATION_REGISTRY = [
|
|
|
136
136
|
docs_url: "https://docs.granola.ai/docs/api/mcp",
|
|
137
137
|
beta: true
|
|
138
138
|
},
|
|
139
|
+
{
|
|
140
|
+
id: "anchor-browser",
|
|
141
|
+
name: "Anchor Browser",
|
|
142
|
+
category: "workspace-productivity",
|
|
143
|
+
description: "Cloud browser for agents \u2014 drive any website that lacks an API (LinkedIn, Sales Navigator, supplier portals) via a hosted, stealth Chromium with persistent-login profiles. Wired as Anchor's HOSTED streamable-HTTP MCP at https://api.anchorbrowser.io/mcp.",
|
|
144
|
+
// ENG-5855: api-key header auth (NOT OAuth, NOT a local stdio package).
|
|
145
|
+
// The manager writes ANCHOR_BROWSER_API_KEY to .env.integrations from the
|
|
146
|
+
// stored api_key credential; the hosted MCP authenticates on the
|
|
147
|
+
// `anchor-api-key` header. The `anchor-session-id` header binds an
|
|
148
|
+
// authenticated profile session — its value is minted per-session by the
|
|
149
|
+
// manager (ENG-5857); until then `envDefaults` seeds it empty so
|
|
150
|
+
// stateless browsing works and no literal `${...}` placeholder ships.
|
|
151
|
+
// Tool surface (25 `anchor_*` tools) is the hosted MCP's, validated in
|
|
152
|
+
// the ENG-5854 spike (docs/spikes/eng-5854-anchor-browser-persistent-login.md).
|
|
153
|
+
supported_auth_types: ["api_key"],
|
|
154
|
+
capabilities: [
|
|
155
|
+
{ id: "anchor-browser:browse", name: "Browse & Read", description: "Navigate and read pages \u2014 snapshot, screenshot, page HTML, tabs, console, network requests, wait (anchor_navigate, anchor_snapshot, anchor_take_screenshot, anchor_get_body_html, anchor_tab_list, anchor_console_messages, anchor_network_requests, anchor_wait_for, anchor_navigate_back/forward)", access: "read" },
|
|
156
|
+
{ id: "anchor-browser:interact", name: "Interact", description: "Act on pages \u2014 click, type, hover, drag, select options, press keys, handle dialogs, upload files, resize, manage tabs (anchor_click, anchor_type, anchor_hover, anchor_drag, anchor_select_option, anchor_press_key, anchor_handle_dialog, anchor_file_upload, anchor_resize, anchor_tab_new/select/close, anchor_close)", access: "write" },
|
|
157
|
+
{ id: "anchor-browser:export", name: "Export & Codegen", description: "Save the current page as PDF and generate Playwright code for a scenario (anchor_pdf_save, anchor_generate_playwright_code)", access: "write" }
|
|
158
|
+
],
|
|
159
|
+
docs_url: "https://docs.anchorbrowser.io/introduction",
|
|
160
|
+
beta: true,
|
|
161
|
+
remoteMcp: {
|
|
162
|
+
type: "http",
|
|
163
|
+
url: "https://api.anchorbrowser.io/mcp",
|
|
164
|
+
headers: {
|
|
165
|
+
"anchor-api-key": "${ANCHOR_BROWSER_API_KEY}",
|
|
166
|
+
"anchor-session-id": "${ANCHOR_BROWSER_SESSION_ID}"
|
|
167
|
+
},
|
|
168
|
+
// ENG-5857 mints the real session id; default empty so the header
|
|
169
|
+
// resolves cleanly (no profile bound → ephemeral session) until then.
|
|
170
|
+
envDefaults: { ANCHOR_BROWSER_SESSION_ID: "" }
|
|
171
|
+
}
|
|
172
|
+
},
|
|
139
173
|
{
|
|
140
174
|
id: "postiz",
|
|
141
175
|
name: "Postiz",
|
|
@@ -174,7 +208,15 @@ var INTEGRATION_REGISTRY = [
|
|
|
174
208
|
{ id: "qmd:search", name: "Search Memory", description: "Semantic + keyword search over indexed memory files", access: "read" },
|
|
175
209
|
{ id: "qmd:get", name: "Get Memory", description: "Read memory files by path and line range", access: "read" }
|
|
176
210
|
],
|
|
177
|
-
beta: true
|
|
211
|
+
beta: true,
|
|
212
|
+
// ENG-5815: migrated from buildMcpJson's hardcoded if-block. qmd is
|
|
213
|
+
// the simplest of the four pre-data-driven entries — no env, no
|
|
214
|
+
// conditional logic, just `qmd mcp`. The byte-identical render is
|
|
215
|
+
// pinned by claudecode-qmd-data-driven.test.ts.
|
|
216
|
+
nativeMcp: {
|
|
217
|
+
command: "qmd",
|
|
218
|
+
args: ["mcp"]
|
|
219
|
+
}
|
|
178
220
|
},
|
|
179
221
|
{
|
|
180
222
|
id: "v0",
|
|
@@ -310,6 +352,35 @@ var INTEGRATION_REGISTRY = [
|
|
|
310
352
|
},
|
|
311
353
|
docs_url: "https://www.coderabbit.ai/cli"
|
|
312
354
|
},
|
|
355
|
+
{
|
|
356
|
+
id: "aws",
|
|
357
|
+
name: "AWS",
|
|
358
|
+
category: "infrastructure",
|
|
359
|
+
description: "Amazon Web Services \u2014 query AWS APIs (EC2, S3, IAM, Lambda, etc.) via AWS Labs' official AWS API MCP server",
|
|
360
|
+
supported_auth_types: ["api_key", "managed", "none"],
|
|
361
|
+
capabilities: [
|
|
362
|
+
{ id: "aws:read", name: "Read AWS Resources", description: "List and describe AWS resources across services (EC2, S3, IAM, Lambda, \u2026)", access: "read" },
|
|
363
|
+
{ id: "aws:write", name: "Write AWS Resources", description: "Create and update AWS resources. Pair with an aws-no-destructive-ops guardrail.", access: "write" }
|
|
364
|
+
],
|
|
365
|
+
docs_url: "https://github.com/awslabs/mcp/tree/main/src/aws-api-mcp-server",
|
|
366
|
+
beta: true,
|
|
367
|
+
// ENG-5815: first integration shipped purely via the data-driven
|
|
368
|
+
// path — buildMcpJson never grew an `aws` if-block. The AWS Labs
|
|
369
|
+
// AWS API MCP server runs through uvx (Python tooling), which the
|
|
370
|
+
// host bootstrap installs alongside python3. Credentials are
|
|
371
|
+
// resolved via the standard AWS_* env / shared credentials file
|
|
372
|
+
// chain on the host; the spec doesn't override them.
|
|
373
|
+
nativeMcp: {
|
|
374
|
+
command: "uvx",
|
|
375
|
+
args: ["awslabs.aws-api-mcp-server@latest"],
|
|
376
|
+
env: {
|
|
377
|
+
AWS_REGION: "{{empty_if_no_env.AWS_REGION}}",
|
|
378
|
+
AWS_PROFILE: "{{empty_if_no_env.AWS_PROFILE}}",
|
|
379
|
+
PATH: "{{process_env.PATH}}",
|
|
380
|
+
HOME: "{{process_env.HOME}}"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
313
384
|
{
|
|
314
385
|
id: "custom",
|
|
315
386
|
name: "Custom Integration",
|
|
@@ -2285,6 +2356,299 @@ function extractAllowedDomains(input) {
|
|
|
2285
2356
|
}
|
|
2286
2357
|
registerFramework(nemoClawAdapter);
|
|
2287
2358
|
|
|
2359
|
+
// ../../packages/core/dist/provisioning/mcp-config-guards.js
|
|
2360
|
+
import { chmodSync as chmodSync4, existsSync as existsSync4, readFileSync as readFileSync4, renameSync as renameSync3, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
2361
|
+
|
|
2362
|
+
// ../../packages/core/dist/provisioning/mcp-secret-lint.js
|
|
2363
|
+
var LITERAL_SECRET_PATTERNS = [
|
|
2364
|
+
// Slack bot token — `xoxb-<workspace>-<...>`
|
|
2365
|
+
{ name: "slack_bot_token", re: /^xoxb-/ },
|
|
2366
|
+
// Slack app-level token (Socket Mode) — `xapp-<...>`
|
|
2367
|
+
{ name: "slack_app_token", re: /^xapp-/ },
|
|
2368
|
+
// AGT host API key — `tlk_<...>` (see claudecode-plugin-augmented README).
|
|
2369
|
+
{ name: "agt_host_api_key", re: /^tlk_/ },
|
|
2370
|
+
// Composio / generic api-key prefix — `ak_<...>`
|
|
2371
|
+
{ name: "composio_api_key", re: /^ak_/ },
|
|
2372
|
+
// Telegram bot token — `<bot id>:AA<...>` (BotFather format). ENG-5901
|
|
2373
|
+
// PR 4: the original `\d{10}:AAE` (from the issue AC) was too narrow —
|
|
2374
|
+
// live tokens on agt-aws-1 carry `AA` + a varying third character
|
|
2375
|
+
// (don/scout/stirling all had AA-not-E tokens the lint and migration
|
|
2376
|
+
// missed). Bot ids are 8–12 digits; the token part always starts `AA`.
|
|
2377
|
+
{ name: "telegram_bot_token", re: /^\d{8,12}:AA[A-Za-z0-9_-]/ },
|
|
2378
|
+
// ENG-5901 extension beyond the original AC's five patterns: a literal
|
|
2379
|
+
// JWT (`eyJ...`) is the shape of a leaked AGT_API_KEY, which the
|
|
2380
|
+
// value-prefix patterns above would otherwise miss. Header values often
|
|
2381
|
+
// carry it behind `Bearer ` (or a copy-pasted `Authorization: Bearer `),
|
|
2382
|
+
// so those prefixes are optionally consumed (CodeRabbit #1731).
|
|
2383
|
+
// Templates (`Bearer ${AGT_API_KEY}`) and concrete non-secret values
|
|
2384
|
+
// (UUIDs, hosts) never put `eyJ` after the prefix, so this stays
|
|
2385
|
+
// false-positive-safe inside .mcp.json.
|
|
2386
|
+
{ name: "jwt_agt_api_key", re: /^(?:authorization:\s*)?(?:bearer\s+)?eyJ[A-Za-z0-9_-]+\./i }
|
|
2387
|
+
];
|
|
2388
|
+
function matchLiteralSecret(value) {
|
|
2389
|
+
for (const { name, re } of LITERAL_SECRET_PATTERNS) {
|
|
2390
|
+
if (re.test(value))
|
|
2391
|
+
return name;
|
|
2392
|
+
}
|
|
2393
|
+
return null;
|
|
2394
|
+
}
|
|
2395
|
+
function scanRecord(server, record, location, findings) {
|
|
2396
|
+
if (!record)
|
|
2397
|
+
return;
|
|
2398
|
+
for (const [field, value] of Object.entries(record)) {
|
|
2399
|
+
if (typeof value !== "string")
|
|
2400
|
+
continue;
|
|
2401
|
+
const pattern = matchLiteralSecret(value);
|
|
2402
|
+
if (pattern)
|
|
2403
|
+
findings.push({ server, field, location, pattern });
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function scanConfigForLiteralSecrets(config) {
|
|
2407
|
+
const findings = [];
|
|
2408
|
+
if (typeof config !== "object" || config === null)
|
|
2409
|
+
return findings;
|
|
2410
|
+
const servers = config.mcpServers;
|
|
2411
|
+
if (typeof servers !== "object" || servers === null)
|
|
2412
|
+
return findings;
|
|
2413
|
+
for (const [server, raw] of Object.entries(servers)) {
|
|
2414
|
+
if (typeof raw !== "object" || raw === null)
|
|
2415
|
+
continue;
|
|
2416
|
+
const entry = raw;
|
|
2417
|
+
scanRecord(server, entry.env, "env", findings);
|
|
2418
|
+
scanRecord(server, entry.headers, "header", findings);
|
|
2419
|
+
}
|
|
2420
|
+
return findings;
|
|
2421
|
+
}
|
|
2422
|
+
function formatLiteralSecretRejection(f) {
|
|
2423
|
+
return `[mcp-write] [literal-secret-rejected] field=${f.field} server=${f.server} location=${f.location} pattern=${f.pattern}`;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// ../../packages/core/dist/provisioning/mcp-config-guards.js
|
|
2427
|
+
var MCP_FILE_MODE = 384;
|
|
2428
|
+
var lastRejectionFingerprintByPath = /* @__PURE__ */ new Map();
|
|
2429
|
+
var REQUIRED_ENV_RULES_BY_SERVER = {
|
|
2430
|
+
"cloud-broker": [
|
|
2431
|
+
{ key: "AGT_HOST", mustBeConcrete: false },
|
|
2432
|
+
{ key: "AGT_API_KEY", mustBeConcrete: false },
|
|
2433
|
+
// ENG-4744: this is the bug shape — writer used to omit this
|
|
2434
|
+
// entirely, or render it as a literal `${AGT_AGENT_ID}` instead
|
|
2435
|
+
// of the agent's real UUID. The broker has no way to substitute
|
|
2436
|
+
// it post-spawn, so a placeholder here = silently broken agent.
|
|
2437
|
+
{ key: "AGT_AGENT_ID", mustBeConcrete: true }
|
|
2438
|
+
]
|
|
2439
|
+
};
|
|
2440
|
+
var PLACEHOLDER_RE = /\$\{[^}]+\}/;
|
|
2441
|
+
function validateRenderedMcpConfig(config) {
|
|
2442
|
+
const errors = [];
|
|
2443
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
2444
|
+
return {
|
|
2445
|
+
ok: false,
|
|
2446
|
+
errors: [
|
|
2447
|
+
{
|
|
2448
|
+
kind: "invalid_json_shape",
|
|
2449
|
+
server: "*",
|
|
2450
|
+
message: "config root must be a non-null object"
|
|
2451
|
+
}
|
|
2452
|
+
]
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
const root = config;
|
|
2456
|
+
if (root.mcpServers === void 0) {
|
|
2457
|
+
return { ok: true };
|
|
2458
|
+
}
|
|
2459
|
+
if (typeof root.mcpServers !== "object" || root.mcpServers === null) {
|
|
2460
|
+
return {
|
|
2461
|
+
ok: false,
|
|
2462
|
+
errors: [
|
|
2463
|
+
{
|
|
2464
|
+
kind: "invalid_json_shape",
|
|
2465
|
+
server: "*",
|
|
2466
|
+
message: "mcpServers must be an object"
|
|
2467
|
+
}
|
|
2468
|
+
]
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
if (Array.isArray(root.mcpServers)) {
|
|
2472
|
+
return {
|
|
2473
|
+
ok: false,
|
|
2474
|
+
errors: [
|
|
2475
|
+
{
|
|
2476
|
+
kind: "invalid_json_shape",
|
|
2477
|
+
server: "*",
|
|
2478
|
+
message: "mcpServers must be an object"
|
|
2479
|
+
}
|
|
2480
|
+
]
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
for (const [serverKey, raw] of Object.entries(root.mcpServers)) {
|
|
2484
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2485
|
+
errors.push({
|
|
2486
|
+
kind: "invalid_json_shape",
|
|
2487
|
+
server: serverKey,
|
|
2488
|
+
message: `entry must be an object`
|
|
2489
|
+
});
|
|
2490
|
+
continue;
|
|
2491
|
+
}
|
|
2492
|
+
const entry = raw;
|
|
2493
|
+
const entryRecord = entry;
|
|
2494
|
+
if (typeof entryRecord["url"] === "string") {
|
|
2495
|
+
const type = entryRecord["type"];
|
|
2496
|
+
if (type !== "http" && type !== "sse") {
|
|
2497
|
+
errors.push({
|
|
2498
|
+
kind: "remote_mcp_missing_type",
|
|
2499
|
+
server: serverKey,
|
|
2500
|
+
message: `url-based entry must include \`type: "http"\` or \`type: "sse"\` (Claude Code MCP schema requires it)`
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
const rules = REQUIRED_ENV_RULES_BY_SERVER[serverKey];
|
|
2505
|
+
if (rules) {
|
|
2506
|
+
const env = entry.env ?? {};
|
|
2507
|
+
for (const rule of rules) {
|
|
2508
|
+
const value = env[rule.key];
|
|
2509
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2510
|
+
errors.push({
|
|
2511
|
+
kind: "missing_required_env",
|
|
2512
|
+
server: serverKey,
|
|
2513
|
+
message: `missing required env key: ${rule.key}`
|
|
2514
|
+
});
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
if (rule.mustBeConcrete && PLACEHOLDER_RE.test(value)) {
|
|
2518
|
+
errors.push({
|
|
2519
|
+
kind: "unexpanded_placeholder",
|
|
2520
|
+
server: serverKey,
|
|
2521
|
+
message: `env.${rule.key} contains an unexpanded \${...} placeholder; expected a concrete value`
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
2528
|
+
}
|
|
2529
|
+
function formatValidationErrors(errors) {
|
|
2530
|
+
return errors.map((e) => `${e.server}:${e.kind}=${e.message}`).join("; ");
|
|
2531
|
+
}
|
|
2532
|
+
function safeWriteJsonAtomic(path, content, opts = {}) {
|
|
2533
|
+
const keepBackup = opts.keepBackup !== false;
|
|
2534
|
+
const rename = opts.renamer ?? renameSync3;
|
|
2535
|
+
const tmpPath = `${path}.new`;
|
|
2536
|
+
const bakPath = `${path}.bak`;
|
|
2537
|
+
let movedOriginalToBackup = false;
|
|
2538
|
+
try {
|
|
2539
|
+
writeFileSync4(tmpPath, content);
|
|
2540
|
+
if (opts.mode !== void 0) {
|
|
2541
|
+
chmodSync4(tmpPath, opts.mode);
|
|
2542
|
+
}
|
|
2543
|
+
} catch (err) {
|
|
2544
|
+
try {
|
|
2545
|
+
if (existsSync4(tmpPath))
|
|
2546
|
+
unlinkSync3(tmpPath);
|
|
2547
|
+
} catch {
|
|
2548
|
+
}
|
|
2549
|
+
throw err;
|
|
2550
|
+
}
|
|
2551
|
+
try {
|
|
2552
|
+
if (keepBackup && existsSync4(path)) {
|
|
2553
|
+
rename(path, bakPath);
|
|
2554
|
+
movedOriginalToBackup = true;
|
|
2555
|
+
}
|
|
2556
|
+
rename(tmpPath, path);
|
|
2557
|
+
} catch (err) {
|
|
2558
|
+
try {
|
|
2559
|
+
if (existsSync4(tmpPath))
|
|
2560
|
+
unlinkSync3(tmpPath);
|
|
2561
|
+
} catch {
|
|
2562
|
+
}
|
|
2563
|
+
if (movedOriginalToBackup && !existsSync4(path) && existsSync4(bakPath)) {
|
|
2564
|
+
try {
|
|
2565
|
+
renameSync3(bakPath, path);
|
|
2566
|
+
} catch {
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
throw err;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function safeWriteMcpJson(path, config) {
|
|
2573
|
+
const validation = validateRenderedMcpConfig(config);
|
|
2574
|
+
if (!validation.ok) {
|
|
2575
|
+
return { written: false, errors: validation.errors };
|
|
2576
|
+
}
|
|
2577
|
+
const secretFindings = scanConfigForLiteralSecrets(config);
|
|
2578
|
+
if (secretFindings.length > 0) {
|
|
2579
|
+
const fingerprint = secretFindings.map((f) => `${f.server}.${f.field}.${f.location}`).sort().join("|");
|
|
2580
|
+
if (lastRejectionFingerprintByPath.get(path) !== fingerprint) {
|
|
2581
|
+
lastRejectionFingerprintByPath.set(path, fingerprint);
|
|
2582
|
+
for (const f of secretFindings) {
|
|
2583
|
+
process.stderr.write(`${formatLiteralSecretRejection(f)}
|
|
2584
|
+
`);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
return {
|
|
2588
|
+
written: false,
|
|
2589
|
+
errors: secretFindings.map((f) => ({
|
|
2590
|
+
kind: "literal_secret",
|
|
2591
|
+
server: f.server,
|
|
2592
|
+
message: `literal secret in ${f.location} field '${f.field}' (pattern ${f.pattern}); expected a \${VAR} template`
|
|
2593
|
+
}))
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
lastRejectionFingerprintByPath.delete(path);
|
|
2597
|
+
safeWriteJsonAtomic(path, JSON.stringify(config, null, 2), { mode: MCP_FILE_MODE });
|
|
2598
|
+
return { written: true, errors: [] };
|
|
2599
|
+
}
|
|
2600
|
+
var SECRET_FIELD_NAME_RE = /TOKEN|KEY|SECRET|BEARER|PASSWORD/i;
|
|
2601
|
+
function collectSecretFields(config) {
|
|
2602
|
+
const out = /* @__PURE__ */ new Map();
|
|
2603
|
+
if (typeof config !== "object" || config === null)
|
|
2604
|
+
return out;
|
|
2605
|
+
const servers = config.mcpServers;
|
|
2606
|
+
if (typeof servers !== "object" || servers === null)
|
|
2607
|
+
return out;
|
|
2608
|
+
for (const [server, raw] of Object.entries(servers)) {
|
|
2609
|
+
if (typeof raw !== "object" || raw === null)
|
|
2610
|
+
continue;
|
|
2611
|
+
const entry = raw;
|
|
2612
|
+
for (const [block, location] of [
|
|
2613
|
+
[entry.env, "env"],
|
|
2614
|
+
[entry.headers, "header"]
|
|
2615
|
+
]) {
|
|
2616
|
+
if (!block)
|
|
2617
|
+
continue;
|
|
2618
|
+
for (const [field, value] of Object.entries(block)) {
|
|
2619
|
+
if (typeof value !== "string")
|
|
2620
|
+
continue;
|
|
2621
|
+
if (!SECRET_FIELD_NAME_RE.test(field))
|
|
2622
|
+
continue;
|
|
2623
|
+
out.set(`${server}\0${field}`, { location, value });
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return out;
|
|
2628
|
+
}
|
|
2629
|
+
function mcpMirrorParityErrors(provision2, project) {
|
|
2630
|
+
const a = collectSecretFields(provision2);
|
|
2631
|
+
const b = collectSecretFields(project);
|
|
2632
|
+
const mismatches = [];
|
|
2633
|
+
const keys = /* @__PURE__ */ new Set([...a.keys(), ...b.keys()]);
|
|
2634
|
+
for (const key of keys) {
|
|
2635
|
+
const [server, field] = key.split("\0");
|
|
2636
|
+
const pv = a.get(key);
|
|
2637
|
+
const pj = b.get(key);
|
|
2638
|
+
if (pv && !pj) {
|
|
2639
|
+
mismatches.push({ server, field, location: pv.location, reason: "missing-in-project" });
|
|
2640
|
+
} else if (!pv && pj) {
|
|
2641
|
+
mismatches.push({ server, field, location: pj.location, reason: "missing-in-provision" });
|
|
2642
|
+
} else if (pv && pj && pv.value !== pj.value) {
|
|
2643
|
+
mismatches.push({ server, field, location: pv.location, reason: "value-diverges" });
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return mismatches;
|
|
2647
|
+
}
|
|
2648
|
+
function formatMirrorMismatch(m) {
|
|
2649
|
+
return `[mcp-mirror] [parity-violation] server=${m.server} field=${m.field} location=${m.location} reason=${m.reason}`;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2288
2652
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/identity.js
|
|
2289
2653
|
function buildMemorySection(hasQmd) {
|
|
2290
2654
|
const recall = hasQmd ? `### Recall
|
|
@@ -2533,15 +2897,15 @@ later tick:
|
|
|
2533
2897
|
- For long-form output (>500 chars), lead with a one-line summary,
|
|
2534
2898
|
then a blank line, then the full content. Channel formatters
|
|
2535
2899
|
truncate gracefully but the lede always lands.
|
|
2536
|
-
- **\`
|
|
2537
|
-
|
|
2538
|
-
attempted but
|
|
2539
|
-
-
|
|
2540
|
-
|
|
2541
|
-
duplicate of another task, asker
|
|
2542
|
-
already current).
|
|
2543
|
-
|
|
2544
|
-
|
|
2900
|
+
- **\`kanban_move\` with \`status="failed"\`** (pass a \`notes\` reason)
|
|
2901
|
+
when something went wrong \u2014 missing access, credential failure, tool
|
|
2902
|
+
error. The work was attempted but couldn't complete.
|
|
2903
|
+
- **No-longer-needed work**: there is no "cancelled" status, so close it
|
|
2904
|
+
with **\`kanban_done\`** and a \`result\` that says why it wasn't
|
|
2905
|
+
needed (precondition no longer holds, duplicate of another task, asker
|
|
2906
|
+
changed their mind, data was already current). Do this generously \u2014
|
|
2907
|
+
it's cheaper than running unneeded work, and the result line tells the
|
|
2908
|
+
user you consciously stood it down rather than silently dropping it.
|
|
2545
2909
|
- **\`kanban_update\` with notes** if you're blocked but might unblock
|
|
2546
2910
|
later; leave the row in \`in_progress\` and pick up other work.
|
|
2547
2911
|
|
|
@@ -2644,42 +3008,6 @@ If your charter doesn't authorise team-scope skill writes
|
|
|
2644
3008
|
refused server-side \u2014 surface that error to the user rather than
|
|
2645
3009
|
falling back to a local-disk write.
|
|
2646
3010
|
|
|
2647
|
-
`;
|
|
2648
|
-
}
|
|
2649
|
-
function buildProgressHeartbeatSection(resolvedChannels) {
|
|
2650
|
-
const hasSlack = !!resolvedChannels?.includes("slack");
|
|
2651
|
-
const hasDirectChat = !!resolvedChannels?.includes("direct-chat");
|
|
2652
|
-
if (!hasSlack && !hasDirectChat)
|
|
2653
|
-
return "";
|
|
2654
|
-
const bullets = [];
|
|
2655
|
-
if (hasSlack) {
|
|
2656
|
-
bullets.push("- **Slack threads:** `slack_progress_start` (channel + thread_ts from the inbound `<channel>` tag, label = short task name) \u2192 `slack_progress_update` between steps (pass `step_label`, optional `step_current`/`step_total`) \u2192 `slack_progress_complete` (summary) or `slack_progress_fail` (one-sentence reason).");
|
|
2657
|
-
}
|
|
2658
|
-
if (hasDirectChat) {
|
|
2659
|
-
bullets.push("- **Direct Chat sessions:** `direct_chat_progress_start` (session_id from the inbound tag, label) \u2192 `direct_chat_progress_update` \u2192 `direct_chat_progress_complete` / `_fail`.");
|
|
2660
|
-
}
|
|
2661
|
-
return `
|
|
2662
|
-
## Progress heartbeats during multi-step work
|
|
2663
|
-
|
|
2664
|
-
When a task involves **\u22653 tool calls or is likely to take >5 seconds**
|
|
2665
|
-
of tool work, open an in-place progress anchor instead of streaming
|
|
2666
|
-
"working on\u2026" / "still working\u2026" / "done" messages. The operator sees
|
|
2667
|
-
one message that ticks forward as you advance, not a thread flooded
|
|
2668
|
-
with status pings.
|
|
2669
|
-
|
|
2670
|
-
${bullets.join("\n")}
|
|
2671
|
-
|
|
2672
|
-
The state machine debounces updates to roughly one API call per second
|
|
2673
|
-
per channel, so it's safe to call \`_update\` after every step rather
|
|
2674
|
-
than batching. Always end with \`_complete\` or \`_fail\` \u2014 leaving an
|
|
2675
|
-
anchor in the "working" state means a future stale-state sweep flips it
|
|
2676
|
-
to \u26A0\uFE0F "agent unresponsive" once the heartbeat goes cold.
|
|
2677
|
-
|
|
2678
|
-
**Skip the heartbeat for one-shot replies.** A single \`slack.reply\` /
|
|
2679
|
-
\`direct_chat.reply\` is still the right pattern when there's no
|
|
2680
|
-
multi-step work to surface. Heartbeats are for the case where you'd
|
|
2681
|
-
otherwise emit several intermediate messages.
|
|
2682
|
-
|
|
2683
3011
|
`;
|
|
2684
3012
|
}
|
|
2685
3013
|
function buildPersonalitySection(seed) {
|
|
@@ -2900,6 +3228,37 @@ function formatConfigLines(config) {
|
|
|
2900
3228
|
return ` - ${k}: ${rendered}`;
|
|
2901
3229
|
});
|
|
2902
3230
|
}
|
|
3231
|
+
var EMAIL_DOMAIN_RESTRICT_DEF = "email.domain_restrict";
|
|
3232
|
+
function stringList(value) {
|
|
3233
|
+
return Array.isArray(value) ? value.filter((d) => typeof d === "string") : [];
|
|
3234
|
+
}
|
|
3235
|
+
function formatEmailDomainConfigLines(config) {
|
|
3236
|
+
const mode = typeof config.mode === "string" ? config.mode : void 0;
|
|
3237
|
+
const lines = [];
|
|
3238
|
+
if (mode)
|
|
3239
|
+
lines.push(` - mode: ${mode}`);
|
|
3240
|
+
if (mode === "allowlist") {
|
|
3241
|
+
const allowed = stringList(config.allowed_domains);
|
|
3242
|
+
lines.push(` - allowed_domains: ${allowed.length ? allowed.join(", ") : "(none \u2014 no external email permitted)"}`);
|
|
3243
|
+
} else if (mode === "blocklist") {
|
|
3244
|
+
const blocked = stringList(config.blocked_domains);
|
|
3245
|
+
lines.push(` - blocked_domains: ${blocked.length ? blocked.join(", ") : "(none)"}`);
|
|
3246
|
+
} else if (mode === "internal_only") {
|
|
3247
|
+
lines.push(` - only your organization's own email domain is permitted`);
|
|
3248
|
+
}
|
|
3249
|
+
lines.push(` *Advisory: honor this restriction \u2014 it is guidance in your instructions, not a hard block at send time.*`);
|
|
3250
|
+
return lines;
|
|
3251
|
+
}
|
|
3252
|
+
var CALENDAR_CONFIDENTIALITY_DEF = "calendar.confidentiality";
|
|
3253
|
+
function formatCalendarConfidentialityLines(config) {
|
|
3254
|
+
const stage = typeof config.stage === "string" ? config.stage : "shadow";
|
|
3255
|
+
const lines = [` - stage: ${stage}`];
|
|
3256
|
+
lines.push(` *Cross-turn re-query: when answering a different person about your principal's calendar, ALWAYS re-query the calendar tool \u2014 never reuse meeting details remembered from an earlier turn that involved a different recipient. The tool's response is filtered per-recipient at the API layer; relying on memory bypasses the filter.*`);
|
|
3257
|
+
if (stage !== "enforce") {
|
|
3258
|
+
lines.push(` *Currently observe-only \u2014 the API records redaction decisions to guardrail_audit_log but returns calendar responses unchanged. Treat the policy as authoritative anyway; the runtime flip is operator-side.*`);
|
|
3259
|
+
}
|
|
3260
|
+
return lines;
|
|
3261
|
+
}
|
|
2903
3262
|
function renderGuardrailBullet(g) {
|
|
2904
3263
|
const lines = [];
|
|
2905
3264
|
const header = `- **${g.displayName}** (${g.category}, from ${g.source})`;
|
|
@@ -2907,16 +3266,26 @@ function renderGuardrailBullet(g) {
|
|
|
2907
3266
|
if (g.description?.trim()) {
|
|
2908
3267
|
lines.push(` ${g.description.trim()}`);
|
|
2909
3268
|
}
|
|
2910
|
-
lines.push(...formatConfigLines(g.config));
|
|
3269
|
+
lines.push(...g.definitionId === EMAIL_DOMAIN_RESTRICT_DEF ? formatEmailDomainConfigLines(g.config) : g.definitionId === CALENDAR_CONFIDENTIALITY_DEF ? formatCalendarConfidentialityLines(g.config) : formatConfigLines(g.config));
|
|
2911
3270
|
if (g.overrideApplied && g.overrideReason?.trim()) {
|
|
2912
3271
|
lines.push(` *Override applied: ${g.overrideReason.trim()}*`);
|
|
2913
3272
|
}
|
|
2914
3273
|
return lines.join("\n");
|
|
2915
3274
|
}
|
|
3275
|
+
function effectiveCalendarEnforcement(g) {
|
|
3276
|
+
if (g.definitionId !== CALENDAR_CONFIDENTIALITY_DEF)
|
|
3277
|
+
return g.enforcement;
|
|
3278
|
+
const stage = typeof g.config?.["stage"] === "string" ? g.config["stage"] : "shadow";
|
|
3279
|
+
if (stage === "enforce")
|
|
3280
|
+
return "enforce";
|
|
3281
|
+
if (stage === "warn" && g.enforcement !== "enforce")
|
|
3282
|
+
return "warn";
|
|
3283
|
+
return g.enforcement;
|
|
3284
|
+
}
|
|
2916
3285
|
function buildGuardrailsSection(guardrails) {
|
|
2917
3286
|
if (!guardrails || guardrails.length === 0)
|
|
2918
3287
|
return "";
|
|
2919
|
-
const active = guardrails.filter((g) => g.enforcement !== "disabled");
|
|
3288
|
+
const active = guardrails.map((g) => ({ ...g, enforcement: effectiveCalendarEnforcement(g) })).filter((g) => g.enforcement !== "disabled");
|
|
2920
3289
|
if (active.length === 0)
|
|
2921
3290
|
return "";
|
|
2922
3291
|
const enforce = active.filter((g) => g.enforcement === "enforce");
|
|
@@ -2992,34 +3361,73 @@ acknowledge before you start.
|
|
|
2992
3361
|
- **FAST (< 60s):** handle inline. Reply via the channel tool
|
|
2993
3362
|
(slack.reply / telegram.reply / directchat.reply) and end your turn.
|
|
2994
3363
|
|
|
2995
|
-
- **SLOW (\u2265 60s):**
|
|
3364
|
+
- **SLOW (\u2265 60s):** acknowledge first, then handle inline.
|
|
2996
3365
|
1. Send a one-line acknowledgement via the channel tool \u2014 short, warm,
|
|
2997
3366
|
and tell the user you'll come back. Example shape (don't copy verbatim,
|
|
2998
3367
|
match your voice): "On it \u2014 this'll take a minute or two, I'll ping
|
|
2999
3368
|
when it's done."
|
|
3000
|
-
2.
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3369
|
+
2. Do the work yourself in this same session. Use whatever tools you
|
|
3370
|
+
need (MCP, skills, file reads, etc.) \u2014 your parent session has the
|
|
3371
|
+
full MCP surface bound.
|
|
3372
|
+
3. Reply with the result via the channel tool (\`slack.reply\` /
|
|
3373
|
+
\`telegram.reply\` / \`directchat.reply\`), addressing the same thread
|
|
3374
|
+
/ chat / conversation you acknowledged in step 1.
|
|
3375
|
+
|
|
3376
|
+
> **Why inline and not sub-agent dispatch right now:** there is an
|
|
3377
|
+
> upstream Claude Code bug
|
|
3378
|
+
> ([anthropics/claude-code#64909](https://github.com/anthropics/claude-code/issues/64909))
|
|
3379
|
+
> where sub-agents dispatched via the Task tool with an explicit
|
|
3380
|
+
> \`tools:\` allowlist (which is the shape \`channel-message-handler\`
|
|
3381
|
+
> uses) get an **empty MCP tool registry** \u2014 every \`mcp__*\` call
|
|
3382
|
+
> returns "No such tool available", including the channel reply tools.
|
|
3383
|
+
> Dispatching a slow channel reply to \`channel-message-handler\` will
|
|
3384
|
+
> therefore silently fail to land: you'd post the one-line ack, the
|
|
3385
|
+
> sub-agent would do the analysis fine, but its \`slack.reply\` call
|
|
3386
|
+
> would error and the user would never see the substantive reply.
|
|
3387
|
+
> Empirically confirmed 2026-06-03 with a 6-tool probe: 0/6 MCP tools
|
|
3388
|
+
> bound inside \`channel-message-handler\`. Until Anthropic ships the
|
|
3389
|
+
> fix, handling slow channel replies inline is the only working path.
|
|
3390
|
+
|
|
3391
|
+
**Why this triage decision still matters more than any other instruction
|
|
3392
|
+
below:** if you skip the acknowledgement and just dive into slow work
|
|
3393
|
+
silently, operators send follow-up messages that queue behind you
|
|
3394
|
+
wondering whether you got the original. The ack-first-then-work pattern
|
|
3395
|
+
keeps users oriented. The kanban-tracking-link convention,
|
|
3396
|
+
work-management "create a task" guidance, and Slack reply patterns ALL
|
|
3397
|
+
apply to whatever you post \u2014 the ack and the eventual full reply alike.
|
|
3398
|
+
|
|
3399
|
+
If the work turns out to be unexpectedly slow after you started inline
|
|
3400
|
+
without acknowledging (because you thought it was a FAST request), post
|
|
3401
|
+
a quick "this is taking longer than I expected, still working" line
|
|
3402
|
+
rather than going silent. Operators care about responsiveness, not
|
|
3403
|
+
consistency.
|
|
3404
|
+
|
|
3405
|
+
## Background dispatch for non-channel work
|
|
3406
|
+
|
|
3407
|
+
For background tool work that **isn't** a channel reply \u2014 multi-step data
|
|
3408
|
+
pulls, CRM enrichments, research workflows, cross-MCP orchestration \u2014 use
|
|
3409
|
+
\`subagent_type: general-purpose\` (Anthropic's built-in). It inherits the
|
|
3410
|
+
full MCP tool surface from this session and reliably binds every
|
|
3411
|
+
\`mcp__*\` server you have available.
|
|
3412
|
+
|
|
3413
|
+
**Why not \`augmented-worker\` for now:** there is an upstream Claude Code
|
|
3414
|
+
bug ([anthropics/claude-code#64909](https://github.com/anthropics/claude-code/issues/64909))
|
|
3415
|
+
where sub-agents with an explicit \`tools:\` allowlist get an empty MCP
|
|
3416
|
+
tool registry \u2014 every \`mcp__*\` call returns "No such tool available."
|
|
3417
|
+
\`general-purpose\` uses \`tools: *\` (inherit-all) and escapes the bug.
|
|
3418
|
+
Once Anthropic ships the fix, \`augmented-worker\` becomes preferred again
|
|
3419
|
+
(restricted tool surface for safety + working MCP binding); the dispatch
|
|
3420
|
+
recommendation here will flip back automatically.
|
|
3421
|
+
|
|
3422
|
+
For slow **channel** replies, see \xA7 FIRST ACTION above \u2014 those are
|
|
3423
|
+
currently handled inline (not dispatched) because \`channel-message-handler\`
|
|
3424
|
+
shares the explicit-allowlist shape and so suffers the same upstream
|
|
3425
|
+
bug. Empirically confirmed 2026-06-03 on agt-aws-1 with a 6-tool probe:
|
|
3426
|
+
0/6 MCP tools bound inside \`channel-message-handler\` (matching the
|
|
3427
|
+
\`augmented-worker\` result). When Anthropic ships the upstream fix, both
|
|
3428
|
+
named sub-agents will work again and the FIRST ACTION triage will switch
|
|
3429
|
+
back to dispatch.
|
|
3430
|
+
|
|
3023
3431
|
${activeTasksSection}${personalitySection}## Identity
|
|
3024
3432
|
|
|
3025
3433
|
- Code Name: ${frontmatter.code_name}
|
|
@@ -3028,13 +3436,30 @@ ${activeTasksSection}${personalitySection}## Identity
|
|
|
3028
3436
|
- Risk Tier: ${frontmatter.risk_tier}
|
|
3029
3437
|
- Timezone: ${timezone?.trim() || "UTC"}
|
|
3030
3438
|
- Channels: ${channelList}
|
|
3439
|
+
|
|
3440
|
+
> **What the Channels list above means** (ENG-5851): \`Channels:\`
|
|
3441
|
+
> enumerates the messaging **protocols** you may use \u2014 \`slack\`,
|
|
3442
|
+
> \`telegram\`, \`msteams\`, etc. It is **not** a list of specific
|
|
3443
|
+
> Slack channels / Telegram chats / Teams threads you're approved to
|
|
3444
|
+
> post in. There is no per-recipient "approved channels" allowlist
|
|
3445
|
+
> anywhere in this platform; you decide where to post based on the
|
|
3446
|
+
> task and the conversation context. If you call a send tool and the
|
|
3447
|
+
> target channel rejects it (e.g. Slack returns \`not_in_channel\` /
|
|
3448
|
+
> \`channel_not_found\`, or Teams returns \`team_not_allowed\`), surface
|
|
3449
|
+
> the error to the user with the recovery action \u2014 typically asking
|
|
3450
|
+
> them to run \`/invite @<your bot handle>\` in the channel so you can
|
|
3451
|
+
> post there next time. Do **not** refuse a posting request on the
|
|
3452
|
+
> grounds that the channel "isn't on the allowlist" \u2014 that conflation
|
|
3453
|
+
> is the bug ENG-5851 was filed to fix.
|
|
3031
3454
|
${resolvedChannels?.includes("slack") ? `
|
|
3032
3455
|
## Slack
|
|
3033
3456
|
|
|
3034
3457
|
You have a Slack MCP server connected. **First, see \xA7 FIRST ACTION on
|
|
3035
3458
|
every channel message: triage** at the top of this document \u2014 decide
|
|
3036
|
-
fast vs slow before anything else,
|
|
3037
|
-
|
|
3459
|
+
fast vs slow before anything else, then acknowledge inline before
|
|
3460
|
+
diving into slow work (sub-agent dispatch for channel replies is
|
|
3461
|
+
currently disabled due to an upstream Claude Code bug; see the FIRST
|
|
3462
|
+
ACTION section for the full rationale).
|
|
3038
3463
|
|
|
3039
3464
|
For fast requests, respond directly in the conversation. You can also
|
|
3040
3465
|
proactively use:
|
|
@@ -3192,15 +3617,21 @@ When you receive a request via any channel (Slack, Telegram, direct chat):
|
|
|
3192
3617
|
3. **Create the kanban task** with kanban.add. Title should be specific
|
|
3193
3618
|
enough to be self-explanatory \u2014 "Pull Linear ENG sprint velocity for
|
|
3194
3619
|
this fortnight" beats "Linear stats".
|
|
3195
|
-
4. Reply in the channel thread
|
|
3620
|
+
4. Reply in the channel thread with a brief acknowledgement that names the
|
|
3621
|
+
created task: "On it \u2014 <task title>".
|
|
3622
|
+
- **On Slack, do NOT paste the kanban URL.** A progress card with an
|
|
3623
|
+
**Open card** button is posted into the thread automatically when the
|
|
3624
|
+
task is channel-sourced \u2014 a pasted link on top of it is duplicate noise.
|
|
3625
|
+
- On channels without a progress card (Telegram, direct chat), include
|
|
3626
|
+
the tracking link: "On it \u2014 tracking here: ${kanbanUrl ?? "my kanban board"}".
|
|
3196
3627
|
5. Move the task to in_progress with kanban.move
|
|
3197
3628
|
6. Do the work
|
|
3198
3629
|
7. Mark done with kanban.done including a result summary
|
|
3199
3630
|
8. Reply in the channel thread with the result
|
|
3200
3631
|
|
|
3201
3632
|
Everything that isn't exempt gets a task \u2014 but only after the request is
|
|
3202
|
-
clear. Don't bury a clarifying question underneath
|
|
3203
|
-
ask the question alone, no
|
|
3633
|
+
clear. Don't bury a clarifying question underneath an "on it" acknowledgement;
|
|
3634
|
+
ask the question alone, no task created yet, and let the user answer before
|
|
3204
3635
|
anything goes on the board.
|
|
3205
3636
|
|
|
3206
3637
|
When asked about existing work, tasks, or what you've been doing \u2014 call kanban.list
|
|
@@ -3325,163 +3756,90 @@ The marginal cost of completeness is near zero. Do the whole thing.
|
|
|
3325
3756
|
${frontmatter.environment === "prod" ? "- Production environment: exercise extra caution with all operations.\n" : ""}`;
|
|
3326
3757
|
}
|
|
3327
3758
|
|
|
3759
|
+
// ../../packages/core/dist/provisioning/env-integrations-file.js
|
|
3760
|
+
function shellQuote(value) {
|
|
3761
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3762
|
+
}
|
|
3763
|
+
var CHANNEL_SECRET_ENV_KEYS = [
|
|
3764
|
+
"SLACK_BOT_TOKEN",
|
|
3765
|
+
"SLACK_APP_TOKEN",
|
|
3766
|
+
"TELEGRAM_BOT_TOKEN",
|
|
3767
|
+
"MSTEAMS_CLIENT_SECRET"
|
|
3768
|
+
];
|
|
3769
|
+
var MCP_SERVER_SECRET_ENV_KEYS = [
|
|
3770
|
+
"COMPOSIO_API_KEY",
|
|
3771
|
+
"PIPEDREAM_CLIENT_SECRET"
|
|
3772
|
+
];
|
|
3773
|
+
var PRESERVED_ENV_KEYS = [
|
|
3774
|
+
...CHANNEL_SECRET_ENV_KEYS,
|
|
3775
|
+
...MCP_SERVER_SECRET_ENV_KEYS
|
|
3776
|
+
];
|
|
3777
|
+
var HEADER = "# Augmented integrations \u2014 auto-generated, do not edit";
|
|
3778
|
+
function parseEnvFileEntries(content) {
|
|
3779
|
+
const out = /* @__PURE__ */ new Map();
|
|
3780
|
+
for (const line of content.split("\n")) {
|
|
3781
|
+
if (!line || line.startsWith("#") || !line.includes("="))
|
|
3782
|
+
continue;
|
|
3783
|
+
const eqIdx = line.indexOf("=");
|
|
3784
|
+
out.set(line.slice(0, eqIdx), line.slice(eqIdx + 1));
|
|
3785
|
+
}
|
|
3786
|
+
return out;
|
|
3787
|
+
}
|
|
3788
|
+
function renderEnvIntegrations(entries) {
|
|
3789
|
+
const lines = [HEADER];
|
|
3790
|
+
for (const [key, rendered] of entries)
|
|
3791
|
+
lines.push(`${key}=${rendered}`);
|
|
3792
|
+
return lines.join("\n") + "\n";
|
|
3793
|
+
}
|
|
3794
|
+
function mergeEnvIntegrationsContent(existing, args) {
|
|
3795
|
+
const current = existing === null ? /* @__PURE__ */ new Map() : parseEnvFileEntries(existing);
|
|
3796
|
+
let next;
|
|
3797
|
+
if (args.mode === "upsert") {
|
|
3798
|
+
next = new Map(current);
|
|
3799
|
+
for (const [key, raw] of Object.entries(args.updates)) {
|
|
3800
|
+
if (raw === null)
|
|
3801
|
+
next.delete(key);
|
|
3802
|
+
else
|
|
3803
|
+
next.set(key, shellQuote(raw));
|
|
3804
|
+
}
|
|
3805
|
+
} else {
|
|
3806
|
+
next = /* @__PURE__ */ new Map();
|
|
3807
|
+
for (const [key, raw] of Object.entries(args.updates)) {
|
|
3808
|
+
if (raw !== null)
|
|
3809
|
+
next.set(key, shellQuote(raw));
|
|
3810
|
+
}
|
|
3811
|
+
const preserve = args.preserveKeys ?? PRESERVED_ENV_KEYS;
|
|
3812
|
+
for (const key of preserve) {
|
|
3813
|
+
if (key in args.updates)
|
|
3814
|
+
continue;
|
|
3815
|
+
if (!next.has(key) && current.has(key)) {
|
|
3816
|
+
next.set(key, current.get(key));
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
return renderEnvIntegrations(next);
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3328
3823
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
|
|
3329
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, chmodSync as
|
|
3824
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, chmodSync as chmodSync5, readdirSync, rmSync, copyFileSync } from "fs";
|
|
3330
3825
|
import { join as join4, relative, dirname as dirname4 } from "path";
|
|
3331
3826
|
import { homedir as homedir3 } from "os";
|
|
3332
3827
|
import { execFile as execFile3 } from "child_process";
|
|
3333
3828
|
|
|
3334
|
-
// ../../packages/core/dist/provisioning/mcp-config-guards.js
|
|
3335
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync as renameSync3, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
3336
|
-
var REQUIRED_ENV_RULES_BY_SERVER = {
|
|
3337
|
-
"cloud-broker": [
|
|
3338
|
-
{ key: "AGT_HOST", mustBeConcrete: false },
|
|
3339
|
-
{ key: "AGT_API_KEY", mustBeConcrete: false },
|
|
3340
|
-
// ENG-4744: this is the bug shape — writer used to omit this
|
|
3341
|
-
// entirely, or render it as a literal `${AGT_AGENT_ID}` instead
|
|
3342
|
-
// of the agent's real UUID. The broker has no way to substitute
|
|
3343
|
-
// it post-spawn, so a placeholder here = silently broken agent.
|
|
3344
|
-
{ key: "AGT_AGENT_ID", mustBeConcrete: true }
|
|
3345
|
-
]
|
|
3346
|
-
};
|
|
3347
|
-
var PLACEHOLDER_RE = /\$\{[^}]+\}/;
|
|
3348
|
-
function validateRenderedMcpConfig(config) {
|
|
3349
|
-
const errors = [];
|
|
3350
|
-
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
3351
|
-
return {
|
|
3352
|
-
ok: false,
|
|
3353
|
-
errors: [
|
|
3354
|
-
{
|
|
3355
|
-
kind: "invalid_json_shape",
|
|
3356
|
-
server: "*",
|
|
3357
|
-
message: "config root must be a non-null object"
|
|
3358
|
-
}
|
|
3359
|
-
]
|
|
3360
|
-
};
|
|
3361
|
-
}
|
|
3362
|
-
const root = config;
|
|
3363
|
-
if (root.mcpServers === void 0) {
|
|
3364
|
-
return { ok: true };
|
|
3365
|
-
}
|
|
3366
|
-
if (typeof root.mcpServers !== "object" || root.mcpServers === null) {
|
|
3367
|
-
return {
|
|
3368
|
-
ok: false,
|
|
3369
|
-
errors: [
|
|
3370
|
-
{
|
|
3371
|
-
kind: "invalid_json_shape",
|
|
3372
|
-
server: "*",
|
|
3373
|
-
message: "mcpServers must be an object"
|
|
3374
|
-
}
|
|
3375
|
-
]
|
|
3376
|
-
};
|
|
3377
|
-
}
|
|
3378
|
-
if (Array.isArray(root.mcpServers)) {
|
|
3379
|
-
return {
|
|
3380
|
-
ok: false,
|
|
3381
|
-
errors: [
|
|
3382
|
-
{
|
|
3383
|
-
kind: "invalid_json_shape",
|
|
3384
|
-
server: "*",
|
|
3385
|
-
message: "mcpServers must be an object"
|
|
3386
|
-
}
|
|
3387
|
-
]
|
|
3388
|
-
};
|
|
3389
|
-
}
|
|
3390
|
-
for (const [serverKey, raw] of Object.entries(root.mcpServers)) {
|
|
3391
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
3392
|
-
errors.push({
|
|
3393
|
-
kind: "invalid_json_shape",
|
|
3394
|
-
server: serverKey,
|
|
3395
|
-
message: `entry must be an object`
|
|
3396
|
-
});
|
|
3397
|
-
continue;
|
|
3398
|
-
}
|
|
3399
|
-
const entry = raw;
|
|
3400
|
-
const entryRecord = entry;
|
|
3401
|
-
if (typeof entryRecord["url"] === "string") {
|
|
3402
|
-
const type = entryRecord["type"];
|
|
3403
|
-
if (type !== "http" && type !== "sse") {
|
|
3404
|
-
errors.push({
|
|
3405
|
-
kind: "remote_mcp_missing_type",
|
|
3406
|
-
server: serverKey,
|
|
3407
|
-
message: `url-based entry must include \`type: "http"\` or \`type: "sse"\` (Claude Code MCP schema requires it)`
|
|
3408
|
-
});
|
|
3409
|
-
}
|
|
3410
|
-
}
|
|
3411
|
-
const rules = REQUIRED_ENV_RULES_BY_SERVER[serverKey];
|
|
3412
|
-
if (rules) {
|
|
3413
|
-
const env = entry.env ?? {};
|
|
3414
|
-
for (const rule of rules) {
|
|
3415
|
-
const value = env[rule.key];
|
|
3416
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
3417
|
-
errors.push({
|
|
3418
|
-
kind: "missing_required_env",
|
|
3419
|
-
server: serverKey,
|
|
3420
|
-
message: `missing required env key: ${rule.key}`
|
|
3421
|
-
});
|
|
3422
|
-
continue;
|
|
3423
|
-
}
|
|
3424
|
-
if (rule.mustBeConcrete && PLACEHOLDER_RE.test(value)) {
|
|
3425
|
-
errors.push({
|
|
3426
|
-
kind: "unexpanded_placeholder",
|
|
3427
|
-
server: serverKey,
|
|
3428
|
-
message: `env.${rule.key} contains an unexpanded \${...} placeholder; expected a concrete value`
|
|
3429
|
-
});
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
3435
|
-
}
|
|
3436
|
-
function formatValidationErrors(errors) {
|
|
3437
|
-
return errors.map((e) => `${e.server}:${e.kind}=${e.message}`).join("; ");
|
|
3438
|
-
}
|
|
3439
|
-
function safeWriteJsonAtomic(path, content, opts = {}) {
|
|
3440
|
-
const keepBackup = opts.keepBackup !== false;
|
|
3441
|
-
const rename = opts.renamer ?? renameSync3;
|
|
3442
|
-
const tmpPath = `${path}.new`;
|
|
3443
|
-
const bakPath = `${path}.bak`;
|
|
3444
|
-
let movedOriginalToBackup = false;
|
|
3445
|
-
try {
|
|
3446
|
-
writeFileSync4(tmpPath, content);
|
|
3447
|
-
} catch (err) {
|
|
3448
|
-
throw err;
|
|
3449
|
-
}
|
|
3450
|
-
try {
|
|
3451
|
-
if (keepBackup && existsSync4(path)) {
|
|
3452
|
-
rename(path, bakPath);
|
|
3453
|
-
movedOriginalToBackup = true;
|
|
3454
|
-
}
|
|
3455
|
-
rename(tmpPath, path);
|
|
3456
|
-
} catch (err) {
|
|
3457
|
-
try {
|
|
3458
|
-
if (existsSync4(tmpPath))
|
|
3459
|
-
unlinkSync3(tmpPath);
|
|
3460
|
-
} catch {
|
|
3461
|
-
}
|
|
3462
|
-
if (movedOriginalToBackup && !existsSync4(path) && existsSync4(bakPath)) {
|
|
3463
|
-
try {
|
|
3464
|
-
renameSync3(bakPath, path);
|
|
3465
|
-
} catch {
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
throw err;
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
function safeWriteMcpJson(path, config) {
|
|
3472
|
-
const validation = validateRenderedMcpConfig(config);
|
|
3473
|
-
if (!validation.ok) {
|
|
3474
|
-
return { written: false, errors: validation.errors };
|
|
3475
|
-
}
|
|
3476
|
-
safeWriteJsonAtomic(path, JSON.stringify(config, null, 2));
|
|
3477
|
-
return { written: true, errors: [] };
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
3829
|
// ../../packages/core/dist/provisioning/remote-mcp.js
|
|
3481
3830
|
function envVarForToken(definitionId) {
|
|
3482
3831
|
return `${definitionId.replace(/-/g, "_").toUpperCase()}_ACCESS_TOKEN`;
|
|
3483
3832
|
}
|
|
3484
3833
|
function buildRemoteMcpEntry(definitionId) {
|
|
3834
|
+
const def = INTEGRATION_REGISTRY.find((d) => d.id === definitionId);
|
|
3835
|
+
if (def?.remoteMcp) {
|
|
3836
|
+
const spec = def.remoteMcp;
|
|
3837
|
+
return {
|
|
3838
|
+
type: spec.type ?? "http",
|
|
3839
|
+
url: spec.url,
|
|
3840
|
+
...spec.headers ? { headers: { ...spec.headers } } : {}
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3485
3843
|
const provider = OAUTH_PROVIDERS[definitionId];
|
|
3486
3844
|
if (!provider?.mcpUrl)
|
|
3487
3845
|
return null;
|
|
@@ -3494,11 +3852,158 @@ function buildRemoteMcpEntry(definitionId) {
|
|
|
3494
3852
|
};
|
|
3495
3853
|
}
|
|
3496
3854
|
|
|
3855
|
+
// ../../packages/core/dist/provisioning/native-mcp.js
|
|
3856
|
+
function buildNativeMcpEntry(spec, ctx) {
|
|
3857
|
+
const resolvedCommand = resolveTemplate(spec.command, ctx);
|
|
3858
|
+
if (resolvedCommand.omit) {
|
|
3859
|
+
throw new Error("NativeMcpSpec: empty_if_no_env is only valid in env values (not in `command`)");
|
|
3860
|
+
}
|
|
3861
|
+
const command = resolvedCommand.value;
|
|
3862
|
+
const args = spec.args.map((a, i) => {
|
|
3863
|
+
const resolved = resolveTemplate(a, ctx);
|
|
3864
|
+
if (resolved.omit) {
|
|
3865
|
+
throw new Error(`NativeMcpSpec: empty_if_no_env is only valid in env values (not in args[${i}])`);
|
|
3866
|
+
}
|
|
3867
|
+
return resolved.value;
|
|
3868
|
+
});
|
|
3869
|
+
if (spec.env === void 0) {
|
|
3870
|
+
return { command, args };
|
|
3871
|
+
}
|
|
3872
|
+
const env = {};
|
|
3873
|
+
for (const [k, raw] of Object.entries(spec.env)) {
|
|
3874
|
+
const { value, omit } = resolveTemplate(raw, ctx);
|
|
3875
|
+
if (omit)
|
|
3876
|
+
continue;
|
|
3877
|
+
env[k] = value;
|
|
3878
|
+
}
|
|
3879
|
+
return { command, args, env };
|
|
3880
|
+
}
|
|
3881
|
+
function resolveTemplate(input, ctx) {
|
|
3882
|
+
const TOKEN = /\{\{([^}]+)\}\}/g;
|
|
3883
|
+
const hasEmptyIfNoEnv = /\{\{\s*empty_if_no_env\./.test(input);
|
|
3884
|
+
const isWholeValueEmptyIfNoEnv = /^\{\{\s*empty_if_no_env\.[^}]+\}\}$/.test(input);
|
|
3885
|
+
if (hasEmptyIfNoEnv && !isWholeValueEmptyIfNoEnv) {
|
|
3886
|
+
throw new Error(`NativeMcpSpec: empty_if_no_env must be the sole content of the value, never mixed with literal text or other tokens (value: ${JSON.stringify(input)})`);
|
|
3887
|
+
}
|
|
3888
|
+
let omit = false;
|
|
3889
|
+
const value = input.replace(TOKEN, (whole, expr) => {
|
|
3890
|
+
const trimmed = expr.trim();
|
|
3891
|
+
if (trimmed === "agent_id")
|
|
3892
|
+
return ctx.agentId;
|
|
3893
|
+
if (trimmed === "agent_code_name")
|
|
3894
|
+
return ctx.agentCodeName;
|
|
3895
|
+
if (trimmed === "integration_id")
|
|
3896
|
+
return ctx.integration?.id ?? "";
|
|
3897
|
+
if (trimmed.startsWith("process_env.")) {
|
|
3898
|
+
const name = trimmed.slice("process_env.".length);
|
|
3899
|
+
return process.env[name] ?? "";
|
|
3900
|
+
}
|
|
3901
|
+
if (trimmed.startsWith("empty_if_no_env.")) {
|
|
3902
|
+
const name = trimmed.slice("empty_if_no_env.".length);
|
|
3903
|
+
const v = process.env[name] ?? "";
|
|
3904
|
+
if (v.length === 0) {
|
|
3905
|
+
omit = true;
|
|
3906
|
+
return "";
|
|
3907
|
+
}
|
|
3908
|
+
return v;
|
|
3909
|
+
}
|
|
3910
|
+
return whole;
|
|
3911
|
+
});
|
|
3912
|
+
return { value, omit };
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3497
3915
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
|
|
3498
3916
|
var VALID_CODE_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3499
3917
|
var SECRET_FILE_MODE = 384;
|
|
3500
|
-
function
|
|
3501
|
-
|
|
3918
|
+
function writeEnvIntegrationsForAgent(codeName, args) {
|
|
3919
|
+
const agentDir = getAgentDir(codeName);
|
|
3920
|
+
const envPath = join4(agentDir, ".env.integrations");
|
|
3921
|
+
let existing = null;
|
|
3922
|
+
try {
|
|
3923
|
+
existing = readFileSync5(envPath, "utf-8");
|
|
3924
|
+
} catch {
|
|
3925
|
+
}
|
|
3926
|
+
if (existing === null && Object.keys(args.updates).length === 0)
|
|
3927
|
+
return;
|
|
3928
|
+
const content = mergeEnvIntegrationsContent(existing, args);
|
|
3929
|
+
writeFileSync5(envPath, content, { mode: SECRET_FILE_MODE });
|
|
3930
|
+
try {
|
|
3931
|
+
chmodSync5(envPath, SECRET_FILE_MODE);
|
|
3932
|
+
} catch {
|
|
3933
|
+
}
|
|
3934
|
+
try {
|
|
3935
|
+
const projectDir = getProjectDir(codeName);
|
|
3936
|
+
mkdirSync4(projectDir, { recursive: true });
|
|
3937
|
+
const dest = join4(projectDir, ".env.integrations");
|
|
3938
|
+
writeFileSync5(dest, content, { mode: SECRET_FILE_MODE });
|
|
3939
|
+
try {
|
|
3940
|
+
chmodSync5(dest, SECRET_FILE_MODE);
|
|
3941
|
+
} catch {
|
|
3942
|
+
}
|
|
3943
|
+
} catch (err) {
|
|
3944
|
+
process.stderr.write(`[env-integrations] [mirror-write-failed] agent=${codeName} error=${err.message}
|
|
3945
|
+
`);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
var MIGRATABLE_FIELD_TO_ENV_VAR = {
|
|
3949
|
+
SLACK_BOT_TOKEN: "SLACK_BOT_TOKEN",
|
|
3950
|
+
SLACK_APP_TOKEN: "SLACK_APP_TOKEN",
|
|
3951
|
+
TELEGRAM_BOT_TOKEN: "TELEGRAM_BOT_TOKEN",
|
|
3952
|
+
MSTEAMS_CLIENT_SECRET: "MSTEAMS_CLIENT_SECRET",
|
|
3953
|
+
PIPEDREAM_CLIENT_SECRET: "PIPEDREAM_CLIENT_SECRET",
|
|
3954
|
+
"x-api-key": "COMPOSIO_API_KEY",
|
|
3955
|
+
AGT_API_KEY: "AGT_API_KEY"
|
|
3956
|
+
};
|
|
3957
|
+
function migrateExistingLiteralSecrets(codeName) {
|
|
3958
|
+
const mcpJsonPath = join4(getAgentDir(codeName), "provision", ".mcp.json");
|
|
3959
|
+
let config;
|
|
3960
|
+
try {
|
|
3961
|
+
config = JSON.parse(readFileSync5(mcpJsonPath, "utf-8"));
|
|
3962
|
+
} catch {
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3965
|
+
let existingEnvKeys = /* @__PURE__ */ new Set();
|
|
3966
|
+
try {
|
|
3967
|
+
existingEnvKeys = new Set(parseEnvFileEntries(readFileSync5(join4(getAgentDir(codeName), ".env.integrations"), "utf-8")).keys());
|
|
3968
|
+
} catch {
|
|
3969
|
+
}
|
|
3970
|
+
const updates = {};
|
|
3971
|
+
let hoisted = 0;
|
|
3972
|
+
for (const raw of Object.values(config.mcpServers ?? {})) {
|
|
3973
|
+
if (typeof raw !== "object" || raw === null)
|
|
3974
|
+
continue;
|
|
3975
|
+
const entry = raw;
|
|
3976
|
+
for (const block of [entry.env, entry.headers]) {
|
|
3977
|
+
if (!block)
|
|
3978
|
+
continue;
|
|
3979
|
+
for (const [field, envVar] of Object.entries(MIGRATABLE_FIELD_TO_ENV_VAR)) {
|
|
3980
|
+
const value = block[field];
|
|
3981
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("${"))
|
|
3982
|
+
continue;
|
|
3983
|
+
if (envVar !== "AGT_API_KEY" && !existingEnvKeys.has(envVar)) {
|
|
3984
|
+
updates[envVar] = value;
|
|
3985
|
+
}
|
|
3986
|
+
block[field] = `\${${envVar}}`;
|
|
3987
|
+
hoisted++;
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
const unmapped = scanConfigForLiteralSecrets(config).map((f) => `${f.server}.${f.field}`);
|
|
3992
|
+
if (hoisted === 0 && unmapped.length === 0)
|
|
3993
|
+
return;
|
|
3994
|
+
if (hoisted === 0) {
|
|
3995
|
+
process.stderr.write(`[mcp-migrate] [no-mappable-literals] agent=${codeName} unmapped=${unmapped.join(",")}
|
|
3996
|
+
`);
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
if (Object.keys(updates).length > 0) {
|
|
4000
|
+
writeEnvIntegrationsForAgent(codeName, { mode: "upsert", updates });
|
|
4001
|
+
}
|
|
4002
|
+
if (writeMcpJsonGuarded(codeName, mcpJsonPath, config)) {
|
|
4003
|
+
syncMcpToProject(codeName);
|
|
4004
|
+
process.stderr.write(`[mcp-migrate] [literals-hoisted] agent=${codeName} hoisted=${hoisted}${unmapped.length > 0 ? ` unmapped=${unmapped.join(",")}` : ""}
|
|
4005
|
+
`);
|
|
4006
|
+
}
|
|
3502
4007
|
}
|
|
3503
4008
|
function assertValidCodeName(codeName) {
|
|
3504
4009
|
if (!VALID_CODE_NAME.test(codeName)) {
|
|
@@ -3587,10 +4092,23 @@ function syncMcpToProject(codeName) {
|
|
|
3587
4092
|
try {
|
|
3588
4093
|
const content = readFileSync5(provisionMcpPath, "utf-8");
|
|
3589
4094
|
mkdirSync4(projectDir, { recursive: true });
|
|
3590
|
-
writeFileSync5(projectMcpPath, content);
|
|
4095
|
+
writeFileSync5(projectMcpPath, content, { mode: MCP_FILE_MODE });
|
|
4096
|
+
try {
|
|
4097
|
+
chmodSync5(projectMcpPath, MCP_FILE_MODE);
|
|
4098
|
+
} catch {
|
|
4099
|
+
}
|
|
4100
|
+
try {
|
|
4101
|
+
const mismatches = mcpMirrorParityErrors(JSON.parse(content), JSON.parse(readFileSync5(projectMcpPath, "utf-8")));
|
|
4102
|
+
for (const m of mismatches) {
|
|
4103
|
+
process.stderr.write(`${formatMirrorMismatch(m)} agent=${codeName}
|
|
4104
|
+
`);
|
|
4105
|
+
}
|
|
4106
|
+
} catch {
|
|
4107
|
+
}
|
|
3591
4108
|
} catch {
|
|
3592
4109
|
}
|
|
3593
4110
|
renderChannelMessageHandlerForAgent(codeName);
|
|
4111
|
+
renderAugmentedWorkerForAgent(codeName);
|
|
3594
4112
|
}
|
|
3595
4113
|
var INTEGRATIONS_SUMMARY_FILE = "integrations-summary.json";
|
|
3596
4114
|
function integrationsSummaryPath(codeName) {
|
|
@@ -3696,7 +4214,15 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3696
4214
|
continue;
|
|
3697
4215
|
}
|
|
3698
4216
|
}
|
|
3699
|
-
|
|
4217
|
+
if (file === ".mcp.json") {
|
|
4218
|
+
writeFileSync5(dest, srcContent, { mode: MCP_FILE_MODE });
|
|
4219
|
+
try {
|
|
4220
|
+
chmodSync5(dest, MCP_FILE_MODE);
|
|
4221
|
+
} catch {
|
|
4222
|
+
}
|
|
4223
|
+
} else {
|
|
4224
|
+
writeFileSync5(dest, srcContent);
|
|
4225
|
+
}
|
|
3700
4226
|
} catch {
|
|
3701
4227
|
}
|
|
3702
4228
|
}
|
|
@@ -3782,14 +4308,23 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3782
4308
|
return !(entry && typeof entry["url"] === "string" && entry["url"].startsWith("/"));
|
|
3783
4309
|
}));
|
|
3784
4310
|
projectMcp["mcpServers"] = { ...stripRelativeUrls(projectServers), ...stripRelativeUrls(agentServers) };
|
|
3785
|
-
writeFileSync5(projectMcpPath, JSON.stringify(projectMcp, null, 2));
|
|
4311
|
+
writeFileSync5(projectMcpPath, JSON.stringify(projectMcp, null, 2), { mode: MCP_FILE_MODE });
|
|
4312
|
+
try {
|
|
4313
|
+
chmodSync5(projectMcpPath, MCP_FILE_MODE);
|
|
4314
|
+
} catch {
|
|
4315
|
+
}
|
|
3786
4316
|
} catch {
|
|
3787
4317
|
}
|
|
3788
4318
|
const agentDir = getAgentDir(codeName);
|
|
3789
4319
|
for (const envFile of [".env", ".env.integrations"]) {
|
|
3790
4320
|
try {
|
|
3791
4321
|
const content = readFileSync5(join4(agentDir, envFile), "utf-8");
|
|
3792
|
-
|
|
4322
|
+
const envDest = join4(projectDir, envFile);
|
|
4323
|
+
writeFileSync5(envDest, content, { mode: SECRET_FILE_MODE });
|
|
4324
|
+
try {
|
|
4325
|
+
chmodSync5(envDest, SECRET_FILE_MODE);
|
|
4326
|
+
} catch {
|
|
4327
|
+
}
|
|
3793
4328
|
} catch {
|
|
3794
4329
|
}
|
|
3795
4330
|
}
|
|
@@ -3804,7 +4339,7 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3804
4339
|
const upToDate = existsSync5(hookDest) && readFileSync5(hookDest, "utf-8") === srcContent;
|
|
3805
4340
|
if (!upToDate)
|
|
3806
4341
|
writeFileSync5(hookDest, srcContent);
|
|
3807
|
-
|
|
4342
|
+
chmodSync5(hookDest, 493);
|
|
3808
4343
|
}
|
|
3809
4344
|
} catch {
|
|
3810
4345
|
}
|
|
@@ -4321,6 +4856,7 @@ function buildChannelMessageHandlerAgent(args) {
|
|
|
4321
4856
|
"Glob",
|
|
4322
4857
|
"Skill",
|
|
4323
4858
|
"Agent",
|
|
4859
|
+
"ToolSearch",
|
|
4324
4860
|
...mcpWildcards
|
|
4325
4861
|
].join(", ");
|
|
4326
4862
|
const integrationsBlock = integrations.length === 0 ? "" : `
|
|
@@ -4353,12 +4889,88 @@ The parent passes you in the task description:
|
|
|
4353
4889
|
Your job:
|
|
4354
4890
|
|
|
4355
4891
|
1. Do the work the message asks for (Xero pull, research, multi-step skill, etc.). Use whichever tools you need.
|
|
4356
|
-
2. Post the reply yourself via the matching channel tool \u2014 slack.reply for Slack, telegram.reply for Telegram, teams.reply for Microsoft Teams,
|
|
4892
|
+
2. Post the reply yourself via the matching channel tool \u2014 slack.reply for Slack, telegram.reply for Telegram, teams.reply for Microsoft Teams, direct_chat.reply for Direct Chat. Use the metadata the parent gave you to address the right thread/conversation.
|
|
4357
4893
|
3. End. Do not do additional follow-up work; if more is needed, the user will send another message.
|
|
4358
4894
|
|
|
4359
4895
|
Do NOT post intermediate progress updates unless the work spans 5+ minutes \u2014 keep noise low. The parent already sent a single-line acknowledgement before dispatching you.
|
|
4360
4896
|
${integrationsBlock}`;
|
|
4361
4897
|
}
|
|
4898
|
+
function buildAugmentedWorkerAgent(args) {
|
|
4899
|
+
const mcpServerKeys = args?.mcpServerKeys ?? [];
|
|
4900
|
+
const integrations = args?.integrations ?? [];
|
|
4901
|
+
const mcpWildcards = Array.from(new Set(mcpServerKeys.map((k) => `mcp__${sanitizeMcpName(k)}__*`)));
|
|
4902
|
+
const tools = [
|
|
4903
|
+
"Bash",
|
|
4904
|
+
"Read",
|
|
4905
|
+
"Write",
|
|
4906
|
+
"Edit",
|
|
4907
|
+
"Grep",
|
|
4908
|
+
"Glob",
|
|
4909
|
+
"Skill",
|
|
4910
|
+
"Agent",
|
|
4911
|
+
"ToolSearch",
|
|
4912
|
+
...mcpWildcards
|
|
4913
|
+
].join(", ");
|
|
4914
|
+
const integrationsBlock = integrations.length === 0 ? "" : `
|
|
4915
|
+
## Integrations available
|
|
4916
|
+
|
|
4917
|
+
You inherit the parent agent's environment, including credentials for the integrations below. Env vars follow the convention \`<DEFINITION_ID>_ACCESS_TOKEN\` for OAuth and \`<DEFINITION_ID>_API_KEY\` for API-key integrations (e.g. \`GITHUB_ACCESS_TOKEN\`, \`POSTIZ_API_KEY\`). Where a CLI is listed, prefer it over raw curl \u2014 the CLI handles auth automatically.
|
|
4918
|
+
|
|
4919
|
+
${integrations.map((i) => {
|
|
4920
|
+
const cli = i.cliBinary ? ` \u2014 use the \`${i.cliBinary}\` CLI` : "";
|
|
4921
|
+
const desc = i.description ? `. ${i.description}` : "";
|
|
4922
|
+
return `- **${i.name}**${cli}${desc}`;
|
|
4923
|
+
}).join("\n")}
|
|
4924
|
+
|
|
4925
|
+
If a capability seems missing, **check first** \u2014 run the CLI, list tools (\`mcp__augmented__list_tools\` or equivalent), or confirm the relevant env var is set **without printing its value** (e.g. \`[ -n "$POSTIZ_API_KEY" ] && echo present || echo absent\`, never \`echo $POSTIZ_API_KEY\`). Do not claim a capability is absent without verifying \u2014 the parent's environment is yours.
|
|
4926
|
+
`;
|
|
4927
|
+
return `---
|
|
4928
|
+
name: augmented-worker
|
|
4929
|
+
description: Background worker for multi-step tool tasks the parent doesn't want to inline (data pulls, multi-API workflows, CRM enrichments, research that needs MCP tools). Carries an explicit \`mcp__*\` wildcard allowlist for every server the parent has wired. **Until [anthropics/claude-code#64909](https://github.com/anthropics/claude-code/issues/64909) ships an upstream fix, prefer \`subagent_type: general-purpose\` for MCP-tool dispatch** \u2014 the bug is in Claude Code's sub-agent dispatch path: sub-agents with an explicit \`tools:\` allowlist get an empty MCP tool registry (every \`mcp__*\` call returns "No such tool available."), while \`general-purpose\` (\`tools: *\` inherit-all) correctly binds the full MCP surface. This subagent's allowlist is correct and will work the moment Anthropic lands the fix; until then it is retained for the eventual structural fix and the rare case where you specifically need a restricted tool surface AND can accept the MCP gap. See [[ENG-5938]] for the workaround tracker.
|
|
4930
|
+
background: true
|
|
4931
|
+
tools: ${tools}
|
|
4932
|
+
---
|
|
4933
|
+
|
|
4934
|
+
You are dispatched by the parent agent to do a multi-step task in the background while the parent's listener turn stays free.
|
|
4935
|
+
|
|
4936
|
+
## What you can do
|
|
4937
|
+
|
|
4938
|
+
Your \`tools:\` allowlist (above) names every MCP server the parent has connected \u2014 Granola, Composio toolkits, Slack/Telegram/Direct-Chat channel tools, the platform \`mcp__augmented__*\` bridge, native integrations (Xero / Postiz / qmd / AWS), etc. \u2014 plus the built-ins (\`Bash\`, \`Read\`, \`Write\`, \`Edit\`, \`Grep\`, \`Glob\`, \`Skill\`, \`Agent\`). All environment variables the parent has are yours: OAuth access tokens (\`GITHUB_ACCESS_TOKEN\`), API keys (\`POSTIZ_API_KEY\`), native-CLI binaries (\`gh\`, \`aws\`, \`xero\`).
|
|
4939
|
+
|
|
4940
|
+
## Hard rules \u2014 Credential Access Control
|
|
4941
|
+
|
|
4942
|
+
1. **Never** read raw secrets out of \`.mcp.json\`, \`~/.augmented/*/provision/.mcp.json\`, \`.env.integrations\`, or any agent config file. Those files contain bot tokens, API keys, and OAuth credentials. The Credential Access Control guardrail (\`block_read: true\` on secrets) treats reads of those values as a violation regardless of intent. As **defence-in-depth (not structural enforcement)**, the plugin's \`settings.json\` also denies \`Bash(cat:*/.mcp.json)\`, \`Bash(cat:*/.env.integrations)\`, and \`Bash(jq:*/.mcp.json)\` (ENG-5901 / ADR-0018) \u2014 these block the obvious copy-paste paths but a determined in-process reader can still reach the values; the durable fix is Phase 2/3 of ADR-0018.
|
|
4943
|
+
2. **Never** post Slack messages via raw \`chat.postMessage\` + a bot token lifted from config. Use the channel MCP's reply tool (\`mcp__slack-channel__slack_reply\`, \`mcp__telegram-channel__telegram_reply\`, \`mcp__direct-chat__direct_chat_reply\`, etc.) so the call goes through the audited path.
|
|
4944
|
+
3. If an \`mcp__*\` tool you expect to be available returns "No such tool available.", **stop and surface the gap to the parent** in your summary rather than working around it. A missing MCP binding is a platform bug worth fixing \u2014 it's the exact failure shape this sub-agent was added to prevent (see ENG-5897 / ENG-5905).
|
|
4945
|
+
4. When verifying a capability is wired, confirm the relevant env var exists **without printing its value** \u2014 use \`[ -n "$POSTIZ_API_KEY" ] && echo present || echo absent\`, never \`echo $POSTIZ_API_KEY\`. The Credential Access Control guardrail covers tool-call output as well as file reads.
|
|
4946
|
+
|
|
4947
|
+
## What to return
|
|
4948
|
+
|
|
4949
|
+
Hand the parent a tight summary of what you did, what you found, and any follow-ups it should know about. Do not echo intermediate tool transcripts \u2014 keep the parent's context window clean. If you produced an artifact (a file, a draft, a record ID), name it and where it lives.
|
|
4950
|
+
${integrationsBlock}`;
|
|
4951
|
+
}
|
|
4952
|
+
function renderAugmentedWorkerForAgent(codeName) {
|
|
4953
|
+
const agentDir = getAgentDir(codeName);
|
|
4954
|
+
const projectDir = getProjectDir(codeName);
|
|
4955
|
+
const provisionMcpPath = join4(agentDir, "provision", ".mcp.json");
|
|
4956
|
+
let mcpServerKeys;
|
|
4957
|
+
try {
|
|
4958
|
+
const config = JSON.parse(readFileSync5(provisionMcpPath, "utf-8"));
|
|
4959
|
+
mcpServerKeys = Object.keys(config.mcpServers ?? {});
|
|
4960
|
+
} catch {
|
|
4961
|
+
return;
|
|
4962
|
+
}
|
|
4963
|
+
const integrations = readIntegrationsSummaryForAgent(codeName);
|
|
4964
|
+
const content = buildAugmentedWorkerAgent({ mcpServerKeys, integrations });
|
|
4965
|
+
for (const baseDir of [agentDir, projectDir]) {
|
|
4966
|
+
const target = join4(baseDir, ".claude", "agents", "augmented-worker.md");
|
|
4967
|
+
try {
|
|
4968
|
+
mkdirSync4(dirname4(target), { recursive: true });
|
|
4969
|
+
writeFileSync5(target, content);
|
|
4970
|
+
} catch {
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4362
4974
|
function buildPostizMcpEntry(integration) {
|
|
4363
4975
|
const rawBaseUrl = integration.config["base_url"];
|
|
4364
4976
|
const postizBaseUrl = typeof rawBaseUrl === "string" ? rawBaseUrl.trim() : "";
|
|
@@ -4386,7 +4998,13 @@ function buildMcpJson(input) {
|
|
|
4386
4998
|
args: [localMcpPath],
|
|
4387
4999
|
env: {
|
|
4388
5000
|
AGT_HOST: process.env["AGT_HOST"] ?? "",
|
|
4389
|
-
|
|
5001
|
+
// ENG-5901 Track D: templated — the manager exports AGT_API_KEY to
|
|
5002
|
+
// every spawn env (getApiKey()), and Claude Code substitutes at
|
|
5003
|
+
// MCP-launch (same contract as AGT_RUN_ID below). The impersonation
|
|
5004
|
+
// redeem path renders this server-side with no AGT_API_KEY in env;
|
|
5005
|
+
// impersonate-mcp-rewrite.ts treats the literal `${AGT_API_KEY}`
|
|
5006
|
+
// placeholder as fillable and swaps in the operator token.
|
|
5007
|
+
AGT_API_KEY: "${AGT_API_KEY}",
|
|
4390
5008
|
AGT_AGENT_ID: input.agent.agent_id,
|
|
4391
5009
|
AGT_AGENT_CODE_NAME: input.agent.code_name,
|
|
4392
5010
|
// ENG-4561: Claude Code substitutes `${VAR}` in .mcp.json env values
|
|
@@ -4405,12 +5023,16 @@ function buildMcpJson(input) {
|
|
|
4405
5023
|
HOME: process.env["HOME"] ?? ""
|
|
4406
5024
|
}
|
|
4407
5025
|
};
|
|
4408
|
-
const
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
5026
|
+
for (const integration of input.integrations ?? []) {
|
|
5027
|
+
const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
|
|
5028
|
+
if (!def?.nativeMcp)
|
|
5029
|
+
continue;
|
|
5030
|
+
const key = def.nativeMcp.key ?? integration.definition_id;
|
|
5031
|
+
mcpServers[key] = buildNativeMcpEntry(def.nativeMcp, {
|
|
5032
|
+
agentId: input.agent.agent_id,
|
|
5033
|
+
agentCodeName: input.agent.code_name,
|
|
5034
|
+
integration
|
|
5035
|
+
});
|
|
4414
5036
|
}
|
|
4415
5037
|
const xeroIntegration = input.integrations?.find((i) => i.definition_id === "xero");
|
|
4416
5038
|
if (xeroIntegration) {
|
|
@@ -4505,7 +5127,7 @@ function mapScheduledTasks(tasks) {
|
|
|
4505
5127
|
};
|
|
4506
5128
|
});
|
|
4507
5129
|
}
|
|
4508
|
-
function buildUrlMcpServerEntry(url, headers) {
|
|
5130
|
+
function buildUrlMcpServerEntry(url, headers, type = "http") {
|
|
4509
5131
|
const hasHeaders = !!headers && Object.keys(headers).length > 0;
|
|
4510
5132
|
if (url.includes("composio.dev") && hasHeaders) {
|
|
4511
5133
|
return { type: "http", url, headers };
|
|
@@ -4528,7 +5150,7 @@ function buildUrlMcpServerEntry(url, headers) {
|
|
|
4528
5150
|
};
|
|
4529
5151
|
}
|
|
4530
5152
|
if (hasHeaders) {
|
|
4531
|
-
return { type
|
|
5153
|
+
return { type, url, headers };
|
|
4532
5154
|
}
|
|
4533
5155
|
return { command: "npx", args: ["-y", "mcp-remote", url, "--allow-http"] };
|
|
4534
5156
|
}
|
|
@@ -4609,6 +5231,18 @@ var claudeCodeAdapter = {
|
|
|
4609
5231
|
integrations: integrationSummaries
|
|
4610
5232
|
})
|
|
4611
5233
|
},
|
|
5234
|
+
// ENG-5905: project-scope augmented-worker sub-agent — sibling of
|
|
5235
|
+
// channel-message-handler, same dynamic render shape, used by the
|
|
5236
|
+
// parent for general multi-step background work that doesn't
|
|
5237
|
+
// require a channel reply. Closes the gap ENG-5897 left open
|
|
5238
|
+
// (the plugin-scope static file never reached the runtime).
|
|
5239
|
+
{
|
|
5240
|
+
relativePath: ".claude/agents/augmented-worker.md",
|
|
5241
|
+
content: buildAugmentedWorkerAgent({
|
|
5242
|
+
mcpServerKeys: initialMcpServerKeys,
|
|
5243
|
+
integrations: integrationSummaries
|
|
5244
|
+
})
|
|
5245
|
+
},
|
|
4612
5246
|
{
|
|
4613
5247
|
relativePath: `provision/${INTEGRATIONS_SUMMARY_FILE}`,
|
|
4614
5248
|
content: JSON.stringify(integrationSummaries, null, 2)
|
|
@@ -4726,7 +5360,7 @@ ${sections}`
|
|
|
4726
5360
|
if (envLines.length > 1) {
|
|
4727
5361
|
const envPath = join4(agentDir, ".env");
|
|
4728
5362
|
writeFileSync5(envPath, envLines.join("\n") + "\n");
|
|
4729
|
-
|
|
5363
|
+
chmodSync5(envPath, SECRET_FILE_MODE);
|
|
4730
5364
|
}
|
|
4731
5365
|
},
|
|
4732
5366
|
// Claude Code has no gateway process — methods intentionally omitted
|
|
@@ -4750,6 +5384,14 @@ ${sections}`
|
|
|
4750
5384
|
},
|
|
4751
5385
|
writeChannelCredentials(codeName, channelId, config, options) {
|
|
4752
5386
|
const tzEnv = options?.agentTimezone && options.agentTimezone.trim() !== "" ? { TZ: options.agentTimezone.trim() } : {};
|
|
5387
|
+
const senderPolicyMode = options?.senderPolicy?.mode;
|
|
5388
|
+
const senderPolicyTeamId = options?.senderPolicy?.mode === "team_agents_only" || options?.senderPolicy?.mode === "manager_only" || options?.senderPolicy?.mode === "team_only" ? options.senderPolicy.team_id : void 0;
|
|
5389
|
+
const slackPrincipalId = options?.senderPolicy?.mode === "manager_only" ? options.senderPolicy.principal?.slack_user_id : void 0;
|
|
5390
|
+
const teamsPrincipalId = options?.senderPolicy?.mode === "manager_only" ? options.senderPolicy.principal?.teams_aad_object_id : void 0;
|
|
5391
|
+
const slackTeamPrincipalIds = options?.senderPolicy?.mode === "team_only" ? options.senderPolicy.team_principals?.slack_user_ids?.join(",") : void 0;
|
|
5392
|
+
const teamsTeamPrincipalIds = options?.senderPolicy?.mode === "team_only" ? options.senderPolicy.team_principals?.teams_aad_object_ids?.join(",") : void 0;
|
|
5393
|
+
const senderPolicyInternalOnly = options?.senderPolicy?.internal_only === true;
|
|
5394
|
+
const senderPolicyEnv = senderPolicyTeamId ? { AGT_TEAM_ID: senderPolicyTeamId } : {};
|
|
4753
5395
|
const agentDir = getAgentDir(codeName);
|
|
4754
5396
|
mkdirSync4(agentDir, { recursive: true });
|
|
4755
5397
|
const isPersistent = options?.sessionMode === "persistent";
|
|
@@ -4762,17 +5404,28 @@ ${sections}`
|
|
|
4762
5404
|
const localTelegramChannel = join4(getHomeDir3(), ".augmented", "_mcp", "telegram-channel.js");
|
|
4763
5405
|
const resolvedAgtHostForTelegram = process.env["AGT_HOST"]?.trim() || "https://api.augmented.team";
|
|
4764
5406
|
const resolvedAgtApiKeyForTelegram = process.env["AGT_API_KEY"]?.trim();
|
|
5407
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5408
|
+
mode: "upsert",
|
|
5409
|
+
updates: { TELEGRAM_BOT_TOKEN: botToken }
|
|
5410
|
+
});
|
|
4765
5411
|
const telegramEnv = {
|
|
4766
|
-
TELEGRAM_BOT_TOKEN:
|
|
5412
|
+
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN}",
|
|
4767
5413
|
AGT_AGENT_CODE_NAME: codeName,
|
|
4768
5414
|
AGT_HOST: resolvedAgtHostForTelegram,
|
|
4769
|
-
...resolvedAgtApiKeyForTelegram ? { AGT_API_KEY:
|
|
5415
|
+
...resolvedAgtApiKeyForTelegram ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
4770
5416
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {},
|
|
4771
5417
|
...tzEnv
|
|
4772
5418
|
};
|
|
4773
5419
|
if (allowedChats && allowedChats.length > 0) {
|
|
4774
5420
|
telegramEnv.TELEGRAM_ALLOWED_CHATS = allowedChats.join(",");
|
|
4775
5421
|
}
|
|
5422
|
+
const rawDiagnosticChatIds = config["diagnostic_chat_ids"];
|
|
5423
|
+
if (Array.isArray(rawDiagnosticChatIds)) {
|
|
5424
|
+
const diagnosticChatIds = rawDiagnosticChatIds.map((v) => typeof v === "string" || typeof v === "number" ? String(v).trim() : "").filter((v) => v.length > 0);
|
|
5425
|
+
if (diagnosticChatIds.length > 0) {
|
|
5426
|
+
telegramEnv.TELEGRAM_DIAGNOSTIC_CHAT_IDS = diagnosticChatIds.join(",");
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
4776
5429
|
const rawPeerAgentMode = config["peer_agent_mode"];
|
|
4777
5430
|
if (rawPeerAgentMode === "listen" || rawPeerAgentMode === "respond") {
|
|
4778
5431
|
telegramEnv.TELEGRAM_PEER_AGENT_MODE = rawPeerAgentMode;
|
|
@@ -4837,6 +5490,7 @@ ${sections}`
|
|
|
4837
5490
|
const appToken = config["app_token"];
|
|
4838
5491
|
const threadAutoFollow = config["thread_auto_follow"];
|
|
4839
5492
|
const channelResponseMode = config["channel_response_mode"];
|
|
5493
|
+
const allowedUsers = Array.isArray(config["allowed_users"]) ? config["allowed_users"].filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : [];
|
|
4840
5494
|
const blockKitEnabled = config["block_kit_enabled"] === true;
|
|
4841
5495
|
const blockKitAskUserEnabled = config["block_kit_ask_user_enabled"] === true;
|
|
4842
5496
|
const blockKitDisabled = process.env["SLACK_BLOCK_KIT_DISABLED"] === "true";
|
|
@@ -4846,7 +5500,9 @@ ${sections}`
|
|
|
4846
5500
|
...blockKitAskUserEnabled ? { SLACK_BLOCK_KIT_ASK_USER_ENABLED: "true" } : {},
|
|
4847
5501
|
...blockKitDisabled ? { SLACK_BLOCK_KIT_DISABLED: "true" } : {},
|
|
4848
5502
|
...blockKitAskUserEnabled ? { AGT_HOST: resolvedAgtHost } : {},
|
|
4849
|
-
|
|
5503
|
+
// ENG-5901 Track D: template (manager exports the real value
|
|
5504
|
+
// to every spawn env); gate unchanged.
|
|
5505
|
+
...blockKitAskUserEnabled && process.env["AGT_API_KEY"] ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
4850
5506
|
...blockKitAskUserEnabled && options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
4851
5507
|
} : {};
|
|
4852
5508
|
if (botToken) {
|
|
@@ -4875,16 +5531,26 @@ ${sections}`
|
|
|
4875
5531
|
const slackResolvedAgtApiKey = process.env["AGT_API_KEY"]?.trim();
|
|
4876
5532
|
const slackAgtAuthEnv = {
|
|
4877
5533
|
AGT_HOST: resolvedAgtHost,
|
|
4878
|
-
|
|
5534
|
+
// ENG-5901 Track D: template — manager exports AGT_API_KEY to
|
|
5535
|
+
// every spawn env (getApiKey()); the gate still keys off the
|
|
5536
|
+
// manager actually having one.
|
|
5537
|
+
...slackResolvedAgtApiKey ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
4879
5538
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
4880
5539
|
};
|
|
5540
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5541
|
+
mode: "upsert",
|
|
5542
|
+
updates: {
|
|
5543
|
+
SLACK_BOT_TOKEN: botToken,
|
|
5544
|
+
...appToken ? { SLACK_APP_TOKEN: appToken } : {}
|
|
5545
|
+
}
|
|
5546
|
+
});
|
|
4881
5547
|
const localSlackChannel = join4(getHomeDir3(), ".augmented", "_mcp", "slack-channel.js");
|
|
4882
5548
|
const slackEntry = {
|
|
4883
5549
|
command: existsSync5(localSlackChannel) ? "node" : "npx",
|
|
4884
5550
|
args: existsSync5(localSlackChannel) ? [localSlackChannel] : ["-y", "@augmented/claude-code-channel-slack"],
|
|
4885
5551
|
env: {
|
|
4886
|
-
SLACK_BOT_TOKEN:
|
|
4887
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5552
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5553
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
4888
5554
|
...threadAutoFollow && threadAutoFollow !== "off" ? { SLACK_THREAD_AUTO_FOLLOW: threadAutoFollow } : {},
|
|
4889
5555
|
// ENG-4464: only emit when non-default — `mention_only` is the
|
|
4890
5556
|
// default in slack-response-mode.ts, so omitting keeps the env
|
|
@@ -4901,7 +5567,45 @@ ${sections}`
|
|
|
4901
5567
|
// (ENG-4936) honours PEER_DISABLED with identical
|
|
4902
5568
|
// semantics. Only emit when non-default so the env block
|
|
4903
5569
|
// stays clean for the common case.
|
|
4904
|
-
...peerDisabledMode !== "off" ? { PEER_DISABLED: peerDisabledMode } : {}
|
|
5570
|
+
...peerDisabledMode !== "off" ? { PEER_DISABLED: peerDisabledMode } : {},
|
|
5571
|
+
// ENG-5841: SLACK_SENDER_POLICY drives slack-inbound-filter.ts.
|
|
5572
|
+
// Only emitted when the effective mode is restrictive (default
|
|
5573
|
+
// 'all' is the absence of the env var — same convention the
|
|
5574
|
+
// MCP filter uses on its own end).
|
|
5575
|
+
...senderPolicyMode ? { SLACK_SENDER_POLICY: senderPolicyMode } : {},
|
|
5576
|
+
...senderPolicyEnv,
|
|
5577
|
+
// AGT_TEAM_ID when team_agents_only
|
|
5578
|
+
// ENG-5842: principal ID for manager_only — Slack user_id from
|
|
5579
|
+
// people.contact_preferences.slack_user_id. Omitted when the
|
|
5580
|
+
// principal has no Slack ID; MCP filter fails closed on the
|
|
5581
|
+
// missing env var by dropping all human inbound.
|
|
5582
|
+
...slackPrincipalId ? { SLACK_SENDER_POLICY_PRINCIPAL_ID: slackPrincipalId } : {},
|
|
5583
|
+
// ENG-5871: team_only mode injects a comma-separated list of
|
|
5584
|
+
// team-member Slack user_ids. Absent/empty = MCP filter drops
|
|
5585
|
+
// humans on Slack but still admits same-team agents via label.
|
|
5586
|
+
// No additional shape parsing on the MCP side — `.split(',')`
|
|
5587
|
+
// works because Slack user_ids are `[A-Z0-9]+` (no commas).
|
|
5588
|
+
...slackTeamPrincipalIds ? { SLACK_SENDER_POLICY_TEAM_PRINCIPAL_IDS: slackTeamPrincipalIds } : {},
|
|
5589
|
+
// ENG-5843: org-boundary gate. SLACK_INTERNAL_ONLY signals the
|
|
5590
|
+
// filter to check sender's workspace against SLACK_HOME_TEAM_ID
|
|
5591
|
+
// (sourced from the bot install's team_id, populated by
|
|
5592
|
+
// auth.test at install / first-run). Omitted unless explicitly
|
|
5593
|
+
// enabled — the consumer's env-absent default is "no gate".
|
|
5594
|
+
// When INTERNAL_ONLY is true but home team_id can't be
|
|
5595
|
+
// resolved (config['team_id'] not set on the install), the
|
|
5596
|
+
// MCP boot guard fails closed at startup rather than admitting
|
|
5597
|
+
// every sender as "internal".
|
|
5598
|
+
...senderPolicyInternalOnly ? { SLACK_INTERNAL_ONLY: "true" } : {},
|
|
5599
|
+
...senderPolicyInternalOnly && typeof config["team_id"] === "string" && config["team_id"].length > 0 ? { SLACK_HOME_TEAM_ID: config["team_id"] } : {},
|
|
5600
|
+
// ENG-6035: per-agent diagnostic/restart allowlist. Gates
|
|
5601
|
+
// /investigate-<code-name> (fail-closed: command disabled when
|
|
5602
|
+
// unset) and /restart-<code-name> (open when unset). An explicit
|
|
5603
|
+
// env entry here overrides any host-level systemd value, which
|
|
5604
|
+
// is the point — the host-wide drop-in pattern wrongly scoped
|
|
5605
|
+
// the allowlist to every agent on the host. Omitted when empty
|
|
5606
|
+
// so the host fallback (and the fail-closed /investigate
|
|
5607
|
+
// default) still apply to unconfigured agents.
|
|
5608
|
+
...allowedUsers.length > 0 ? { SLACK_ALLOWED_USERS: allowedUsers.join(",") } : {}
|
|
4905
5609
|
}
|
|
4906
5610
|
};
|
|
4907
5611
|
const provisionMcpPath = join4(agentDir, "provision", ".mcp.json");
|
|
@@ -4957,6 +5661,8 @@ ${sections}`
|
|
|
4957
5661
|
const slackAutoFollowEnv = slackThreadAutoFollow && slackThreadAutoFollow !== "off" ? { SLACK_THREAD_AUTO_FOLLOW: slackThreadAutoFollow } : {};
|
|
4958
5662
|
const slackChannelResponseMode = config["channel_response_mode"];
|
|
4959
5663
|
const slackResponseModeEnv = slackChannelResponseMode && slackChannelResponseMode !== "mention_only" ? { SLACK_CHANNEL_RESPONSE_MODE: slackChannelResponseMode } : {};
|
|
5664
|
+
const slackAllowedUsersList = Array.isArray(config["allowed_users"]) ? config["allowed_users"].filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : [];
|
|
5665
|
+
const slackAllowedUsersEnv = slackAllowedUsersList.length > 0 ? { SLACK_ALLOWED_USERS: slackAllowedUsersList.join(",") } : {};
|
|
4960
5666
|
const oneshotBlockKitEnabled = config["block_kit_enabled"] === true;
|
|
4961
5667
|
const oneshotBlockKitAskUserEnabled = config["block_kit_ask_user_enabled"] === true;
|
|
4962
5668
|
const oneshotBlockKitDisabled = process.env["SLACK_BLOCK_KIT_DISABLED"] === "true";
|
|
@@ -4966,7 +5672,8 @@ ${sections}`
|
|
|
4966
5672
|
...oneshotBlockKitAskUserEnabled ? { SLACK_BLOCK_KIT_ASK_USER_ENABLED: "true" } : {},
|
|
4967
5673
|
...oneshotBlockKitDisabled ? { SLACK_BLOCK_KIT_DISABLED: "true" } : {},
|
|
4968
5674
|
...oneshotBlockKitAskUserEnabled ? { AGT_HOST: oneshotResolvedAgtHost } : {},
|
|
4969
|
-
|
|
5675
|
+
// ENG-5901 Track D: template; manager spawn env carries the value.
|
|
5676
|
+
...oneshotBlockKitAskUserEnabled && process.env["AGT_API_KEY"] ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
4970
5677
|
...oneshotBlockKitAskUserEnabled && options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
4971
5678
|
} : {};
|
|
4972
5679
|
const slackPeerEnv = {};
|
|
@@ -4999,18 +5706,27 @@ ${sections}`
|
|
|
4999
5706
|
const slackAgtAuthEnv = {
|
|
5000
5707
|
AGT_HOST: process.env["AGT_HOST"]?.trim() || "https://api.augmented.team",
|
|
5001
5708
|
AGT_AGENT_CODE_NAME: codeName,
|
|
5002
|
-
|
|
5709
|
+
// ENG-5901 Track D: template; manager spawn env carries the value.
|
|
5710
|
+
...slackResolvedAgtApiKey ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
5003
5711
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
5004
5712
|
};
|
|
5713
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5714
|
+
mode: "upsert",
|
|
5715
|
+
updates: {
|
|
5716
|
+
SLACK_BOT_TOKEN: botToken,
|
|
5717
|
+
...appToken ? { SLACK_APP_TOKEN: appToken } : {}
|
|
5718
|
+
}
|
|
5719
|
+
});
|
|
5005
5720
|
if (isPersistent && existsSync5(localSlackChannel)) {
|
|
5006
5721
|
mcpServers["slack"] = {
|
|
5007
5722
|
command: "node",
|
|
5008
5723
|
args: [localSlackChannel],
|
|
5009
5724
|
env: {
|
|
5010
|
-
SLACK_BOT_TOKEN:
|
|
5011
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5725
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5726
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
5012
5727
|
...slackAutoFollowEnv,
|
|
5013
5728
|
...slackResponseModeEnv,
|
|
5729
|
+
...slackAllowedUsersEnv,
|
|
5014
5730
|
...oneshotBlockKitEnv,
|
|
5015
5731
|
...slackPeerEnv,
|
|
5016
5732
|
...slackAgtAuthEnv,
|
|
@@ -5022,10 +5738,11 @@ ${sections}`
|
|
|
5022
5738
|
command: "npx",
|
|
5023
5739
|
args: ["-y", "@augmented/claude-code-channel-slack"],
|
|
5024
5740
|
env: {
|
|
5025
|
-
SLACK_BOT_TOKEN:
|
|
5026
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5741
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5742
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
5027
5743
|
...slackAutoFollowEnv,
|
|
5028
5744
|
...slackResponseModeEnv,
|
|
5745
|
+
...slackAllowedUsersEnv,
|
|
5029
5746
|
...oneshotBlockKitEnv,
|
|
5030
5747
|
...slackPeerEnv,
|
|
5031
5748
|
...slackAgtAuthEnv,
|
|
@@ -5060,12 +5777,17 @@ ${sections}`
|
|
|
5060
5777
|
const msteamsAgtAuthEnv = {
|
|
5061
5778
|
AGT_HOST: process.env["AGT_HOST"]?.trim() || "https://api.augmented.team",
|
|
5062
5779
|
AGT_AGENT_CODE_NAME: codeName,
|
|
5063
|
-
|
|
5780
|
+
// ENG-5901 Track D: template; manager spawn env carries the value.
|
|
5781
|
+
...msResolvedAgtApiKey ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
5064
5782
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
5065
5783
|
};
|
|
5784
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5785
|
+
mode: "upsert",
|
|
5786
|
+
updates: { MSTEAMS_CLIENT_SECRET: clientSecret }
|
|
5787
|
+
});
|
|
5066
5788
|
const teamsEnv = {
|
|
5067
5789
|
MSTEAMS_APP_ID: appId,
|
|
5068
|
-
MSTEAMS_CLIENT_SECRET:
|
|
5790
|
+
MSTEAMS_CLIENT_SECRET: "${MSTEAMS_CLIENT_SECRET}",
|
|
5069
5791
|
MSTEAMS_TENANT_ID: tenantId,
|
|
5070
5792
|
...botObjectId ? { MSTEAMS_BOT_OBJECT_ID: botObjectId } : {},
|
|
5071
5793
|
...allowedTeamIds.length > 0 ? { MSTEAMS_ALLOWED_TEAMS: allowedTeamIds.join(",") } : {},
|
|
@@ -5077,7 +5799,28 @@ ${sections}`
|
|
|
5077
5799
|
...peerTeamIds.length > 0 ? { MSTEAMS_PEER_TEAM_IDS: peerTeamIds.join(",") } : {},
|
|
5078
5800
|
...knownPeerBotIds.length > 0 ? { MSTEAMS_KNOWN_PEER_BOT_IDS: knownPeerBotIds.join(",") } : {},
|
|
5079
5801
|
...msteamsAgtAuthEnv,
|
|
5080
|
-
...tzEnv
|
|
5802
|
+
...tzEnv,
|
|
5803
|
+
// ENG-5841: MSTEAMS_SENDER_POLICY drives teams-inbound-filter.ts.
|
|
5804
|
+
// Mirrors the Slack branch above — only emitted when restrictive.
|
|
5805
|
+
...senderPolicyMode ? { MSTEAMS_SENDER_POLICY: senderPolicyMode } : {},
|
|
5806
|
+
...senderPolicyEnv,
|
|
5807
|
+
// AGT_TEAM_ID when team_agents_only
|
|
5808
|
+
// ENG-5842: principal ID for manager_only — Teams AAD object id from
|
|
5809
|
+
// people.contact_preferences.teams_aad_object_id. Same fail-closed
|
|
5810
|
+
// contract as the Slack branch above.
|
|
5811
|
+
...teamsPrincipalId ? { MSTEAMS_SENDER_POLICY_PRINCIPAL_ID: teamsPrincipalId } : {},
|
|
5812
|
+
// ENG-5871: team_only mode injects a comma-separated list of team-
|
|
5813
|
+
// member Teams AAD object IDs. Same fail-closed contract as Slack.
|
|
5814
|
+
// AAD IDs are UUIDs (no commas), so .split(',') is safe.
|
|
5815
|
+
...teamsTeamPrincipalIds ? { MSTEAMS_SENDER_POLICY_TEAM_PRINCIPAL_IDS: teamsTeamPrincipalIds } : {},
|
|
5816
|
+
// ENG-5843: org-boundary gate. MSTEAMS_INTERNAL_ONLY + MSTEAMS_HOME_TENANT_ID
|
|
5817
|
+
// mirror the Slack pair. Source is the same tenantId the existing
|
|
5818
|
+
// MSTEAMS_TENANT_ID env var already carries — defaulting to "common"
|
|
5819
|
+
// would be wrong here (it'd admit any tenant), so we skip the
|
|
5820
|
+
// SLACK_HOME_TEAM_ID-equivalent emission when the install hasn't
|
|
5821
|
+
// pinned a real tenant. MCP boot guard fails closed.
|
|
5822
|
+
...senderPolicyInternalOnly ? { MSTEAMS_INTERNAL_ONLY: "true" } : {},
|
|
5823
|
+
...senderPolicyInternalOnly && tenantId !== "common" ? { MSTEAMS_HOME_TENANT_ID: tenantId } : {}
|
|
5081
5824
|
};
|
|
5082
5825
|
if (isPersistent && existsSync5(localTeamsChannel)) {
|
|
5083
5826
|
mcpServers["msteams"] = {
|
|
@@ -5131,6 +5874,12 @@ ${sections}`
|
|
|
5131
5874
|
});
|
|
5132
5875
|
return changed;
|
|
5133
5876
|
},
|
|
5877
|
+
// ENG-5901 PR 3: hoist pre-Track-D literal secrets out of the on-disk
|
|
5878
|
+
// .mcp.json so the armed lint stops rejecting every incremental write.
|
|
5879
|
+
// See migrateExistingLiteralSecrets for the full story.
|
|
5880
|
+
migrateSecretStorage(codeName) {
|
|
5881
|
+
migrateExistingLiteralSecrets(codeName);
|
|
5882
|
+
},
|
|
5134
5883
|
seedProfileConfig(codeName) {
|
|
5135
5884
|
const agentDir = getAgentDir(codeName);
|
|
5136
5885
|
const projectDir = getProjectDir(codeName);
|
|
@@ -5162,19 +5911,19 @@ ${sections}`
|
|
|
5162
5911
|
...integration,
|
|
5163
5912
|
credentials: decryptIntegrationCredentials(integration.credentials)
|
|
5164
5913
|
}));
|
|
5165
|
-
const
|
|
5914
|
+
const envUpdates = {};
|
|
5166
5915
|
for (const integration of decryptedIntegrations) {
|
|
5167
5916
|
const prefix = integration.definition_id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
5168
5917
|
const creds = integration.credentials;
|
|
5169
5918
|
if (integration.auth_type === "oauth2") {
|
|
5170
5919
|
const accessToken = creds.access_token;
|
|
5171
5920
|
if (accessToken) {
|
|
5172
|
-
|
|
5921
|
+
envUpdates[`${prefix}_ACCESS_TOKEN`] = accessToken;
|
|
5173
5922
|
}
|
|
5174
5923
|
} else if (integration.auth_type === "api_key") {
|
|
5175
5924
|
const apiKey = creds.api_key;
|
|
5176
5925
|
if (apiKey) {
|
|
5177
|
-
|
|
5926
|
+
envUpdates[`${prefix}_API_KEY`] = apiKey;
|
|
5178
5927
|
}
|
|
5179
5928
|
}
|
|
5180
5929
|
if (integration.config) {
|
|
@@ -5183,20 +5932,37 @@ ${sections}`
|
|
|
5183
5932
|
if (typeof value === "string" && value) {
|
|
5184
5933
|
const upperKey = key.toUpperCase();
|
|
5185
5934
|
const envKey = upperKey.startsWith(`${prefix}_`) ? upperKey : `${prefix}_${upperKey}`;
|
|
5186
|
-
|
|
5935
|
+
envUpdates[envKey] = value;
|
|
5187
5936
|
}
|
|
5188
5937
|
}
|
|
5189
5938
|
}
|
|
5190
5939
|
}
|
|
5191
|
-
|
|
5192
|
-
const
|
|
5193
|
-
|
|
5194
|
-
|
|
5940
|
+
for (const integration of decryptedIntegrations) {
|
|
5941
|
+
const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
|
|
5942
|
+
const defaults = def?.remoteMcp?.envDefaults;
|
|
5943
|
+
if (!defaults)
|
|
5944
|
+
continue;
|
|
5945
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
5946
|
+
if (key in envUpdates)
|
|
5947
|
+
continue;
|
|
5948
|
+
envUpdates[key] = value;
|
|
5949
|
+
}
|
|
5195
5950
|
}
|
|
5951
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5952
|
+
mode: "replace-preserving",
|
|
5953
|
+
updates: envUpdates
|
|
5954
|
+
});
|
|
5196
5955
|
writeXurlStoreForIntegrations(decryptedIntegrations);
|
|
5197
|
-
const
|
|
5198
|
-
|
|
5199
|
-
|
|
5956
|
+
for (const integration of decryptedIntegrations) {
|
|
5957
|
+
const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
|
|
5958
|
+
if (!def?.nativeMcp)
|
|
5959
|
+
continue;
|
|
5960
|
+
const key = def.nativeMcp.key ?? integration.definition_id;
|
|
5961
|
+
this.writeMcpServer(codeName, key, buildNativeMcpEntry(def.nativeMcp, {
|
|
5962
|
+
agentId: agentId ?? "",
|
|
5963
|
+
agentCodeName: codeName,
|
|
5964
|
+
integration
|
|
5965
|
+
}));
|
|
5200
5966
|
}
|
|
5201
5967
|
const xeroIntegration = integrations.find((i) => i.definition_id === "xero");
|
|
5202
5968
|
if (xeroIntegration) {
|
|
@@ -5255,16 +6021,15 @@ ${sections}`
|
|
|
5255
6021
|
}
|
|
5256
6022
|
}
|
|
5257
6023
|
if (this.removeMcpServer) {
|
|
6024
|
+
const nativeMcpKeys = INTEGRATION_REGISTRY.filter((d) => d.nativeMcp !== void 0).map((d) => d.nativeMcp.key ?? d.id);
|
|
5258
6025
|
const integrationDerivedKeys = /* @__PURE__ */ new Set([
|
|
5259
|
-
"qmd",
|
|
5260
6026
|
"xero",
|
|
5261
6027
|
"postiz",
|
|
5262
6028
|
"cloud-broker",
|
|
6029
|
+
...nativeMcpKeys,
|
|
5263
6030
|
...Object.entries(OAUTH_PROVIDERS).filter(([, provider]) => Boolean(provider.mcpUrl)).map(([id]) => id)
|
|
5264
6031
|
]);
|
|
5265
6032
|
const expectedKeys = /* @__PURE__ */ new Set();
|
|
5266
|
-
if (hasQmd)
|
|
5267
|
-
expectedKeys.add("qmd");
|
|
5268
6033
|
if (xeroIntegration)
|
|
5269
6034
|
expectedKeys.add("xero");
|
|
5270
6035
|
if (postizIntegration)
|
|
@@ -5272,6 +6037,10 @@ ${sections}`
|
|
|
5272
6037
|
if (hasCloudBroker)
|
|
5273
6038
|
expectedKeys.add("cloud-broker");
|
|
5274
6039
|
for (const integration of integrations) {
|
|
6040
|
+
const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
|
|
6041
|
+
if (def?.nativeMcp) {
|
|
6042
|
+
expectedKeys.add(def.nativeMcp.key ?? integration.definition_id);
|
|
6043
|
+
}
|
|
5275
6044
|
if (buildRemoteMcpEntry(integration.definition_id)) {
|
|
5276
6045
|
expectedKeys.add(integration.definition_id);
|
|
5277
6046
|
}
|
|
@@ -5303,12 +6072,18 @@ ${sections}`
|
|
|
5303
6072
|
const envSrc = join4(agentDir2, ".env.integrations");
|
|
5304
6073
|
try {
|
|
5305
6074
|
const envContent = readFileSync5(envSrc, "utf-8");
|
|
5306
|
-
|
|
6075
|
+
const envDest = join4(projectDir, ".env.integrations");
|
|
6076
|
+
writeFileSync5(envDest, envContent, { mode: SECRET_FILE_MODE });
|
|
6077
|
+
try {
|
|
6078
|
+
chmodSync5(envDest, SECRET_FILE_MODE);
|
|
6079
|
+
} catch {
|
|
6080
|
+
}
|
|
5307
6081
|
} catch {
|
|
5308
6082
|
}
|
|
5309
6083
|
} catch {
|
|
5310
6084
|
}
|
|
5311
6085
|
renderChannelMessageHandlerForAgent(codeName);
|
|
6086
|
+
renderAugmentedWorkerForAgent(codeName);
|
|
5312
6087
|
},
|
|
5313
6088
|
writeMcpServer(codeName, serverId, config) {
|
|
5314
6089
|
const agentDir = getAgentDir(codeName);
|
|
@@ -5326,7 +6101,27 @@ ${sections}`
|
|
|
5326
6101
|
const mcpServers = mcpConfig["mcpServers"];
|
|
5327
6102
|
let serverEntry;
|
|
5328
6103
|
if ("url" in config) {
|
|
5329
|
-
serverEntry = buildUrlMcpServerEntry(config.url, config.headers);
|
|
6104
|
+
serverEntry = buildUrlMcpServerEntry(config.url, config.headers, config.type);
|
|
6105
|
+
const hoisted = {};
|
|
6106
|
+
const entryHeaders = serverEntry.headers;
|
|
6107
|
+
if (entryHeaders) {
|
|
6108
|
+
const composioKey = entryHeaders["x-api-key"];
|
|
6109
|
+
if (composioKey && !composioKey.includes("${")) {
|
|
6110
|
+
hoisted["COMPOSIO_API_KEY"] = composioKey;
|
|
6111
|
+
entryHeaders["x-api-key"] = "${COMPOSIO_API_KEY}";
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
const entryEnv = serverEntry.env;
|
|
6115
|
+
if (entryEnv) {
|
|
6116
|
+
const pdSecret = entryEnv["PIPEDREAM_CLIENT_SECRET"];
|
|
6117
|
+
if (pdSecret && !pdSecret.includes("${")) {
|
|
6118
|
+
hoisted["PIPEDREAM_CLIENT_SECRET"] = pdSecret;
|
|
6119
|
+
entryEnv["PIPEDREAM_CLIENT_SECRET"] = "${PIPEDREAM_CLIENT_SECRET}";
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
if (Object.keys(hoisted).length > 0) {
|
|
6123
|
+
writeEnvIntegrationsForAgent(codeName, { mode: "upsert", updates: hoisted });
|
|
6124
|
+
}
|
|
5330
6125
|
} else {
|
|
5331
6126
|
serverEntry = { command: config.command };
|
|
5332
6127
|
if (config.args?.length)
|
|
@@ -5379,14 +6174,14 @@ ${sections}`
|
|
|
5379
6174
|
mkdirSync4(join4(filePath, ".."), { recursive: true });
|
|
5380
6175
|
if (isPluginManaged && existsSync5(filePath)) {
|
|
5381
6176
|
try {
|
|
5382
|
-
|
|
6177
|
+
chmodSync5(filePath, READ_WRITE_MODE);
|
|
5383
6178
|
} catch {
|
|
5384
6179
|
}
|
|
5385
6180
|
}
|
|
5386
6181
|
writeFileSync5(filePath, file.content);
|
|
5387
6182
|
if (isPluginManaged) {
|
|
5388
6183
|
try {
|
|
5389
|
-
|
|
6184
|
+
chmodSync5(filePath, READ_ONLY_MODE);
|
|
5390
6185
|
} catch {
|
|
5391
6186
|
}
|
|
5392
6187
|
}
|
|
@@ -5567,7 +6362,7 @@ ${sections}`
|
|
|
5567
6362
|
return;
|
|
5568
6363
|
const tokenPath = join4(agentDir, ".tokens.json");
|
|
5569
6364
|
writeFileSync5(tokenPath, JSON.stringify(tokens, null, 2));
|
|
5570
|
-
|
|
6365
|
+
chmodSync5(tokenPath, SECRET_FILE_MODE);
|
|
5571
6366
|
}
|
|
5572
6367
|
};
|
|
5573
6368
|
registerFramework(claudeCodeAdapter);
|
|
@@ -5933,12 +6728,15 @@ function setActiveTeam(slug) {
|
|
|
5933
6728
|
config.active_team = slug;
|
|
5934
6729
|
saveConfig(config);
|
|
5935
6730
|
}
|
|
5936
|
-
var
|
|
6731
|
+
var PROD_AGT_HOST = "https://api.augmented.team";
|
|
6732
|
+
var AGT_HOST_UNSET_MESSAGE = "AGT_HOST is not set. Set it to the API URL for your stage before running this command:\n Production: export AGT_HOST=https://api.augmented.team\n Non-prod stage: export AGT_HOST=https://<stage>.api.staging.augmented.team\n Local development: export AGT_HOST=http://api.agt.localhost:1355\n\nFor first-time host setup, pass --api-host <url> to `agt setup` instead \u2014 the setup command does NOT silently default to prod (ENG-5831).";
|
|
5937
6733
|
function getHost() {
|
|
5938
6734
|
const envHost = process.env["AGT_HOST"]?.trim();
|
|
5939
|
-
|
|
6735
|
+
if (!envHost) {
|
|
6736
|
+
throw new Error(AGT_HOST_UNSET_MESSAGE);
|
|
6737
|
+
}
|
|
6738
|
+
return envHost;
|
|
5940
6739
|
}
|
|
5941
|
-
var AGT_HOST = getHost();
|
|
5942
6740
|
function requireHost() {
|
|
5943
6741
|
return getHost();
|
|
5944
6742
|
}
|
|
@@ -6142,7 +6940,7 @@ import { homedir as homedir6, userInfo } from "os";
|
|
|
6142
6940
|
import { spawn as spawn3 } from "child_process";
|
|
6143
6941
|
|
|
6144
6942
|
// src/lib/watchdog.ts
|
|
6145
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, closeSync, chmodSync as
|
|
6943
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, closeSync, chmodSync as chmodSync6 } from "fs";
|
|
6146
6944
|
import { join as join7 } from "path";
|
|
6147
6945
|
import { spawn as spawn2, execFileSync } from "child_process";
|
|
6148
6946
|
var DEFAULT_CONFIG_DIR = join7(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
@@ -6236,7 +7034,7 @@ function startWatchdog(opts) {
|
|
|
6236
7034
|
const { logFile } = getManagerPaths(configDir);
|
|
6237
7035
|
const logFd = openSync(logFile, "a", 384);
|
|
6238
7036
|
try {
|
|
6239
|
-
|
|
7037
|
+
chmodSync6(logFile, 384);
|
|
6240
7038
|
} catch {
|
|
6241
7039
|
}
|
|
6242
7040
|
const intervalSec = String(Math.max(Math.floor(opts.intervalMs / 1e3), 5));
|
|
@@ -6749,9 +7547,12 @@ async function managerUninstallSystemUnitCommand() {
|
|
|
6749
7547
|
export {
|
|
6750
7548
|
getIntegration,
|
|
6751
7549
|
extractCommandNotFound,
|
|
7550
|
+
LITERAL_SECRET_PATTERNS,
|
|
7551
|
+
safeWriteJsonAtomic,
|
|
6752
7552
|
INTEGRATIONS_SECTION_START,
|
|
6753
7553
|
INTEGRATIONS_SECTION_END,
|
|
6754
7554
|
estimateActiveTasksTokens,
|
|
7555
|
+
CHANNEL_SECRET_ENV_KEYS,
|
|
6755
7556
|
provisionStopHook,
|
|
6756
7557
|
provisionIsolationHook,
|
|
6757
7558
|
provisionOrientHook,
|
|
@@ -6761,7 +7562,7 @@ export {
|
|
|
6761
7562
|
getApiKey,
|
|
6762
7563
|
getActiveTeam,
|
|
6763
7564
|
setActiveTeam,
|
|
6764
|
-
|
|
7565
|
+
PROD_AGT_HOST,
|
|
6765
7566
|
getHost,
|
|
6766
7567
|
requireHost,
|
|
6767
7568
|
exchangeApiKey,
|
|
@@ -6785,4 +7586,4 @@ export {
|
|
|
6785
7586
|
managerInstallSystemUnitCommand,
|
|
6786
7587
|
managerUninstallSystemUnitCommand
|
|
6787
7588
|
};
|
|
6788
|
-
//# sourceMappingURL=chunk-
|
|
7589
|
+
//# sourceMappingURL=chunk-N2EBL4PZ.js.map
|