@integrity-labs/agt-cli 0.27.8-test.9 → 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 +798 -98
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-HT6EETEL.js → chunk-45I2X4M7.js} +361 -26
- package/dist/chunk-45I2X4M7.js.map +1 -0
- package/dist/{chunk-NFBRJGNU.js → chunk-N2EBL4PZ.js} +915 -239
- package/dist/chunk-N2EBL4PZ.js.map +1 -0
- package/dist/{chunk-F4NG4EXD.js → chunk-SJPAXXTW.js} +512 -80
- package/dist/chunk-SJPAXXTW.js.map +1 -0
- package/dist/{claude-pair-runtime-OBAJZDXK.js → claude-pair-runtime-OUGV74LY.js} +2 -2
- package/dist/lib/manager-worker.js +2058 -508
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/index.js +143 -67
- package/dist/mcp/slack-channel.js +1059 -297
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +999 -140
- package/dist/{persistent-session-SBSOZG74.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-F4NG4EXD.js.map +0 -1
- package/dist/chunk-HT6EETEL.js.map +0 -1
- package/dist/chunk-NFBRJGNU.js.map +0 -1
- package/dist/responsiveness-probe-DU4IJ2RZ.js +0 -33
- package/dist/responsiveness-probe-DU4IJ2RZ.js.map +0 -1
- /package/dist/{claude-pair-runtime-OBAJZDXK.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
- /package/dist/{persistent-session-SBSOZG74.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",
|
|
@@ -2322,6 +2356,299 @@ function extractAllowedDomains(input) {
|
|
|
2322
2356
|
}
|
|
2323
2357
|
registerFramework(nemoClawAdapter);
|
|
2324
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
|
+
|
|
2325
2652
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/identity.js
|
|
2326
2653
|
function buildMemorySection(hasQmd) {
|
|
2327
2654
|
const recall = hasQmd ? `### Recall
|
|
@@ -2570,15 +2897,15 @@ later tick:
|
|
|
2570
2897
|
- For long-form output (>500 chars), lead with a one-line summary,
|
|
2571
2898
|
then a blank line, then the full content. Channel formatters
|
|
2572
2899
|
truncate gracefully but the lede always lands.
|
|
2573
|
-
- **\`
|
|
2574
|
-
|
|
2575
|
-
attempted but
|
|
2576
|
-
-
|
|
2577
|
-
|
|
2578
|
-
duplicate of another task, asker
|
|
2579
|
-
already current).
|
|
2580
|
-
|
|
2581
|
-
|
|
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.
|
|
2582
2909
|
- **\`kanban_update\` with notes** if you're blocked but might unblock
|
|
2583
2910
|
later; leave the row in \`in_progress\` and pick up other work.
|
|
2584
2911
|
|
|
@@ -2922,6 +3249,16 @@ function formatEmailDomainConfigLines(config) {
|
|
|
2922
3249
|
lines.push(` *Advisory: honor this restriction \u2014 it is guidance in your instructions, not a hard block at send time.*`);
|
|
2923
3250
|
return lines;
|
|
2924
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
|
+
}
|
|
2925
3262
|
function renderGuardrailBullet(g) {
|
|
2926
3263
|
const lines = [];
|
|
2927
3264
|
const header = `- **${g.displayName}** (${g.category}, from ${g.source})`;
|
|
@@ -2929,16 +3266,26 @@ function renderGuardrailBullet(g) {
|
|
|
2929
3266
|
if (g.description?.trim()) {
|
|
2930
3267
|
lines.push(` ${g.description.trim()}`);
|
|
2931
3268
|
}
|
|
2932
|
-
lines.push(...g.definitionId === EMAIL_DOMAIN_RESTRICT_DEF ? formatEmailDomainConfigLines(g.config) : 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));
|
|
2933
3270
|
if (g.overrideApplied && g.overrideReason?.trim()) {
|
|
2934
3271
|
lines.push(` *Override applied: ${g.overrideReason.trim()}*`);
|
|
2935
3272
|
}
|
|
2936
3273
|
return lines.join("\n");
|
|
2937
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
|
+
}
|
|
2938
3285
|
function buildGuardrailsSection(guardrails) {
|
|
2939
3286
|
if (!guardrails || guardrails.length === 0)
|
|
2940
3287
|
return "";
|
|
2941
|
-
const active = guardrails.filter((g) => g.enforcement !== "disabled");
|
|
3288
|
+
const active = guardrails.map((g) => ({ ...g, enforcement: effectiveCalendarEnforcement(g) })).filter((g) => g.enforcement !== "disabled");
|
|
2942
3289
|
if (active.length === 0)
|
|
2943
3290
|
return "";
|
|
2944
3291
|
const enforce = active.filter((g) => g.enforcement === "enforce");
|
|
@@ -3014,33 +3361,72 @@ acknowledge before you start.
|
|
|
3014
3361
|
- **FAST (< 60s):** handle inline. Reply via the channel tool
|
|
3015
3362
|
(slack.reply / telegram.reply / directchat.reply) and end your turn.
|
|
3016
3363
|
|
|
3017
|
-
- **SLOW (\u2265 60s):**
|
|
3364
|
+
- **SLOW (\u2265 60s):** acknowledge first, then handle inline.
|
|
3018
3365
|
1. Send a one-line acknowledgement via the channel tool \u2014 short, warm,
|
|
3019
3366
|
and tell the user you'll come back. Example shape (don't copy verbatim,
|
|
3020
3367
|
match your voice): "On it \u2014 this'll take a minute or two, I'll ping
|
|
3021
3368
|
when it's done."
|
|
3022
|
-
2.
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
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.
|
|
3044
3430
|
|
|
3045
3431
|
${activeTasksSection}${personalitySection}## Identity
|
|
3046
3432
|
|
|
@@ -3050,13 +3436,30 @@ ${activeTasksSection}${personalitySection}## Identity
|
|
|
3050
3436
|
- Risk Tier: ${frontmatter.risk_tier}
|
|
3051
3437
|
- Timezone: ${timezone?.trim() || "UTC"}
|
|
3052
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.
|
|
3053
3454
|
${resolvedChannels?.includes("slack") ? `
|
|
3054
3455
|
## Slack
|
|
3055
3456
|
|
|
3056
3457
|
You have a Slack MCP server connected. **First, see \xA7 FIRST ACTION on
|
|
3057
3458
|
every channel message: triage** at the top of this document \u2014 decide
|
|
3058
|
-
fast vs slow before anything else,
|
|
3059
|
-
|
|
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).
|
|
3060
3463
|
|
|
3061
3464
|
For fast requests, respond directly in the conversation. You can also
|
|
3062
3465
|
proactively use:
|
|
@@ -3214,15 +3617,21 @@ When you receive a request via any channel (Slack, Telegram, direct chat):
|
|
|
3214
3617
|
3. **Create the kanban task** with kanban.add. Title should be specific
|
|
3215
3618
|
enough to be self-explanatory \u2014 "Pull Linear ENG sprint velocity for
|
|
3216
3619
|
this fortnight" beats "Linear stats".
|
|
3217
|
-
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"}".
|
|
3218
3627
|
5. Move the task to in_progress with kanban.move
|
|
3219
3628
|
6. Do the work
|
|
3220
3629
|
7. Mark done with kanban.done including a result summary
|
|
3221
3630
|
8. Reply in the channel thread with the result
|
|
3222
3631
|
|
|
3223
3632
|
Everything that isn't exempt gets a task \u2014 but only after the request is
|
|
3224
|
-
clear. Don't bury a clarifying question underneath
|
|
3225
|
-
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
|
|
3226
3635
|
anything goes on the board.
|
|
3227
3636
|
|
|
3228
3637
|
When asked about existing work, tasks, or what you've been doing \u2014 call kanban.list
|
|
@@ -3347,163 +3756,90 @@ The marginal cost of completeness is near zero. Do the whole thing.
|
|
|
3347
3756
|
${frontmatter.environment === "prod" ? "- Production environment: exercise extra caution with all operations.\n" : ""}`;
|
|
3348
3757
|
}
|
|
3349
3758
|
|
|
3350
|
-
// ../../packages/core/dist/provisioning/
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
const
|
|
3372
|
-
|
|
3373
|
-
return {
|
|
3374
|
-
ok: false,
|
|
3375
|
-
errors: [
|
|
3376
|
-
{
|
|
3377
|
-
kind: "invalid_json_shape",
|
|
3378
|
-
server: "*",
|
|
3379
|
-
message: "config root must be a non-null object"
|
|
3380
|
-
}
|
|
3381
|
-
]
|
|
3382
|
-
};
|
|
3383
|
-
}
|
|
3384
|
-
const root = config;
|
|
3385
|
-
if (root.mcpServers === void 0) {
|
|
3386
|
-
return { ok: true };
|
|
3387
|
-
}
|
|
3388
|
-
if (typeof root.mcpServers !== "object" || root.mcpServers === null) {
|
|
3389
|
-
return {
|
|
3390
|
-
ok: false,
|
|
3391
|
-
errors: [
|
|
3392
|
-
{
|
|
3393
|
-
kind: "invalid_json_shape",
|
|
3394
|
-
server: "*",
|
|
3395
|
-
message: "mcpServers must be an object"
|
|
3396
|
-
}
|
|
3397
|
-
]
|
|
3398
|
-
};
|
|
3399
|
-
}
|
|
3400
|
-
if (Array.isArray(root.mcpServers)) {
|
|
3401
|
-
return {
|
|
3402
|
-
ok: false,
|
|
3403
|
-
errors: [
|
|
3404
|
-
{
|
|
3405
|
-
kind: "invalid_json_shape",
|
|
3406
|
-
server: "*",
|
|
3407
|
-
message: "mcpServers must be an object"
|
|
3408
|
-
}
|
|
3409
|
-
]
|
|
3410
|
-
};
|
|
3411
|
-
}
|
|
3412
|
-
for (const [serverKey, raw] of Object.entries(root.mcpServers)) {
|
|
3413
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
3414
|
-
errors.push({
|
|
3415
|
-
kind: "invalid_json_shape",
|
|
3416
|
-
server: serverKey,
|
|
3417
|
-
message: `entry must be an object`
|
|
3418
|
-
});
|
|
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("="))
|
|
3419
3782
|
continue;
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
const entryRecord = entry;
|
|
3423
|
-
if (typeof entryRecord["url"] === "string") {
|
|
3424
|
-
const type = entryRecord["type"];
|
|
3425
|
-
if (type !== "http" && type !== "sse") {
|
|
3426
|
-
errors.push({
|
|
3427
|
-
kind: "remote_mcp_missing_type",
|
|
3428
|
-
server: serverKey,
|
|
3429
|
-
message: `url-based entry must include \`type: "http"\` or \`type: "sse"\` (Claude Code MCP schema requires it)`
|
|
3430
|
-
});
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
const rules = REQUIRED_ENV_RULES_BY_SERVER[serverKey];
|
|
3434
|
-
if (rules) {
|
|
3435
|
-
const env = entry.env ?? {};
|
|
3436
|
-
for (const rule of rules) {
|
|
3437
|
-
const value = env[rule.key];
|
|
3438
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
3439
|
-
errors.push({
|
|
3440
|
-
kind: "missing_required_env",
|
|
3441
|
-
server: serverKey,
|
|
3442
|
-
message: `missing required env key: ${rule.key}`
|
|
3443
|
-
});
|
|
3444
|
-
continue;
|
|
3445
|
-
}
|
|
3446
|
-
if (rule.mustBeConcrete && PLACEHOLDER_RE.test(value)) {
|
|
3447
|
-
errors.push({
|
|
3448
|
-
kind: "unexpanded_placeholder",
|
|
3449
|
-
server: serverKey,
|
|
3450
|
-
message: `env.${rule.key} contains an unexpanded \${...} placeholder; expected a concrete value`
|
|
3451
|
-
});
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3783
|
+
const eqIdx = line.indexOf("=");
|
|
3784
|
+
out.set(line.slice(0, eqIdx), line.slice(eqIdx + 1));
|
|
3455
3785
|
}
|
|
3456
|
-
return
|
|
3457
|
-
}
|
|
3458
|
-
function formatValidationErrors(errors) {
|
|
3459
|
-
return errors.map((e) => `${e.server}:${e.kind}=${e.message}`).join("; ");
|
|
3786
|
+
return out;
|
|
3460
3787
|
}
|
|
3461
|
-
function
|
|
3462
|
-
const
|
|
3463
|
-
const
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
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));
|
|
3476
3804
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
if (
|
|
3481
|
-
|
|
3482
|
-
} catch {
|
|
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));
|
|
3483
3810
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
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));
|
|
3488
3817
|
}
|
|
3489
3818
|
}
|
|
3490
|
-
throw err;
|
|
3491
|
-
}
|
|
3492
|
-
}
|
|
3493
|
-
function safeWriteMcpJson(path, config) {
|
|
3494
|
-
const validation = validateRenderedMcpConfig(config);
|
|
3495
|
-
if (!validation.ok) {
|
|
3496
|
-
return { written: false, errors: validation.errors };
|
|
3497
3819
|
}
|
|
3498
|
-
|
|
3499
|
-
return { written: true, errors: [] };
|
|
3820
|
+
return renderEnvIntegrations(next);
|
|
3500
3821
|
}
|
|
3501
3822
|
|
|
3823
|
+
// ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
|
|
3824
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, chmodSync as chmodSync5, readdirSync, rmSync, copyFileSync } from "fs";
|
|
3825
|
+
import { join as join4, relative, dirname as dirname4 } from "path";
|
|
3826
|
+
import { homedir as homedir3 } from "os";
|
|
3827
|
+
import { execFile as execFile3 } from "child_process";
|
|
3828
|
+
|
|
3502
3829
|
// ../../packages/core/dist/provisioning/remote-mcp.js
|
|
3503
3830
|
function envVarForToken(definitionId) {
|
|
3504
3831
|
return `${definitionId.replace(/-/g, "_").toUpperCase()}_ACCESS_TOKEN`;
|
|
3505
3832
|
}
|
|
3506
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
|
+
}
|
|
3507
3843
|
const provider = OAUTH_PROVIDERS[definitionId];
|
|
3508
3844
|
if (!provider?.mcpUrl)
|
|
3509
3845
|
return null;
|
|
@@ -3579,8 +3915,95 @@ function resolveTemplate(input, ctx) {
|
|
|
3579
3915
|
// ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
|
|
3580
3916
|
var VALID_CODE_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3581
3917
|
var SECRET_FILE_MODE = 384;
|
|
3582
|
-
function
|
|
3583
|
-
|
|
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
|
+
}
|
|
3584
4007
|
}
|
|
3585
4008
|
function assertValidCodeName(codeName) {
|
|
3586
4009
|
if (!VALID_CODE_NAME.test(codeName)) {
|
|
@@ -3669,10 +4092,23 @@ function syncMcpToProject(codeName) {
|
|
|
3669
4092
|
try {
|
|
3670
4093
|
const content = readFileSync5(provisionMcpPath, "utf-8");
|
|
3671
4094
|
mkdirSync4(projectDir, { recursive: true });
|
|
3672
|
-
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
|
+
}
|
|
3673
4108
|
} catch {
|
|
3674
4109
|
}
|
|
3675
4110
|
renderChannelMessageHandlerForAgent(codeName);
|
|
4111
|
+
renderAugmentedWorkerForAgent(codeName);
|
|
3676
4112
|
}
|
|
3677
4113
|
var INTEGRATIONS_SUMMARY_FILE = "integrations-summary.json";
|
|
3678
4114
|
function integrationsSummaryPath(codeName) {
|
|
@@ -3778,7 +4214,15 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3778
4214
|
continue;
|
|
3779
4215
|
}
|
|
3780
4216
|
}
|
|
3781
|
-
|
|
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
|
+
}
|
|
3782
4226
|
} catch {
|
|
3783
4227
|
}
|
|
3784
4228
|
}
|
|
@@ -3864,14 +4308,23 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3864
4308
|
return !(entry && typeof entry["url"] === "string" && entry["url"].startsWith("/"));
|
|
3865
4309
|
}));
|
|
3866
4310
|
projectMcp["mcpServers"] = { ...stripRelativeUrls(projectServers), ...stripRelativeUrls(agentServers) };
|
|
3867
|
-
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
|
+
}
|
|
3868
4316
|
} catch {
|
|
3869
4317
|
}
|
|
3870
4318
|
const agentDir = getAgentDir(codeName);
|
|
3871
4319
|
for (const envFile of [".env", ".env.integrations"]) {
|
|
3872
4320
|
try {
|
|
3873
4321
|
const content = readFileSync5(join4(agentDir, envFile), "utf-8");
|
|
3874
|
-
|
|
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
|
+
}
|
|
3875
4328
|
} catch {
|
|
3876
4329
|
}
|
|
3877
4330
|
}
|
|
@@ -3886,7 +4339,7 @@ function deployArtifactsToProject(codeName, provisionDir) {
|
|
|
3886
4339
|
const upToDate = existsSync5(hookDest) && readFileSync5(hookDest, "utf-8") === srcContent;
|
|
3887
4340
|
if (!upToDate)
|
|
3888
4341
|
writeFileSync5(hookDest, srcContent);
|
|
3889
|
-
|
|
4342
|
+
chmodSync5(hookDest, 493);
|
|
3890
4343
|
}
|
|
3891
4344
|
} catch {
|
|
3892
4345
|
}
|
|
@@ -4403,6 +4856,7 @@ function buildChannelMessageHandlerAgent(args) {
|
|
|
4403
4856
|
"Glob",
|
|
4404
4857
|
"Skill",
|
|
4405
4858
|
"Agent",
|
|
4859
|
+
"ToolSearch",
|
|
4406
4860
|
...mcpWildcards
|
|
4407
4861
|
].join(", ");
|
|
4408
4862
|
const integrationsBlock = integrations.length === 0 ? "" : `
|
|
@@ -4441,6 +4895,82 @@ Your job:
|
|
|
4441
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.
|
|
4442
4896
|
${integrationsBlock}`;
|
|
4443
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
|
+
}
|
|
4444
4974
|
function buildPostizMcpEntry(integration) {
|
|
4445
4975
|
const rawBaseUrl = integration.config["base_url"];
|
|
4446
4976
|
const postizBaseUrl = typeof rawBaseUrl === "string" ? rawBaseUrl.trim() : "";
|
|
@@ -4468,7 +4998,13 @@ function buildMcpJson(input) {
|
|
|
4468
4998
|
args: [localMcpPath],
|
|
4469
4999
|
env: {
|
|
4470
5000
|
AGT_HOST: process.env["AGT_HOST"] ?? "",
|
|
4471
|
-
|
|
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}",
|
|
4472
5008
|
AGT_AGENT_ID: input.agent.agent_id,
|
|
4473
5009
|
AGT_AGENT_CODE_NAME: input.agent.code_name,
|
|
4474
5010
|
// ENG-4561: Claude Code substitutes `${VAR}` in .mcp.json env values
|
|
@@ -4591,7 +5127,7 @@ function mapScheduledTasks(tasks) {
|
|
|
4591
5127
|
};
|
|
4592
5128
|
});
|
|
4593
5129
|
}
|
|
4594
|
-
function buildUrlMcpServerEntry(url, headers) {
|
|
5130
|
+
function buildUrlMcpServerEntry(url, headers, type = "http") {
|
|
4595
5131
|
const hasHeaders = !!headers && Object.keys(headers).length > 0;
|
|
4596
5132
|
if (url.includes("composio.dev") && hasHeaders) {
|
|
4597
5133
|
return { type: "http", url, headers };
|
|
@@ -4614,7 +5150,7 @@ function buildUrlMcpServerEntry(url, headers) {
|
|
|
4614
5150
|
};
|
|
4615
5151
|
}
|
|
4616
5152
|
if (hasHeaders) {
|
|
4617
|
-
return { type
|
|
5153
|
+
return { type, url, headers };
|
|
4618
5154
|
}
|
|
4619
5155
|
return { command: "npx", args: ["-y", "mcp-remote", url, "--allow-http"] };
|
|
4620
5156
|
}
|
|
@@ -4695,6 +5231,18 @@ var claudeCodeAdapter = {
|
|
|
4695
5231
|
integrations: integrationSummaries
|
|
4696
5232
|
})
|
|
4697
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
|
+
},
|
|
4698
5246
|
{
|
|
4699
5247
|
relativePath: `provision/${INTEGRATIONS_SUMMARY_FILE}`,
|
|
4700
5248
|
content: JSON.stringify(integrationSummaries, null, 2)
|
|
@@ -4812,7 +5360,7 @@ ${sections}`
|
|
|
4812
5360
|
if (envLines.length > 1) {
|
|
4813
5361
|
const envPath = join4(agentDir, ".env");
|
|
4814
5362
|
writeFileSync5(envPath, envLines.join("\n") + "\n");
|
|
4815
|
-
|
|
5363
|
+
chmodSync5(envPath, SECRET_FILE_MODE);
|
|
4816
5364
|
}
|
|
4817
5365
|
},
|
|
4818
5366
|
// Claude Code has no gateway process — methods intentionally omitted
|
|
@@ -4836,20 +5384,14 @@ ${sections}`
|
|
|
4836
5384
|
},
|
|
4837
5385
|
writeChannelCredentials(codeName, channelId, config, options) {
|
|
4838
5386
|
const tzEnv = options?.agentTimezone && options.agentTimezone.trim() !== "" ? { TZ: options.agentTimezone.trim() } : {};
|
|
4839
|
-
const
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
// sentinel keys consumed by the per-channel spread below
|
|
4848
|
-
_SENDER_POLICY_MODE: sp.mode
|
|
4849
|
-
};
|
|
4850
|
-
})();
|
|
4851
|
-
const senderPolicyMode = senderPolicyEnv["_SENDER_POLICY_MODE"];
|
|
4852
|
-
delete senderPolicyEnv["_SENDER_POLICY_MODE"];
|
|
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 } : {};
|
|
4853
5395
|
const agentDir = getAgentDir(codeName);
|
|
4854
5396
|
mkdirSync4(agentDir, { recursive: true });
|
|
4855
5397
|
const isPersistent = options?.sessionMode === "persistent";
|
|
@@ -4862,17 +5404,28 @@ ${sections}`
|
|
|
4862
5404
|
const localTelegramChannel = join4(getHomeDir3(), ".augmented", "_mcp", "telegram-channel.js");
|
|
4863
5405
|
const resolvedAgtHostForTelegram = process.env["AGT_HOST"]?.trim() || "https://api.augmented.team";
|
|
4864
5406
|
const resolvedAgtApiKeyForTelegram = process.env["AGT_API_KEY"]?.trim();
|
|
5407
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5408
|
+
mode: "upsert",
|
|
5409
|
+
updates: { TELEGRAM_BOT_TOKEN: botToken }
|
|
5410
|
+
});
|
|
4865
5411
|
const telegramEnv = {
|
|
4866
|
-
TELEGRAM_BOT_TOKEN:
|
|
5412
|
+
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN}",
|
|
4867
5413
|
AGT_AGENT_CODE_NAME: codeName,
|
|
4868
5414
|
AGT_HOST: resolvedAgtHostForTelegram,
|
|
4869
|
-
...resolvedAgtApiKeyForTelegram ? { AGT_API_KEY:
|
|
5415
|
+
...resolvedAgtApiKeyForTelegram ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
4870
5416
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {},
|
|
4871
5417
|
...tzEnv
|
|
4872
5418
|
};
|
|
4873
5419
|
if (allowedChats && allowedChats.length > 0) {
|
|
4874
5420
|
telegramEnv.TELEGRAM_ALLOWED_CHATS = allowedChats.join(",");
|
|
4875
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
|
+
}
|
|
4876
5429
|
const rawPeerAgentMode = config["peer_agent_mode"];
|
|
4877
5430
|
if (rawPeerAgentMode === "listen" || rawPeerAgentMode === "respond") {
|
|
4878
5431
|
telegramEnv.TELEGRAM_PEER_AGENT_MODE = rawPeerAgentMode;
|
|
@@ -4937,6 +5490,7 @@ ${sections}`
|
|
|
4937
5490
|
const appToken = config["app_token"];
|
|
4938
5491
|
const threadAutoFollow = config["thread_auto_follow"];
|
|
4939
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()) : [];
|
|
4940
5494
|
const blockKitEnabled = config["block_kit_enabled"] === true;
|
|
4941
5495
|
const blockKitAskUserEnabled = config["block_kit_ask_user_enabled"] === true;
|
|
4942
5496
|
const blockKitDisabled = process.env["SLACK_BLOCK_KIT_DISABLED"] === "true";
|
|
@@ -4946,7 +5500,9 @@ ${sections}`
|
|
|
4946
5500
|
...blockKitAskUserEnabled ? { SLACK_BLOCK_KIT_ASK_USER_ENABLED: "true" } : {},
|
|
4947
5501
|
...blockKitDisabled ? { SLACK_BLOCK_KIT_DISABLED: "true" } : {},
|
|
4948
5502
|
...blockKitAskUserEnabled ? { AGT_HOST: resolvedAgtHost } : {},
|
|
4949
|
-
|
|
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}" } : {},
|
|
4950
5506
|
...blockKitAskUserEnabled && options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
4951
5507
|
} : {};
|
|
4952
5508
|
if (botToken) {
|
|
@@ -4975,16 +5531,26 @@ ${sections}`
|
|
|
4975
5531
|
const slackResolvedAgtApiKey = process.env["AGT_API_KEY"]?.trim();
|
|
4976
5532
|
const slackAgtAuthEnv = {
|
|
4977
5533
|
AGT_HOST: resolvedAgtHost,
|
|
4978
|
-
|
|
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}" } : {},
|
|
4979
5538
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
4980
5539
|
};
|
|
5540
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5541
|
+
mode: "upsert",
|
|
5542
|
+
updates: {
|
|
5543
|
+
SLACK_BOT_TOKEN: botToken,
|
|
5544
|
+
...appToken ? { SLACK_APP_TOKEN: appToken } : {}
|
|
5545
|
+
}
|
|
5546
|
+
});
|
|
4981
5547
|
const localSlackChannel = join4(getHomeDir3(), ".augmented", "_mcp", "slack-channel.js");
|
|
4982
5548
|
const slackEntry = {
|
|
4983
5549
|
command: existsSync5(localSlackChannel) ? "node" : "npx",
|
|
4984
5550
|
args: existsSync5(localSlackChannel) ? [localSlackChannel] : ["-y", "@augmented/claude-code-channel-slack"],
|
|
4985
5551
|
env: {
|
|
4986
|
-
SLACK_BOT_TOKEN:
|
|
4987
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5552
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5553
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
4988
5554
|
...threadAutoFollow && threadAutoFollow !== "off" ? { SLACK_THREAD_AUTO_FOLLOW: threadAutoFollow } : {},
|
|
4989
5555
|
// ENG-4464: only emit when non-default — `mention_only` is the
|
|
4990
5556
|
// default in slack-response-mode.ts, so omitting keeps the env
|
|
@@ -5007,8 +5573,39 @@ ${sections}`
|
|
|
5007
5573
|
// 'all' is the absence of the env var — same convention the
|
|
5008
5574
|
// MCP filter uses on its own end).
|
|
5009
5575
|
...senderPolicyMode ? { SLACK_SENDER_POLICY: senderPolicyMode } : {},
|
|
5010
|
-
...senderPolicyEnv
|
|
5576
|
+
...senderPolicyEnv,
|
|
5011
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(",") } : {}
|
|
5012
5609
|
}
|
|
5013
5610
|
};
|
|
5014
5611
|
const provisionMcpPath = join4(agentDir, "provision", ".mcp.json");
|
|
@@ -5064,6 +5661,8 @@ ${sections}`
|
|
|
5064
5661
|
const slackAutoFollowEnv = slackThreadAutoFollow && slackThreadAutoFollow !== "off" ? { SLACK_THREAD_AUTO_FOLLOW: slackThreadAutoFollow } : {};
|
|
5065
5662
|
const slackChannelResponseMode = config["channel_response_mode"];
|
|
5066
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(",") } : {};
|
|
5067
5666
|
const oneshotBlockKitEnabled = config["block_kit_enabled"] === true;
|
|
5068
5667
|
const oneshotBlockKitAskUserEnabled = config["block_kit_ask_user_enabled"] === true;
|
|
5069
5668
|
const oneshotBlockKitDisabled = process.env["SLACK_BLOCK_KIT_DISABLED"] === "true";
|
|
@@ -5073,7 +5672,8 @@ ${sections}`
|
|
|
5073
5672
|
...oneshotBlockKitAskUserEnabled ? { SLACK_BLOCK_KIT_ASK_USER_ENABLED: "true" } : {},
|
|
5074
5673
|
...oneshotBlockKitDisabled ? { SLACK_BLOCK_KIT_DISABLED: "true" } : {},
|
|
5075
5674
|
...oneshotBlockKitAskUserEnabled ? { AGT_HOST: oneshotResolvedAgtHost } : {},
|
|
5076
|
-
|
|
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}" } : {},
|
|
5077
5677
|
...oneshotBlockKitAskUserEnabled && options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
5078
5678
|
} : {};
|
|
5079
5679
|
const slackPeerEnv = {};
|
|
@@ -5106,18 +5706,27 @@ ${sections}`
|
|
|
5106
5706
|
const slackAgtAuthEnv = {
|
|
5107
5707
|
AGT_HOST: process.env["AGT_HOST"]?.trim() || "https://api.augmented.team",
|
|
5108
5708
|
AGT_AGENT_CODE_NAME: codeName,
|
|
5109
|
-
|
|
5709
|
+
// ENG-5901 Track D: template; manager spawn env carries the value.
|
|
5710
|
+
...slackResolvedAgtApiKey ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
5110
5711
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
5111
5712
|
};
|
|
5713
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5714
|
+
mode: "upsert",
|
|
5715
|
+
updates: {
|
|
5716
|
+
SLACK_BOT_TOKEN: botToken,
|
|
5717
|
+
...appToken ? { SLACK_APP_TOKEN: appToken } : {}
|
|
5718
|
+
}
|
|
5719
|
+
});
|
|
5112
5720
|
if (isPersistent && existsSync5(localSlackChannel)) {
|
|
5113
5721
|
mcpServers["slack"] = {
|
|
5114
5722
|
command: "node",
|
|
5115
5723
|
args: [localSlackChannel],
|
|
5116
5724
|
env: {
|
|
5117
|
-
SLACK_BOT_TOKEN:
|
|
5118
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5725
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5726
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
5119
5727
|
...slackAutoFollowEnv,
|
|
5120
5728
|
...slackResponseModeEnv,
|
|
5729
|
+
...slackAllowedUsersEnv,
|
|
5121
5730
|
...oneshotBlockKitEnv,
|
|
5122
5731
|
...slackPeerEnv,
|
|
5123
5732
|
...slackAgtAuthEnv,
|
|
@@ -5129,10 +5738,11 @@ ${sections}`
|
|
|
5129
5738
|
command: "npx",
|
|
5130
5739
|
args: ["-y", "@augmented/claude-code-channel-slack"],
|
|
5131
5740
|
env: {
|
|
5132
|
-
SLACK_BOT_TOKEN:
|
|
5133
|
-
...appToken ? { SLACK_APP_TOKEN:
|
|
5741
|
+
SLACK_BOT_TOKEN: "${SLACK_BOT_TOKEN}",
|
|
5742
|
+
...appToken ? { SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" } : {},
|
|
5134
5743
|
...slackAutoFollowEnv,
|
|
5135
5744
|
...slackResponseModeEnv,
|
|
5745
|
+
...slackAllowedUsersEnv,
|
|
5136
5746
|
...oneshotBlockKitEnv,
|
|
5137
5747
|
...slackPeerEnv,
|
|
5138
5748
|
...slackAgtAuthEnv,
|
|
@@ -5167,12 +5777,17 @@ ${sections}`
|
|
|
5167
5777
|
const msteamsAgtAuthEnv = {
|
|
5168
5778
|
AGT_HOST: process.env["AGT_HOST"]?.trim() || "https://api.augmented.team",
|
|
5169
5779
|
AGT_AGENT_CODE_NAME: codeName,
|
|
5170
|
-
|
|
5780
|
+
// ENG-5901 Track D: template; manager spawn env carries the value.
|
|
5781
|
+
...msResolvedAgtApiKey ? { AGT_API_KEY: "${AGT_API_KEY}" } : {},
|
|
5171
5782
|
...options?.agentId ? { AGT_AGENT_ID: options.agentId } : {}
|
|
5172
5783
|
};
|
|
5784
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5785
|
+
mode: "upsert",
|
|
5786
|
+
updates: { MSTEAMS_CLIENT_SECRET: clientSecret }
|
|
5787
|
+
});
|
|
5173
5788
|
const teamsEnv = {
|
|
5174
5789
|
MSTEAMS_APP_ID: appId,
|
|
5175
|
-
MSTEAMS_CLIENT_SECRET:
|
|
5790
|
+
MSTEAMS_CLIENT_SECRET: "${MSTEAMS_CLIENT_SECRET}",
|
|
5176
5791
|
MSTEAMS_TENANT_ID: tenantId,
|
|
5177
5792
|
...botObjectId ? { MSTEAMS_BOT_OBJECT_ID: botObjectId } : {},
|
|
5178
5793
|
...allowedTeamIds.length > 0 ? { MSTEAMS_ALLOWED_TEAMS: allowedTeamIds.join(",") } : {},
|
|
@@ -5188,8 +5803,24 @@ ${sections}`
|
|
|
5188
5803
|
// ENG-5841: MSTEAMS_SENDER_POLICY drives teams-inbound-filter.ts.
|
|
5189
5804
|
// Mirrors the Slack branch above — only emitted when restrictive.
|
|
5190
5805
|
...senderPolicyMode ? { MSTEAMS_SENDER_POLICY: senderPolicyMode } : {},
|
|
5191
|
-
...senderPolicyEnv
|
|
5806
|
+
...senderPolicyEnv,
|
|
5192
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 } : {}
|
|
5193
5824
|
};
|
|
5194
5825
|
if (isPersistent && existsSync5(localTeamsChannel)) {
|
|
5195
5826
|
mcpServers["msteams"] = {
|
|
@@ -5243,6 +5874,12 @@ ${sections}`
|
|
|
5243
5874
|
});
|
|
5244
5875
|
return changed;
|
|
5245
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
|
+
},
|
|
5246
5883
|
seedProfileConfig(codeName) {
|
|
5247
5884
|
const agentDir = getAgentDir(codeName);
|
|
5248
5885
|
const projectDir = getProjectDir(codeName);
|
|
@@ -5274,19 +5911,19 @@ ${sections}`
|
|
|
5274
5911
|
...integration,
|
|
5275
5912
|
credentials: decryptIntegrationCredentials(integration.credentials)
|
|
5276
5913
|
}));
|
|
5277
|
-
const
|
|
5914
|
+
const envUpdates = {};
|
|
5278
5915
|
for (const integration of decryptedIntegrations) {
|
|
5279
5916
|
const prefix = integration.definition_id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
5280
5917
|
const creds = integration.credentials;
|
|
5281
5918
|
if (integration.auth_type === "oauth2") {
|
|
5282
5919
|
const accessToken = creds.access_token;
|
|
5283
5920
|
if (accessToken) {
|
|
5284
|
-
|
|
5921
|
+
envUpdates[`${prefix}_ACCESS_TOKEN`] = accessToken;
|
|
5285
5922
|
}
|
|
5286
5923
|
} else if (integration.auth_type === "api_key") {
|
|
5287
5924
|
const apiKey = creds.api_key;
|
|
5288
5925
|
if (apiKey) {
|
|
5289
|
-
|
|
5926
|
+
envUpdates[`${prefix}_API_KEY`] = apiKey;
|
|
5290
5927
|
}
|
|
5291
5928
|
}
|
|
5292
5929
|
if (integration.config) {
|
|
@@ -5295,16 +5932,26 @@ ${sections}`
|
|
|
5295
5932
|
if (typeof value === "string" && value) {
|
|
5296
5933
|
const upperKey = key.toUpperCase();
|
|
5297
5934
|
const envKey = upperKey.startsWith(`${prefix}_`) ? upperKey : `${prefix}_${upperKey}`;
|
|
5298
|
-
|
|
5935
|
+
envUpdates[envKey] = value;
|
|
5299
5936
|
}
|
|
5300
5937
|
}
|
|
5301
5938
|
}
|
|
5302
5939
|
}
|
|
5303
|
-
|
|
5304
|
-
const
|
|
5305
|
-
|
|
5306
|
-
|
|
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
|
+
}
|
|
5307
5950
|
}
|
|
5951
|
+
writeEnvIntegrationsForAgent(codeName, {
|
|
5952
|
+
mode: "replace-preserving",
|
|
5953
|
+
updates: envUpdates
|
|
5954
|
+
});
|
|
5308
5955
|
writeXurlStoreForIntegrations(decryptedIntegrations);
|
|
5309
5956
|
for (const integration of decryptedIntegrations) {
|
|
5310
5957
|
const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
|
|
@@ -5425,12 +6072,18 @@ ${sections}`
|
|
|
5425
6072
|
const envSrc = join4(agentDir2, ".env.integrations");
|
|
5426
6073
|
try {
|
|
5427
6074
|
const envContent = readFileSync5(envSrc, "utf-8");
|
|
5428
|
-
|
|
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
|
+
}
|
|
5429
6081
|
} catch {
|
|
5430
6082
|
}
|
|
5431
6083
|
} catch {
|
|
5432
6084
|
}
|
|
5433
6085
|
renderChannelMessageHandlerForAgent(codeName);
|
|
6086
|
+
renderAugmentedWorkerForAgent(codeName);
|
|
5434
6087
|
},
|
|
5435
6088
|
writeMcpServer(codeName, serverId, config) {
|
|
5436
6089
|
const agentDir = getAgentDir(codeName);
|
|
@@ -5448,7 +6101,27 @@ ${sections}`
|
|
|
5448
6101
|
const mcpServers = mcpConfig["mcpServers"];
|
|
5449
6102
|
let serverEntry;
|
|
5450
6103
|
if ("url" in config) {
|
|
5451
|
-
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
|
+
}
|
|
5452
6125
|
} else {
|
|
5453
6126
|
serverEntry = { command: config.command };
|
|
5454
6127
|
if (config.args?.length)
|
|
@@ -5501,14 +6174,14 @@ ${sections}`
|
|
|
5501
6174
|
mkdirSync4(join4(filePath, ".."), { recursive: true });
|
|
5502
6175
|
if (isPluginManaged && existsSync5(filePath)) {
|
|
5503
6176
|
try {
|
|
5504
|
-
|
|
6177
|
+
chmodSync5(filePath, READ_WRITE_MODE);
|
|
5505
6178
|
} catch {
|
|
5506
6179
|
}
|
|
5507
6180
|
}
|
|
5508
6181
|
writeFileSync5(filePath, file.content);
|
|
5509
6182
|
if (isPluginManaged) {
|
|
5510
6183
|
try {
|
|
5511
|
-
|
|
6184
|
+
chmodSync5(filePath, READ_ONLY_MODE);
|
|
5512
6185
|
} catch {
|
|
5513
6186
|
}
|
|
5514
6187
|
}
|
|
@@ -5689,7 +6362,7 @@ ${sections}`
|
|
|
5689
6362
|
return;
|
|
5690
6363
|
const tokenPath = join4(agentDir, ".tokens.json");
|
|
5691
6364
|
writeFileSync5(tokenPath, JSON.stringify(tokens, null, 2));
|
|
5692
|
-
|
|
6365
|
+
chmodSync5(tokenPath, SECRET_FILE_MODE);
|
|
5693
6366
|
}
|
|
5694
6367
|
};
|
|
5695
6368
|
registerFramework(claudeCodeAdapter);
|
|
@@ -6267,7 +6940,7 @@ import { homedir as homedir6, userInfo } from "os";
|
|
|
6267
6940
|
import { spawn as spawn3 } from "child_process";
|
|
6268
6941
|
|
|
6269
6942
|
// src/lib/watchdog.ts
|
|
6270
|
-
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";
|
|
6271
6944
|
import { join as join7 } from "path";
|
|
6272
6945
|
import { spawn as spawn2, execFileSync } from "child_process";
|
|
6273
6946
|
var DEFAULT_CONFIG_DIR = join7(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
@@ -6361,7 +7034,7 @@ function startWatchdog(opts) {
|
|
|
6361
7034
|
const { logFile } = getManagerPaths(configDir);
|
|
6362
7035
|
const logFd = openSync(logFile, "a", 384);
|
|
6363
7036
|
try {
|
|
6364
|
-
|
|
7037
|
+
chmodSync6(logFile, 384);
|
|
6365
7038
|
} catch {
|
|
6366
7039
|
}
|
|
6367
7040
|
const intervalSec = String(Math.max(Math.floor(opts.intervalMs / 1e3), 5));
|
|
@@ -6874,9 +7547,12 @@ async function managerUninstallSystemUnitCommand() {
|
|
|
6874
7547
|
export {
|
|
6875
7548
|
getIntegration,
|
|
6876
7549
|
extractCommandNotFound,
|
|
7550
|
+
LITERAL_SECRET_PATTERNS,
|
|
7551
|
+
safeWriteJsonAtomic,
|
|
6877
7552
|
INTEGRATIONS_SECTION_START,
|
|
6878
7553
|
INTEGRATIONS_SECTION_END,
|
|
6879
7554
|
estimateActiveTasksTokens,
|
|
7555
|
+
CHANNEL_SECRET_ENV_KEYS,
|
|
6880
7556
|
provisionStopHook,
|
|
6881
7557
|
provisionIsolationHook,
|
|
6882
7558
|
provisionOrientHook,
|
|
@@ -6910,4 +7586,4 @@ export {
|
|
|
6910
7586
|
managerInstallSystemUnitCommand,
|
|
6911
7587
|
managerUninstallSystemUnitCommand
|
|
6912
7588
|
};
|
|
6913
|
-
//# sourceMappingURL=chunk-
|
|
7589
|
+
//# sourceMappingURL=chunk-N2EBL4PZ.js.map
|