@integrity-labs/agt-cli 0.27.7-test.6 → 0.27.8-test.10

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.
@@ -9,7 +9,7 @@ import {
9
9
  parseDeliveryTarget,
10
10
  registerFramework,
11
11
  wrapScheduledTaskPrompt
12
- } from "./chunk-YSBGIXJG.js";
12
+ } from "./chunk-6HFXSNNY.js";
13
13
 
14
14
  // ../../packages/core/dist/integrations/registry.js
15
15
  var INTEGRATION_REGISTRY = [
@@ -174,7 +174,15 @@ var INTEGRATION_REGISTRY = [
174
174
  { id: "qmd:search", name: "Search Memory", description: "Semantic + keyword search over indexed memory files", access: "read" },
175
175
  { id: "qmd:get", name: "Get Memory", description: "Read memory files by path and line range", access: "read" }
176
176
  ],
177
- beta: true
177
+ beta: true,
178
+ // ENG-5815: migrated from buildMcpJson's hardcoded if-block. qmd is
179
+ // the simplest of the four pre-data-driven entries — no env, no
180
+ // conditional logic, just `qmd mcp`. The byte-identical render is
181
+ // pinned by claudecode-qmd-data-driven.test.ts.
182
+ nativeMcp: {
183
+ command: "qmd",
184
+ args: ["mcp"]
185
+ }
178
186
  },
179
187
  {
180
188
  id: "v0",
@@ -310,6 +318,35 @@ var INTEGRATION_REGISTRY = [
310
318
  },
311
319
  docs_url: "https://www.coderabbit.ai/cli"
312
320
  },
