@integrity-labs/agt-cli 0.28.651 → 0.28.653

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-DVZESZDD.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-XXP4NGEP.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-U52EA7NZ.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-E6BFNZUV.js.map
@@ -55,7 +55,7 @@ import {
55
55
  safeWriteJsonAtomic,
56
56
  setConfigHash,
57
57
  tripClass
58
- } from "../chunk-EXMRWMXQ.js";
58
+ } from "../chunk-CXZEIJRN.js";
59
59
  import {
60
60
  getProjectDir as getProjectDir2,
61
61
  getReadyTasks,
@@ -111,7 +111,7 @@ import {
111
111
  takeZombieDetection,
112
112
  toOpencodeModel,
113
113
  writeEgressAllowlist
114
- } from "../chunk-EUXCRRZU.js";
114
+ } from "../chunk-E26EKDFP.js";
115
115
  import {
116
116
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
117
117
  AnchorSessionClient,
@@ -192,7 +192,7 @@ import {
192
192
  subagentActivityAgeSeconds,
193
193
  sumTranscriptUsageInWindow,
194
194
  transcriptActivityAgeSeconds
195
- } from "../chunk-V5MUX3GI.js";
195
+ } from "../chunk-HRMF6VUK.js";
196
196
  import {
197
197
  reapOrphanChannelMcps
198
198
  } from "../chunk-XWVM4KPK.js";
