@integrity-labs/agt-cli 0.28.773 → 0.28.775

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.
Files changed (24) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-75UAJFJJ.js → chunk-E33WDZNL.js} +3851 -3783
  3. package/dist/chunk-E33WDZNL.js.map +1 -0
  4. package/dist/{chunk-UVA7VXG4.js → chunk-FHOU7LRX.js} +9187 -9156
  5. package/dist/chunk-FHOU7LRX.js.map +1 -0
  6. package/dist/{chunk-UL2W5NY6.js → chunk-VCAWCBMT.js} +13 -13
  7. package/dist/{claude-pair-runtime-5ULX4UVC.js → claude-pair-runtime-QQBNL5WW.js} +2 -2
  8. package/dist/lib/manager-worker.js +13 -13
  9. package/dist/mcp/direct-chat-channel.js +68 -0
  10. package/dist/mcp/index.js +160 -13
  11. package/dist/mcp/origami.js +68 -0
  12. package/dist/mcp/slack-channel.js +68 -0
  13. package/dist/mcp/telegram-channel.js +68 -0
  14. package/dist/{persistent-session-E4AHQHRA.js → persistent-session-UEQ7A6QP.js} +3 -3
  15. package/dist/{responsiveness-probe-EN6IOLBG.js → responsiveness-probe-CKU7IQK6.js} +3 -3
  16. package/dist/{session-auth-dead-LNYMXEHY.js → session-auth-dead-ZP5YGGW3.js} +2 -2
  17. package/package.json +1 -1
  18. package/dist/chunk-75UAJFJJ.js.map +0 -1
  19. package/dist/chunk-UVA7VXG4.js.map +0 -1
  20. /package/dist/{chunk-UL2W5NY6.js.map → chunk-VCAWCBMT.js.map} +0 -0
  21. /package/dist/{claude-pair-runtime-5ULX4UVC.js.map → claude-pair-runtime-QQBNL5WW.js.map} +0 -0
  22. /package/dist/{persistent-session-E4AHQHRA.js.map → persistent-session-UEQ7A6QP.js.map} +0 -0
  23. /package/dist/{responsiveness-probe-EN6IOLBG.js.map → responsiveness-probe-CKU7IQK6.js.map} +0 -0
  24. /package/dist/{session-auth-dead-LNYMXEHY.js.map → session-auth-dead-ZP5YGGW3.js.map} +0 -0
@@ -18,7 +18,7 @@ import {
18
18
  rotateDailySession,
19
19
  sessionFileExists,
20
20
  todayLocalIso
21
- } from "./chunk-75UAJFJJ.js";
21
+ } from "./chunk-E33WDZNL.js";
22
22
  import {
23
23
  reapOrphanChannelMcps
24
24
  } from "./chunk-XWVM4KPK.js";
@@ -5050,11 +5050,12 @@ function getProjectDir(codeName) {
5050
5050
  }
5051
5051
 