321
+ {
322
+ id: "aws",
323
+ name: "AWS",
324
+ category: "infrastructure",
325
+ description: "Amazon Web Services \u2014 query AWS APIs (EC2, S3, IAM, Lambda, etc.) via AWS Labs' official AWS API MCP server",
326
+ supported_auth_types: ["api_key", "managed", "none"],
327
+ capabilities: [
328
+ { id: "aws:read", name: "Read AWS Resources", description: "List and describe AWS resources across services (EC2, S3, IAM, Lambda, \u2026)", access: "read" },
329
+ { id: "aws:write", name: "Write AWS Resources", description: "Create and update AWS resources. Pair with an aws-no-destructive-ops guardrail.", access: "write" }
330
+ ],
331
+ docs_url: "https://github.com/awslabs/mcp/tree/main/src/aws-api-mcp-server",
332
+ beta: true,
333
+ // ENG-5815: first integration shipped purely via the data-driven
334
+ // path — buildMcpJson never grew an `aws` if-block. The AWS Labs
335
+ // AWS API MCP server runs through uvx (Python tooling), which the
336
+ // host bootstrap installs alongside python3. Credentials are
337
+ // resolved via the standard AWS_* env / shared credentials file
338
+ // chain on the host; the spec doesn't override them.
339
+ nativeMcp: {
340
+ command: "uvx",
341
+ args: ["awslabs.aws-api-mcp-server@latest"],
342
+ env: {
343
+ AWS_REGION: "{{empty_if_no_env.AWS_REGION}}",
344
+ AWS_PROFILE: "{{empty_if_no_env.AWS_PROFILE}}",
345
+ PATH: "{{process_env.PATH}}",
346
+ HOME: "{{process_env.HOME}}"
347
+ }
348
+ }
349
+ },
313
350
  {
314
351
  id: "custom",
315
352
  name: "Custom Integration",
@@ -2644,42 +2681,6 @@ If your charter doesn't authorise team-scope skill writes
2644
2681
  refused server-side \u2014 surface that error to the user rather than
2645
2682
  falling back to a local-disk write.
2646
2683
 
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
2684
  `;
2684
2685
  }
2685
2686
  function buildPersonalitySection(seed) {
@@ -2900,6 +2901,37 @@ function formatConfigLines(config) {
2900
2901
  return ` - ${k}: ${rendered}`;
2901
2902
  });
2902
2903
  }
2904
+ var EMAIL_DOMAIN_RESTRICT_DEF = "email.domain_restrict";
2905
+ function stringList(value) {
2906
+ return Array.isArray(value) ? value.filter((d) => typeof d === "string") : [];
2907
+ }
2908
+ function formatEmailDomainConfigLines(config) {
2909
+ const mode = typeof config.mode === "string" ? config.mode : void 0;
2910
+ const lines = [];
2911
+ if (mode)
2912
+ lines.push(` - mode: ${mode}`);
2913
+ if (mode === "allowlist") {
2914
+ const allowed = stringList(config.allowed_domains);
2915
+ lines.push(` - allowed_domains: ${allowed.length ? allowed.join(", ") : "(none \u2014 no external email permitted)"}`);
2916
+ } else if (mode === "blocklist") {
2917
+ const blocked = stringList(config.blocked_domains);
2918
+ lines.push(` - blocked_domains: ${blocked.length ? blocked.join(", ") : "(none)"}`);
2919
+ } else if (mode === "internal_only") {
2920
+ lines.push(` - only your organization's own email domain is permitted`);
2921
+ }
2922
+ lines.push(` *Advisory: honor this restriction \u2014 it is guidance in your instructions, not a hard block at send time.*`);
2923
+ return lines;
2924
+ }
2925
+ var CALENDAR_CONFIDENTIALITY_DEF = "calendar.confidentiality";
2926
+ function formatCalendarConfidentialityLines(config) {
2927
+ const stage = typeof config.stage === "string" ? config.stage : "shadow";
2928
+ const lines = [` - stage: ${stage}`];
2929
+ 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.*`);
2930
+ if (stage !== "enforce") {
2931
+ 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.*`);
2932
+ }
2933
+ return lines;
2934
+ }
2903
2935
  function renderGuardrailBullet(g) {
2904
2936
  const lines = [];
2905
2937
  const header = `- **${g.displayName}** (${g.category}, from ${g.source})`;
@@ -2907,16 +2939,26 @@ function renderGuardrailBullet(g) {
2907
2939
  if (g.description?.trim()) {
2908
2940
  lines.push(` ${g.description.trim()}`);
2909
2941
  }
2910
- lines.push(...formatConfigLines(g.config));
2942
+ lines.push(...g.definitionId === EMAIL_DOMAIN_RESTRICT_DEF ? formatEmailDomainConfigLines(g.config) : g.definitionId === CALENDAR_CONFIDENTIALITY_DEF ? formatCalendarConfidentialityLines(g.config) : formatConfigLines(g.config));
2911
2943
  if (g.overrideApplied && g.overrideReason?.trim()) {
2912
2944
  lines.push(` *Override applied: ${g.overrideReason.trim()}*`);
2913
2945
  }
2914
2946
  return lines.join("\n");
2915
2947
  }
2948
+ function effectiveCalendarEnforcement(g) {
2949
+ if (g.definitionId !== CALENDAR_CONFIDENTIALITY_DEF)
2950
+ return g.enforcement;
2951
+ const stage = typeof g.config?.["stage"] === "string" ? g.config["stage"] : "shadow";
2952
+ if (stage === "enforce")
2953
+ return "enforce";
2954
+ if (stage === "warn" && g.enforcement !== "enforce")
2955
+ return "warn";
2956
+ return g.enforcement;
2957
+ }
2916
2958
  function buildGuardrailsSection(guardrails) {
2917
2959
  if (!guardrails || guardrails.length === 0)
2918
2960
  return "";
2919
- const active = guardrails.filter((g) => g.enforcement !== "disabled");
2961
+ const active = guardrails.map((g) => ({ ...g, enforcement: effectiveCalendarEnforcement(g) })).filter((g) => g.enforcement !== "disabled");
2920
2962
  if (active.length === 0)
2921
2963
  return "";
2922
2964
  const enforce = active.filter((g) => g.enforcement === "enforce");
@@ -3019,7 +3061,7 @@ inline replies \u2014 they do NOT replace this dispatch decision.
3019
3061
  If the work turns out to be unexpectedly slow after you started inline,
3020
3062
  finish the current sub-step, then dispatch the rest. Don't apologise for
3021
3063
  mid-task switching \u2014 operators care about responsiveness, not consistency.
3022
- ${buildProgressHeartbeatSection(resolvedChannels)}
3064
+
3023
3065
  ${activeTasksSection}${personalitySection}## Identity
3024
3066
 
3025
3067
  - Code Name: ${frontmatter.code_name}
@@ -3028,6 +3070,21 @@ ${activeTasksSection}${personalitySection}## Identity
3028
3070
  - Risk Tier: ${frontmatter.risk_tier}
3029
3071
  - Timezone: ${timezone?.trim() || "UTC"}
3030
3072
  - Channels: ${channelList}
3073
+
3074
+ > **What the Channels list above means** (ENG-5851): \`Channels:\`
3075
+ > enumerates the messaging **protocols** you may use \u2014 \`slack\`,
3076
+ > \`telegram\`, \`msteams\`, etc. It is **not** a list of specific
3077
+ > Slack channels / Telegram chats / Teams threads you're approved to
3078
+ > post in. There is no per-recipient "approved channels" allowlist
3079
+ > anywhere in this platform; you decide where to post based on the
3080
+ > task and the conversation context. If you call a send tool and the
3081
+ > target channel rejects it (e.g. Slack returns \`not_in_channel\` /
3082
+ > \`channel_not_found\`, or Teams returns \`team_not_allowed\`), surface
3083
+ > the error to the user with the recovery action \u2014 typically asking
3084
+ > them to run \`/invite @<your bot handle>\` in the channel so you can
3085
+ > post there next time. Do **not** refuse a posting request on the
3086
+ > grounds that the channel "isn't on the allowlist" \u2014 that conflation
3087
+ > is the bug ENG-5851 was filed to fix.
3031
3088
  ${resolvedChannels?.includes("slack") ? `
3032
3089
  ## Slack
3033
3090
 
@@ -3494,6 +3551,66 @@ function buildRemoteMcpEntry(definitionId) {
3494
3551
  };
3495
3552
  }
3496
3553
 
3554
+ // ../../packages/core/dist/provisioning/native-mcp.js
3555
+ function buildNativeMcpEntry(spec, ctx) {
3556
+ const resolvedCommand = resolveTemplate(spec.command, ctx);
3557
+ if (resolvedCommand.omit) {
3558
+ throw new Error("NativeMcpSpec: empty_if_no_env is only valid in env values (not in `command`)");
3559
+ }
3560
+ const command = resolvedCommand.value;
3561
+ const args = spec.args.map((a, i) => {
3562
+ const resolved = resolveTemplate(a, ctx);
3563
+ if (resolved.omit) {
3564
+ throw new Error(`NativeMcpSpec: empty_if_no_env is only valid in env values (not in args[${i}])`);
3565
+ }
3566
+ return resolved.value;
3567
+ });
3568
+ if (spec.env === void 0) {
3569
+ return { command, args };
3570
+ }
3571
+ const env = {};
3572
+ for (const [k, raw] of Object.entries(spec.env)) {
3573
+ const { value, omit } = resolveTemplate(raw, ctx);
3574
+ if (omit)
3575
+ continue;
3576
+ env[k] = value;
3577
+ }
3578
+ return { command, args, env };
3579
+ }
3580
+ function resolveTemplate(input, ctx) {
3581
+ const TOKEN = /\{\{([^}]+)\}\}/g;
3582
+ const hasEmptyIfNoEnv = /\{\{\s*empty_if_no_env\./.test(input);
3583
+ const isWholeValueEmptyIfNoEnv = /^\{\{\s*empty_if_no_env\.[^}]+\}\}$/.test(input);
3584
+ if (hasEmptyIfNoEnv && !isWholeValueEmptyIfNoEnv) {
3585
+ 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)})`);
3586
+ }
3587
+ let omit = false;
3588
+ const value = input.replace(TOKEN, (whole, expr) => {
3589
+ const trimmed = expr.trim();
3590
+ if (trimmed === "agent_id")
3591
+ return ctx.agentId;
3592
+ if (trimmed === "agent_code_name")
3593
+ return ctx.agentCodeName;
3594
+ if (trimmed === "integration_id")
3595
+ return ctx.integration?.id ?? "";
3596
+ if (trimmed.startsWith("process_env.")) {
3597
+ const name = trimmed.slice("process_env.".length);
3598
+ return process.env[name] ?? "";
3599
+ }
3600
+ if (trimmed.startsWith("empty_if_no_env.")) {
3601
+ const name = trimmed.slice("empty_if_no_env.".length);
3602
+ const v = process.env[name] ?? "";
3603
+ if (v.length === 0) {
3604
+ omit = true;
3605
+ return "";
3606
+ }
3607
+ return v;
3608
+ }
3609
+ return whole;
3610
+ });
3611
+ return { value, omit };
3612
+ }
3613
+
3497
3614
  // ../../packages/core/dist/provisioning/frameworks/claudecode/index.js
3498
3615
  var VALID_CODE_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3499
3616
  var SECRET_FILE_MODE = 384;
@@ -4353,7 +4470,7 @@ The parent passes you in the task description:
4353
4470
  Your job:
4354
4471
 
4355
4472
  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, directchat.reply for Direct Chat. Use the metadata the parent gave you to address the right thread/conversation.
4473
+ 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
4474
  3. End. Do not do additional follow-up work; if more is needed, the user will send another message.
4358
4475
 
4359
4476
  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.
@@ -4405,12 +4522,16 @@ function buildMcpJson(input) {
4405
4522
  HOME: process.env["HOME"] ?? ""
4406
4523
  }
4407
4524
  };
4408
- const hasQmd = input.integrations?.some((i) => i.definition_id === "qmd");
4409
- if (hasQmd) {
4410
- mcpServers["qmd"] = {
4411
- command: "qmd",
4412
- args: ["mcp"]
4413
- };
4525
+ for (const integration of input.integrations ?? []) {
4526
+ const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
4527
+ if (!def?.nativeMcp)
4528
+ continue;
4529
+ const key = def.nativeMcp.key ?? integration.definition_id;
4530
+ mcpServers[key] = buildNativeMcpEntry(def.nativeMcp, {
4531
+ agentId: input.agent.agent_id,
4532
+ agentCodeName: input.agent.code_name,
4533
+ integration
4534
+ });
4414
4535
  }
4415
4536
  const xeroIntegration = input.integrations?.find((i) => i.definition_id === "xero");
4416
4537
  if (xeroIntegration) {
@@ -4750,6 +4871,12 @@ ${sections}`
4750
4871
  },
