@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.
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiError,
|
|
3
3
|
CHANNEL_SECRET_ENV_KEYS,
|
|
4
|
+
FIRST_CONNECT_WINDOW_MS,
|
|
4
5
|
HostFlagStore,
|
|
6
|
+
RestartBreaker,
|
|
5
7
|
SUPERVISOR_RESTART_EXIT_CODE,
|
|
6
8
|
api,
|
|
7
9
|
atomicWriteFileSync,
|
|
@@ -25,9 +27,11 @@ import {
|
|
|
25
27
|
getCachedClaudeAuthMode,
|
|
26
28
|
getHostId,
|
|
27
29
|
givenUpMcpServerKeys,
|
|
30
|
+
isProvisioningReloadReason,
|
|
28
31
|
isTransientAuthFailure,
|
|
29
32
|
liveProxyExtraHeaderVars,
|
|
30
33
|
liveProxyTokenVars,
|
|
34
|
+
markQuarantinedForProbe,
|
|
31
35
|
migrateAgentDirToIdKeyed,
|
|
32
36
|
pinAllowsUrgent,
|
|
33
37
|
provision,
|
|
@@ -38,14 +42,17 @@ import {
|
|
|
38
42
|
provisionPreCompactHook,
|
|
39
43
|
provisionSessionStateHook,
|
|
40
44
|
provisionStopHook,
|
|
45
|
+
readEnvNumber,
|
|
41
46
|
reapMissingMcpSessions,
|
|
42
47
|
reapStaleMcpChildren,
|
|
48
|
+
reaperRestartBreakerReason,
|
|
43
49
|
reconstructQuarantinedMcpServerEntry,
|
|
44
50
|
requireHost,
|
|
45
51
|
resolveEffectivePinRaw,
|
|
46
52
|
safeWriteJsonAtomic,
|
|
47
|
-
setConfigHash
|
|
48
|
-
|
|
53
|
+
setConfigHash,
|
|
54
|
+
tripClass
|
|
55
|
+
} from "../chunk-N4Y5IBZR.js";
|
|
49
56
|
import {
|
|
50
57
|
getProjectDir as getProjectDir2,
|
|
51
58
|
getReadyTasks,
|
|
@@ -56,7 +63,6 @@ import {
|
|
|
56
63
|
import {
|
|
57
64
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
58
65
|
AnchorSessionClient,
|
|
59
|
-
BIND_FAILURE_QUARANTINE_THRESHOLD,
|
|
60
66
|
CLAUDE_MD_MAX_CHARS,
|
|
61
67
|
CONVERSATION_FAILURE_CATEGORIES,
|
|
62
68
|
DEFAULT_FRAMEWORK,
|
|
@@ -67,9 +73,6 @@ import {
|
|
|
67
73
|
KANBAN_CHECK_COMMAND,
|
|
68
74
|
KANBAN_NUDGE_ACTIONABLE_STATUSES,
|
|
69
75
|
MAX_AVATAR_ENV_URL_BYTES,
|
|
70
|
-
MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING,
|
|
71
|
-
RESTART_BREAKER_PROVISIONING_MAX,
|
|
72
|
-
RESTART_BREAKER_PROVISIONING_WINDOW_MS,
|
|
73
76
|
SCHEDULED_TURN_MARKER_FILENAME,
|
|
74
77
|
SUPPRESS_SENTINEL,
|
|
75
78
|
StreamEncoder,
|
|
@@ -845,315 +848,8 @@ function logClaudeMdCeilingTransition(codeName, report, log2) {
|
|
|
845
848
|
return line;
|
|
846
849
|
}
|
|
847
850
|
|
|
848
|
-
// src/lib/restart-breaker.ts
|
|
849
|
-
function reaperRestartBreakerReason(activeKeys) {
|
|
850
|
-
return activeKeys.length >= 2 ? "mcp-presence-reaper" : void 0;
|
|
851
|
-
}
|
|
852
|
-
var KNOWN_REASON_CLASS_MAP = {
|
|
853
|
-
crash: true,
|
|
854
|
-
provisioning: true,
|
|
855
|
-
"credential-rotation": true,
|
|
856
|
-
// ENG-8215: the self-healing class (bind-remediation - the manager's OWN
|
|
857
|
-
// repair attempts, which must never count toward a trip). Adding it here is
|
|
858
|
-
// forced by the Record type, which is exactly why that shape was chosen over
|
|
859
|
-
// a Set literal (ENG-7577, CodeRabbit on #3861) - a new union member cannot
|
|
860
|
-
// be silently omitted from the rehydration allowlist.
|
|
861
|
-
"self-healing": true
|
|
862
|
-
};
|
|
863
|
-
var KNOWN_REASON_CLASSES = new Set(
|
|
864
|
-
Object.keys(KNOWN_REASON_CLASS_MAP)
|
|
865
|
-
);
|
|
866
|
-
var PROVISIONING_RELOAD_REASONS = /* @__PURE__ */ new Set([
|
|
867
|
-
"hot-reload-mcp",
|
|
868
|
-
"managed-mcp-churn",
|
|
869
|
-
// ENG-8215: 'bind-remediation' MOVED OUT of this set into SELF_HEALING_REASONS.
|
|
870
|
-
// It is the manager repairing itself, not a config reload, and counting it
|
|
871
|
-
// meant the harder the platform tried to fix an agent the more certain it
|
|
872
|
-
// became that it should give up. Deliberately NOT re-added here.
|
|
873
|
-
// ENG-7576: the dashboard integration-add stop itself - the first restart of
|
|
874
|
-
// the very burst this class exists for, previously uncounted entirely.
|
|
875
|
-
"integration-change",
|
|
876
|
-
// ENG-7771: a sender_policy delivery restart is a deliberate config-delivery
|
|
877
|
-
// reload (operator changed the policy, or the fail-closed first-poll verify
|
|
878
|
-
// after a manager restart with no persisted baseline), not a crash. On the
|
|
879
|
-
// tight crash tally, a host whose sender-policy-baseline.json write keeps
|
|
880
|
-
// failing (disk full, permissions) would convert the fail-closed restart
|
|
881
|
-
// into repeated crash tallies across a multi-deploy day and trip the
|
|
882
|
-
// breaker - taking the agent DOWN, which is strictly worse than the stale
|
|
883
|
-
// policy the restart exists to fix. The looser provisioning tally still
|
|
884
|
-
// trips + pages on sustained thrash.
|
|
885
|
-
"sender-policy-change"
|
|
886
|
-
]);
|
|
887
|
-
function isProvisioningReloadReason(reason) {
|
|
888
|
-
return PROVISIONING_RELOAD_REASONS.has(reason);
|
|
889
|
-
}
|
|
890
|
-
var CREDENTIAL_ROTATION_REASONS = /* @__PURE__ */ new Set([
|
|
891
|
-
"credential-rotation"
|
|
892
|
-
]);
|
|
893
|
-
function isCredentialRotationReason(reason) {
|
|
894
|
-
return CREDENTIAL_ROTATION_REASONS.has(reason);
|
|
895
|
-
}
|
|
896
|
-
var SELF_HEALING_REASONS = /* @__PURE__ */ new Set([
|
|
897
|
-
"bind-remediation"
|
|
898
|
-
]);
|
|
899
|
-
function isSelfHealingReason(reason) {
|
|
900
|
-
return SELF_HEALING_REASONS.has(reason);
|
|
901
|
-
}
|
|
902
|
-
function restartReasonClass(reason) {
|
|
903
|
-
if (isCredentialRotationReason(reason)) return "credential-rotation";
|
|
904
|
-
if (isSelfHealingReason(reason)) return "self-healing";
|
|
905
|
-
return isProvisioningReloadReason(reason) ? "provisioning" : "crash";
|
|
906
|
-
}
|
|
907
|
-
function countByClass(events) {
|
|
908
|
-
let crash = 0;
|
|
909
|
-
let provisioning = 0;
|
|
910
|
-
let credentialRotation = 0;
|
|
911
|
-
let selfHealing = 0;
|
|
912
|
-
for (const e of events) {
|
|
913
|
-
const klass = restartReasonClass(e.reason);
|
|
914
|
-
if (klass === "provisioning") provisioning += 1;
|
|
915
|
-
else if (klass === "credential-rotation") credentialRotation += 1;
|
|
916
|
-
else if (klass === "self-healing") selfHealing += 1;
|
|
917
|
-
else crash += 1;
|
|
918
|
-
}
|
|
919
|
-
return {
|
|
920
|
-
crash,
|
|
921
|
-
provisioning,
|
|
922
|
-
"credential-rotation": credentialRotation,
|
|
923
|
-
"self-healing": selfHealing
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
function maxProvisioningBucket(events) {
|
|
927
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
928
|
-
for (const e of events) {
|
|
929
|
-
if (!isProvisioningReloadReason(e.reason)) continue;
|
|
930
|
-
const key = e.integrationKey ?? "";
|
|
931
|
-
buckets.set(key, (buckets.get(key) ?? 0) + 1);
|
|
932
|
-
}
|
|
933
|
-
let best = { count: 0, key: "" };
|
|
934
|
-
for (const [key, count] of buckets) {
|
|
935
|
-
if (count > best.count) best = { count, key };
|
|
936
|
-
}
|
|
937
|
-
return best;
|
|
938
|
-
}
|
|
939
|
-
function tripClass(trip) {
|
|
940
|
-
if (trip.trippedClass) return trip.trippedClass;
|
|
941
|
-
if (!Array.isArray(trip.eventsAtTrip) || trip.eventsAtTrip.length === 0) return "crash";
|
|
942
|
-
return trip.eventsAtTrip.every((e) => isProvisioningReloadReason(e.reason)) ? "provisioning" : "crash";
|
|
943
|
-
}
|
|
944
|
-
var DEFAULT_MAX = 2;
|
|
945
|
-
var DEFAULT_WINDOW_MS = 6e5;
|
|
946
|
-
var DEFAULT_PROVISIONING_MAX = RESTART_BREAKER_PROVISIONING_MAX;
|
|
947
|
-
var DEFAULT_PROVISIONING_WINDOW_MS = RESTART_BREAKER_PROVISIONING_WINDOW_MS;
|
|
948
|
-
function readEnvNumber(name, fallback) {
|
|
949
|
-
const raw = process.env[name];
|
|
950
|
-
if (!raw) return fallback;
|
|
951
|
-
const parsed = Number(raw);
|
|
952
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
953
|
-
}
|
|
954
|
-
var RestartBreaker = class {
|
|
955
|
-
max;
|
|
956
|
-
windowMs;
|
|
957
|
-
/**
|
|
958
|
-
* ENG-8215: NOT readonly - the constructor may clamp it upward to preserve the
|
|
959
|
-
* quarantine-fires-first ordering against a runtime override. Never mutated
|
|
960
|
-
* after construction.
|
|
961
|
-
*/
|
|
962
|
-
provisioningMax;
|
|
963
|
-
provisioningWindowMs;
|
|
964
|
-
/** Longest window either tally needs — how long the events log must retain. */
|
|
965
|
-
retentionMs;
|
|
966
|
-
now;
|
|
967
|
-
events = /* @__PURE__ */ new Map();
|
|
968
|
-
trips = /* @__PURE__ */ new Map();
|
|
969
|
-
/**
|
|
970
|
-
* ENG-8215: set when a supplied `provisioningMax` was raised to keep MCP
|
|
971
|
-
* quarantine ahead of the breaker. Read once by the manager at construction so
|
|
972
|
-
* an overridden-and-clamped value is visible in manager.log rather than
|
|
973
|
-
* silently discarded.
|
|
974
|
-
*/
|
|
975
|
-
quarantineOrderingClamped;
|
|
976
|
-
constructor(opts = {}) {
|
|
977
|
-
this.max = opts.max ?? readEnvNumber("AGT_RESTART_BREAKER_MAX", DEFAULT_MAX);
|
|
978
|
-
this.windowMs = opts.windowMs ?? readEnvNumber("AGT_RESTART_BREAKER_WINDOW_MS", DEFAULT_WINDOW_MS);
|
|
979
|
-
this.provisioningMax = opts.provisioningMax ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_MAX", DEFAULT_PROVISIONING_MAX);
|
|
980
|
-
this.provisioningWindowMs = opts.provisioningWindowMs ?? readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_WINDOW_MS", DEFAULT_PROVISIONING_WINDOW_MS);
|
|
981
|
-
this.now = opts.now ?? Date.now;
|
|
982
|
-
if (!Number.isFinite(this.max) || this.max < 1) {
|
|
983
|
-
throw new Error("restart-breaker max must be a finite number >= 1");
|
|
984
|
-
}
|
|
985
|
-
if (!Number.isFinite(this.windowMs) || this.windowMs < 1e3) {
|
|
986
|
-
throw new Error("restart-breaker windowMs must be a finite number >= 1000");
|
|
987
|
-
}
|
|
988
|
-
if (!Number.isFinite(this.provisioningMax) || this.provisioningMax < 1) {
|
|
989
|
-
throw new Error("restart-breaker provisioningMax must be a finite number >= 1");
|
|
990
|
-
}
|
|
991
|
-
if (this.provisioningMax < MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING) {
|
|
992
|
-
this.quarantineOrderingClamped = {
|
|
993
|
-
requested: this.provisioningMax,
|
|
994
|
-
applied: MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING,
|
|
995
|
-
quarantineThreshold: BIND_FAILURE_QUARANTINE_THRESHOLD
|
|
996
|
-
};
|
|
997
|
-
this.provisioningMax = MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING;
|
|
998
|
-
}
|
|
999
|
-
if (!Number.isFinite(this.provisioningWindowMs) || this.provisioningWindowMs < 1e3) {
|
|
1000
|
-
throw new Error("restart-breaker provisioningWindowMs must be a finite number >= 1000");
|
|
1001
|
-
}
|
|
1002
|
-
this.retentionMs = Math.max(this.windowMs, this.provisioningWindowMs);
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* ENG-8215: the clamp record, if the configured provisioning max had to be
|
|
1006
|
-
* raised to keep MCP quarantine ahead of the breaker. `undefined` when the
|
|
1007
|
-
* configured value already satisfied the ordering.
|
|
1008
|
-
*/
|
|
1009
|
-
getQuarantineOrderingClamp() {
|
|
1010
|
-
return this.quarantineOrderingClamped;
|
|
1011
|
-
}
|
|
1012
|
-
/** True if this agent's breaker is currently tripped (manager must skip spawn). */
|
|
1013
|
-
isTripped(codeName) {
|
|
1014
|
-
return this.trips.has(codeName);
|
|
1015
|
-
}
|
|
1016
|
-
getTrip(codeName) {
|
|
1017
|
-
return this.trips.get(codeName);
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Record a restart event. If recording this event puts the count
|
|
1021
|
-
* inside the window above `max`, the breaker trips and the call site
|
|
1022
|
-
* should NOT respawn.
|
|
1023
|
-
*
|
|
1024
|
-
* Idempotent on already-tripped breakers: returns the existing trip
|
|
1025
|
-
* without double-counting events. Callers may still record the reason
|
|
1026
|
-
* via the decision-log for forensics.
|
|
1027
|
-
*/
|
|
1028
|
-
record(codeName, reason, integrationKey) {
|
|
1029
|
-
const existing = this.trips.get(codeName);
|
|
1030
|
-
if (existing) {
|
|
1031
|
-
const bucket = maxProvisioningBucket(existing.eventsAtTrip);
|
|
1032
|
-
return {
|
|
1033
|
-
tripped: false,
|
|
1034
|
-
trip: existing,
|
|
1035
|
-
windowCount: existing.eventsAtTrip.length,
|
|
1036
|
-
classCounts: countByClass(existing.eventsAtTrip),
|
|
1037
|
-
maxProvisioningBucket: bucket.count,
|
|
1038
|
-
maxProvisioningBucketKey: bucket.key
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
const at = this.now();
|
|
1042
|
-
const retentionCutoff = at - this.retentionMs;
|
|
1043
|
-
const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= retentionCutoff);
|
|
1044
|
-
prior.push(
|
|
1045
|
-
isProvisioningReloadReason(reason) && integrationKey ? { reason, at, integrationKey } : { reason, at }
|
|
1046
|
-
);
|
|
1047
|
-
this.events.set(codeName, prior);
|
|
1048
|
-
const crashCount = prior.filter(
|
|
1049
|
-
(e) => e.at >= at - this.windowMs && restartReasonClass(e.reason) === "crash"
|
|
1050
|
-
).length;
|
|
1051
|
-
const provEvents = prior.filter(
|
|
1052
|
-
(e) => e.at >= at - this.provisioningWindowMs && isProvisioningReloadReason(e.reason)
|
|
1053
|
-
);
|
|
1054
|
-
const credRotCount = prior.filter(
|
|
1055
|
-
(e) => e.at >= at - this.provisioningWindowMs && isCredentialRotationReason(e.reason)
|
|
1056
|
-
).length;
|
|
1057
|
-
const selfHealCount = prior.filter(
|
|
1058
|
-
(e) => e.at >= at - this.provisioningWindowMs && isSelfHealingReason(e.reason)
|
|
1059
|
-
).length;
|
|
1060
|
-
const windowCount = prior.filter((e) => e.at >= at - this.windowMs).length;
|
|
1061
|
-
const classCounts = {
|
|
1062
|
-
crash: crashCount,
|
|
1063
|
-
provisioning: provEvents.length,
|
|
1064
|
-
"credential-rotation": credRotCount,
|
|
1065
|
-
"self-healing": selfHealCount
|
|
1066
|
-
};
|
|
1067
|
-
const provBucket = maxProvisioningBucket(provEvents);
|
|
1068
|
-
let trippedClass;
|
|
1069
|
-
let tripEvents;
|
|
1070
|
-
let tripWindowMs = this.windowMs;
|
|
1071
|
-
if (crashCount > this.max) {
|
|
1072
|
-
trippedClass = "crash";
|
|
1073
|
-
tripEvents = prior.filter(
|
|
1074
|
-
(e) => e.at >= at - this.windowMs && restartReasonClass(e.reason) === "crash"
|
|
1075
|
-
);
|
|
1076
|
-
tripWindowMs = this.windowMs;
|
|
1077
|
-
} else if (provBucket.count > this.provisioningMax) {
|
|
1078
|
-
trippedClass = "provisioning";
|
|
1079
|
-
tripEvents = provEvents.filter((e) => (e.integrationKey ?? "") === provBucket.key);
|
|
1080
|
-
tripWindowMs = this.provisioningWindowMs;
|
|
1081
|
-
}
|
|
1082
|
-
if (trippedClass && tripEvents) {
|
|
1083
|
-
const trip = {
|
|
1084
|
-
trippedAt: at,
|
|
1085
|
-
eventsAtTrip: [...tripEvents],
|
|
1086
|
-
statusMessage: formatStatusMessage(tripEvents, tripWindowMs, trippedClass, provBucket.key),
|
|
1087
|
-
// ENG-7577: persist the tripping class so the recovery path can gate on
|
|
1088
|
-
// it after a manager restart, without re-deriving it from prose.
|
|
1089
|
-
trippedClass
|
|
1090
|
-
};
|
|
1091
|
-
this.trips.set(codeName, trip);
|
|
1092
|
-
this.events.delete(codeName);
|
|
1093
|
-
return {
|
|
1094
|
-
tripped: true,
|
|
1095
|
-
trip,
|
|
1096
|
-
windowCount,
|
|
1097
|
-
classCounts,
|
|
1098
|
-
trippedClass,
|
|
1099
|
-
maxProvisioningBucket: provBucket.count,
|
|
1100
|
-
maxProvisioningBucketKey: provBucket.key
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
return {
|
|
1104
|
-
tripped: false,
|
|
1105
|
-
windowCount,
|
|
1106
|
-
classCounts,
|
|
1107
|
-
maxProvisioningBucket: provBucket.count,
|
|
1108
|
-
maxProvisioningBucketKey: provBucket.key
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
/** Operator-initiated reset: drops the trip + the events log for this agent. */
|
|
1112
|
-
clear(codeName) {
|
|
1113
|
-
this.trips.delete(codeName);
|
|
1114
|
-
this.events.delete(codeName);
|
|
1115
|
-
}
|
|
1116
|
-
/** Snapshot tripped agents for `manager-state.json`. */
|
|
1117
|
-
serialize() {
|
|
1118
|
-
return Object.fromEntries(this.trips.entries());
|
|
1119
|
-
}
|
|
1120
|
-
/**
|
|
1121
|
-
* Rehydrate trip state from `manager-state.json`. Called once at
|
|
1122
|
-
* worker startup. Window history is intentionally NOT persisted — only
|
|
1123
|
-
* tripped state. A tripped breaker survives manager restart; an
|
|
1124
|
-
* un-tripped one starts a fresh window in the new worker.
|
|
1125
|
-
*/
|
|
1126
|
-
hydrate(saved) {
|
|
1127
|
-
if (!saved) return;
|
|
1128
|
-
for (const [codeName, trip] of Object.entries(saved)) {
|
|
1129
|
-
if (trip && typeof trip.trippedAt === "number" && Array.isArray(trip.eventsAtTrip)) {
|
|
1130
|
-
const klass = trip.trippedClass;
|
|
1131
|
-
this.trips.set(
|
|
1132
|
-
codeName,
|
|
1133
|
-
klass === void 0 || KNOWN_REASON_CLASSES.has(klass) ? trip : { ...trip, trippedClass: void 0 }
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
/** Test helper — current in-window event count for `codeName`. */
|
|
1139
|
-
windowCount(codeName) {
|
|
1140
|
-
const cutoff = this.now() - this.windowMs;
|
|
1141
|
-
return (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff).length;
|
|
1142
|
-
}
|
|
1143
|
-
};
|
|
1144
|
-
function formatStatusMessage(events, windowMs, klass = "crash", integrationKey = "") {
|
|
1145
|
-
const last = events[events.length - 1];
|
|
1146
|
-
const windowLabel = windowMs < 6e4 ? `${Math.round(windowMs / 1e3)}s` : `${(windowMs / 6e4).toFixed(1).replace(/\.0$/, "")}min`;
|
|
1147
|
-
const reasonCounts = /* @__PURE__ */ new Map();
|
|
1148
|
-
for (const e of events) reasonCounts.set(e.reason, (reasonCounts.get(e.reason) ?? 0) + 1);
|
|
1149
|
-
const breakdown = Array.from(reasonCounts.entries()).map(([r, n]) => `${r}=${n}`).join(", ");
|
|
1150
|
-
const classLabel = klass === "provisioning" ? "provisioning-reload " : "";
|
|
1151
|
-
const integrationLabel = klass === "provisioning" && integrationKey ? ` for integration '${integrationKey}'` : "";
|
|
1152
|
-
return `Circuit breaker tripped: ${events.length} ${classLabel}restarts in ${windowLabel}${integrationLabel} (${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
851
|
// src/lib/mcp-flap-dampener.ts
|
|
1156
|
-
var
|
|
852
|
+
var DEFAULT_WINDOW_MS = 6e5;
|
|
1157
853
|
var DEFAULT_MIN_TRANSITIONS = 4;
|
|
1158
854
|
var DEFAULT_MIN_DISTINCT = 2;
|
|
1159
855
|
var DEFAULT_LONG_WINDOW_MS = 36e5;
|
|
@@ -1182,7 +878,7 @@ var McpFlapDampener = class {
|
|
|
1182
878
|
// see that method for the Kylie/dig (2026-06-29) rationale.
|
|
1183
879
|
lastFlapAt = /* @__PURE__ */ new Map();
|
|
1184
880
|
constructor(opts = {}) {
|
|
1185
|
-
this.windowMs = opts.windowMs ?? readEnvNumber2("AGT_MCP_FLAP_WINDOW_MS",
|
|
881
|
+
this.windowMs = opts.windowMs ?? readEnvNumber2("AGT_MCP_FLAP_WINDOW_MS", DEFAULT_WINDOW_MS);
|
|
1186
882
|
this.minTransitions = opts.minTransitions ?? readEnvNumber2("AGT_MCP_FLAP_MIN_TRANSITIONS", DEFAULT_MIN_TRANSITIONS);
|
|
1187
883
|
this.minDistinct = opts.minDistinct ?? readEnvNumber2("AGT_MCP_FLAP_MIN_DISTINCT", DEFAULT_MIN_DISTINCT);
|
|
1188
884
|
this.longWindowMs = opts.longWindowMs ?? readEnvNumber2("AGT_MCP_FLAP_LONG_WINDOW_MS", DEFAULT_LONG_WINDOW_MS);
|
|
@@ -1586,7 +1282,11 @@ async function runConnectivityProbes(integrations, options = {}) {
|
|
|
1586
1282
|
cliBinary: integ.cli_binary,
|
|
1587
1283
|
mcpServerKey: integ.mcp_server_key,
|
|
1588
1284
|
connectivityTest: integ.connectivity_test ?? null,
|
|
1589
|
-
inlineServerEntry: integ.inline_server_entry
|
|
1285
|
+
inlineServerEntry: integ.inline_server_entry,
|
|
1286
|
+
// ENG-8363: row age + quarantine status decide whether a missing
|
|
1287
|
+
// `.mcp.json` key is "not written yet" (transient) or "not wired" (down).
|
|
1288
|
+
createdAt: integ.created_at ?? null,
|
|
1289
|
+
expectedAbsent: integ.quarantined ?? false
|
|
1590
1290
|
};
|
|
1591
1291
|
let outcome;
|
|
1592
1292
|
try {
|
|
@@ -9209,8 +8909,8 @@ var restartBreaker = new RestartBreaker();
|
|
|
9209
8909
|
}
|
|
9210
8910
|
}
|
|
9211
8911
|
var reportedTrips = /* @__PURE__ */ new Map();
|
|
9212
|
-
var
|
|
9213
|
-
var
|
|
8912
|
+
var RESTART_BREAKER_PROVISIONING_MAX = readEnvNumber("AGT_RESTART_BREAKER_PROVISIONING_MAX", 5);
|
|
8913
|
+
var RESTART_BREAKER_PROVISIONING_WINDOW_MS = readEnvNumber(
|
|
9214
8914
|
"AGT_RESTART_BREAKER_PROVISIONING_WINDOW_MS",
|
|
9215
8915
|
18e5
|
|
9216
8916
|
);
|
|
@@ -9255,13 +8955,13 @@ function recordRestartForBreaker(codeName, reason) {
|
|
|
9255
8955
|
let integrationKey;
|
|
9256
8956
|
if (isProvisioningReloadReason(reason)) {
|
|
9257
8957
|
const attribution = agentState.provisioningAttribution.get(codeName);
|
|
9258
|
-
if (attribution && Date.now() - attribution.at <=
|
|
8958
|
+
if (attribution && Date.now() - attribution.at <= RESTART_BREAKER_PROVISIONING_WINDOW_MS) {
|
|
9259
8959
|
integrationKey = attribution.key;
|
|
9260
8960
|
}
|
|
9261
8961
|
}
|
|
9262
8962
|
const result = restartBreaker.record(codeName, reason, integrationKey);
|
|
9263
8963
|
if (!result.tripped || !result.trip) {
|
|
9264
|
-
if (isProvisioningReloadReason(reason) && result.maxProvisioningBucket >=
|
|
8964
|
+
if (isProvisioningReloadReason(reason) && result.maxProvisioningBucket >= RESTART_BREAKER_PROVISIONING_MAX) {
|
|
9265
8965
|
const culprit = result.maxProvisioningBucketKey ? ` [${result.maxProvisioningBucketKey}]` : "";
|
|
9266
8966
|
log(
|
|
9267
8967
|
`[restart-breaker] agent=${codeName} provisioning-reload churn at the trip bar: ${result.maxProvisioningBucket} restarts for one integration${culprit} in window (reason=${reason}); one more will auto-pause the agent; that integration is likely churning (ENG-7560, ENG-7812)`
|
|
@@ -9650,11 +9350,7 @@ function bindRemediationEnabled() {
|
|
|
9650
9350
|
return hostFlagStore().getBoolean("bind-remediation");
|
|
9651
9351
|
}
|
|
9652
9352
|
var BIND_REMEDIATION_JITTER_MS = 3e4;
|
|
9653
|
-
var BIND_REMEDIATION_FIRST_CONNECT_WINDOW_MS =
|
|
9654
|
-
"AGT_BIND_REMEDIATION_FIRST_CONNECT_WINDOW_MS",
|
|
9655
|
-
9e5
|
|
9656
|
-
// 15 min
|
|
9657
|
-
);
|
|
9353
|
+
var BIND_REMEDIATION_FIRST_CONNECT_WINDOW_MS = FIRST_CONNECT_WINDOW_MS;
|
|
9658
9354
|
var BIND_REMEDIATION_MAX_CONNECTIVITY_DEFERRALS = readEnvNumber(
|
|
9659
9355
|
"AGT_BIND_REMEDIATION_MAX_CONNECTIVITY_DEFERRALS",
|
|
9660
9356
|
3
|
|
@@ -9954,7 +9650,13 @@ async function runAgentConnectivityProbes(agent, integrations, projectDir) {
|
|
|
9954
9650
|
// ENG-8055: forward the reconstructed inline server entry (managed
|
|
9955
9651
|
// quarantined rows) so the MCP probe legs hit the real server instead of
|
|
9956
9652
|
// the missing `.mcp.json` key. Absent on normal (active) rows.
|
|
9957
|
-
inline_server_entry: i.inline_server_entry
|
|
9653
|
+
inline_server_entry: i.inline_server_entry,
|
|
9654
|
+
// ENG-8363: age + quarantine status, so an absent `.mcp.json` key is
|
|
9655
|
+
// classified rather than guessed. Forwarding both is what makes the
|
|
9656
|
+
// escalation to `down` reachable at all — an omitted `created_at` reads
|
|
9657
|
+
// as "still settling" and never escalates (fail-open by design).
|
|
9658
|
+
created_at: i.created_at ?? null,
|
|
9659
|
+
quarantined: i.quarantined ?? false
|
|
9958
9660
|
};
|
|
9959
9661
|
}),
|
|
9960
9662
|
{ probeDeps, intervalMs: intervalSec * 1e3 }
|
|
@@ -10251,7 +9953,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
10251
9953
|
var lastVersionCheckAt = 0;
|
|
10252
9954
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
10253
9955
|
var lastResponsivenessProbeAt = 0;
|
|
10254
|
-
var agtCliVersion = true ? "0.28.
|
|
9956
|
+
var agtCliVersion = true ? "0.28.501" : "dev";
|
|
10255
9957
|
function resolveBrewPath(execFileSync2) {
|
|
10256
9958
|
try {
|
|
10257
9959
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -13315,7 +13017,7 @@ async function processAgent(agent, agentStates) {
|
|
|
13315
13017
|
);
|
|
13316
13018
|
}
|
|
13317
13019
|
}
|
|
13318
|
-
probeSet = [...probeSet, ...qRows];
|
|
13020
|
+
probeSet = [...probeSet, ...markQuarantinedForProbe(qRows)];
|
|
13319
13021
|
log(
|
|
13320
13022
|
`Auto-resume: connectivity-probing ${qRows.length} quarantined integration(s) for '${agent.code_name}'`
|
|
13321
13023
|
);
|