@integrity-labs/agt-cli 0.28.1111 → 0.28.1113
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 +5 -5
- package/dist/{chunk-4PTXGOVO.js → chunk-JX7ZXCNZ.js} +2 -2
- package/dist/{chunk-TZM6YVE7.js → chunk-OBZB6MUE.js} +105 -9
- package/dist/chunk-OBZB6MUE.js.map +1 -0
- package/dist/{chunk-2UHVPGB3.js → chunk-TXNVB3DN.js} +4 -4
- package/dist/{claude-pair-runtime-L7KXG3DD.js → claude-pair-runtime-GL67TYXC.js} +2 -2
- package/dist/lib/manager-worker.js +16 -16
- package/dist/mcp/direct-chat-channel.js +332 -14
- package/dist/mcp/index.js +340 -38
- package/dist/mcp/origami.js +104 -8
- package/dist/mcp/slack-channel.js +340 -14
- package/dist/mcp/telegram-channel.js +472 -70
- package/dist/{persistent-session-HSYN3SQ2.js → persistent-session-6KIQF4O7.js} +3 -3
- package/dist/{responsiveness-probe-MO4L5O6F.js → responsiveness-probe-GGJNITME.js} +3 -3
- package/dist/{session-auth-dead-ZKI4XCSI.js → session-auth-dead-LMCPZJ4K.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-TZM6YVE7.js.map +0 -1
- /package/dist/{chunk-4PTXGOVO.js.map → chunk-JX7ZXCNZ.js.map} +0 -0
- /package/dist/{chunk-2UHVPGB3.js.map → chunk-TXNVB3DN.js.map} +0 -0
- /package/dist/{claude-pair-runtime-L7KXG3DD.js.map → claude-pair-runtime-GL67TYXC.js.map} +0 -0
- /package/dist/{persistent-session-HSYN3SQ2.js.map → persistent-session-6KIQF4O7.js.map} +0 -0
- /package/dist/{responsiveness-probe-MO4L5O6F.js.map → responsiveness-probe-GGJNITME.js.map} +0 -0
- /package/dist/{session-auth-dead-ZKI4XCSI.js.map → session-auth-dead-LMCPZJ4K.js.map} +0 -0
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
parseEnvFileEntries,
|
|
17
17
|
parseEnvIntegrations,
|
|
18
18
|
shellQuote
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-JX7ZXCNZ.js";
|
|
20
20
|
import {
|
|
21
21
|
BIND_FAILURE_QUARANTINE_THRESHOLD,
|
|
22
22
|
INTEGRATIONS_SECTION_END,
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
sessionTranscriptDir,
|
|
80
80
|
worseConnectivityOutcome,
|
|
81
81
|
wrapScheduledTaskPrompt
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-OBZB6MUE.js";
|
|
83
83
|
import {
|
|
84
84
|
classifyUnanswerablePane,
|
|
85
85
|
isConsumerTermsDialogVisible,
|
|
@@ -7518,7 +7518,7 @@ function exchangeFailureKind(err) {
|
|
|
7518
7518
|
}
|
|
7519
7519
|
|
|
7520
7520
|
// src/lib/api-client.ts
|
|
7521
|
-
var agtCliVersion = true ? "0.28.
|
|
7521
|
+
var agtCliVersion = true ? "0.28.1113" : "dev";
|
|
7522
7522
|
var lastConfigHash = null;
|
|
7523
7523
|
function setConfigHash(hash) {
|
|
7524
7524
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -12492,4 +12492,4 @@ export {
|
|
|
12492
12492
|
managerInstallSystemUnitCommand,
|
|
12493
12493
|
managerUninstallSystemUnitCommand
|
|
12494
12494
|
};
|
|
12495
|
-
//# sourceMappingURL=chunk-
|
|
12495
|
+
//# sourceMappingURL=chunk-TXNVB3DN.js.map
|
|
@@ -266,7 +266,7 @@ async function spawnPairSession(session) {
|
|
|
266
266
|
return { ok: true };
|
|
267
267
|
} catch {
|
|
268
268
|
}
|
|
269
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
269
|
+
const { resolveClaudeBinary } = await import("./persistent-session-6KIQF4O7.js");
|
|
270
270
|
const claudeBin = resolveClaudeBinary();
|
|
271
271
|
const pairEnv = {
|
|
272
272
|
...process.env,
|
|
@@ -734,4 +734,4 @@ export {
|
|
|
734
734
|
startClaudePair,
|
|
735
735
|
submitClaudePairCode
|
|
736
736
|
};
|
|
737
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
737
|
+
//# sourceMappingURL=claude-pair-runtime-GL67TYXC.js.map
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
safeWriteJsonAtomic,
|
|
69
69
|
setConfigHash,
|
|
70
70
|
tripClass
|
|
71
|
-
} from "../chunk-
|
|
71
|
+
} from "../chunk-TXNVB3DN.js";
|
|
72
72
|
import {
|
|
73
73
|
getProjectDir as getProjectDir2,
|
|
74
74
|
getReadyTasks,
|
|
@@ -155,7 +155,7 @@ import {
|
|
|
155
155
|
webFetchBaselineValue,
|
|
156
156
|
webFetchRestartNeeded,
|
|
157
157
|
writeEgressAllowlist
|
|
158
|
-
} from "../chunk-
|
|
158
|
+
} from "../chunk-JX7ZXCNZ.js";
|
|
159
159
|
import {
|
|
160
160
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
161
161
|
AnchorSessionClient,
|
|
@@ -265,7 +265,7 @@ import {
|
|
|
265
265
|
subagentActivityAgeSeconds,
|
|
266
266
|
sumTranscriptUsageInWindow,
|
|
267
267
|
transcriptActivityAgeSeconds
|
|
268
|
-
} from "../chunk-
|
|
268
|
+
} from "../chunk-OBZB6MUE.js";
|
|
269
269
|
import {
|
|
270
270
|
classifyUnanswerablePane,
|
|
271
271
|
paneRemedy
|
|
@@ -14143,7 +14143,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
|
|
|
14143
14143
|
var dayRolloverInboundHold = /* @__PURE__ */ new Map();
|
|
14144
14144
|
var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
|
|
14145
14145
|
async function channelInboundActivityAgeSecondsFor(codeName) {
|
|
14146
|
-
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-
|
|
14146
|
+
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-GGJNITME.js");
|
|
14147
14147
|
const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
|
|
14148
14148
|
if (newest === null) return null;
|
|
14149
14149
|
return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
|
|
@@ -14901,7 +14901,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
14901
14901
|
var lastVersionCheckAt = 0;
|
|
14902
14902
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
14903
14903
|
var lastResponsivenessProbeAt = 0;
|
|
14904
|
-
var agtCliVersion = true ? "0.28.
|
|
14904
|
+
var agtCliVersion = true ? "0.28.1113" : "dev";
|
|
14905
14905
|
function resolveBrewPath(execFileSync3) {
|
|
14906
14906
|
try {
|
|
14907
14907
|
const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -16518,7 +16518,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
16518
16518
|
if (codeNames.length === 0) return;
|
|
16519
16519
|
void (async () => {
|
|
16520
16520
|
try {
|
|
16521
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16521
|
+
const { collectDiagnostics } = await import("../persistent-session-6KIQF4O7.js");
|
|
16522
16522
|
await api.post("/host/heartbeat", {
|
|
16523
16523
|
host_id: hostId,
|
|
16524
16524
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -16660,7 +16660,7 @@ async function pollCycleInner() {
|
|
|
16660
16660
|
}
|
|
16661
16661
|
try {
|
|
16662
16662
|
const { detectHostSecurity } = await import("../host-security-KQYYZZVW.js");
|
|
16663
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
16663
|
+
const { collectDiagnostics } = await import("../persistent-session-6KIQF4O7.js");
|
|
16664
16664
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
16665
16665
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor) : void 0;
|
|
16666
16666
|
let tailscaleHostname;
|
|
@@ -16868,7 +16868,7 @@ async function pollCycleInner() {
|
|
|
16868
16868
|
collectPanelessActivityProbes,
|
|
16869
16869
|
getResponsivenessIntervalMs,
|
|
16870
16870
|
occupancyQualificationClassifications
|
|
16871
|
-
} = await import("../responsiveness-probe-
|
|
16871
|
+
} = await import("../responsiveness-probe-GGJNITME.js");
|
|
16872
16872
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
16873
16873
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
16874
16874
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -16985,7 +16985,7 @@ async function pollCycleInner() {
|
|
|
16985
16985
|
collectResponsivenessProbes,
|
|
16986
16986
|
livePendingInboundOldestAgeSeconds,
|
|
16987
16987
|
parkPendingInbound
|
|
16988
|
-
} = await import("../responsiveness-probe-
|
|
16988
|
+
} = await import("../responsiveness-probe-GGJNITME.js");
|
|
16989
16989
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-M6AFZWNU.js");
|
|
16990
16990
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
16991
16991
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -17271,7 +17271,7 @@ async function pollCycleInner() {
|
|
|
17271
17271
|
}
|
|
17272
17272
|
}
|
|
17273
17273
|
);
|
|
17274
|
-
const { parkPendingInbound } = await import("../responsiveness-probe-
|
|
17274
|
+
const { parkPendingInbound } = await import("../responsiveness-probe-GGJNITME.js");
|
|
17275
17275
|
const { parked, deadLettered } = parkPendingInbound(codeName, new Date(fatalNowMs));
|
|
17276
17276
|
paneFatalStreak.delete(codeName);
|
|
17277
17277
|
paneFatalRestarts.set(codeName, [...history, fatalNowMs]);
|
|
@@ -17302,7 +17302,7 @@ async function pollCycleInner() {
|
|
|
17302
17302
|
TRANSCRIPT_MOUNT_ESCALATION_REFRESH_MS,
|
|
17303
17303
|
bindSourceEscalations
|
|
17304
17304
|
} = await import("../transcript-mount-health-OHE6IVJV.js");
|
|
17305
|
-
const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-
|
|
17305
|
+
const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-6KIQF4O7.js");
|
|
17306
17306
|
const mountConfig = resolveTranscriptMountConfig();
|
|
17307
17307
|
const liveAgents = agentState.persistentSessionAgents;
|
|
17308
17308
|
for (const tracked of [
|
|
@@ -17467,7 +17467,7 @@ async function pollCycleInner() {
|
|
|
17467
17467
|
}
|
|
17468
17468
|
try {
|
|
17469
17469
|
const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-VR5QGP3J.js");
|
|
17470
|
-
const { probeSessionAuth } = await import("../session-auth-dead-
|
|
17470
|
+
const { probeSessionAuth } = await import("../session-auth-dead-LMCPZJ4K.js");
|
|
17471
17471
|
const observations = [];
|
|
17472
17472
|
const pendingCacheCommits = [];
|
|
17473
17473
|
const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
|
|
@@ -22054,7 +22054,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
22054
22054
|
try {
|
|
22055
22055
|
const freshId = rotateSessionForWedge(codeName);
|
|
22056
22056
|
consecutiveWedgeCycles.delete(codeName);
|
|
22057
|
-
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-
|
|
22057
|
+
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-GGJNITME.js");
|
|
22058
22058
|
const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
|
|
22059
22059
|
const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
|
|
22060
22060
|
const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
|
|
@@ -22097,7 +22097,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
22097
22097
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
22098
22098
|
void (async () => {
|
|
22099
22099
|
try {
|
|
22100
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
22100
|
+
const { collectDiagnostics } = await import("../persistent-session-6KIQF4O7.js");
|
|
22101
22101
|
await api.post("/host/heartbeat", {
|
|
22102
22102
|
host_id: hostId,
|
|
22103
22103
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -22148,7 +22148,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
22148
22148
|
}
|
|
22149
22149
|
try {
|
|
22150
22150
|
const hostId = await getHostId();
|
|
22151
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
22151
|
+
const { collectDiagnostics } = await import("../persistent-session-6KIQF4O7.js");
|
|
22152
22152
|
await api.post("/host/heartbeat", {
|
|
22153
22153
|
host_id: hostId,
|
|
22154
22154
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
|
|
@@ -22821,7 +22821,7 @@ async function processClaudePairSessions(agents) {
|
|
|
22821
22821
|
// cannot take it for itself — by the time it runs, the write it is looking
|
|
22822
22822
|
// for may already have happened.
|
|
22823
22823
|
snapshotPairConfig
|
|
22824
|
-
} = await import("../claude-pair-runtime-
|
|
22824
|
+
} = await import("../claude-pair-runtime-GL67TYXC.js");
|
|
22825
22825
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
22826
22826
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
22827
22827
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -33059,6 +33059,51 @@ var OAUTH_PROVIDERS = {
|
|
|
33059
33059
|
}
|
|
33060
33060
|
]
|
|
33061
33061
|
},
|
|
33062
|
+
"otter": {
|
|
33063
|
+
// CS-2029: Otter.ai's official MCP — remote streamable-HTTP at
|
|
33064
|
+
// https://mcp.otter.ai/mcp. Same Granola/Kajabi shape: OAuth 2.0
|
|
33065
|
+
// authorization-code with PKCE (S256) and a public client
|
|
33066
|
+
// (token_endpoint_auth_method 'none') issued via Dynamic Client
|
|
33067
|
+
// Registration (RFC 7591). Values below are taken verbatim from the
|
|
33068
|
+
// metadata, not inferred:
|
|
33069
|
+
// - https://mcp.otter.ai/.well-known/oauth-protected-resource names the
|
|
33070
|
+
// AS as https://otter.ai/ and advertises scopes profile:read,
|
|
33071
|
+
// conversations:read;
|
|
33072
|
+
// - https://otter.ai/.well-known/oauth-authorization-server gives
|
|
33073
|
+
// authorize /oauth2/authorize, token /oauth/token, revoke
|
|
33074
|
+
// /oauth/revoke_token, register /oauth/register, PKCE S256, `none`,
|
|
33075
|
+
// and the refresh_token grant.
|
|
33076
|
+
// NOTE the authorize path is /oauth2/authorize while token/revoke live
|
|
33077
|
+
// under /oauth/ — that is Otter's own split, do not "normalise" it.
|
|
33078
|
+
// The prod client was registered 2026-10-10 against /oauth/register with
|
|
33079
|
+
// scope 'profile:read conversations:read' and BOTH API callbacks,
|
|
33080
|
+
// https://api.augmented.team/... and https://api.ninjafy.com/integrations/oauth/callback,
|
|
33081
|
+
// because redirect_uri follows API_PUBLIC_URL and the ENG-11060 flip makes
|
|
33082
|
+
// the ninjafy name canonical; Otter returns no RFC 7592 handle, so a redirect
|
|
33083
|
+
// cannot be added later without a new client and a re-consent per connection.
|
|
33084
|
+
// OAUTH_OTTER_CLIENT_ID is set from its output (docs/runbooks/otter-dogfood.md). Otter advertises ONLY
|
|
33085
|
+
// read scopes, so this integration is read-only by construction: there is
|
|
33086
|
+
// no write scope to request even by mistake.
|
|
33087
|
+
definitionId: "otter",
|
|
33088
|
+
authorizeUrl: "https://otter.ai/oauth2/authorize",
|
|
33089
|
+
tokenUrl: "https://otter.ai/oauth/token",
|
|
33090
|
+
revokeUrl: "https://otter.ai/oauth/revoke_token",
|
|
33091
|
+
defaultScopes: ["profile:read", "conversations:read"],
|
|
33092
|
+
supportsRefresh: true,
|
|
33093
|
+
extraAuthorizeParams: {},
|
|
33094
|
+
clientAuthMethod: "body",
|
|
33095
|
+
pkce: "S256",
|
|
33096
|
+
publicClient: true,
|
|
33097
|
+
mcpUrl: "https://mcp.otter.ai/mcp",
|
|
33098
|
+
// Curated surface (matches the catalog seed's defined_scopes[].tools).
|
|
33099
|
+
// These are the server's three tools, all read-only. The names are
|
|
33100
|
+
// Otter's own, quoted from the tools' descriptions ("use `otter_search`
|
|
33101
|
+
// first", "call otter_get_user_info() first"), not Composio's wrapped
|
|
33102
|
+
// OTTER_AI_MCP_* slugs. An allowlisted name the server does not advertise
|
|
33103
|
+
// is logged and SKIPPED by the proxy, so confirm against the first live
|
|
33104
|
+
// tools/list after a real consent (runbook Step 3).
|
|
33105
|
+
toolAllowlist: ["otter_get_user_info", "otter_search", "otter_fetch"]
|
|
33106
|
+
},
|
|
33062
33107
|
"notion-cli": {
|
|
33063
33108
|
// Notion's public OAuth app. Tokens are workspace-scoped and long-lived —
|
|
33064
33109
|
// Notion does not issue refresh_tokens, so `supportsRefresh: false` and
|
|
@@ -33692,6 +33737,34 @@ var INTEGRATION_REGISTRY = [
|
|
|
33692
33737
|
docs_url: "https://help.kajabi.com/articles/api-integrations/connect-kajabi-to-claude-or-chatgpt",
|
|
33693
33738
|
beta: true
|
|
33694
33739
|
},
|
|
33740
|
+
{
|
|
33741
|
+
id: "otter",
|
|
33742
|
+
name: "Otter.ai",
|
|
33743
|
+
category: "knowledge",
|
|
33744
|
+
description: "Read Otter.ai meetings: search recordings by date, attendee or topic, and read summaries, action items and full speaker-attributed transcripts.",
|
|
33745
|
+
// CS-2029: same remote streamable-HTTP MCP + OAuth pattern as Granola/Kajabi.
|
|
33746
|
+
// Otter's server implements the MCP discovery chain (RFC 9728 protected-
|
|
33747
|
+
// resource metadata at mcp.otter.ai → RFC 8414 AS metadata at otter.ai →
|
|
33748
|
+
// RFC 7591 registration); auth is OAuth 2.0 authorization-code with PKCE
|
|
33749
|
+
// (S256) and a public client registered via one-time Dynamic Client
|
|
33750
|
+
// Registration (scripts/dcr-register.ts against https://otter.ai/oauth/register
|
|
33751
|
+
// → OAUTH_OTTER_CLIENT_ID, --scope 'profile:read conversations:read').
|
|
33752
|
+
// Consent is brokered by the webapp through the shared
|
|
33753
|
+
// /integrations/oauth/authorize → /callback path, and the access_token
|
|
33754
|
+
// reaches the host via the generic remote-oauth-proxy
|
|
33755
|
+
// (OAUTH_PROVIDERS.otter.mcpUrl). Read-only by construction: Otter
|
|
33756
|
+
// advertises no write scope.
|
|
33757
|
+
supported_auth_types: ["oauth2"],
|
|
33758
|
+
capabilities: [
|
|
33759
|
+
{ id: "otter:read", name: "Read Meetings & Transcripts", description: "Find recorded meetings by date, attendee, topic or title, and read their summaries, action items, outlines and full transcripts (otter_get_user_info, otter_search, otter_fetch)", access: "read" }
|
|
33760
|
+
],
|
|
33761
|
+
// Otter's own Help Center article for the MCP server (CodeRabbit, #6906). It
|
|
33762
|
+
// exists even though help.otter.ai answers 403 to non-browser clients; the
|
|
33763
|
+
// help-centre API confirmed it (title "Otter MCP Server"). The MCP endpoint
|
|
33764
|
+
// itself is a JSON-RPC URL, not a page (Obi Wan 8305072d5414).
|
|
33765
|
+
docs_url: "https://help.otter.ai/hc/en-us/articles/35287607569687-Otter-MCP-Server",
|
|
33766
|
+
beta: true
|
|
33767
|
+
},
|
|
33695
33768
|
{
|
|
33696
33769
|
id: "anchor-browser",
|
|
33697
33770
|
name: "Anchor Browser",
|
|
@@ -38455,14 +38528,37 @@ var FLAG_REGISTRY = [
|
|
|
38455
38528
|
},
|
|
38456
38529
|
{
|
|
38457
38530
|
key: "platform-maintenance-mode",
|
|
38458
|
-
description: 'Platform-wide maintenance mode (ENG-6506)
|
|
38459
|
-
flagType: "
|
|
38460
|
-
|
|
38461
|
-
//
|
|
38462
|
-
//
|
|
38463
|
-
//
|
|
38464
|
-
//
|
|
38465
|
-
|
|
38531
|
+
description: 'Platform-wide maintenance mode (ENG-6506; widened to modes by ENG-11436). off = normal operation (ships dark; channels behave exactly as today). tools-offline = every agent KEEPS conversing but every Augmented tool that WRITES to the control plane refuses with a reason, so nothing is created during a window whose database restore would discard it; reads are untouched. fleet-offline = the previous boolean-true behaviour AND the write gate: admitted human inbound gets a "we are offline for maintenance" notice instead of being dispatched, for whole-platform downtime such as DB upgrades. The modes are CUMULATIVE \u2014 fleet-offline gates writes too, because silencing channel inbound does not stop a /loop or a scheduled task filing work. Read host-side per inbound and per tool call from the flags cache (no MCP restart). (Proactive "back online" follow-up is the ENG-6508 fast-follow, not this flag.)',
|
|
38532
|
+
flagType: "enum",
|
|
38533
|
+
allowedValues: ["off", "tools-offline", "fleet-offline"],
|
|
38534
|
+
// Declared safe value is `off` (normal operation). Fail-safe direction: a
|
|
38535
|
+
// flag-DB read error or absent flag map must never silence the fleet OR take
|
|
38536
|
+
// its write tools away on its own — agents stay fully functional unless an
|
|
38537
|
+
// operator deliberately sets a mode from the admin Feature Flags page. No
|
|
38538
|
+
// envVar: net-new control, no pre-flags env gate to migrate.
|
|
38539
|
+
//
|
|
38540
|
+
// ENG-11436 widened this from a boolean rather than adding a second flag, so
|
|
38541
|
+
// there is ONE operator control whose modes are mutually exclusive by type
|
|
38542
|
+
// instead of two similar booleans with four combinations.
|
|
38543
|
+
//
|
|
38544
|
+
// The host resolver GRANDFATHERS a boolean-shaped cache value
|
|
38545
|
+
// (true -> fleet-offline, false -> off), and the DIRECTION it protects is
|
|
38546
|
+
// narrower than an earlier version of this comment claimed (Obi Wan
|
|
38547
|
+
// `e3f0d2c154b9` on #6903). It said "an older manager still writing a boolean
|
|
38548
|
+
// must not make a central flip a silent no-op". That situation cannot arise:
|
|
38549
|
+
// once this registry change deploys, normalizeFlagValue only ever yields one
|
|
38550
|
+
// of the three strings, feature-flags-host.ts writes the heartbeat map
|
|
38551
|
+
// verbatim, so no manager writes a boolean again. What grandfathering
|
|
38552
|
+
// actually covers is a NEW MCP reading a STALE PRE-DEPLOY cache that still
|
|
38553
|
+
// holds one.
|
|
38554
|
+
//
|
|
38555
|
+
// The direction that DOES happen is not handled and cannot be: an OLD MCP
|
|
38556
|
+
// reads the new string with resolveHostBooleanFlag, gets undefined, and keeps
|
|
38557
|
+
// dispatching. That is a real regression against ENG-6506 until the fleet
|
|
38558
|
+
// converges, and it is why the runbook makes convergence a deploy-order
|
|
38559
|
+
// PRECONDITION rather than offering a boolean fallback — there is none, since
|
|
38560
|
+
// an enum flag rejects `{value: true}` with a 400.
|
|
38561
|
+
defaultValue: "off",
|
|
38466
38562
|
// Flipping this takes the ENTIRE fleet offline to end users; a mis-flip is a
|
|
38467
38563
|
// platform-wide availability incident, so mutations require explicit
|
|
38468
38564
|
// confirmation (the admin flip-reach modal).
|
|
@@ -39786,6 +39882,37 @@ function resolveHostBooleanFlag(opts) {
|
|
|
39786
39882
|
if (cached2 !== void 0) return cached2;
|
|
39787
39883
|
return opts.defaultValue;
|
|
39788
39884
|
}
|
|
39885
|
+
var MAINTENANCE_FLAG_KEY = "platform-maintenance-mode";
|
|
39886
|
+
function parseMaintenanceMode(raw) {
|
|
39887
|
+
if (raw === void 0) return void 0;
|
|
39888
|
+
const v = raw.trim().toLowerCase();
|
|
39889
|
+
if (v === "") return void 0;
|
|
39890
|
+
if (v === "off" || v === "tools-offline" || v === "fleet-offline") return v;
|
|
39891
|
+
const b = envBoolean(raw);
|
|
39892
|
+
if (b === true) return "fleet-offline";
|
|
39893
|
+
if (b === false) return "off";
|
|
39894
|
+
return void 0;
|
|
39895
|
+
}
|
|
39896
|
+
function cachedMaintenanceMode(path) {
|
|
39897
|
+
try {
|
|
39898
|
+
if (!existsSync4(path)) return void 0;
|
|
39899
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
39900
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
39901
|
+
const flags = parsed.flags;
|
|
39902
|
+
if (!flags || typeof flags !== "object") return void 0;
|
|
39903
|
+
const value = flags[MAINTENANCE_FLAG_KEY];
|
|
39904
|
+
if (typeof value === "string") return parseMaintenanceMode(value);
|
|
39905
|
+
if (typeof value === "boolean") return value ? "fleet-offline" : "off";
|
|
39906
|
+
return void 0;
|
|
39907
|
+
} catch {
|
|
39908
|
+
return void 0;
|
|
39909
|
+
}
|
|
39910
|
+
}
|
|
39911
|
+
function resolveMaintenanceMode(opts) {
|
|
39912
|
+
const cached2 = cachedMaintenanceMode(opts?.cachePath ?? defaultFlagsCachePath());
|
|
39913
|
+
if (cached2 !== void 0) return cached2;
|
|
39914
|
+
return "off";
|
|
39915
|
+
}
|
|
39789
39916
|
|
|
39790
39917
|
// ../core/dist/channels/deflection-causes.js
|
|
39791
39918
|
var DEFLECTION_CAUSES = [
|
|
@@ -41033,17 +41160,157 @@ function createKanbanCardActiveClient(args) {
|
|
|
41033
41160
|
}
|
|
41034
41161
|
|
|
41035
41162
|
// src/maintenance-mode.ts
|
|
41036
|
-
var FLAG_KEY = "platform-maintenance-mode";
|
|
41037
41163
|
var MAINTENANCE_OFFLINE_MESSAGE = "The Ninjafy platform is offline for scheduled maintenance right now, so I can't pick this up \u2014 please try again shortly.";
|
|
41038
41164
|
function isMaintenanceModeActive(opts) {
|
|
41039
|
-
return
|
|
41040
|
-
key: FLAG_KEY,
|
|
41041
|
-
envVar: "",
|
|
41042
|
-
defaultValue: false,
|
|
41165
|
+
return resolveMaintenanceMode({
|
|
41043
41166
|
cachePath: opts?.cachePath,
|
|
41044
41167
|
env: opts?.env
|
|
41045
|
-
});
|
|
41168
|
+
}) === "fleet-offline";
|
|
41169
|
+
}
|
|
41170
|
+
|
|
41171
|
+
// src/maintenance-tools-offline.ts
|
|
41172
|
+
function isToolsOfflineActive(opts) {
|
|
41173
|
+
const mode = resolveMaintenanceMode({ cachePath: opts?.cachePath, env: opts?.env });
|
|
41174
|
+
return mode === "tools-offline" || mode === "fleet-offline";
|
|
41175
|
+
}
|
|
41176
|
+
var MAINTENANCE_GATED_TOOLS = /* @__PURE__ */ new Set([
|
|
41177
|
+
// Tasks.
|
|
41178
|
+
"kanban_add",
|
|
41179
|
+
"kanban_update",
|
|
41180
|
+
"kanban_move",
|
|
41181
|
+
"kanban_done",
|
|
41182
|
+
"kanban_reopen",
|
|
41183
|
+
"kanban_assign",
|
|
41184
|
+
"kanban_reassign",
|
|
41185
|
+
"kanban_recall",
|
|
41186
|
+
"kanban_respond",
|
|
41187
|
+
"kanban_progress",
|
|
41188
|
+
"kanban_log",
|
|
41189
|
+
"kanban_assign_project",
|
|
41190
|
+
"assign_kanban_to_human",
|
|
41191
|
+
// Canvases. `dashboards_request_refresh` is gated because it enqueues
|
|
41192
|
+
// server-side work, not because it renders anything itself.
|
|
41193
|
+
"dashboards_upsert",
|
|
41194
|
+
"dashboards_persist_widget",
|
|
41195
|
+
"dashboards_request_refresh",
|
|
41196
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
41197
|
+
// FORWARDED writes (ENG-11436 / CodeRabbit on #6903). These reach the agent
|
|
41198
|
+
// through `registerForwardedApiTools()` -> `server.registerTool`, from
|
|
41199
|
+
// `packages/api/src/routes/mcp-tools.ts`, with names resolved at RUNTIME. They
|
|
41200
|
+
// are gated only because index.ts now wraps that method too; before that, this
|
|
41201
|
+
// whole block would have been a list the gate could not see.
|
|
41202
|
+
//
|
|
41203
|
+
// CodeRabbit named five (workflow_update, workflow_run_{start,phase,end},
|
|
41204
|
+
// memory_delete). Enumerating the surface to check them found 37 of the 50
|
|
41205
|
+
// forwarded tools unclassified, so the five were a subset — these are all of
|
|
41206
|
+
// the writes among them. The test now classifies that surface exhaustively, so
|
|
41207
|
+
// the next addition fails CI rather than slipping through.
|
|
41208
|
+
"composio_upload_file",
|
|
41209
|
+
"memory_delete",
|
|
41210
|
+
"memory_save",
|
|
41211
|
+
"onboarding_advance",
|
|
41212
|
+
"onboarding_provision_skills",
|
|
41213
|
+
"onboarding_provision_tasks",
|
|
41214
|
+
"onboarding_record_manager_contact",
|
|
41215
|
+
"request_batch_approval",
|
|
41216
|
+
"request_integration_access",
|
|
41217
|
+
"skill_contribute_fragment",
|
|
41218
|
+
"skill_create",
|
|
41219
|
+
"skill_delete",
|
|
41220
|
+
"skill_improve",
|
|
41221
|
+
"skill_propose_revision",
|
|
41222
|
+
"skill_publish_to_marketplace",
|
|
41223
|
+
"skill_update",
|
|
41224
|
+
"submit_performance_review",
|
|
41225
|
+
"workflow_run_end",
|
|
41226
|
+
"workflow_run_phase",
|
|
41227
|
+
"workflow_run_start",
|
|
41228
|
+
"workflow_update",
|
|
41229
|
+
// CHANNEL-SERVER write (Obi Wan `b3537c1e8495` on #6903). Dispatched by NAME in
|
|
41230
|
+
// slack-channel.ts / telegram-channel.ts / direct-chat-channel.ts, which are
|
|
41231
|
+
// separate processes that never call server.tool — so this one is enforced by
|
|
41232
|
+
// `refuseIfToolsOffline` at each dispatch point, not by the registration
|
|
41233
|
+
// wrapper. It inserts a `pending_interaction` row, the same durable
|
|
41234
|
+
// human-actionable record that gates request_buttons / request_form.
|
|
41235
|
+
"channel_request_input",
|
|
41236
|
+
// `slack.ask_user` is the SAME write under a dotted name, and it was missed
|
|
41237
|
+
// because the test's enumerator could not see dotted names at all (Obi Wan
|
|
41238
|
+
// `122ac55ff278` / `d7245eddd77f` on #6903). handleAskUser calls
|
|
41239
|
+
// runtime.createPendingInteraction -> POST /host/pending-interactions ->
|
|
41240
|
+
// inserts `pending_interaction`. Ungated, an agent on a /loop files the row,
|
|
41241
|
+
// the restore discards it, and the agent blocks forever on a resolution that
|
|
41242
|
+
// no longer exists — the exact failure the entry above is gated for.
|
|
41243
|
+
//
|
|
41244
|
+
// Verified it is the ONLY dotted channel tool that writes: createPendingInteraction
|
|
41245
|
+
// is CALLED once in the three channel servers (slack-channel.ts:8053);
|
|
41246
|
+
// slack-block-kit-runtime.ts only re-exports it.
|
|
41247
|
+
"slack.ask_user",
|
|
41248
|
+
// Workflows. FORWARDED, not local — it reaches the agent through
|
|
41249
|
+
// `server.registerTool` in registerForwardedApiTools, so it is gated only
|
|
41250
|
+
// because that method is now wrapped too (CodeRabbit on #6903).
|
|
41251
|
+
"workflow_create",
|
|
41252
|
+
// Knowledge.
|
|
41253
|
+
"knowledge_add",
|
|
41254
|
+
"knowledge_update",
|
|
41255
|
+
"knowledge_delete",
|
|
41256
|
+
// Projects and project tagging.
|
|
41257
|
+
"projects_create",
|
|
41258
|
+
"projects_update",
|
|
41259
|
+
"artefacts_assign_project",
|
|
41260
|
+
// Routines.
|
|
41261
|
+
"routine_propose",
|
|
41262
|
+
"routine_revise",
|
|
41263
|
+
"routine_retire",
|
|
41264
|
+
"submit_routine_output",
|
|
41265
|
+
// Standing rules.
|
|
41266
|
+
"rule_add",
|
|
41267
|
+
"rule_remove",
|
|
41268
|
+
// Scheduled work.
|
|
41269
|
+
"schedule_create",
|
|
41270
|
+
"schedule_update",
|
|
41271
|
+
"schedule_delete",
|
|
41272
|
+
// Requests that file a durable record a human is expected to act on. Filing
|
|
41273
|
+
// one mid-window is worse than refusing it: the record is discarded by the
|
|
41274
|
+
// restore, so the agent would have promised a human a follow-up that no
|
|
41275
|
+
// longer exists anywhere.
|
|
41276
|
+
"request_approval",
|
|
41277
|
+
"request_buttons",
|
|
41278
|
+
"request_channel_access",
|
|
41279
|
+
"request_form",
|
|
41280
|
+
"request_reconnect",
|
|
41281
|
+
"request_feature",
|
|
41282
|
+
"request_feature_withdraw",
|
|
41283
|
+
// ADR-0025 approval tools, registered by `approval-tools.ts` against the same
|
|
41284
|
+
// server object, so the wrapper in index.ts covers them too.
|
|
41285
|
+
"cancel_approval",
|
|
41286
|
+
// Status / identity.
|
|
41287
|
+
"status_standup",
|
|
41288
|
+
"status_update",
|
|
41289
|
+
"update_identity",
|
|
41290
|
+
"update_timezone",
|
|
41291
|
+
// Fleet changes.
|
|
41292
|
+
"recruit_agent",
|
|
41293
|
+
"integration_improve"
|
|
41294
|
+
]);
|
|
41295
|
+
function toolsOfflineRefusalText(toolName) {
|
|
41296
|
+
return `Ninjafy is in a control-plane maintenance window, so \`${toolName}\` is unavailable right now. Nothing was created, changed or saved. Do NOT retry this call, and do not try a different Ninjafy tool to work around it \u2014 every tool that writes to the control plane is refusing for the same reason, and the window is short. Tell the person plainly that you cannot save this at the moment and ask them to bring it back afterwards; carry on with anything that does not need saving.`;
|
|
41297
|
+
}
|
|
41298
|
+
function refuseIfToolsOffline(name, opts) {
|
|
41299
|
+
if (!MAINTENANCE_GATED_TOOLS.has(name)) return null;
|
|
41300
|
+
const isActive = opts?.isActive ?? (() => isToolsOfflineActive());
|
|
41301
|
+
if (!isActive()) return null;
|
|
41302
|
+
return { content: [{ type: "text", text: toolsOfflineRefusalText(name) }] };
|
|
41303
|
+
}
|
|
41304
|
+
function shouldSendToolsOfflineNotice(opts) {
|
|
41305
|
+
const active = opts.isActive ?? (() => isToolsOfflineActive());
|
|
41306
|
+
if (!active()) return false;
|
|
41307
|
+
const now = opts.now ?? Date.now();
|
|
41308
|
+
const last = opts.cache.get(opts.key);
|
|
41309
|
+
if (last !== void 0 && now - last < opts.cooldownMs) return false;
|
|
41310
|
+
opts.cache.set(opts.key, now);
|
|
41311
|
+
return true;
|
|
41046
41312
|
}
|
|
41313
|
+
var TOOLS_OFFLINE_USER_NOTICE = "Heads up \u2014 Ninjafy is in a maintenance window right now, so I can't save anything: no tasks, canvases, notes or scheduled items. I can still talk things through, and you can ask me to set it up once maintenance is done.";
|
|
41047
41314
|
|
|
41048
41315
|
// src/account-enforcement-notice.ts
|
|
41049
41316
|
import { readFileSync as readFileSync11 } from "fs";
|
|
@@ -41460,6 +41727,49 @@ async function apiPost(path, body) {
|
|
|
41460
41727
|
}
|
|
41461
41728
|
return res;
|
|
41462
41729
|
}
|
|
41730
|
+
var DIRECT_CHAT_TOOLS_OFFLINE_CACHE = /* @__PURE__ */ new Map();
|
|
41731
|
+
var DIRECT_CHAT_TOOLS_OFFLINE_COOLDOWN_MS = (() => {
|
|
41732
|
+
const raw = process.env["DIRECT_CHAT_TOOLS_OFFLINE_COOLDOWN_SEC"];
|
|
41733
|
+
const n = raw ? Number(raw) : NaN;
|
|
41734
|
+
return Number.isFinite(n) && n >= 0 ? n * 1e3 : 60 * 60 * 1e3;
|
|
41735
|
+
})();
|
|
41736
|
+
async function maybeSendDirectChatToolsOfflineNotice(sessionId) {
|
|
41737
|
+
const key = `${sessionId}|tools_offline`;
|
|
41738
|
+
try {
|
|
41739
|
+
if (!shouldSendToolsOfflineNotice({
|
|
41740
|
+
cache: DIRECT_CHAT_TOOLS_OFFLINE_CACHE,
|
|
41741
|
+
key,
|
|
41742
|
+
cooldownMs: DIRECT_CHAT_TOOLS_OFFLINE_COOLDOWN_MS
|
|
41743
|
+
})) {
|
|
41744
|
+
return;
|
|
41745
|
+
}
|
|
41746
|
+
const res = await apiPost("/host/direct-chat/reply", {
|
|
41747
|
+
agent_id: AGT_AGENT_ID,
|
|
41748
|
+
session_id: sessionId,
|
|
41749
|
+
content: TOOLS_OFFLINE_USER_NOTICE,
|
|
41750
|
+
// An extra message alongside the agent's real reply, which has already
|
|
41751
|
+
// advanced the cursor.
|
|
41752
|
+
// cursor-advance-allow: deliberately EMPTY — nothing to advance, no count to read.
|
|
41753
|
+
message_ids: [],
|
|
41754
|
+
// kind:'notice' — the system speaking, not the agent, and owes no reply.
|
|
41755
|
+
kind: "notice"
|
|
41756
|
+
});
|
|
41757
|
+
const data = await res.json().catch(() => ({}));
|
|
41758
|
+
if (!res.ok || data.error) {
|
|
41759
|
+
DIRECT_CHAT_TOOLS_OFFLINE_CACHE.delete(key);
|
|
41760
|
+
process.stderr.write(
|
|
41761
|
+
`direct-chat-channel: tools-offline notice failed: ${data.error ?? res.status}
|
|
41762
|
+
`
|
|
41763
|
+
);
|
|
41764
|
+
}
|
|
41765
|
+
} catch (err) {
|
|
41766
|
+
DIRECT_CHAT_TOOLS_OFFLINE_CACHE.delete(key);
|
|
41767
|
+
process.stderr.write(
|
|
41768
|
+
`direct-chat-channel: tools-offline notice error: ${err.message}
|
|
41769
|
+
`
|
|
41770
|
+
);
|
|
41771
|
+
}
|
|
41772
|
+
}
|
|
41463
41773
|
async function maybeSendDirectChatAccountWarn(sessionId) {
|
|
41464
41774
|
try {
|
|
41465
41775
|
if (!shouldSendAccountWarn({
|
|
@@ -41657,6 +41967,10 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
41657
41967
|
]
|
|
41658
41968
|
}));
|
|
41659
41969
|
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
41970
|
+
{
|
|
41971
|
+
const offline = refuseIfToolsOffline(req.params.name);
|
|
41972
|
+
if (offline) return offline;
|
|
41973
|
+
}
|
|
41660
41974
|
const { name, arguments: args } = req.params;
|
|
41661
41975
|
const impersonationRefusal = shouldRefuseImpersonatedEgress(
|
|
41662
41976
|
name,
|
|
@@ -41758,6 +42072,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
41758
42072
|
}
|
|
41759
42073
|
}
|
|
41760
42074
|
await maybeSendDirectChatAccountWarn(session_id);
|
|
42075
|
+
void maybeSendDirectChatToolsOfflineNotice(session_id).catch(() => {
|
|
42076
|
+
});
|
|
41761
42077
|
return { content: [{ type: "text", text: "sent" }] };
|
|
41762
42078
|
} catch (err) {
|
|
41763
42079
|
return {
|
|
@@ -41799,6 +42115,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
41799
42115
|
clearDirectChatPendingMarkersForSession(DIRECT_CHAT_PENDING_INBOUND_DIR, session_id);
|
|
41800
42116
|
recordDirectChatDelivery(session_id, message_ids);
|
|
41801
42117
|
await maybeSendDirectChatAccountWarn(session_id);
|
|
42118
|
+
void maybeSendDirectChatToolsOfflineNotice(session_id).catch(() => {
|
|
42119
|
+
});
|
|
41802
42120
|
return { content: [{ type: "text", text: "sent" }] };
|
|
41803
42121
|
} catch (err) {
|
|
41804
42122
|
return {
|