4751
4872
  writeChannelCredentials(codeName, channelId, config, options) {
4752
4873
  const tzEnv = options?.agentTimezone && options.agentTimezone.trim() !== "" ? { TZ: options.agentTimezone.trim() } : {};
4874
+ const senderPolicyMode = options?.senderPolicy?.mode;
4875
+ const senderPolicyTeamId = options?.senderPolicy?.mode === "team_agents_only" || options?.senderPolicy?.mode === "manager_only" ? options.senderPolicy.team_id : void 0;
4876
+ const slackPrincipalId = options?.senderPolicy?.mode === "manager_only" ? options.senderPolicy.principal?.slack_user_id : void 0;
4877
+ const teamsPrincipalId = options?.senderPolicy?.mode === "manager_only" ? options.senderPolicy.principal?.teams_aad_object_id : void 0;
4878
+ const senderPolicyInternalOnly = options?.senderPolicy?.internal_only === true;
4879
+ const senderPolicyEnv = senderPolicyTeamId ? { AGT_TEAM_ID: senderPolicyTeamId } : {};
4753
4880
  const agentDir = getAgentDir(codeName);
4754
4881
  mkdirSync4(agentDir, { recursive: true });
4755
4882
  const isPersistent = options?.sessionMode === "persistent";
@@ -4901,7 +5028,30 @@ ${sections}`
4901
5028
  // (ENG-4936) honours PEER_DISABLED with identical
4902
5029
  // semantics. Only emit when non-default so the env block
4903
5030
  // stays clean for the common case.
4904
- ...peerDisabledMode !== "off" ? { PEER_DISABLED: peerDisabledMode } : {}
5031
+ ...peerDisabledMode !== "off" ? { PEER_DISABLED: peerDisabledMode } : {},
5032
+ // ENG-5841: SLACK_SENDER_POLICY drives slack-inbound-filter.ts.
5033
+ // Only emitted when the effective mode is restrictive (default
5034
+ // 'all' is the absence of the env var — same convention the
5035
+ // MCP filter uses on its own end).
5036
+ ...senderPolicyMode ? { SLACK_SENDER_POLICY: senderPolicyMode } : {},
5037
+ ...senderPolicyEnv,
5038
+ // AGT_TEAM_ID when team_agents_only
5039
+ // ENG-5842: principal ID for manager_only — Slack user_id from
5040
+ // people.contact_preferences.slack_user_id. Omitted when the
5041
+ // principal has no Slack ID; MCP filter fails closed on the
5042
+ // missing env var by dropping all human inbound.
5043
+ ...slackPrincipalId ? { SLACK_SENDER_POLICY_PRINCIPAL_ID: slackPrincipalId } : {},
5044
+ // ENG-5843: org-boundary gate. SLACK_INTERNAL_ONLY signals the
5045
+ // filter to check sender's workspace against SLACK_HOME_TEAM_ID
5046
+ // (sourced from the bot install's team_id, populated by
5047
+ // auth.test at install / first-run). Omitted unless explicitly
5048
+ // enabled — the consumer's env-absent default is "no gate".
5049
+ // When INTERNAL_ONLY is true but home team_id can't be
5050
+ // resolved (config['team_id'] not set on the install), the
5051
+ // MCP boot guard fails closed at startup rather than admitting
5052
+ // every sender as "internal".
5053
+ ...senderPolicyInternalOnly ? { SLACK_INTERNAL_ONLY: "true" } : {},
5054
+ ...senderPolicyInternalOnly && typeof config["team_id"] === "string" && config["team_id"].length > 0 ? { SLACK_HOME_TEAM_ID: config["team_id"] } : {}
4905
5055
  }
4906
5056
  };
4907
5057
  const provisionMcpPath = join4(agentDir, "provision", ".mcp.json");
@@ -5077,7 +5227,24 @@ ${sections}`
5077
5227
  ...peerTeamIds.length > 0 ? { MSTEAMS_PEER_TEAM_IDS: peerTeamIds.join(",") } : {},
5078
5228
  ...knownPeerBotIds.length > 0 ? { MSTEAMS_KNOWN_PEER_BOT_IDS: knownPeerBotIds.join(",") } : {},
5079
5229
  ...msteamsAgtAuthEnv,
5080
- ...tzEnv
5230
+ ...tzEnv,
5231
+ // ENG-5841: MSTEAMS_SENDER_POLICY drives teams-inbound-filter.ts.
5232
+ // Mirrors the Slack branch above — only emitted when restrictive.
5233
+ ...senderPolicyMode ? { MSTEAMS_SENDER_POLICY: senderPolicyMode } : {},
5234
+ ...senderPolicyEnv,
5235
+ // AGT_TEAM_ID when team_agents_only
5236
+ // ENG-5842: principal ID for manager_only — Teams AAD object id from
5237
+ // people.contact_preferences.teams_aad_object_id. Same fail-closed
5238
+ // contract as the Slack branch above.
5239
+ ...teamsPrincipalId ? { MSTEAMS_SENDER_POLICY_PRINCIPAL_ID: teamsPrincipalId } : {},
5240
+ // ENG-5843: org-boundary gate. MSTEAMS_INTERNAL_ONLY + MSTEAMS_HOME_TENANT_ID
5241
+ // mirror the Slack pair. Source is the same tenantId the existing
5242
+ // MSTEAMS_TENANT_ID env var already carries — defaulting to "common"
5243
+ // would be wrong here (it'd admit any tenant), so we skip the
5244
+ // SLACK_HOME_TEAM_ID-equivalent emission when the install hasn't
5245
+ // pinned a real tenant. MCP boot guard fails closed.
5246
+ ...senderPolicyInternalOnly ? { MSTEAMS_INTERNAL_ONLY: "true" } : {},
5247
+ ...senderPolicyInternalOnly && tenantId !== "common" ? { MSTEAMS_HOME_TENANT_ID: tenantId } : {}
5081
5248
  };
