@integrity-labs/agt-cli 0.28.500 → 0.28.501

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/agt.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-ICNRQ4L5.js";
43
+ } from "../chunk-N4Y5IBZR.js";
44
44
  import {
45
45
  AnchorSessionClient,
46
46
  CHANNEL_REGISTRY,
@@ -4830,7 +4830,7 @@ import { execFileSync, execSync } from "child_process";
4830
4830
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4831
4831
  import chalk18 from "chalk";
4832
4832
  import ora16 from "ora";
4833
- var cliVersion = true ? "0.28.500" : "dev";
4833
+ var cliVersion = true ? "0.28.501" : "dev";
4834
4834
  async function fetchLatestVersion() {
4835
4835
  const host2 = getHost();
4836
4836
  if (!host2) return null;
@@ -6002,7 +6002,7 @@ function handleError(err) {
6002
6002
  }
6003
6003
 
6004
6004
  // src/bin/agt.ts
6005
- var cliVersion2 = true ? "0.28.500" : "dev";
6005
+ var cliVersion2 = true ? "0.28.501" : "dev";
6006
6006
  var program = new Command();
6007
6007
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6008
6008
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -1,10 +1,14 @@
1
1
  import {
2
+ BIND_FAILURE_QUARANTINE_THRESHOLD,
2
3
  INTEGRATIONS_SECTION_END,
3
4
  INTEGRATIONS_SECTION_START,
4
5
  INTEGRATION_REGISTRY,
5
6
  LATE_BOUND_VARS,
7
+ MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING,
6
8
  OAUTH_PROVIDERS,
7
9
  REMOTE_MCP_PROXY_ENV,
10
+ RESTART_BREAKER_PROVISIONING_MAX,
11
+ RESTART_BREAKER_PROVISIONING_WINDOW_MS,
8
12
  bestConnectivityEvidence,
9
13
  buildForwardHeaders,
10
14
  buildHostBrokeredRemoteMcpEntry,
@@ -5057,7 +5061,7 @@ function exchangeFailureKind(err) {
5057
5061
  }
5058
5062
 
5059
5063
  // src/lib/api-client.ts
5060
- var agtCliVersion = true ? "0.28.500" : "dev";
5064
+ var agtCliVersion = true ? "0.28.501" : "dev";
5061
5065
  var lastConfigHash = null;
5062
5066
  function setConfigHash(hash) {
5063
5067
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -6420,6 +6424,363 @@ function reapMissingMcpSessions(args) {
6420
6424
  };
6421
6425
  }
6422
6426
 
6427
+ // src/lib/restart-breaker.ts
6428
+ function reaperRestartBreakerReason(activeKeys) {
6429
+ return activeKeys.length >= 2 ? "mcp-presence-reaper" : void 0;
6430
+ }
6431
+ var KNOWN_REASON_CLASS_MAP = {
6432
+ crash: true,
6433
+ provisioning: true,
6434
+ "credential-rotation": true,
6435
+ // ENG-8215: the self-healing class (bind-remediation - the manager's OWN
6436
+ // repair attempts, which must never count toward a trip). Adding it here is
6437
+ // forced by the Record type, which is exactly why that shape was chosen over
6438
+ // a Set literal (ENG-7577, CodeRabbit on #3861) - a new union member cannot
6439
+ // be silently omitted from the rehydration allowlist.
6440
+ "self-healing": true
6441
+ };
6442
+ var KNOWN_REASON_CLASSES = new Set(
6443
+ Object.keys(KNOWN_REASON_CLASS_MAP)
6444
+ );
6445
+ var PROVISIONING_RELOAD_REASONS = /* @__PURE__ */ new Set([
6446
+ "hot-reload-mcp",
6447
+ "managed-mcp-churn",
6448
+ // ENG-8215: 'bind-remediation' MOVED OUT of this set into SELF_HEALING_REASONS.
6449
+ // It is the manager repairing itself, not a config reload, and counting it
6450
+ // meant the harder the platform tried to fix an agent the more certain it
6451
+ // became that it should give up. Deliberately NOT re-added here.
6452
+ // ENG-7576: the dashboard integration-add stop itself - the first restart of
6453
+ // the very burst this class exists for, previously uncounted entirely.
6454
+ "integration-change",
6455
+ // ENG-7771: a sender_policy delivery restart is a deliberate config-delivery
6456
+ // reload (operator changed the policy, or the fail-closed first-poll verify
6457
+ // after a manager restart with no persisted baseline), not a crash. On the
6458
+ // tight crash tally, a host whose sender-policy-baseline.json write keeps
6459
+ // failing (disk full, permissions) would convert the fail-closed restart
6460
+ // into repeated crash tallies across a multi-deploy day and trip the
6461
+ // breaker - taking the agent DOWN, which is strictly worse than the stale
6462
+ // policy the restart exists to fix. The looser provisioning tally still
6463
+ // trips + pages on sustained thrash.
6464
+ "sender-policy-change"
6465
+ ]);
6466
+ function isProvisioningReloadReason(reason) {
6467
+ return PROVISIONING_RELOAD_REASONS.has(reason);
6468
+ }
6469
+ var CREDENTIAL_ROTATION_REASONS = /* @__PURE__ */ new Set([
6470
+ "credential-rotation"
6471
+ ]);
6472
+ function isCredentialRotationReason(reason) {
6473
+ return CREDENTIAL_ROTATION_REASONS.has(reason);
6474
+ }
6475
+ var SELF_HEALING_REASONS = /* @__PURE__ */ new Set([
6476
+ "bind-remediation"
6477
+ ]);
6478
+ function isSelfHealingReason(reason) {
6479
+ return SELF_HEALING_REASONS.has(reason);
6480
+ }
6481
+ function restartReasonClass(reason) {
6482
+ if (isCredentialRotationReason(reason)) return "credential-rotation";
6483
+ if (isSelfHealingReason(reason)) return "self-healing";
6484
+ return isProvisioningReloadReason(reason) ? "provisioning" : "crash";
6485
+ }
6486
+ function countByClass(events) {
6487
+ let crash = 0;
6488
+ let provisioning = 0;
6489
+ let credentialRotation = 0;
6490
+ let selfHealing = 0;
6491
+ for (const e of events) {
6492
+ const klass = restartReasonClass(e.reason);
6493
+ if (klass === "provisioning") provisioning += 1;
6494
+ else if (klass === "credential-rotation") credentialRotation += 1;
6495
+ else if (klass === "self-healing") selfHealing += 1;
6496
+ else crash += 1;
6497
+ }
6498
+ return {
6499
+ crash,
6500
+ provisioning,
6501
+ "credential-rotation": credentialRotation,
6502
+ "self-healing": selfHealing
6503
+ };
6504
+ }
6505
+ function maxProvisioningBucket(events) {
6506
+ const buckets = /* @__PURE__ */ new Map();
6507
+ for (const e of events) {
6508
+ if (!isProvisioningReloadReason(e.reason)) continue;
6509
+ const key = e.integrationKey ?? "";
6510
+ buckets.set(key, (buckets.get(key) ?? 0) + 1);
6511
+ }
6512
+ let best = { count: 0, key: "" };
6513
+ for (const [key, count] of buckets) {
6514
+ if (count > best.count) best = { count, key };
6515
+ }
6516
+ return best;
6517
+ }
6518
+ function tripClass(trip) {
6519
+ if (trip.trippedClass) return trip.trippedClass;
6520
+ if (!Array.isArray(trip.eventsAtTrip) || trip.eventsAtTrip.length === 0) return "crash";
6521
+ return trip.eventsAtTrip.every((e) => isProvisioningReloadReason(e.reason)) ? "provisioning" : "crash";
6522
+ }
6523
+ var DEFAULT_MAX = 2;
6524
+ var DEFAULT_WINDOW_MS = 6e5;
6525
+ var DEFAULT_PROVISIONING_MAX = RESTART_BREAKER_PROVISIONING_MAX;
6526
+ var DEFAULT_PROVISIONING_WINDOW_MS = RESTART_BREAKER_PROVISIONING_WINDOW_MS;
6527
+ function readEnvNumber(name, fallback) {
6528
+ const raw = process.env[name];
6529
+ if (!raw) return fallback;
6530
+ const parsed = Number(raw);
6531
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
6532
+ }
6533
+ var RestartBreaker = class {
6534
+ max;
6535
+ windowMs;
6536
+ /**
6537
+ * ENG-8215: NOT readonly - the constructor may clamp it upward to preserve the
6538
+ * quarantine-fires-first ordering against a runtime override. Never mutated
6539
+ * after construction.
6540
+ */
6541
+ provisioningMax;
6542
+ provisioningWindowMs;
6543
+ /** Longest window either tally needs — how long the events log must retain. */
6544
+ retentionMs;
6545
+ now;
6546
+ events = /* @__PURE__ */ new Map();
6547
+ trips = /* @__PURE__ */ new Map();
6548
+ /**
6549
+ * ENG-8215: set when a supplied `provisioningMax` was raised to keep MCP
6550
+ * quarantine ahead of the breaker. Read once by the manager at construction so
6551
+ * an overridden-and-clamped value is visible in manager.log rather than
6552
+ * silently discarded.
6553
+ */
6554
+ quarantineOrderingClamped;
6555
+ constructor(opts = {}) {
6556
+ this.max = opts.max ?? readEnvNumber("AGT_RESTART_BREAKER_MAX", DEFAULT_MAX);
6557
+ this.windowMs = opts.windowMs ?? readEnvNumber("AGT_RESTART_BREAKER_WINDOW_MS", DEFAULT_WINDOW_MS);
6558
+ this.provisioningMax = opts.provisioningMax ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_MAX", DEFAULT_PROVISIONING_MAX);
6559
+ this.provisioningWindowMs = opts.provisioningWindowMs ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_WINDOW_MS", DEFAULT_PROVISIONING_WINDOW_MS);
6560
+ this.now = opts.now ?? Date.now;
6561
+ if (!Number.isFinite(this.max) || this.max < 1) {
6562
+ throw new Error("restart-breaker max must be a finite number >= 1");
6563
+ }
6564
+ if (!Number.isFinite(this.windowMs) || this.windowMs < 1e3) {
6565
+ throw new Error("restart-breaker windowMs must be a finite number >= 1000");
6566
+ }
6567
+ if (!Number.isFinite(this.provisioningMax) || this.provisioningMax < 1) {
6568
+ throw new Error("restart-breaker provisioningMax must be a finite number >= 1");
6569
+ }
6570
+ if (this.provisioningMax < MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING) {
6571
+ this.quarantineOrderingClamped = {
6572
+ requested: this.provisioningMax,
6573
+ applied: MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING,
6574
+ quarantineThreshold: BIND_FAILURE_QUARANTINE_THRESHOLD
6575
+ };
6576
+ this.provisioningMax = MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING;
6577
+ }
6578
+ if (!Number.isFinite(this.provisioningWindowMs) || this.provisioningWindowMs < 1e3) {
6579
+ throw new Error("restart-breaker provisioningWindowMs must be a finite number >= 1000");
6580
+ }
6581
+ this.retentionMs = Math.max(this.windowMs, this.provisioningWindowMs);
6582
+ }
6583
+ /**
6584
+ * ENG-8215: the clamp record, if the configured provisioning max had to be
6585
+ * raised to keep MCP quarantine ahead of the breaker. `undefined` when the
6586
+ * configured value already satisfied the ordering.
6587
+ */
6588
+ getQuarantineOrderingClamp() {
6589
+ return this.quarantineOrderingClamped;
6590
+ }
6591
+ /** True if this agent's breaker is currently tripped (manager must skip spawn). */
6592
+ isTripped(codeName) {
6593
+ return this.trips.has(codeName);
6594
+ }
6595
+ getTrip(codeName) {
6596
+ return this.trips.get(codeName);
6597
+ }
6598
+ /**
6599
+ * Record a restart event. If recording this event puts the count
6600
+ * inside the window above `max`, the breaker trips and the call site
6601
+ * should NOT respawn.
6602
+ *
6603
+ * Idempotent on already-tripped breakers: returns the existing trip
6604
+ * without double-counting events. Callers may still record the reason
6605
+ * via the decision-log for forensics.
6606
+ */
6607
+ record(codeName, reason, integrationKey) {
6608
+ const existing = this.trips.get(codeName);
6609
+ if (existing) {
6610
+ const bucket = maxProvisioningBucket(existing.eventsAtTrip);
6611
+ return {
6612
+ tripped: false,
6613
+ trip: existing,
6614
+ windowCount: existing.eventsAtTrip.length,
6615
+ classCounts: countByClass(existing.eventsAtTrip),
6616
+ maxProvisioningBucket: bucket.count,
6617
+ maxProvisioningBucketKey: bucket.key
6618
+ };
6619
+ }
6620
+ const at = this.now();
6621
+ const retentionCutoff = at - this.retentionMs;
6622
+ const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= retentionCutoff);
6623
+ prior.push(
6624
+ isProvisioningReloadReason(reason) && integrationKey ? { reason, at, integrationKey } : { reason, at }
6625
+ );
6626
+ this.events.set(codeName, prior);
6627
+ const crashCount = prior.filter(
6628
+ (e) => e.at >= at - this.windowMs && restartReasonClass(e.reason) === "crash"
6629
+ ).length;
6630
+ const provEvents = prior.filter(
6631
+ (e) => e.at >= at - this.provisioningWindowMs && isProvisioningReloadReason(e.reason)
6632
+ );
6633
+ const credRotCount = prior.filter(
6634
+ (e) => e.at >= at - this.provisioningWindowMs && isCredentialRotationReason(e.reason)
6635
+ ).length;
6636
+ const selfHealCount = prior.filter(
6637
+ (e) => e.at >= at - this.provisioningWindowMs && isSelfHealingReason(e.reason)
6638
+ ).length;
6639
+ const windowCount = prior.filter((e) => e.at >= at - this.windowMs).length;
6640
+ const classCounts = {
6641
+ crash: crashCount,
6642
+ provisioning: provEvents.length,
6643
+ "credential-rotation": credRotCount,
6644
+ "self-healing": selfHealCount
6645
+ };
6646
+ const provBucket = maxProvisioningBucket(provEvents);
6647
+ let trippedClass;
6648
+ let tripEvents;
6649
+ let tripWindowMs = this.windowMs;
6650
+ if (crashCount > this.max) {
6651
+ trippedClass = "crash";
6652
+ tripEvents = prior.filter(
6653
+ (e) => e.at >= at - this.windowMs && restartReasonClass(e.reason) === "crash"
6654
+ );
6655
+ tripWindowMs = this.windowMs;
6656
+ } else if (provBucket.count > this.provisioningMax) {
6657
+ trippedClass = "provisioning";
6658
+ tripEvents = provEvents.filter((e) => (e.integrationKey ?? "") === provBucket.key);
6659
+ tripWindowMs = this.provisioningWindowMs;
6660
+ }
6661
+ if (trippedClass && tripEvents) {
6662
+ const trip = {
6663
+ trippedAt: at,
6664
+ eventsAtTrip: [...tripEvents],
6665
+ statusMessage: formatStatusMessage(tripEvents, tripWindowMs, trippedClass, provBucket.key),
6666
+ // ENG-7577: persist the tripping class so the recovery path can gate on
6667
+ // it after a manager restart, without re-deriving it from prose.
6668
+ trippedClass
6669
+ };
6670
+ this.trips.set(codeName, trip);
6671
+ this.events.delete(codeName);
6672
+ return {
6673
+ tripped: true,
6674
+ trip,
6675
+ windowCount,
6676
+ classCounts,
6677
+ trippedClass,
6678
+ maxProvisioningBucket: provBucket.count,
6679
+ maxProvisioningBucketKey: provBucket.key
6680
+ };
6681
+ }
6682
+ return {
6683
+ tripped: false,
6684
+ windowCount,
6685
+ classCounts,
6686
+ maxProvisioningBucket: provBucket.count,
6687
+ maxProvisioningBucketKey: provBucket.key
6688
+ };
6689
+ }
6690
+ /** Operator-initiated reset: drops the trip + the events log for this agent. */
6691
+ clear(codeName) {
6692
+ this.trips.delete(codeName);
6693
+ this.events.delete(codeName);
6694
+ }
6695
+ /** Snapshot tripped agents for `manager-state.json`. */
6696
+ serialize() {
6697
+ return Object.fromEntries(this.trips.entries());
6698
+ }
6699
+ /**
6700
+ * Rehydrate trip state from `manager-state.json`. Called once at
6701
+ * worker startup. Window history is intentionally NOT persisted — only
6702
+ * tripped state. A tripped breaker survives manager restart; an
6703
+ * un-tripped one starts a fresh window in the new worker.
6704
+ */
6705
+ hydrate(saved) {
6706
+ if (!saved) return;
6707
+ for (const [codeName, trip] of Object.entries(saved)) {
6708
+ if (trip && typeof trip.trippedAt === "number" && Array.isArray(trip.eventsAtTrip)) {
6709
+ const klass = trip.trippedClass;
6710
+ this.trips.set(
6711
+ codeName,
6712
+ klass === void 0 || KNOWN_REASON_CLASSES.has(klass) ? trip : { ...trip, trippedClass: void 0 }
6713
+ );
6714
+ }
6715
+ }
6716
+ }
6717
+ /** Test helper — current in-window event count for `codeName`. */
6718
+ windowCount(codeName) {
6719
+ const cutoff = this.now() - this.windowMs;
6720
+ return (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff).length;
6721
+ }
6722
+ };
6723
+ function formatStatusMessage(events, windowMs, klass = "crash", integrationKey = "") {
6724
+ const last = events[events.length - 1];
6725
+ const windowLabel = windowMs < 6e4 ? `${Math.round(windowMs / 1e3)}s` : `${(windowMs / 6e4).toFixed(1).replace(/\.0$/, "")}min`;
6726
+ const reasonCounts = /* @__PURE__ */ new Map();
6727
+ for (const e of events) reasonCounts.set(e.reason, (reasonCounts.get(e.reason) ?? 0) + 1);
6728
+ const breakdown = Array.from(reasonCounts.entries()).map(([r, n]) => `${r}=${n}`).join(", ");
6729
+ const classLabel = klass === "provisioning" ? "provisioning-reload " : "";
6730
+ const integrationLabel = klass === "provisioning" && integrationKey ? ` for integration '${integrationKey}'` : "";
6731
+ return `Circuit breaker tripped: ${events.length} ${classLabel}restarts in ${windowLabel}${integrationLabel} (${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`;
6732
+ }
6733
+
6734
+ // src/lib/mcp-config-lookup.ts
6735
+ function markQuarantinedForProbe(rows) {
6736
+ return rows.map((row) => ({ ...row, quarantined: true }));
6737
+ }
6738
+ function inlineEntryLookup(value) {
6739
+ return value ? { ok: true, value } : { ok: false, cause: "wrong-shape" };
6740
+ }
6741
+ var FIRST_CONNECT_WINDOW_MS = readEnvNumber(
6742
+ "AGT_BIND_REMEDIATION_FIRST_CONNECT_WINDOW_MS",
6743
+ 9e5
6744
+ // 15 min
6745
+ );
6746
+ function isWithinFirstConnectWindow(createdAt, now, windowMs = FIRST_CONNECT_WINDOW_MS) {
6747
+ if (!createdAt) return true;
6748
+ const addedAt = Date.parse(createdAt);
6749
+ if (!Number.isFinite(addedAt)) return true;
6750
+ return now - addedAt <= windowMs;
6751
+ }
6752
+ function verdictForUnavailableMcpConfig(cause, ctx) {
6753
+ const where = `MCP ${ctx.transport} server '${ctx.serverKey}'`;
6754
+ if (cause === "file-unreadable") {
6755
+ return { status: "transient_error", message: `${where}: ${ctx.source} unreadable or unparseable` };
6756
+ }
6757
+ if (cause === "key-absent") {
6758
+ if (ctx.expectedAbsent) {
6759
+ return {
6760
+ status: "transient_error",
6761
+ message: `${where}: not in ${ctx.source} (quarantined \u2014 absence expected)`
6762
+ };
6763
+ }
6764
+ if (isWithinFirstConnectWindow(ctx.createdAt, ctx.now, ctx.windowMs)) {
6765
+ return {
6766
+ status: "transient_error",
6767
+ message: `${where}: not yet written to ${ctx.source} (within first-connect window)`
6768
+ };
6769
+ }
6770
+ return { status: "down", message: `${where}: not wired in ${ctx.source}` };
6771
+ }
6772
+ if (ctx.expectedAbsent) {
6773
+ return {
6774
+ status: "transient_error",
6775
+ message: `${where}: reconstructed ${ctx.source} entry is not ${ctx.transport}-probeable (quarantined)`
6776
+ };
6777
+ }
6778
+ return {
6779
+ status: "down",
6780
+ message: `${where}: ${ctx.source} entry is not ${ctx.transport}-probeable`
6781
+ };
6782
+ }
6783
+
6423
6784
  // src/lib/connectivity-probe-context.ts
6424
6785
  import { join as join5 } from "path";
6425
6786
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
@@ -6753,15 +7114,17 @@ function resolveHttpServerEntry(entry, env) {
6753
7114
  return null;
6754
7115
  }
6755
7116
  function readMcpHttpServerConfig(projectDir, serverKey, env) {
7117
+ let servers;
6756
7118
  try {
6757
7119
  const raw = readFileSync6(join5(projectDir, ".mcp.json"), "utf-8");
6758
- const servers = JSON.parse(raw).mcpServers ?? {};
6759
- const entry = servers[serverKey];
6760
- if (!entry) return null;
6761
- return resolveHttpServerEntry(entry, env);
7120
+ servers = JSON.parse(raw).mcpServers ?? {};
6762
7121
  } catch {
6763
- return null;
7122
+ return { ok: false, cause: "file-unreadable" };
6764
7123
  }
7124
+ const entry = servers[serverKey];
7125
+ if (!entry) return { ok: false, cause: "key-absent" };
7126
+ const value = resolveHttpServerEntry(entry, env);
7127
+ return value ? { ok: true, value } : { ok: false, cause: "wrong-shape" };
6765
7128
  }
6766
7129
  function resolveDeclaredServerKey(projectDir, definitionId, derivedKey) {
6767
7130
  try {
@@ -6805,15 +7168,17 @@ function resolveStdioServerEntry(entry, env) {
6805
7168
  };
6806
7169
  }
6807
7170
  function readMcpStdioServerConfig(projectDir, serverKey, env) {
7171
+ let servers;
6808
7172
  try {
6809
7173
  const raw = readFileSync6(join5(projectDir, ".mcp.json"), "utf-8");
6810
- const servers = JSON.parse(raw).mcpServers ?? {};
6811
- const entry = servers[serverKey];
6812
- if (!entry) return null;
6813
- return resolveStdioServerEntry(entry, env);
7174
+ servers = JSON.parse(raw).mcpServers ?? {};
6814
7175
  } catch {
6815
- return null;
7176
+ return { ok: false, cause: "file-unreadable" };
6816
7177
  }
7178
+ const entry = servers[serverKey];
7179
+ if (!entry) return { ok: false, cause: "key-absent" };
7180
+ const value = resolveStdioServerEntry(entry, env);
7181
+ return value ? { ok: true, value } : { ok: false, cause: "wrong-shape" };
6817
7182
  }
6818
7183
  function deriveMcpServerKey(input) {
6819
7184
  const kind = resolveConnectivityProbe({
@@ -6844,11 +7209,18 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
6844
7209
  runCli: (binary, args) => runCliProbe(binary, args, { env: probeEnv }),
6845
7210
  mcpProbe: async (target) => {
6846
7211
  const serverKey = target.inlineServerEntry ? target.serverKey : resolveDeclaredServerKey(projectDir, target.definitionId, target.serverKey) ?? target.serverKey;
6847
- const cfg = target.inlineServerEntry ? resolveHttpServerEntry(target.inlineServerEntry, probeEnv) : readMcpHttpServerConfig(projectDir, serverKey, probeEnv);
6848
- if (!cfg) {
6849
- const src = target.inlineServerEntry ? "inline config" : ".mcp.json";
6850
- return { status: "transient_error", message: `MCP server '${serverKey}' not resolvable from ${src}` };
7212
+ const lookup = target.inlineServerEntry ? inlineEntryLookup(resolveHttpServerEntry(target.inlineServerEntry, probeEnv)) : readMcpHttpServerConfig(projectDir, serverKey, probeEnv);
7213
+ if (!lookup.ok) {
7214
+ return verdictForUnavailableMcpConfig(lookup.cause, {
7215
+ serverKey,
7216
+ transport: "http",
7217
+ source: target.inlineServerEntry ? "inline config" : ".mcp.json",
7218
+ createdAt: target.createdAt,
7219
+ expectedAbsent: target.expectedAbsent || Boolean(target.inlineServerEntry),
7220
+ now: Date.now()
7221
+ });
6851
7222
  }
7223
+ const cfg = lookup.value;
6852
7224
  if (cfg.unresolved.length > 0) {
6853
7225
  return {
6854
7226
  status: "transient_error",
@@ -6861,15 +7233,28 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
6861
7233
  // the agent's env (the exact command/args/env from `.mcp.json`), handshakes,
6862
7234
  // and calls the read-only `connectivity_test` tool when the descriptor set
6863
7235
  // one (threaded via target.toolName). An unresolvable `${VAR}` in the spawn
6864
- // env → skip as non-escalating, never spawn a doomed server. A missing
6865
- // server entry is a real `down` — the tools aren't wired.
7236
+ // env → skip as non-escalating, never spawn a doomed server.
7237
+ //
7238
+ // ENG-8363: a missing server entry is no longer a flat `down`. It still is
7239
+ // one once the writer has had its chance, but the manager probes BEFORE it
7240
+ // writes `.mcp.json` in the same poll, so the first probe of every
7241
+ // newly-added integration necessarily finds the key absent — that window
7242
+ // reads `transient_error` (and is what keeps ENG-7575's first-connect
7243
+ // backoff engaged). `verdictForUnavailableMcpConfig` owns the distinction.
6866
7244
  mcpStdioProbe: async (target) => {
6867
7245
  const serverKey = target.inlineServerEntry ? target.serverKey : resolveDeclaredServerKey(projectDir, target.definitionId, target.serverKey) ?? target.serverKey;
6868
- const cfg = target.inlineServerEntry ? resolveStdioServerEntry(target.inlineServerEntry, probeEnv) : readMcpStdioServerConfig(projectDir, serverKey, probeEnv);
6869
- if (!cfg) {
6870
- const src = target.inlineServerEntry ? "inline config" : ".mcp.json";
6871
- return { status: "down", message: `MCP stdio server '${serverKey}' not wired in ${src}` };
7246
+ const lookup = target.inlineServerEntry ? inlineEntryLookup(resolveStdioServerEntry(target.inlineServerEntry, probeEnv)) : readMcpStdioServerConfig(projectDir, serverKey, probeEnv);
7247
+ if (!lookup.ok) {
7248
+ return verdictForUnavailableMcpConfig(lookup.cause, {
7249
+ serverKey,
7250
+ transport: "stdio",
7251
+ source: target.inlineServerEntry ? "inline config" : ".mcp.json",
7252
+ createdAt: target.createdAt,
7253
+ expectedAbsent: target.expectedAbsent || Boolean(target.inlineServerEntry),
7254
+ now: Date.now()
7255
+ });
6872
7256
  }
7257
+ const cfg = lookup.value;
6873
7258
  if (cfg.unresolved.length > 0) {
6874
7259
  return {
6875
7260
  status: "transient_error",
@@ -6890,12 +7275,19 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
6890
7275
  // queries with. Inputs come from the agent's OWN wired MCP server: the
6891
7276
  // `x-api-key` header and the `user_id` query param (the agent already
6892
7277
  // authenticates with these), plus the recorded connected_account_id.
6893
- composioProbe: async (serverKey, credentials, inlineServerEntry) => {
6894
- const cfg = inlineServerEntry ? resolveHttpServerEntry(inlineServerEntry, probeEnv) : readMcpHttpServerConfig(projectDir, serverKey, probeEnv);
6895
- if (!cfg) {
6896
- const src = inlineServerEntry ? "inline config" : ".mcp.json";
6897
- return { status: "transient_error", message: `MCP server '${serverKey}' not resolvable from ${src}` };
7278
+ composioProbe: async (serverKey, credentials, inlineServerEntry, rowCtx) => {
7279
+ const lookup = inlineServerEntry ? inlineEntryLookup(resolveHttpServerEntry(inlineServerEntry, probeEnv)) : readMcpHttpServerConfig(projectDir, serverKey, probeEnv);
7280
+ if (!lookup.ok) {
7281
+ return verdictForUnavailableMcpConfig(lookup.cause, {
7282
+ serverKey,
7283
+ transport: "http",
7284
+ source: inlineServerEntry ? "inline config" : ".mcp.json",
7285
+ createdAt: rowCtx?.createdAt,
7286
+ expectedAbsent: rowCtx?.expectedAbsent || Boolean(inlineServerEntry),
7287
+ now: Date.now()
7288
+ });
6898
7289
  }
7290
+ const cfg = lookup.value;
6899
7291
  if (cfg.unresolved.length > 0) {
6900
7292
  return {
6901
7293
  status: "transient_error",
@@ -6921,8 +7313,9 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
6921
7313
  // linkage surfaces as a real `No connected account found` instead of a
6922
7314
  // green handshake. Skips (`null`) when no safe read-only tool is callable.
6923
7315
  composioToolCallProbe: async (target) => {
6924
- const cfg = target.inlineServerEntry ? resolveHttpServerEntry(target.inlineServerEntry, probeEnv) : readMcpHttpServerConfig(projectDir, target.serverKey, probeEnv);
6925
- if (!cfg) return null;
7316
+ const lookup = target.inlineServerEntry ? inlineEntryLookup(resolveHttpServerEntry(target.inlineServerEntry, probeEnv)) : readMcpHttpServerConfig(projectDir, target.serverKey, probeEnv);
7317
+ if (!lookup.ok) return null;
7318
+ const cfg = lookup.value;
6926
7319
  if (cfg.unresolved.length > 0) return null;
6927
7320
  return probeComposioMcpToolCall({
6928
7321
  url: cfg.url,
@@ -7688,6 +8081,10 @@ async function executeConnectivityProbe(target, deps = {}) {
7688
8081
  if (!descriptor.readOnly) {
7689
8082
  throw new Error(`Refusing non-read-only probe for ${target.definitionId}`);
7690
8083
  }
8084
+ const rowFacts = {
8085
+ createdAt: target.createdAt ?? null,
8086
+ expectedAbsent: target.expectedAbsent ?? false
8087
+ };
7691
8088
  switch (descriptor.kind) {
7692
8089
  case "http_provider": {
7693
8090
  const outcome = await probeHttpProvider(target.definitionId, target.credentials, deps.fetchImpl ?? fetch);
@@ -7698,7 +8095,8 @@ async function executeConnectivityProbe(target, deps = {}) {
7698
8095
  const outcome = await deps.composioProbe(
7699
8096
  target.mcpServerKey ?? target.definitionId,
7700
8097
  target.credentials,
7701
- target.inlineServerEntry
8098
+ target.inlineServerEntry,
8099
+ rowFacts
7702
8100
  );
7703
8101
  return outcome ? withEvidence(outcome, "record_only") : outcome;
7704
8102
  }
@@ -7710,7 +8108,8 @@ async function executeConnectivityProbe(target, deps = {}) {
7710
8108
  await deps.mcpProbe({
7711
8109
  serverKey: target.mcpServerKey ?? target.definitionId,
7712
8110
  definitionId: target.definitionId,
7713
- inlineServerEntry: target.inlineServerEntry
8111
+ inlineServerEntry: target.inlineServerEntry,
8112
+ ...rowFacts
7714
8113
  }),
7715
8114
  // ENG-8226: initialize + tools/list. The transport answered; no
7716
8115
  // authenticated operation ran. The dwight `needs_reauth` rows all
@@ -7722,7 +8121,7 @@ async function executeConnectivityProbe(target, deps = {}) {
7722
8121
  if (deps.composioProbe) {
7723
8122
  outcomes.push(
7724
8123
  withEvidence(
7725
- await deps.composioProbe(target.mcpServerKey ?? target.definitionId, target.credentials, target.inlineServerEntry),
8124
+ await deps.composioProbe(target.mcpServerKey ?? target.definitionId, target.credentials, target.inlineServerEntry, rowFacts),
7726
8125
  "record_only"
7727
8126
  )
7728
8127
  );
@@ -7732,6 +8131,7 @@ async function executeConnectivityProbe(target, deps = {}) {
7732
8131
  serverKey: target.mcpServerKey ?? target.definitionId,
7733
8132
  definitionId: target.definitionId,
7734
8133
  inlineServerEntry: target.inlineServerEntry,
8134
+ ...rowFacts,
7735
8135
  // ENG-6242: thread the prescribed tool through to the live tool-call
7736
8136
  // leg. resolveConnectivityProbe only sets these for managed toolkits
7737
8137
  // with a stored override; the probe re-validates read-only and falls
@@ -7754,7 +8154,8 @@ async function executeConnectivityProbe(target, deps = {}) {
7754
8154
  const outcome = await deps.mcpProbe({
7755
8155
  serverKey: target.mcpServerKey ?? target.definitionId,
7756
8156
  definitionId: target.definitionId,
7757
- inlineServerEntry: target.inlineServerEntry
8157
+ inlineServerEntry: target.inlineServerEntry,
8158
+ ...rowFacts
7758
8159
  });
7759
8160
  return withEvidence(outcome, "handshake");
7760
8161
  }
@@ -7764,6 +8165,7 @@ async function executeConnectivityProbe(target, deps = {}) {
7764
8165
  serverKey: target.mcpServerKey ?? target.definitionId,
7765
8166
  definitionId: target.definitionId,
7766
8167
  inlineServerEntry: target.inlineServerEntry,
8168
+ ...rowFacts,
7767
8169
  toolName: descriptor.probeTool ?? null,
7768
8170
  toolArgs: descriptor.probeArgs ?? null
7769
8171
  });
@@ -7849,6 +8251,13 @@ export {
7849
8251
  clearPresenceReaperStateForKeys,
7850
8252
  givenUpMcpServerKeys,
7851
8253
  reapMissingMcpSessions,
8254
+ reaperRestartBreakerReason,
8255
+ isProvisioningReloadReason,
8256
+ tripClass,
8257
+ readEnvNumber,
8258
+ RestartBreaker,
8259
+ markQuarantinedForProbe,
8260
+ FIRST_CONNECT_WINDOW_MS,
7852
8261
  deriveMcpServerKey,
7853
8262
  buildProbeEnv,
7854
8263
  buildConnectivityProbeDeps,
@@ -7871,4 +8280,4 @@ export {
7871
8280
  managerInstallSystemUnitCommand,
7872
8281
  managerUninstallSystemUnitCommand
7873
8282
  };
7874
- //# sourceMappingURL=chunk-ICNRQ4L5.js.map
8283
+ //# sourceMappingURL=chunk-N4Y5IBZR.js.map