5052
5052
  export {
5053
- agentTmpDirFor,
5054
- buildRemoteMcpEntry,
5055
- buildLiveHeaderRemoteMcpProxyEntry,
5056
- buildOAuthRemoteMcpProxyEntry,
5057
- buildNativeMcpEntry,
5053
+ agentRuntimeKey,
5054
+ hardBoundedExecFile,
5055
+ hardBoundedCapture,
5056
+ LATE_BOUND_VARS,
5057
+ expandTemplateVars,
5058
+ parseEnvIntegrations,
5058
5059
  shellQuote,
5059
5060
  CHANNEL_SECRET_ENV_KEYS,
5060
5061
  parseEnvFileEntries,
@@ -5062,13 +5063,11 @@ export {
5062
5063
  collectChannelSpawnSecrets,
5063
5064
  toOpencodeModel,
5064
5065
  mergeOpencodeConfigArtifact,
5066
+ buildRemoteMcpEntry,
5067
+ buildLiveHeaderRemoteMcpProxyEntry,
5068
+ buildOAuthRemoteMcpProxyEntry,
5069
+ buildNativeMcpEntry,
5065
5070
  readChannelServerEnv,
5066
- hardBoundedExecFile,
5067
- hardBoundedCapture,
5068
- LATE_BOUND_VARS,
5069
- expandTemplateVars,
5070
- parseEnvIntegrations,
5071
- agentRuntimeKey,
5072
5071
  log,
5073
5072
  sha256,
5074
5073
  hashFile,
@@ -5084,6 +5083,7 @@ export {
5084
5083
  getOpencodeSessionState,
5085
5084
  getOpencodeTurnHealth,
5086
5085
  getOpencodeActivityAgeSeconds,
5086
+ agentTmpDirFor,
5087
5087
  checkChannelInputs,
5088
5088
  takeWatchdogGiveUpCount,
5089
5089
  creditWatchdogGiveUpCount,
@@ -5141,4 +5141,4 @@ export {
5141
5141
  stopAllSessionsAndWait,
5142
5142
  getProjectDir
5143
5143
  };
5144
- //# sourceMappingURL=chunk-UL2W5NY6.js.map
5144
+ //# sourceMappingURL=chunk-VCAWCBMT.js.map
@@ -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-E4AHQHRA.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-UEQ7A6QP.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-5ULX4UVC.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-QQBNL5WW.js.map
@@ -58,7 +58,7 @@ import {
58
58
  safeWriteJsonAtomic,
59
59
  setConfigHash,
60
60
  tripClass
61
- } from "../chunk-UVA7VXG4.js";
61
+ } from "../chunk-FHOU7LRX.js";
62
62
  import {
63
63
  getProjectDir as getProjectDir2,
64
64
  getReadyTasks,
@@ -120,7 +120,7 @@ import {
120
120
  takeZombieDetection,
121
121
  toOpencodeModel,
122
122
  writeEgressAllowlist
123
- } from "../chunk-UL2W5NY6.js";
123
+ } from "../chunk-VCAWCBMT.js";
124
124
  import {
125
125
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
126
126
  AnchorSessionClient,
@@ -209,7 +209,7 @@ import {
209
209
  subagentActivityAgeSeconds,
210
210
  sumTranscriptUsageInWindow,
211
211
  transcriptActivityAgeSeconds
212
- } from "../chunk-75UAJFJJ.js";
212
+ } from "../chunk-E33WDZNL.js";
213
213
  import {
214
214
  reapOrphanChannelMcps
215
215
  } from "../chunk-XWVM4KPK.js";
@@ -12870,7 +12870,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
12870
12870
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
12871
12871
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
12872
12872
  async function channelInboundActivityAgeSecondsFor(codeName) {
12873
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-EN6IOLBG.js");
12873
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-CKU7IQK6.js");
12874
12874
  const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
12875
12875
  if (newest === null) return null;
12876
12876
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -13530,7 +13530,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
13530
13530
  var lastVersionCheckAt = 0;
13531
13531
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
13532
13532
  var lastResponsivenessProbeAt = 0;
13533
- var agtCliVersion = true ? "0.28.773" : "dev";
13533
+ var agtCliVersion = true ? "0.28.775" : "dev";
13534
13534
  function resolveBrewPath(execFileSync2) {
13535
13535
  try {
13536
13536
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -15074,7 +15074,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
15074
15074
  if (codeNames.length === 0) return;
15075
15075
  void (async () => {
15076
15076
  try {
15077
- const { collectDiagnostics } = await import("../persistent-session-E4AHQHRA.js");
15077
+ const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
15078
15078
  await api.post("/host/heartbeat", {
15079
15079
  host_id: hostId,
15080
15080
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -15215,7 +15215,7 @@ async function pollCycleInner() {
15215
15215
  }
15216
15216
  try {
15217
15217
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
15218
- const { collectDiagnostics } = await import("../persistent-session-E4AHQHRA.js");
15218
+ const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
15219
15219
  const diagCodeNames = [...agentState.persistentSessionAgents];
15220
15220
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
15221
15221
  let tailscaleHostname;
@@ -15384,7 +15384,7 @@ async function pollCycleInner() {
15384
15384
  collectPanelessActivityProbes,
15385
15385
  getResponsivenessIntervalMs,
15386
15386
  occupancyQualificationClassifications
15387
- } = await import("../responsiveness-probe-EN6IOLBG.js");
15387
+ } = await import("../responsiveness-probe-CKU7IQK6.js");
15388
15388
  const probeIntervalMs = getResponsivenessIntervalMs();
15389
15389
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
15390
15390
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -15494,7 +15494,7 @@ async function pollCycleInner() {
15494
15494
  collectResponsivenessProbes,
15495
15495
  livePendingInboundOldestAgeSeconds,
15496
15496
  parkPendingInbound
15497
- } = await import("../responsiveness-probe-EN6IOLBG.js");
15497
+ } = await import("../responsiveness-probe-CKU7IQK6.js");
15498
15498
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
15499
15499
  const wedgeNow = /* @__PURE__ */ new Date();
15500
15500
  const liveAgents = agentState.persistentSessionAgents;
@@ -15650,7 +15650,7 @@ async function pollCycleInner() {
15650
15650
  }
15651
15651
  try {
15652
15652
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
15653
- const { probeSessionAuth } = await import("../session-auth-dead-LNYMXEHY.js");
15653
+ const { probeSessionAuth } = await import("../session-auth-dead-ZP5YGGW3.js");
15654
15654
  const observations = [];
15655
15655
  const pendingCacheCommits = [];
15656
15656
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -19690,7 +19690,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
19690
19690
  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}`));
19691
19691
  void (async () => {
19692
19692
  try {
19693
- const { collectDiagnostics } = await import("../persistent-session-E4AHQHRA.js");
19693
+ const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
19694
19694
  await api.post("/host/heartbeat", {
19695
19695
  host_id: hostId,
19696
19696
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -19736,7 +19736,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
19736
19736
  }
19737
19737
  try {
19738
19738
  const hostId = await getHostId();
19739
- const { collectDiagnostics } = await import("../persistent-session-E4AHQHRA.js");
19739
+ const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
19740
19740
  await api.post("/host/heartbeat", {
19741
19741
  host_id: hostId,
19742
19742
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -20372,7 +20372,7 @@ async function processClaudePairSessions(agents) {
20372
20372
  killPairSession,
20373
20373
  pairTmuxSession,
20374
20374
  finalizeClaudePairOnboarding
20375
- } = await import("../claude-pair-runtime-5ULX4UVC.js");
20375
+ } = await import("../claude-pair-runtime-QQBNL5WW.js");
20376
20376
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
20377
20377
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
20378
20378
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -37139,6 +37139,74 @@ var FLAG_REGISTRY = [
37139
37139
  // dominant callers. This is precisely the deliberate, audited flip ADR-0022
37140
37140
  // §4 reserves confirmation for.
37141
37141
  sensitive: true
37142
+ },
37143
+ {
37144
+ key: "artefact-export-jobs-serve",
37145
+ description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
37146
+ flagType: "boolean",
37147
+ // ON is the no-op default here, which looks like the wrong direction until
37148
+ // you read it with the start lever: serving an empty table is
37149
+ // indistinguishable from not serving at all. Shipping it OFF would instead
37150
+ // mean that arming async later takes TWO flips, and that the second one is
37151
+ // the easy one to forget — leaving a fleet that creates jobs it refuses to
37152
+ // serve, i.e. every slow export failing while the flag page reads "async: on".
37153
+ defaultValue: true
37154
+ },
37155
+ {
37156
+ key: "artefact-export-jobs-start",
37157
+ description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
37158
+ flagType: "boolean",
37159
+ // Dark. The client half of this change publishes with the same merge, and
37160
+ // the fleet has not converged on it at merge time by construction — see the
37161
+ // runbook. Arming is an operator action taken against a measurement.
37162
+ defaultValue: false,
37163
+ // Arming this changes the RESPONSE SHAPE of a route whose callers include a
37164
+ // binary we do not control the version of. ADR-0022 section 4 reserves
37165
+ // confirmation for exactly this: a flip that a single unguarded write should
37166
+ // not be able to make.
37167
+ sensitive: true,
37168
+ // `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
37169
+ //
37170
+ // The field's docblock says it records the CLI whose build first shipped
37171
+ // this flag's HOST-SIDE READER, and this flag has none — it is evaluated
37172
+ // server-side in the export route, so every host "honors" a flip the instant
37173
+ // it is made. Read literally, `since` should therefore be undefined.
37174
+ //
37175
+ // Undefined is the dangerous answer. `classifyHostReach` returns `honors`
37176
+ // for every host when `since` is unset, so the flip-reach modal — the screen
37177
+ // an operator is looking at AT THE MOMENT OF THE FLIP — would report full
37178
+ // reach for the one flag whose entire purpose is "do not flip this until you
37179
+ // have measured the fleet". It would be telling the truth about the
37180
+ // mechanism (the flip does reach every host immediately) while giving
37181
+ // exactly the wrong answer to the question being asked (can those hosts
37182
+ // survive it). The dependency is real; it is on the CLI's ability to SURVIVE
37183
+ // the flag, not to READ it, and the modal cannot tell those apart.
37184
+ //
37185
+ // Pinned ABOVE every published release on purpose, the `agt-bin-path`
37186
+ // precedent: it classifies EVERY host as `stale`, so the modal reports no
37187
+ // reach rather than reach it has no basis for. A guessed number would be
37188
+ // worse than a placeholder, and the two directions are not symmetric —
37189
+ // too HIGH over-warns (annoying), too LOW promises reach to a build that
37190
+ // answers a job id with a TypeError inside file-write logic (a broken export
37191
+ // reported to the agent as "download failed").
37192
+ //
37193
+ // The exact version cannot be known here: the CLI auto-publishes ON MERGE of
37194
+ // this change, patch-bumping from whatever is current, so any pre-merge
37195
+ // value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
37196
+ // trust a version produced by re-running a failed publish job (ENG-9647: a
37197
+ // re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
37198
+ // string would claim a capability the bytes do not carry).
37199
+ //
37200
+ // AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
37201
+ // the flip-reach modal. Nothing in the evaluation or override-write path
37202
+ // consults it, so this changes what an operator is TOLD and does not stop a
37203
+ // flip on an under-`since` host. What holds the line is `defaultValue:
37204
+ // false` + `sensitive: true` + the runbook.
37205
+ //
37206
+ // REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
37207
+ // docs/runbooks/async-export-rollout.md, so the placeholder has a named
37208
+ // owner and a named moment rather than being a deferral nobody holds.
37209
+ since: "0.29.0"
37142
37210
  }
37143
37211
  ];
37144
37212
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
package/dist/mcp/index.js CHANGED
@@ -36790,6 +36790,60 @@ function jsonSchemaPropertyToZod(prop) {
36790
36790
  return external_exports.unknown();
36791
36791
  }
36792
36792
 
36793
+ // src/artefact-export-response.ts
36794
+ var DEFAULT_EXPORT_RETRY_MS = 1e4;
36795
+ function classifyExportResponse(body) {
36796
+ if (typeof body !== "object" || body === null) {
36797
+ throw new Error("export response was not an object");
36798
+ }
36799
+ const candidate = body;
36800
+ if (typeof candidate.job_id === "string" && candidate.job_id.length > 0) {
36801
+ const retry = typeof candidate.retry_after_ms === "number" && Number.isFinite(candidate.retry_after_ms) ? Math.max(0, candidate.retry_after_ms) : null;
36802
+ return {
36803
+ kind: "pending",
36804
+ jobId: candidate.job_id,
36805
+ status: typeof candidate.status === "string" ? candidate.status : null,
36806
+ retryAfterMs: retry
36807
+ };
36808
+ }
36809
+ if (typeof candidate.base64 !== "string" || candidate.base64.length === 0) {
36810
+ throw new Error("export response carried neither rendered bytes nor a job id");
36811
+ }
36812
+ return {
36813
+ kind: "bytes",
36814
+ body: {
36815
+ base64: candidate.base64,
36816
+ content_type: typeof candidate.content_type === "string" ? candidate.content_type : "",
36817
+ filename: typeof candidate.filename === "string" ? candidate.filename : "",
36818
+ format: typeof candidate.format === "string" ? candidate.format : "",
36819
+ version: typeof candidate.version === "number" ? candidate.version : 0
36820
+ }
36821
+ };
36822
+ }
36823
+ function pendingExportText(n) {
36824
+ const wait = Math.max(1, Math.round((n.retryAfterMs ?? DEFAULT_EXPORT_RETRY_MS) / 1e3));
36825
+ const fmt = n.format.toUpperCase();
36826
+ const retryLine = `Call artefacts_download again with the SAME arguments in about ${wait}s. The retry attaches to the render already in progress rather than starting a new one, and items that have already finished come back immediately.`;
36827
+ if (n.totalItems <= 1) {
36828
+ return [
36829
+ `The ${fmt} export is still rendering (job ${n.jobId}); no file was saved yet.`,
36830
+ retryLine,
36831
+ "Nothing has failed \u2014 this document simply takes longer than the export request waits."
36832
+ ].join("\n");
36833
+ }
36834
+ const header = n.saved.length > 0 ? `Rendered ${n.saved.length} of ${n.totalItems} items; item ${n.pendingPosition} is still rendering (job ${n.jobId}), so this is a PARTIAL export.` : `None of the ${n.totalItems} items finished in time; item ${n.pendingPosition} is still rendering (job ${n.jobId}).`;
36835
+ const lines = [header];
36836
+ if (n.saved.length > 0) {
36837
+ lines.push(`Saved so far (items 1-${n.saved.length}):`);
36838
+ lines.push(...n.saved.map((p2) => `- ${p2}`));
36839
+ lines.push(
36840
+ "These files are complete and usable, but they are NOT the whole document \u2014 do not send them as if they were."
36841
+ );
36842
+ }
36843
+ lines.push(retryLine);
36844
+ return lines.join("\n");
36845
+ }
36846
+
36793
36847
  // src/mutation-commit-state.ts
36794
36848
  var API_GATEWAY_INTEGRATION_TIMEOUT_MS = 29e3;
36795
36849
  var GATEWAY_TIMEOUT_MARGIN_MS = 3e3;
@@ -44134,6 +44188,74 @@ var FLAG_REGISTRY = [
44134
44188
  // dominant callers. This is precisely the deliberate, audited flip ADR-0022
44135
44189
  // §4 reserves confirmation for.
44136
44190
  sensitive: true
44191
+ },
44192
+ {
44193
+ key: "artefact-export-jobs-serve",
44194
+ description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
44195
+ flagType: "boolean",
44196
+ // ON is the no-op default here, which looks like the wrong direction until
44197
+ // you read it with the start lever: serving an empty table is
44198
+ // indistinguishable from not serving at all. Shipping it OFF would instead
44199
+ // mean that arming async later takes TWO flips, and that the second one is
44200
+ // the easy one to forget — leaving a fleet that creates jobs it refuses to
44201
+ // serve, i.e. every slow export failing while the flag page reads "async: on".
44202
+ defaultValue: true
44203
+ },
44204
+ {
44205
+ key: "artefact-export-jobs-start",
44206
+ description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
44207
+ flagType: "boolean",
44208
+ // Dark. The client half of this change publishes with the same merge, and
44209
+ // the fleet has not converged on it at merge time by construction — see the
44210
+ // runbook. Arming is an operator action taken against a measurement.
44211
+ defaultValue: false,
44212
+ // Arming this changes the RESPONSE SHAPE of a route whose callers include a
44213
+ // binary we do not control the version of. ADR-0022 section 4 reserves
44214
+ // confirmation for exactly this: a flip that a single unguarded write should
44215
+ // not be able to make.
44216
+ sensitive: true,
44217
+ // `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
44218
+ //
44219
+ // The field's docblock says it records the CLI whose build first shipped
44220
+ // this flag's HOST-SIDE READER, and this flag has none — it is evaluated
44221
+ // server-side in the export route, so every host "honors" a flip the instant
44222
+ // it is made. Read literally, `since` should therefore be undefined.
44223
+ //
44224
+ // Undefined is the dangerous answer. `classifyHostReach` returns `honors`
44225
+ // for every host when `since` is unset, so the flip-reach modal — the screen
44226
+ // an operator is looking at AT THE MOMENT OF THE FLIP — would report full
44227
+ // reach for the one flag whose entire purpose is "do not flip this until you
44228
+ // have measured the fleet". It would be telling the truth about the
44229
+ // mechanism (the flip does reach every host immediately) while giving
44230
+ // exactly the wrong answer to the question being asked (can those hosts
44231
+ // survive it). The dependency is real; it is on the CLI's ability to SURVIVE
44232
+ // the flag, not to READ it, and the modal cannot tell those apart.
44233
+ //
44234
+ // Pinned ABOVE every published release on purpose, the `agt-bin-path`
44235
+ // precedent: it classifies EVERY host as `stale`, so the modal reports no
44236
+ // reach rather than reach it has no basis for. A guessed number would be
44237
+ // worse than a placeholder, and the two directions are not symmetric —
44238
+ // too HIGH over-warns (annoying), too LOW promises reach to a build that
44239
+ // answers a job id with a TypeError inside file-write logic (a broken export
44240
+ // reported to the agent as "download failed").
44241
+ //
44242
+ // The exact version cannot be known here: the CLI auto-publishes ON MERGE of
44243
+ // this change, patch-bumping from whatever is current, so any pre-merge
44244
+ // value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
44245
+ // trust a version produced by re-running a failed publish job (ENG-9647: a
44246
+ // re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
44247
+ // string would claim a capability the bytes do not carry).
44248
+ //
44249
+ // AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
44250
+ // the flip-reach modal. Nothing in the evaluation or override-write path
44251
+ // consults it, so this changes what an operator is TOLD and does not stop a
44252
+ // flip on an under-`since` host. What holds the line is `defaultValue:
44253
+ // false` + `sensitive: true` + the runbook.
44254
+ //
44255
+ // REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
44256
+ // docs/runbooks/async-export-rollout.md, so the placeholder has a named
44257
+ // owner and a named moment rather than being a deferral nobody holds.
44258
+ since: "0.29.0"
44137
44259
  }
44138
44260
  ];
44139
44261
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -47451,21 +47573,36 @@ async function exportArtefactToFile(dir, artefactId, format, item) {
47451
47573
  // it, the gateway replaces our authored error with its opaque envelope and
47452
47574
  // we lose the ability to say whether the render landed.
47453
47575
  //
47454
- // NOT a readiness signal. Raising the budget does not make this caller able
47455
- // to consume ADR-0077's async response: the `data.base64` read just below
47456
- // has no branch for a `{ job_id }` body and would throw on one. So a fleet
47457
- // fully updated to this CLI is converged on the TIMEOUT and no more ready
47458
- // for the RESPONSE than before. The server's async path must stay dark for
47459
- // this route until job-response handling ships here (ENG-9729) — reading a
47460
- // published version number as evidence of a capability it does not carry is
47461
- // exactly the mistake this comment's first paragraph describes.
47576
+ // ENG-9729 closed the readiness gap this comment used to describe: the
47577
+ // response is now classified before anything reads `base64`, so a `{ job_id }`
47578
+ // body is handled rather than thrown on. The budget above is what makes the
47579
+ // server's ~20s grace window reachable from here at all.
47580
+ //
47581
+ // WHAT IS STILL NOT TRUE: a published CLI version is not convergence. This
47582
+ // build can read a job body; the fleet is a mixed-version population and
47583
+ // hosts update overnight, some never. The server's start lever
47584
+ // (`artefact-export-jobs-start`) must therefore stay off until
47585
+ // hosts.agt_version has been MEASURED — docs/runbooks/async-export-rollout.md
47586
+ // — because arming it against an older CLI does not degrade an export, it
47587
+ // throws a TypeError inside file-write logic, which is strictly worse than
47588
+ // the 503 + Retry-After that build already understands.
47462
47589
  FORWARD_TOOL_CALL_TIMEOUT_MS
47463
47590
  );
47591
+ const outcome = classifyExportResponse(data);
47592
+ if (outcome.kind === "pending") {
47593
+ return { kind: "pending", jobId: outcome.jobId, retryAfterMs: outcome.retryAfterMs };
47594
+ }
47464
47595
  const fallback = `${artefactId}${item !== void 0 ? `-item-${item + 1}` : ""}.${format}`;
47465
- const filePath = join4(dir, safeDownloadName(data.filename, fallback));
47466
- const buf = Buffer.from(data.base64, "base64");
47596
+ const filePath = join4(dir, safeDownloadName(outcome.body.filename, fallback));
47597
+ const buf = Buffer.from(outcome.body.base64, "base64");
47467
47598
  await writeFile(filePath, buf);
47468
- return { path: filePath, bytes: buf.length, contentType: data.content_type, version: data.version };
47599
+ return {
47600
+ kind: "file",
47601
+ path: filePath,
47602
+ bytes: buf.length,
47603
+ contentType: outcome.body.content_type,
47604
+ version: outcome.body.version
47605
+ };
47469
47606
  }
47470
47607
  async function resolveItemCountForPng(artefactId) {
47471
47608
  try {
@@ -47498,8 +47635,18 @@ async function downloadArtefactText(params) {
47498
47635
  const saved = [];
47499
47636
  let contentType = "";
47500
47637
  let version2 = 0;
47501
- for (const item of items) {
47502
- const r = await exportArtefactToFile(dir, params.artefact_id, params.format, item);
47638
+ for (let i = 0; i < items.length; i++) {
47639
+ const r = await exportArtefactToFile(dir, params.artefact_id, params.format, items[i]);
47640
+ if (r.kind === "pending") {
47641
+ return pendingExportText({
47642
+ format: params.format,
47643
+ saved: saved.map((f) => f.path),
47644
+ totalItems: items.length,
47645
+ pendingPosition: i + 1,
47646
+ jobId: r.jobId,
47647
+ retryAfterMs: r.retryAfterMs
47648
+ });
47649
+ }
47503
47650
  saved.push({ path: r.path, bytes: r.bytes });
47504
47651
  contentType = r.contentType;
47505
47652
  version2 = r.version;
@@ -42863,6 +42863,74 @@ var FLAG_REGISTRY = [
42863
42863
  // dominant callers. This is precisely the deliberate, audited flip ADR-0022
42864
42864
  // §4 reserves confirmation for.
42865
42865
  sensitive: true
42866
+ },
42867
+ {
42868
+ key: "artefact-export-jobs-serve",
42869
+ description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
42870
+ flagType: "boolean",
42871
+ // ON is the no-op default here, which looks like the wrong direction until
42872
+ // you read it with the start lever: serving an empty table is
42873
+ // indistinguishable from not serving at all. Shipping it OFF would instead
42874
+ // mean that arming async later takes TWO flips, and that the second one is
42875
+ // the easy one to forget — leaving a fleet that creates jobs it refuses to
42876
+ // serve, i.e. every slow export failing while the flag page reads "async: on".
42877
+ defaultValue: true
42878
+ },
42879
+ {
42880
+ key: "artefact-export-jobs-start",
42881
+ description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
42882
+ flagType: "boolean",
42883
+ // Dark. The client half of this change publishes with the same merge, and
42884
+ // the fleet has not converged on it at merge time by construction — see the
42885
+ // runbook. Arming is an operator action taken against a measurement.
42886
+ defaultValue: false,
42887
+ // Arming this changes the RESPONSE SHAPE of a route whose callers include a
42888
+ // binary we do not control the version of. ADR-0022 section 4 reserves
42889
+ // confirmation for exactly this: a flip that a single unguarded write should
42890
+ // not be able to make.
42891
+ sensitive: true,
42892
+ // `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
42893
+ //
42894
+ // The field's docblock says it records the CLI whose build first shipped
42895
+ // this flag's HOST-SIDE READER, and this flag has none — it is evaluated
42896
+ // server-side in the export route, so every host "honors" a flip the instant
42897
+ // it is made. Read literally, `since` should therefore be undefined.
42898
+ //
42899
+ // Undefined is the dangerous answer. `classifyHostReach` returns `honors`
42900
+ // for every host when `since` is unset, so the flip-reach modal — the screen
42901
+ // an operator is looking at AT THE MOMENT OF THE FLIP — would report full
42902
+ // reach for the one flag whose entire purpose is "do not flip this until you
42903
+ // have measured the fleet". It would be telling the truth about the
42904
+ // mechanism (the flip does reach every host immediately) while giving
42905
+ // exactly the wrong answer to the question being asked (can those hosts
42906
+ // survive it). The dependency is real; it is on the CLI's ability to SURVIVE
42907
+ // the flag, not to READ it, and the modal cannot tell those apart.
42908
+ //
42909
+ // Pinned ABOVE every published release on purpose, the `agt-bin-path`
42910
+ // precedent: it classifies EVERY host as `stale`, so the modal reports no
42911
+ // reach rather than reach it has no basis for. A guessed number would be
42912
+ // worse than a placeholder, and the two directions are not symmetric —
42913
+ // too HIGH over-warns (annoying), too LOW promises reach to a build that
42914
+ // answers a job id with a TypeError inside file-write logic (a broken export
42915
+ // reported to the agent as "download failed").
42916
+ //
42917
+ // The exact version cannot be known here: the CLI auto-publishes ON MERGE of
42918
+ // this change, patch-bumping from whatever is current, so any pre-merge
42919
+ // value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
42920
+ // trust a version produced by re-running a failed publish job (ENG-9647: a
42921
+ // re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
42922
+ // string would claim a capability the bytes do not carry).
42923
+ //
42924
+ // AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
42925
+ // the flip-reach modal. Nothing in the evaluation or override-write path
42926
+ // consults it, so this changes what an operator is TOLD and does not stop a
42927
+ // flip on an under-`since` host. What holds the line is `defaultValue:
42928
+ // false` + `sensitive: true` + the runbook.
42929
+ //
42930
+ // REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
42931
+ // docs/runbooks/async-export-rollout.md, so the placeholder has a named
42932
+ // owner and a named moment rather than being a deferral nobody holds.
42933
+ since: "0.29.0"
42866
42934
  }
42867
42935
  ];
42868
42936
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -37386,6 +37386,74 @@ var FLAG_REGISTRY = [
37386
37386
  // dominant callers. This is precisely the deliberate, audited flip ADR-0022
37387
37387
  // §4 reserves confirmation for.
37388
37388
  sensitive: true
37389
+ },
37390
+ {
37391
+ key: "artefact-export-jobs-serve",
37392
+ description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
37393
+ flagType: "boolean",
37394
+ // ON is the no-op default here, which looks like the wrong direction until
37395
+ // you read it with the start lever: serving an empty table is
37396
+ // indistinguishable from not serving at all. Shipping it OFF would instead
37397
+ // mean that arming async later takes TWO flips, and that the second one is
37398
+ // the easy one to forget — leaving a fleet that creates jobs it refuses to
37399
+ // serve, i.e. every slow export failing while the flag page reads "async: on".
37400
+ defaultValue: true
37401
+ },
37402
+ {
37403
+ key: "artefact-export-jobs-start",
37404
+ description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
37405
+ flagType: "boolean",
37406
+ // Dark. The client half of this change publishes with the same merge, and
37407
+ // the fleet has not converged on it at merge time by construction — see the
37408
+ // runbook. Arming is an operator action taken against a measurement.
37409
+ defaultValue: false,
37410
+ // Arming this changes the RESPONSE SHAPE of a route whose callers include a
37411
+ // binary we do not control the version of. ADR-0022 section 4 reserves
37412
+ // confirmation for exactly this: a flip that a single unguarded write should
37413
+ // not be able to make.
37414
+ sensitive: true,
37415
+ // `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
37416
+ //
37417
+ // The field's docblock says it records the CLI whose build first shipped
37418
+ // this flag's HOST-SIDE READER, and this flag has none — it is evaluated
37419
+ // server-side in the export route, so every host "honors" a flip the instant
37420
+ // it is made. Read literally, `since` should therefore be undefined.
37421
+ //
37422
+ // Undefined is the dangerous answer. `classifyHostReach` returns `honors`
37423
+ // for every host when `since` is unset, so the flip-reach modal — the screen
37424
+ // an operator is looking at AT THE MOMENT OF THE FLIP — would report full
37425
+ // reach for the one flag whose entire purpose is "do not flip this until you
37426
+ // have measured the fleet". It would be telling the truth about the
37427
+ // mechanism (the flip does reach every host immediately) while giving
37428
+ // exactly the wrong answer to the question being asked (can those hosts
37429
+ // survive it). The dependency is real; it is on the CLI's ability to SURVIVE
37430
+ // the flag, not to READ it, and the modal cannot tell those apart.
37431
+ //
37432
+ // Pinned ABOVE every published release on purpose, the `agt-bin-path`
37433
+ // precedent: it classifies EVERY host as `stale`, so the modal reports no
37434
+ // reach rather than reach it has no basis for. A guessed number would be
37435
+ // worse than a placeholder, and the two directions are not symmetric —
37436
+ // too HIGH over-warns (annoying), too LOW promises reach to a build that
37437
+ // answers a job id with a TypeError inside file-write logic (a broken export
37438
+ // reported to the agent as "download failed").
37439
+ //
37440
+ // The exact version cannot be known here: the CLI auto-publishes ON MERGE of
37441
+ // this change, patch-bumping from whatever is current, so any pre-merge
37442
+ // value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
37443
+ // trust a version produced by re-running a failed publish job (ENG-9647: a
37444
+ // re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
37445
+ // string would claim a capability the bytes do not carry).
37446
+ //
37447
+ // AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
37448
+ // the flip-reach modal. Nothing in the evaluation or override-write path
37449
+ // consults it, so this changes what an operator is TOLD and does not stop a
37450
+ // flip on an under-`since` host. What holds the line is `defaultValue:
37451
+ // false` + `sensitive: true` + the runbook.
37452
+ //
37453
+ // REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
37454
+ // docs/runbooks/async-export-rollout.md, so the placeholder has a named
37455
+ // owner and a named moment rather than being a deferral nobody holds.
37456
+ since: "0.29.0"
37389
37457
  }
37390
37458
  ];
37391
37459
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));