5082
5249
  if (isPersistent && existsSync5(localTeamsChannel)) {
5083
5250
  mcpServers["msteams"] = {
@@ -5194,9 +5361,16 @@ ${sections}`
5194
5361
  chmodSync4(envPath, SECRET_FILE_MODE);
5195
5362
  }
5196
5363
  writeXurlStoreForIntegrations(decryptedIntegrations);
5197
- const hasQmd = integrations.some((i) => i.definition_id === "qmd");
5198
- if (hasQmd) {
5199
- this.writeMcpServer(codeName, "qmd", { command: "qmd", args: ["mcp"] });
5364
+ for (const integration of decryptedIntegrations) {
5365
+ const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
5366
+ if (!def?.nativeMcp)
5367
+ continue;
5368
+ const key = def.nativeMcp.key ?? integration.definition_id;
5369
+ this.writeMcpServer(codeName, key, buildNativeMcpEntry(def.nativeMcp, {
5370
+ agentId: agentId ?? "",
5371
+ agentCodeName: codeName,
5372
+ integration
5373
+ }));
5200
5374
  }
5201
5375
  const xeroIntegration = integrations.find((i) => i.definition_id === "xero");
5202
5376
  if (xeroIntegration) {
@@ -5255,16 +5429,15 @@ ${sections}`
5255
5429
  }
5256
5430
  }