@@ -11609,7 +11609,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
11609
11609
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
11610
11610
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
11611
11611
  async function channelInboundActivityAgeSecondsFor(codeName) {
11612
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-Q7SVASQS.js");
11612
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-QSJ263ZW.js");
11613
11613
  const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
11614
11614
  if (newest === null) return null;
11615
11615
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -12252,7 +12252,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
12252
12252
  var lastVersionCheckAt = 0;
12253
12253
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
12254
12254
  var lastResponsivenessProbeAt = 0;
12255
- var agtCliVersion = true ? "0.28.651" : "dev";
12255
+ var agtCliVersion = true ? "0.28.653" : "dev";
12256
12256
  function resolveBrewPath(execFileSync3) {
12257
12257
  try {
12258
12258
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -13560,7 +13560,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
13560
13560
  if (codeNames.length === 0) return;
13561
13561
  void (async () => {
13562
13562
  try {
13563
- const { collectDiagnostics } = await import("../persistent-session-DVZESZDD.js");
13563
+ const { collectDiagnostics } = await import("../persistent-session-XXP4NGEP.js");
13564
13564
  await api.post("/host/heartbeat", {
13565
13565
  host_id: hostId,
13566
13566
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -13676,7 +13676,7 @@ async function pollCycleInner() {
13676
13676
  }
13677
13677
  try {
13678
13678
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
13679
- const { collectDiagnostics } = await import("../persistent-session-DVZESZDD.js");
13679
+ const { collectDiagnostics } = await import("../persistent-session-XXP4NGEP.js");
13680
13680
  const diagCodeNames = [...agentState.persistentSessionAgents];
13681
13681
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor) : void 0;
13682
13682
  let tailscaleHostname;
@@ -13799,7 +13799,7 @@ async function pollCycleInner() {
13799
13799
  collectPanelessActivityProbes,
13800
13800
  getResponsivenessIntervalMs,
13801
13801
  occupancyQualificationClassifications
13802
- } = await import("../responsiveness-probe-Q7SVASQS.js");
13802
+ } = await import("../responsiveness-probe-QSJ263ZW.js");
13803
13803
  const probeIntervalMs = getResponsivenessIntervalMs();
13804
13804
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
13805
13805
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -13909,7 +13909,7 @@ async function pollCycleInner() {
13909
13909
  collectResponsivenessProbes,
13910
13910
  livePendingInboundOldestAgeSeconds,
13911
13911
  parkPendingInbound
13912
- } = await import("../responsiveness-probe-Q7SVASQS.js");
13912
+ } = await import("../responsiveness-probe-QSJ263ZW.js");
13913
13913
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
13914
13914
  const wedgeNow = /* @__PURE__ */ new Date();
13915
13915
  const liveAgents = agentState.persistentSessionAgents;
@@ -14065,7 +14065,7 @@ async function pollCycleInner() {
14065
14065
  }
14066
14066
  try {
14067
14067
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
14068
- const { probeSessionAuth } = await import("../session-auth-dead-RV3Z64TM.js");
14068
+ const { probeSessionAuth } = await import("../session-auth-dead-HHZT5KIM.js");
14069
14069
  const observations = [];
14070
14070
  const pendingCacheCommits = [];
14071
14071
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -17698,7 +17698,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
17698
17698
  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}`));
17699
17699
  void (async () => {
17700
17700
  try {
17701
- const { collectDiagnostics } = await import("../persistent-session-DVZESZDD.js");
17701
+ const { collectDiagnostics } = await import("../persistent-session-XXP4NGEP.js");
17702
17702
  await api.post("/host/heartbeat", {
17703
17703
  host_id: hostId,
17704
17704
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -17749,7 +17749,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
17749
17749
  }
17750
17750
  try {
17751
17751
  const hostId = await getHostId();
17752
- const { collectDiagnostics } = await import("../persistent-session-DVZESZDD.js");
17752
+ const { collectDiagnostics } = await import("../persistent-session-XXP4NGEP.js");
17753
17753
  await api.post("/host/heartbeat", {
17754
17754
  host_id: hostId,
17755
17755
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -18346,7 +18346,7 @@ async function processClaudePairSessions(agents) {
18346
18346
  killPairSession,
18347
18347
  pairTmuxSession,
18348
18348
  finalizeClaudePairOnboarding
18349
- } = await import("../claude-pair-runtime-U52EA7NZ.js");
18349
+ } = await import("../claude-pair-runtime-E6BFNZUV.js");
18350
18350
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
18351
18351
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
18352
18352
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -33677,7 +33677,7 @@ var INTEGRATION_REGISTRY = [
33677
33677
  id: "firecrawl",
33678
33678
  name: "Firecrawl",
33679
33679
  category: "knowledge",
33680
- description: "Web data for agents: scrape, map, crawl and search any website for clean structured data, plus scheduled change-detection monitors whose updates arrive in the agent's direct-chat. Connect via the platform-managed account, or bring your own Firecrawl API key.",
33680
+ description: "Web data for agents: scrape, map, crawl and search any website for clean structured data, plus scheduled change-detection monitors whose updates arrive in the agent's direct-chat. Connect via the platform-managed account.",
33681
33681
  // ENG-7217: Firecrawl migrates from a customer-API-key stdio MCP
33682
33682
  // (`npx firecrawl-mcp`, FIRECRAWL_API_KEY) to a PREMIUM, platform-managed
33683
33683
  // integration on the Deck / ElevenLabs model (ADR-0031, epic ENG-6920):
@@ -33688,16 +33688,30 @@ var INTEGRATION_REGISTRY = [
33688
33688
  // is metered at the one control-plane chokepoint and gated on a per-org opt-in
33689
33689
  // + monthly USD cap.
33690
33690
  //
33691
- // ENG-8304 / ADR-0055 (Phase 1b — Firecrawl is the BYO exemplar): `api_key` is
33692
- // now also offered so a customer can bring their OWN Firecrawl key. A BYO
33691
+ // ENG-8304 / ADR-0055 (Phase 1b — Firecrawl is the BYO exemplar): `api_key`
33692
+ // was also offered so a customer could bring their OWN Firecrawl key. A BYO
33693
33693
  // install (`credential_source='byo'`) authenticates with the customer's key
33694
33694
  // (stored encrypted on the install) and is NOT metered — its upstream cost is
33695
- // on the customer's own Firecrawl bill. BYO is gated on the plan-tier
33696
- // `byo_credentials` entitlement; managed stays the platform-key, billed path.
33697
- // `api_key` is the BYO option; the connect UI offers it via the toolkit's
33698
- // `auth_types` seed (Firecrawl surfaces through the premium list, not the
33699
- // native `installable` picker that descriptor is for non-premium natives).
33700
- supported_auth_types: ["none", "api_key"],
33695
+ // on the customer's own Firecrawl bill.
33696
+ //
33697
+ // ENG-9235: `api_key` is PARKED, not abandoned. BYO is gated on the plan-tier
33698
+ // `byo_credentials` entitlement, which only `enterprise` has (the column is
33699
+ // NOT NULL DEFAULT false, and ENG-9143 explicitly set it false for Team). The
33700
+ // connect wizard never reads that flag, so offering `api_key` here sent every
33701
+ // Team/Business customer to firecrawl.dev for a key we would then reject with
33702
+ // a 403 `byo_not_entitled_by_plan` — after they had signed up for it.
33703
+ //
33704
+ // Removing it is also what removes that screen: the wizard's picker only
33705
+ // renders for 2+ selectable auth types (`selectableWizardAuthMethods`), so a
33706
+ // single `['none']` restores the friction-free managed install with no UI
33707
+ // change. Restore `api_key` only together with an entitlement-aware wizard —
33708
+ // see ENG-9236 for the proper fix.
33709
+ //
33710
+ // KEEP IN LOCKSTEP with the `firecrawl` row in
33711
+ // packages/supabase/seeds/toolkit-definitions.json: that seed carries its own
33712
+ // `auth_types` and is what `db:catalog:seed` upserts on every prod deploy, so
33713
+ // editing this array alone changes nothing a customer can see.
33714
+ supported_auth_types: ["none"],
33701
33715
  capabilities: [
33702
33716
  { id: "firecrawl:scrape", name: "Scrape Pages", description: "Fetch a single URL as clean markdown / structured data (firecrawl_scrape).", access: "write" },
33703
33717
  { id: "firecrawl:search", name: "Web Search", description: "Search the web and return ranked results, optionally scraped (firecrawl_search).", access: "write" },
@@ -36357,22 +36371,33 @@ var FLAG_REGISTRY = [
36357
36371
  },
36358
36372
  {
36359
36373
  key: "ninjafy-brand",
36360
- description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: EVERY consumer evaluates in the active-org cookie scope \u2014 sidebar.tsx and top-nav.tsx via usePublicBooleanFlag, and (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 so a host- or agent-scoped row is accepted by the table but never reached by the console. The THEME is resolved SERVER-side deliberately (ENG-8858): the client hook starts at its default and updates after an async GET /flags, so gating a palette on it paints Augmented and repaints \u2014 a full-page colour flash, not just the wordmark flash it already causes. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because getPublicBooleanFlagServer returns the default without a verified session and a pre-auth page has neither a session nor an active org \u2014 a PER-ORG flag structurally cannot brand one. Those surfaces follow only when the stage-wide default flips. Ships dark.',
36374
+ description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: there is now exactly ONE consumer \u2014 (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 evaluating in the active-org cookie scope, so a host- or agent-scoped row is accepted by the table but never reached by the console. It resolves the key ONCE per request and passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop. Resolution is SERVER-side deliberately: the client hook starts at its default and updates after an async GET /flags, so anything gated on it paints Augmented and then repaints. ENG-8858 moved the PALETTE for that reason (a full-page colour flash); ENG-9227 moved the WORDMARK, which had been the last surface still swapping after hydration. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because getPublicBooleanFlagServer returns the default without a verified session and a pre-auth page has neither a session nor an active org \u2014 a PER-ORG flag structurally cannot brand one. Those surfaces follow only when the stage-wide default flips. Ships dark.',
36361
36375
  flagType: "boolean",
36362
36376
  // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
36363
36377
  // fail-closed direction — if the flag DB is unreachable we must show the brand
36364
36378
  // that is currently live and contractually correct, never leak an unannounced
36365
36379
  // rebrand to every customer at once.
36366
36380
  defaultValue: false,
36367
- // Read on BOTH sides, so the key must be in the browser-exposed public map:
36368
- // sidebar.tsx and top-nav.tsx are "use client" and resolve it via
36369
- // usePublicBooleanFlag for the WORDMARK, while (dashboard)/layout.tsx resolves
36370
- // it server-side via getPublicBooleanFlagServer for the THEME (ENG-8858) a
36371
- // palette cannot wait for an async client fetch without flashing. Dropping
36372
- // `public` would break the two client readers, not just the server one.
36373
- // Unlike onboarding-msteams-channel above there is no wrong-org hazard — every
36374
- // reader evaluates inside the active-org cookie's scope, which is exactly the
36375
- // org whose brand should be shown.
36381
+ // ENG-9227: there are NO client readers of this key left. (dashboard)/
36382
+ // layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
36383
+ // passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
36384
+ // ENG-8858 did that for the THEME; ENG-9227 finished the job for the
36385
+ // WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
36386
+ // "augmented.team" to "ninjafy" after hydration.
36387
+ //
36388
+ // `public` MUST STAY ANYWAY, and this is the trap. It is tempting to read
36389
+ // "no client readers" as "no longer needs to be public" — but
36390
+ // getPublicBooleanFlagServer resolves against the very same `GET /flags`
36391
+ // endpoint the browser uses, and that route filters to listPublicFlagKeys()
36392
+ // (routes/flags.ts:72-75). A non-public key is simply absent from the map,
36393
+ // so the server helper would fall through to its default and return `false`
36394
+ // FOREVER — the brand quietly reverting to Augmented for every org, with no
36395
+ // error anywhere. `public` here means "readable through the public map",
36396
+ // not "read by a browser".
36397
+ //
36398
+ // Unlike onboarding-msteams-channel above there is no wrong-org hazard — the
36399
+ // single reader evaluates inside the active-org cookie's scope, which is
36400
+ // exactly the org whose brand should be shown.
36376
36401
  public: true
36377
36402
  },
36378
36403
  {
package/dist/mcp/index.js CHANGED
@@ -22397,10 +22397,32 @@ ${first.duplicate_hint}` : added;
22397
22397
  import { homedir as homedir3, tmpdir } from "os";
22398
22398
  import { basename, isAbsolute, join as join3, resolve as pathResolve, sep } from "path";
22399
22399
 
22400
- // src/skill-body-path.ts
22401
- var SKILL_BODY_PATH_TOOLS = /* @__PURE__ */ new Set(["skill_create", "skill_update"]);
22400
+ // src/host-file-path.ts
22402
22401
  var SKILL_BODY_PATH_MAX_BYTES = 512 * 1024;
22403
- function allowedSkillBodyRoots(agentDir, path) {
22402
+ var AGT_LIVE_CONTENT_PATH_MAX_BYTES = 2 * 1024 * 1024;
22403
+ var HOST_FILE_PATH_TOOLS = /* @__PURE__ */ new Map([
22404
+ [
22405
+ "skill_create",
22406
+ { pathArg: "body_path", targetArg: "body", maxBytes: SKILL_BODY_PATH_MAX_BYTES, label: "a skill body" }
22407
+ ],
22408
+ [
22409
+ "skill_update",
22410
+ { pathArg: "body_path", targetArg: "body", maxBytes: SKILL_BODY_PATH_MAX_BYTES, label: "a skill body" }
22411
+ ],
22412
+ [
22413
+ "agt_live_publish",
22414
+ {
22415
+ pathArg: "content_path",
22416
+ targetArg: "content",
22417
+ maxBytes: AGT_LIVE_CONTENT_PATH_MAX_BYTES,
22418
+ label: "a published page"
22419
+ }
22420
+ ]
22421
+ ]);
22422
+ function hostFilePathArg(toolName) {
22423
+ return HOST_FILE_PATH_TOOLS.get(toolName)?.pathArg;
22424
+ }
22425
+ function allowedHostFileRoots(agentDir, path) {
22404
22426
  return [path.join(agentDir, "project"), path.join(agentDir, "scratch")];
22405
22427
  }
22406
22428
  function assertNoSymlinkComponents(target, roots, path, fs) {
@@ -22425,48 +22447,49 @@ function isContained(candidate, root, sep2) {
22425
22447
  const rootWithSep = root.endsWith(sep2) ? root : root + sep2;
22426
22448
  return candidate.startsWith(rootWithSep);
22427
22449
  }
22428
- function resolveSkillBodyPath(toolName, args, opts) {
22429
- if (!SKILL_BODY_PATH_TOOLS.has(toolName)) return { ok: true, args };
22430
- const raw = args["body_path"];
22450
+ function resolveHostFilePath(toolName, args, opts) {
22451
+ const spec = HOST_FILE_PATH_TOOLS.get(toolName);
22452
+ if (!spec) return { ok: true, args };
22453
+ const raw = args[spec.pathArg];
22431
22454
  if (raw === void 0 || raw === null) return { ok: true, args };
22432
22455
  if (typeof raw !== "string" || raw.trim() === "") {
22433
- return { ok: false, error: "body_path must be a non-empty string." };
22456
+ return { ok: false, error: `${spec.pathArg} must be a non-empty string.` };
22434
22457
  }
22435
- if (typeof args["body"] === "string") {
22458
+ if (typeof args[spec.targetArg] === "string") {
22436
22459
  return {
22437
22460
  ok: false,
22438
- error: "Pass either body or body_path, not both \u2014 they are two ways to say the same thing and I will not guess which one you meant."
22461
+ error: `Pass either ${spec.targetArg} or ${spec.pathArg}, not both \u2014 they are two ways to say the same thing and I will not guess which one you meant.`
22439
22462
  };
22440
22463
  }
22441
22464
  const { agentDir, path, fs } = opts;
22442
- const maxBytes = opts.maxBytes ?? SKILL_BODY_PATH_MAX_BYTES;
22443
- const roots = allowedSkillBodyRoots(agentDir, path);
22465
+ const maxBytes = opts.maxBytes ?? spec.maxBytes;
22466
+ const roots = allowedHostFileRoots(agentDir, path);
22444
22467
  const absolute = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(path.join(agentDir, "project"), raw);
22445
22468
  if (!roots.some((r) => isContained(absolute, r, path.sep))) {
22446
22469
  return {
22447
22470
  ok: false,
22448
- error: `body_path must be inside your own project or scratch directory. Got '${raw}'. Allowed: ${roots.join(", ")}`
22471
+ error: `${spec.pathArg} must be inside your own project or scratch directory. Got '${raw}'. Allowed: ${roots.join(", ")}`
22449
22472
  };
22450
22473
  }
22451
22474
  let real;
22452
22475
  try {
22453
22476
  real = fs.realpathSync(absolute);
22454
22477
  } catch {
22455
- return { ok: false, error: `body_path does not exist or is unreadable: '${raw}'` };
22478
+ return { ok: false, error: `${spec.pathArg} does not exist or is unreadable: '${raw}'` };
22456
22479
  }
22457
22480
  if (!roots.some((r) => isContained(real, r, path.sep))) {
22458
22481
  return {
22459
22482
  ok: false,
22460
- error: `body_path resolves outside your own directories (symlink?). '${raw}' \u2192 '${real}'. Allowed: ${roots.join(", ")}`
22483
+ error: `${spec.pathArg} resolves outside your own directories (symlink?). '${raw}' \u2192 '${real}'. Allowed: ${roots.join(", ")}`
22461
22484
  };
22462
22485
  }
22463
22486
  const walkError = assertNoSymlinkComponents(real, roots, path, fs);
22464
- if (walkError) return { ok: false, error: `body_path: ${walkError}` };
22487
+ if (walkError) return { ok: false, error: `${spec.pathArg}: ${walkError}` };
22465
22488
  let vetted = null;
22466
22489
  try {
22467
22490
  vetted = fs.lstatSync(real);
22468
22491
  } catch {
22469
- return { ok: false, error: `body_path vanished before it could be read: '${raw}'` };
22492
+ return { ok: false, error: `${spec.pathArg} vanished before it could be read: '${raw}'` };
22470
22493
  }
22471
22494
  let fd;
22472
22495
  try {
@@ -22475,7 +22498,7 @@ function resolveSkillBodyPath(toolName, args, opts) {
22475
22498
  const msg = err.message;
22476
22499
  return {
22477
22500
  ok: false,
22478
- error: `body_path could not be opened (it may have been replaced mid-check): '${raw}' \u2014 ${msg}`
22501
+ error: `${spec.pathArg} could not be opened (it may have been replaced mid-check): '${raw}' \u2014 ${msg}`
22479
22502
  };
22480
22503
  }
22481
22504
  try {
@@ -22483,37 +22506,37 @@ function resolveSkillBodyPath(toolName, args, opts) {
22483
22506
  try {
22484
22507
  stat = fs.fstatSync(fd);
22485
22508
  } catch {
22486
- return { ok: false, error: `body_path could not be inspected: '${raw}'` };
22509
+ return { ok: false, error: `${spec.pathArg} could not be inspected: '${raw}'` };
22487
22510
  }
22488
22511
  if (!stat.isFile()) {
22489
- return { ok: false, error: `body_path is not a regular file: '${raw}'` };
22512
+ return { ok: false, error: `${spec.pathArg} is not a regular file: '${raw}'` };
22490
22513
  }
22491
22514
  if (stat.ino !== void 0 && vetted.ino !== void 0) {
22492
22515
  if (stat.ino !== vetted.ino || stat.dev !== vetted.dev) {
22493
22516
  return {
22494
22517
  ok: false,
22495
- error: `body_path changed identity between the check and the open (it was replaced): '${raw}'`
22518
+ error: `${spec.pathArg} changed identity between the check and the open (it was replaced): '${raw}'`
22496
22519
  };
22497
22520
  }
22498
22521
  }
22499
22522
  if (stat.size > maxBytes) {
22500
22523
  return {
22501
22524
  ok: false,
22502
- error: `body_path is ${stat.size} bytes, over the ${maxBytes}-byte limit for a skill body.`
22525
+ error: `${spec.pathArg} is ${stat.size} bytes, over the ${maxBytes}-byte limit for ${spec.label}.`
22503
22526
  };
22504
22527
  }
22505
22528
  let bytes;
22506
22529
  try {
22507
22530
  bytes = fs.readFdSync(fd, maxBytes);
22508
22531
  } catch (err) {
22509
- return { ok: false, error: `body_path could not be read: ${err.message}` };
22532
+ return { ok: false, error: `${spec.pathArg} could not be read: ${err.message}` };
22510
22533
  }
22511
22534
  if (bytes.byteLength > maxBytes) {
22512
- return { ok: false, error: `body_path grew past the ${maxBytes}-byte limit while being read.` };
22535
+ return { ok: false, error: `${spec.pathArg} grew past the ${maxBytes}-byte limit while being read.` };
22513
22536
  }
22514
- const body = fs.decodeUtf8(bytes);
22515
- const next = { ...args, body };
22516
- delete next["body_path"];
22537
+ const value = fs.decodeUtf8(bytes);
22538
+ const next = { ...args, [spec.targetArg]: value };
22539
+ delete next[spec.pathArg];
22517
22540
  return { ok: true, args: next };
22518
22541
  } finally {
22519
22542
  try {
@@ -41016,7 +41039,7 @@ var INTEGRATION_REGISTRY = [
41016
41039
  id: "firecrawl",
41017
41040
  name: "Firecrawl",
41018
41041
  category: "knowledge",
41019
- description: "Web data for agents: scrape, map, crawl and search any website for clean structured data, plus scheduled change-detection monitors whose updates arrive in the agent's direct-chat. Connect via the platform-managed account, or bring your own Firecrawl API key.",
41042
+ description: "Web data for agents: scrape, map, crawl and search any website for clean structured data, plus scheduled change-detection monitors whose updates arrive in the agent's direct-chat. Connect via the platform-managed account.",
41020
41043
  // ENG-7217: Firecrawl migrates from a customer-API-key stdio MCP
41021
41044
  // (`npx firecrawl-mcp`, FIRECRAWL_API_KEY) to a PREMIUM, platform-managed
41022
41045
  // integration on the Deck / ElevenLabs model (ADR-0031, epic ENG-6920):
@@ -41027,16 +41050,30 @@ var INTEGRATION_REGISTRY = [
41027
41050
  // is metered at the one control-plane chokepoint and gated on a per-org opt-in
41028
41051
  // + monthly USD cap.
41029
41052
  //
41030
- // ENG-8304 / ADR-0055 (Phase 1b — Firecrawl is the BYO exemplar): `api_key` is
41031
- // now also offered so a customer can bring their OWN Firecrawl key. A BYO
41053
+ // ENG-8304 / ADR-0055 (Phase 1b — Firecrawl is the BYO exemplar): `api_key`
41054
+ // was also offered so a customer could bring their OWN Firecrawl key. A BYO
41032
41055
  // install (`credential_source='byo'`) authenticates with the customer's key
41033
41056
  // (stored encrypted on the install) and is NOT metered — its upstream cost is
41034
- // on the customer's own Firecrawl bill. BYO is gated on the plan-tier
41035
- // `byo_credentials` entitlement; managed stays the platform-key, billed path.
41036
- // `api_key` is the BYO option; the connect UI offers it via the toolkit's
41037
- // `auth_types` seed (Firecrawl surfaces through the premium list, not the
41038
- // native `installable` picker that descriptor is for non-premium natives).
41039
- supported_auth_types: ["none", "api_key"],
41057
+ // on the customer's own Firecrawl bill.
41058
+ //
41059
+ // ENG-9235: `api_key` is PARKED, not abandoned. BYO is gated on the plan-tier
41060
+ // `byo_credentials` entitlement, which only `enterprise` has (the column is
41061
+ // NOT NULL DEFAULT false, and ENG-9143 explicitly set it false for Team). The
41062
+ // connect wizard never reads that flag, so offering `api_key` here sent every
41063
+ // Team/Business customer to firecrawl.dev for a key we would then reject with
41064
+ // a 403 `byo_not_entitled_by_plan` — after they had signed up for it.
41065
+ //
41066
+ // Removing it is also what removes that screen: the wizard's picker only
41067
+ // renders for 2+ selectable auth types (`selectableWizardAuthMethods`), so a
41068
+ // single `['none']` restores the friction-free managed install with no UI
41069
+ // change. Restore `api_key` only together with an entitlement-aware wizard —
41070
+ // see ENG-9236 for the proper fix.
41071
+ //
41072
+ // KEEP IN LOCKSTEP with the `firecrawl` row in
41073
+ // packages/supabase/seeds/toolkit-definitions.json: that seed carries its own
41074
+ // `auth_types` and is what `db:catalog:seed` upserts on every prod deploy, so
41075
+ // editing this array alone changes nothing a customer can see.
41076
+ supported_auth_types: ["none"],
41040
41077
  capabilities: [
41041
41078
  { id: "firecrawl:scrape", name: "Scrape Pages", description: "Fetch a single URL as clean markdown / structured data (firecrawl_scrape).", access: "write" },
41042
41079
  { id: "firecrawl:search", name: "Web Search", description: "Search the web and return ranked results, optionally scraped (firecrawl_search).", access: "write" },
@@ -43247,22 +43284,33 @@ var FLAG_REGISTRY = [
43247
43284
  },
43248
43285
  {
43249
43286
  key: "ninjafy-brand",
43250
- description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: EVERY consumer evaluates in the active-org cookie scope \u2014 sidebar.tsx and top-nav.tsx via usePublicBooleanFlag, and (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 so a host- or agent-scoped row is accepted by the table but never reached by the console. The THEME is resolved SERVER-side deliberately (ENG-8858): the client hook starts at its default and updates after an async GET /flags, so gating a palette on it paints Augmented and repaints \u2014 a full-page colour flash, not just the wordmark flash it already causes. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because getPublicBooleanFlagServer returns the default without a verified session and a pre-auth page has neither a session nor an active org \u2014 a PER-ORG flag structurally cannot brand one. Those surfaces follow only when the stage-wide default flips. Ships dark.',
43287
+ description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is the USER-FACING BRAND PRESENTATION \u2014 text AND VISUAL THEME. ENG-8858 widened this from text alone: the Ninjafy palette is gated here too, because a colour theme is not text. It stays ONE key rather than gaining a `ninjafy-theme` sibling, so a half-branded state \u2014 ninjafy wordmark over Augmented green \u2014 is unreachable and the rebrand keeps a single kill switch. It must never gate a code identifier, package name, env var or CLI name. EXISTING identifiers stay `Augmented`/`agt` per the CLAUDE.md naming contract, which protects them from premature renaming \u2014 the deep rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here. That contract does NOT require NEW code to carry the old brand: artefacts created specifically for the rebrand take Ninjafy naming (ENG-8078 decision 9; see the CLAUDE.md rebrand carve-out). Either way it is a naming rule, not a flag concern \u2014 this key gates presentation, never an identifier. Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot a subset while everyone else still sees Augmented. Overrides resolve at FOUR grains, most specific first \u2014 agent, team, host, then org-wide (evaluate.ts:79-81); organization_id is always required, so there is no global override. Org and team are the useful pilot grains for THIS flag: there is now exactly ONE consumer \u2014 (dashboard)/layout.tsx via getPublicBooleanFlagServer \u2014 evaluating in the active-org cookie scope, so a host- or agent-scoped row is accepted by the table but never reached by the console. It resolves the key ONCE per request and passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop. Resolution is SERVER-side deliberately: the client hook starts at its default and updates after an async GET /flags, so anything gated on it paints Augmented and then repaints. ENG-8858 moved the PALETTE for that reason (a full-page colour flash); ENG-9227 moved the WORDMARK, which had been the last surface still swapping after hydration. A consequence worth knowing: /login and the marketing routes stay on the Augmented palette whatever an org sets, because getPublicBooleanFlagServer returns the default without a verified session and a pre-auth page has neither a session nor an active org \u2014 a PER-ORG flag structurally cannot brand one. Those surfaces follow only when the stage-wide default flips. Ships dark.',
43251
43288
  flagType: "boolean",
43252
43289
  // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
43253
43290
  // fail-closed direction — if the flag DB is unreachable we must show the brand
43254
43291
  // that is currently live and contractually correct, never leak an unannounced
43255
43292
  // rebrand to every customer at once.
43256
43293
  defaultValue: false,
43257
- // Read on BOTH sides, so the key must be in the browser-exposed public map:
43258
- // sidebar.tsx and top-nav.tsx are "use client" and resolve it via
43259
- // usePublicBooleanFlag for the WORDMARK, while (dashboard)/layout.tsx resolves
43260
- // it server-side via getPublicBooleanFlagServer for the THEME (ENG-8858) a
43261
- // palette cannot wait for an async client fetch without flashing. Dropping
43262
- // `public` would break the two client readers, not just the server one.
43263
- // Unlike onboarding-msteams-channel above there is no wrong-org hazard — every
43264
- // reader evaluates inside the active-org cookie's scope, which is exactly the
43265
- // org whose brand should be shown.
43294
+ // ENG-9227: there are NO client readers of this key left. (dashboard)/
43295
+ // layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
43296
+ // passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
43297
+ // ENG-8858 did that for the THEME; ENG-9227 finished the job for the
43298
+ // WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
43299
+ // "augmented.team" to "ninjafy" after hydration.
43300
+ //
43301
+ // `public` MUST STAY ANYWAY, and this is the trap. It is tempting to read
43302
+ // "no client readers" as "no longer needs to be public" — but
43303
+ // getPublicBooleanFlagServer resolves against the very same `GET /flags`
43304
+ // endpoint the browser uses, and that route filters to listPublicFlagKeys()
43305
+ // (routes/flags.ts:72-75). A non-public key is simply absent from the map,
43306
+ // so the server helper would fall through to its default and return `false`
43307
+ // FOREVER — the brand quietly reverting to Augmented for every org, with no
43308
+ // error anywhere. `public` here means "readable through the public map",
43309
+ // not "read by a browser".
43310
+ //
43311
+ // Unlike onboarding-msteams-channel above there is no wrong-org hazard — the
43312
+ // single reader evaluates inside the active-org cookie's scope, which is
43313
+ // exactly the org whose brand should be shown.
43266
43314
  public: true
43267
43315
  },
43268
43316
  {
@@ -45913,11 +45961,12 @@ async function forwardToolCall(toolName, args) {
45913
45961
  throw new Error("Cannot forward tool call: AGT_AGENT_ID is not set");
45914
45962
  }
45915
45963
  let outgoing = args;
45916
- if (SKILL_BODY_PATH_TOOLS.has(toolName) && args["body_path"] !== void 0) {
45964
+ const pathArg = hostFilePathArg(toolName);
45965
+ if (pathArg && args[pathArg] !== void 0) {
45917
45966
  if (!AGT_AGENT_CODE_NAME) {
45918
- throw new Error("Cannot resolve body_path: AGT_AGENT_CODE_NAME is not set");
45967
+ throw new Error(`Cannot resolve ${pathArg}: AGT_AGENT_CODE_NAME is not set`);
45919
45968
  }
45920
- const resolved = resolveSkillBodyPath(toolName, args, {
45969
+ const resolved = resolveHostFilePath(toolName, args, {
45921
45970
  // ADR-0049 / ENG-7891: the per-agent host dir is agent_id-keyed and the
45922
45971
  // codename path is only a compatibility SYMLINK. That is not a style
45923
45972
  // point here, it is a correctness one: the containment check below
@@ -45943,7 +45992,7 @@ async function forwardToolCall(toolName, args) {
45943
45992
  // so an oversize file is detected rather than buffered whole.
45944
45993
  lstatSync,
45945
45994
  // Raw bytes, decoded only after the byte-length check — see
45946
- // readFdSync's docblock in skill-body-path.ts.
45995
+ // readFdSync's docblock in host-file-path.ts.
45947
45996
  readFdSync: (fd, maxBytes) => {
45948
45997
  const buf = Buffer.alloc(maxBytes + 1);
45949
45998
  let read = 0;