@integrity-labs/agt-cli 0.28.499 → 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 +4 -4
- package/dist/{chunk-Q5O2VAQA.js → chunk-N4Y5IBZR.js} +443 -34
- package/dist/chunk-N4Y5IBZR.js.map +1 -0
- package/dist/{chunk-5XHO4HKA.js → chunk-XKBUTWON.js} +25 -2
- package/dist/chunk-XKBUTWON.js.map +1 -0
- package/dist/{claude-pair-runtime-TA2D5FPK.js → claude-pair-runtime-U5ES3GOY.js} +2 -2
- package/dist/lib/manager-worker.js +42 -336
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-NZMYK2WQ.js → persistent-session-HL3LNPAX.js} +4 -2
- package/dist/{responsiveness-probe-RFANLJWP.js → responsiveness-probe-AZ44ONES.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5XHO4HKA.js.map +0 -1
- package/dist/chunk-Q5O2VAQA.js.map +0 -1
- /package/dist/{claude-pair-runtime-TA2D5FPK.js.map → claude-pair-runtime-U5ES3GOY.js.map} +0 -0
- /package/dist/{persistent-session-NZMYK2WQ.js.map → persistent-session-HL3LNPAX.js.map} +0 -0
- /package/dist/{responsiveness-probe-RFANLJWP.js.map → responsiveness-probe-AZ44ONES.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
success,
|
|
41
41
|
table,
|
|
42
42
|
warn
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-N4Y5IBZR.js";
|
|
44
44
|
import {
|
|
45
45
|
AnchorSessionClient,
|
|
46
46
|
CHANNEL_REGISTRY,
|
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
requiredMcpWildcard,
|
|
72
72
|
resolveChannels,
|
|
73
73
|
serializeManifestForSlackCli
|
|
74
|
-
} from "../chunk-
|
|
74
|
+
} from "../chunk-XKBUTWON.js";
|
|
75
75
|
import "../chunk-XWVM4KPK.js";
|
|
76
76
|
|
|
77
77
|
// src/bin/agt.ts
|
|
@@ -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.
|
|
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.
|
|
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,
|
|
@@ -38,7 +42,7 @@ import {
|
|
|
38
42
|
resolveConnectivityProbe,
|
|
39
43
|
worseConnectivityOutcome,
|
|
40
44
|
wrapScheduledTaskPrompt
|
|
41
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-XKBUTWON.js";
|
|
42
46
|
import {
|
|
43
47
|
parsePsRows
|
|
44
48
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -5057,7 +5061,7 @@ function exchangeFailureKind(err) {
|
|
|
5057
5061
|
}
|
|
5058
5062
|
|
|
5059
5063
|
// src/lib/api-client.ts
|
|
5060
|
-
var agtCliVersion = true ? "0.28.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
6848
|
-
if (!
|
|
6849
|
-
|
|
6850
|
-
|
|
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.
|
|
6865
|
-
//
|
|
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
|
|
6869
|
-
if (!
|
|
6870
|
-
|
|
6871
|
-
|
|
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
|
|
6895
|
-
if (!
|
|
6896
|
-
|
|
6897
|
-
|
|
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
|
|
6925
|
-
if (!
|
|
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-
|
|
8283
|
+
//# sourceMappingURL=chunk-N4Y5IBZR.js.map
|