5257
5431
  if (this.removeMcpServer) {
5432
+ const nativeMcpKeys = INTEGRATION_REGISTRY.filter((d) => d.nativeMcp !== void 0).map((d) => d.nativeMcp.key ?? d.id);
5258
5433
  const integrationDerivedKeys = /* @__PURE__ */ new Set([
5259
- "qmd",
5260
5434
  "xero",
5261
5435
  "postiz",
5262
5436
  "cloud-broker",
5437
+ ...nativeMcpKeys,
5263
5438
  ...Object.entries(OAUTH_PROVIDERS).filter(([, provider]) => Boolean(provider.mcpUrl)).map(([id]) => id)
5264
5439
  ]);
5265
5440
  const expectedKeys = /* @__PURE__ */ new Set();
5266
- if (hasQmd)
5267
- expectedKeys.add("qmd");
5268
5441
  if (xeroIntegration)
5269
5442
  expectedKeys.add("xero");
5270
5443
  if (postizIntegration)
@@ -5272,6 +5445,10 @@ ${sections}`
5272
5445
  if (hasCloudBroker)
5273
5446
  expectedKeys.add("cloud-broker");
5274
5447
  for (const integration of integrations) {
5448
+ const def = INTEGRATION_REGISTRY.find((d) => d.id === integration.definition_id);
5449
+ if (def?.nativeMcp) {
5450
+ expectedKeys.add(def.nativeMcp.key ?? integration.definition_id);
5451
+ }
5275
5452
  if (buildRemoteMcpEntry(integration.definition_id)) {
5276
5453
  expectedKeys.add(integration.definition_id);
5277
5454
  }
@@ -5933,12 +6110,15 @@ function setActiveTeam(slug) {
5933
6110
  config.active_team = slug;
5934
6111
  saveConfig(config);
5935
6112
  }
5936
- var DEFAULT_AGT_HOST = "https://api.augmented.team";
6113
+ var PROD_AGT_HOST = "https://api.augmented.team";
6114
+ 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
6115
  function getHost() {
5938
6116
  const envHost = process.env["AGT_HOST"]?.trim();
5939
- return envHost ? envHost : DEFAULT_AGT_HOST;
6117
+ if (!envHost) {
6118
+ throw new Error(AGT_HOST_UNSET_MESSAGE);
6119
+ }
6120
+ return envHost;
5940
6121
  }
5941
- var AGT_HOST = getHost();
5942
6122
  function requireHost() {
5943
6123
  return getHost();
5944
6124
  }
@@ -6761,7 +6941,7 @@ export {
6761
6941
  getApiKey,
6762
6942
  getActiveTeam,
6763
6943
  setActiveTeam,
6764
- DEFAULT_AGT_HOST,
6944
+ PROD_AGT_HOST,
6765
6945
  getHost,
6766
6946
  requireHost,
6767
6947
  exchangeApiKey,
@@ -6785,4 +6965,4 @@ export {
6785
6965
  managerInstallSystemUnitCommand,
6786
6966
  managerUninstallSystemUnitCommand
6787
6967
  };
6788
- //# sourceMappingURL=chunk-AACMX6LE.js.map
6968
+ //# sourceMappingURL=chunk-2E5OABKX.js.map