@integrity-labs/agt-cli 0.28.891 → 0.28.893

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 (27) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-BH5RFBUM.js → chunk-5464FJVN.js} +86 -22
  3. package/dist/chunk-5464FJVN.js.map +1 -0
  4. package/dist/{chunk-2IVLMJXQ.js → chunk-5YD7HEY7.js} +4 -4
  5. package/dist/{chunk-SXYTFXPO.js → chunk-RAEBKUW2.js} +2 -2
  6. package/dist/{claude-pair-runtime-TCDKO2CZ.js → claude-pair-runtime-3MK5MUPN.js} +2 -2
  7. package/dist/lib/manager-worker.js +14 -39
  8. package/dist/lib/manager-worker.js.map +1 -1
  9. package/dist/mcp/augmented-admin.js +22 -6
  10. package/dist/mcp/direct-chat-channel.js +85 -21
  11. package/dist/mcp/index.js +85 -21
  12. package/dist/mcp/origami.js +85 -21
  13. package/dist/mcp/slack-channel.js +85 -21
  14. package/dist/mcp/telegram-channel.js +85 -21
  15. package/dist/{persistent-session-ZDZSEODJ.js → persistent-session-5HSYQBF7.js} +3 -3
  16. package/dist/{responsiveness-probe-7CY5VZMG.js → responsiveness-probe-ZSB3DLLI.js} +3 -3
  17. package/dist/{session-auth-dead-65TG2DLW.js → session-auth-dead-J7SQAYRR.js} +2 -2
  18. package/package.json +2 -2
  19. package/dist/assets/review/SKILL.md +0 -223
  20. package/dist/assets/review/SWEEP.md +0 -169
  21. package/dist/chunk-BH5RFBUM.js.map +0 -1
  22. /package/dist/{chunk-2IVLMJXQ.js.map → chunk-5YD7HEY7.js.map} +0 -0
  23. /package/dist/{chunk-SXYTFXPO.js.map → chunk-RAEBKUW2.js.map} +0 -0
  24. /package/dist/{claude-pair-runtime-TCDKO2CZ.js.map → claude-pair-runtime-3MK5MUPN.js.map} +0 -0
  25. /package/dist/{persistent-session-ZDZSEODJ.js.map → persistent-session-5HSYQBF7.js.map} +0 -0
  26. /package/dist/{responsiveness-probe-7CY5VZMG.js.map → responsiveness-probe-ZSB3DLLI.js.map} +0 -0
  27. /package/dist/{session-auth-dead-65TG2DLW.js.map → session-auth-dead-J7SQAYRR.js.map} +0 -0
@@ -21478,14 +21478,30 @@ var resumeAgentSchema = strictObject({
21478
21478
  });
21479
21479
  var listAlertsSchema = strictObject({
21480
21480
  severity: external_exports.string().max(16).optional().describe("Filter by severity (critical | warning | info)."),
21481
- // ENG-8566: without this, an old alert of a specific kind is unreachable —
21482
- // the listing is newest-first and capped at 100, and the open board exceeds
21483
- // that. Twelve slack_manifest_drift warnings sat unfindable for 48 days.
21481
+ // ENG-8566: without this, an alert of a specific kind could be unreachable —
21482
+ // the listing is capped at 100 and the open board exceeds that. Twelve
21483
+ // slack_manifest_drift warnings sat unfindable for 48 days.
21484
+ //
21485
+ // ENG-10379 corrected the ordering claim this comment used to carry ("the
21486
+ // listing is newest-first"). It is severity-first, then OLDEST-first within a
21487
+ // severity, so the rows a truncated page drops are the NEWEST of the lowest
21488
+ // severity present. `cursor` below is now the general escape hatch; `kind`
21489
+ // remains the narrow one for when you already know what you are hunting.
21484
21490
  kind: external_exports.string().max(64).optional().describe(
21485
- "Filter by alert kind, exact match (e.g. slack_manifest_drift, host_down, integration_down). Use this to reach older alerts of one kind: the list is newest-first and capped at 100, so without a kind filter an old alert below that cutoff cannot be retrieved at all."
21491
+ "Filter by alert kind, exact match (e.g. slack_manifest_drift, host_down, integration_down). ENG-10379: ordering is severity first (critical, then warning, then info) and OLDEST-first within each severity, so the rows a truncated page drops are the NEWEST of the lowest severity present - not the oldest. Page with `cursor` to reach them; `kind` narrows the set when you already know what you are looking for."
21486
21492
  ),
21487
21493
  open: external_exports.boolean().optional().describe("When true, only currently-open (unclosed) alerts."),
21488
- limit: limitSchema
21494
+ limit: limitSchema,
21495
+ // ENG-10379: the API has accepted a keyset `cursor` since ENG-9295 and returns
21496
+ // `next_cursor`, but this schema never exposed one - and these schemas are
21497
+ // STRICT, so a caller passing `cursor` was REJECTED rather than ignored. The
21498
+ // paging existed and could not be driven from here, which is what left rows
21499
+ // past the 100-row cap unreachable: on a board over the cap those are the
21500
+ // NEWEST alerts of the lowest severity present, and one of them was a 25-hour
21501
+ // channel outage nobody could see.
21502
+ cursor: external_exports.string().min(1).max(512).optional().describe(
21503
+ "Keyset cursor from a previous response's `next_cursor`. Pass it to fetch the NEXT page, and repeat until `next_cursor` is null to walk the whole board. Opaque: it encodes (severity_rank, opened_at, id) as one string, and a malformed cursor is refused rather than silently serving page one again."
21504
+ )
21489
21505
  });
21490
21506
  var requestAccessSchema = strictObject({
21491
21507
  target_org_id: external_exports.string().min(1).max(64).describe("UUID of the customer organization to open scoped read access to."),
@@ -21693,7 +21709,7 @@ server.registerTool(
21693
21709
  );
21694
21710
  server.registerTool(
21695
21711
  "debug_list_alerts",
21696
- { description: "List recent platform alerts across authorized orgs (host-down, agent-stale, probe-timeout, auth-failed), including NULL-team host alerts. Returns a projection per alert (kind, severity, message, source, open/closed state), plus { count, total, truncated } \u2014 `total` is the matches before the 100-row cap, so `truncated: true` means you are NOT seeing everything. Filter with { kind, severity, open, limit }. **ENG-10030 \u2014 two fields that change how you read `message`.** `dedupe_key` is the alert's identity (`<kind>:<subject id>`), so it tells you which ROW an alert is about, not just which agent. `updated_at` tells you whether `message` is CURRENT: `open_alert` overwrites `message` on every re-raise but never touches `opened_at`, so a message is a snapshot unless the monitor re-raised that `(kind, dedupe_key)` since the alert opened \u2014 and it reads like live output either way. ONE DIRECTION IS PROOF, THE OTHER IS A HINT: `updated_at === opened_at` is CONCLUSIVE (never written since insert, so never re-raised, so `message` is frozen at `opened_at` \u2014 check this before proposing a fix for what a message describes, because the code may have moved since); `updated_at > opened_at` is SUGGESTIVE ONLY, because the `alerts_set_updated_at` trigger fires on EVERY write \u2014 an acknowledge, a snooze, a close, or a backfill migration. Compare against `acknowledged_at` / `snoozed_until` / `closed_at`: if `updated_at` matches one of them, that write was the human action, not a re-raise. Matching NONE of them is evidence of a re-raise but not proof, because a migration that touches the row leaves no timestamp of its own. Reading it as a biconditional is expensive: on one measured board 63 of 98 open alerts were acknowledged, so ~64% would falsely report current text. Two reads an hour apart remain the only unambiguous positive test. **Use `kind` to find older alerts**: results are newest-first and hard-capped at 100, so on a busy board an alert below that cutoff is unreachable without it (ENG-8566: 12 slack_manifest_drift warnings sat unfindable for 48 days for exactly this reason).", inputSchema: listAlertsSchema },
21712
+ { description: "List recent platform alerts across authorized orgs (host-down, agent-stale, probe-timeout, auth-failed), including NULL-team host alerts. Returns a projection per alert (kind, severity, message, source, open/closed state), plus { count, total, truncated } \u2014 `total` is the matches before the 100-row cap, so `truncated: true` means you are NOT seeing everything. Filter with { kind, severity, open, limit }. **ENG-10030 \u2014 two fields that change how you read `message`.** `dedupe_key` is the alert's identity (`<kind>:<subject id>`), so it tells you which ROW an alert is about, not just which agent. `updated_at` tells you whether `message` is CURRENT: `open_alert` overwrites `message` on every re-raise but never touches `opened_at`, so a message is a snapshot unless the monitor re-raised that `(kind, dedupe_key)` since the alert opened \u2014 and it reads like live output either way. ONE DIRECTION IS PROOF, THE OTHER IS A HINT: `updated_at === opened_at` is CONCLUSIVE (never written since insert, so never re-raised, so `message` is frozen at `opened_at` \u2014 check this before proposing a fix for what a message describes, because the code may have moved since); `updated_at > opened_at` is SUGGESTIVE ONLY, because the `alerts_set_updated_at` trigger fires on EVERY write \u2014 an acknowledge, a snooze, a close, or a backfill migration. Compare against `acknowledged_at` / `snoozed_until` / `closed_at`: if `updated_at` matches one of them, that write was the human action, not a re-raise. Matching NONE of them is evidence of a re-raise but not proof, because a migration that touches the row leaves no timestamp of its own. Reading it as a biconditional is expensive: on one measured board 63 of 98 open alerts were acknowledged, so ~64% would falsely report current text. Two reads an hour apart remain the only unambiguous positive test. **ORDERING AND PAGING - ENG-10379, and the previous text here was wrong.** Results are ordered by SEVERITY first (critical, then warning, then info) and OLDEST-FIRST within each severity. They are not ordered newest first. So on a board past the 100-row cap, the rows dropped are the NEWEST alerts of the lowest severity present - which is how a sweep that reads one page missed a live 25-hour channel outage. `truncated: true` still means exactly what it always did (more matches exist than this page carries); to REACH them, pass `cursor` set to this response's `next_cursor` and repeat until `next_cursor` comes back null, which walks the whole board in order. `kind` still narrows the set when you already know what you are looking for (ENG-8566: 12 slack_manifest_drift warnings sat unfindable for 48 days).", inputSchema: listAlertsSchema },
21697
21713
  async (args) => {
21698
21714
  try {
21699
21715
  const result = await client.listAlerts(args);
@@ -34867,6 +34867,12 @@ function clampDirectChatHistoryLimit(raw) {
34867
34867
  return floored;
34868
34868
  }
34869
34869
 
34870
+ // ../core/dist/direct-chat/agent-history-notices.js
34871
+ var AGENT_HISTORY_TURNS_ONLY_OR_FILTER = [
34872
+ "payload->>kind.is.null",
34873
+ `payload->>kind.in.("",${[...USER_FACING_NOTICE_KINDS].sort().join(",")})`
34874
+ ].join(",");
34875
+
34870
34876
  // ../core/dist/onboarding/state-machine.js
34871
34877
  var AREA_ORDER = [
34872
34878
  "framing",
@@ -37520,28 +37526,72 @@ var FLAG_REGISTRY = [
37520
37526
  },
37521
37527
  {
37522
37528
  key: "app-top-nav-shell",
37523
- description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). Ships dark.",
37529
+ description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). NO LONGER DARK: ENG-9831 cutover step 1 flipped the default to true, so an org with no override now gets the top-nav shell. The key survives only until cutover step 2 deletes it.",
37524
37530
  flagType: "boolean",
37525
- // Declared safe value is `false` = the current left sidebar. Fail-closed to the
37526
- // shell that ships today if the flag DB is unreachable.
37527
- defaultValue: false,
37528
- // Read CLIENT-SIDE by the dashboard layout + sidebar ("use client") via
37529
- // usePublicBooleanFlag, so the key must be in the browser-exposed public map.
37530
- // Additive layout gate not sensitive.
37531
+ // ENG-9831 (cutover step 1): flipped `false` -> `true`. The comment this
37532
+ // replaces called `false` the fail-closed direction — "the shell that ships
37533
+ // today". That was true while the sidebar was the shell that shipped; the
37534
+ // top nav is now the one being cut over to, so `false` points at the layout
37535
+ // being retired. The old rationale is recorded rather than deleted because
37536
+ // it was correct under a premise that has expired.
37537
+ //
37538
+ // NOTE the same split as ninjafy-brand below: this value is consulted by the
37539
+ // API evaluator when building the `GET /flags` map for an org with no
37540
+ // override. It is NOT the fallback the webapp uses on an outage — that is
37541
+ // the argument passed at the call site. Four call sites carry `true` to
37542
+ // match: (dashboard)/layout.tsx and the three legacy redirect hubs
37543
+ // (settings, team, organization/settings). Flipping the registry alone
37544
+ // leaves the cutover half-applied.
37545
+ defaultValue: true,
37546
+ // ENG-9831: NO LONGER read client-side. It was — via usePublicBooleanFlag in
37547
+ // sidebar.tsx (twice) and top-nav.tsx — and that is precisely what this
37548
+ // slice removed: a client hook starts at its default and corrects after an
37549
+ // async GET /flags, so the console painted the sidebar and then replaced the
37550
+ // WHOLE dashboard chrome after hydration. The ENG-8858/ENG-9227 flash at
37551
+ // layout scale. It is now resolved once server-side in (dashboard)/
37552
+ // layout.tsx and passed to both shells as a required prop.
37553
+ //
37554
+ // `public` MUST STAY ANYWAY — the same trap documented on ninjafy-brand.
37555
+ // getPublicBooleanFlagServer resolves against the very same public-filtered
37556
+ // `GET /flags` the browser uses (routes/flags.ts filters to
37557
+ // listPublicFlagKeys()). Drop `public` and the key is simply absent from the
37558
+ // map, the server helper falls through to its default forever, and every org
37559
+ // silently gets whatever that default happens to be with no error anywhere.
37560
+ // `public` means "readable through the public map", not "read by a browser".
37531
37561
  public: true
37532
37562
  },
37533
37563
  {
37534
37564
  key: "ninjafy-brand",
37535
- 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.',
37565
+ 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 fetchPublicFlags returns null 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. ENG-9830 CORRECTS what used to be written here ("those surfaces follow only when the stage-wide default flips"): they do not follow it at all. Two reasons. The value returned on a null fetch is the CALL SITE\u2019s passed argument, not this registry default; and six such pages (/login, /reset-password, /auth/confirm, the pre-redeem member-signin gate and the two public Slack-install terminal pages) do not mount <BrandStamp/> AT ALL \u2014 deliberately, enumerated with per-page reasons in eng-9266-brand-stamp-coverage.test.ts. They become branded when the flag is REMOVED at cutover step 2 and the palette moves into bare :root, with no read left to gate it. NO LONGER DARK: ENG-9830 cutover step 1 flipped the default to true, so an org with no override now sees Ninjafy. The key survives only until cutover step 2 deletes it.',
37536
37566
  flagType: "boolean",
37537
- // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
37538
- // fail-closed direction if the flag DB is unreachable we must show the brand
37539
- // that is currently live and contractually correct, never leak an unannounced
37540
- // rebrand to every customer at once.
37541
- defaultValue: false,
37567
+ // ENG-9830 (cutover step 1): flipped `false` -> `true`. The comment this
37568
+ // replaces argued `false` was the fail-closed direction "never leak an
37569
+ // UNANNOUNCED rebrand to every customer at once". That was correct for as
37570
+ // long as the rebrand was unannounced. It is not any more: Ninjafy is the
37571
+ // live, announced brand (ENG-9613 reconciled the console onto the master
37572
+ // assets while the flag was already ON in production), so `false` now points
37573
+ // at the brand we no longer ship. A flags outage that reverted a customer to
37574
+ // Augmented would be the incident, not the safe harbour.
37575
+ //
37576
+ // The old rationale is recorded rather than deleted because it is a correct
37577
+ // argument whose PREMISE expired — reading the change without it looks like
37578
+ // someone overriding a deliberate fail-closed default.
37579
+ //
37580
+ // NOTE this value is only consulted by the API evaluator when building the
37581
+ // `GET /flags` map for an org with no override. It is NOT the fallback the
37582
+ // webapp uses on an outage: `getPublicBooleanFlagServer(key, defaultValue)`
37583
+ // returns its own PASSED argument when the fetch fails, so the call sites in
37584
+ // (dashboard)/layout.tsx and components/brand-stamp.tsx carry `true` too.
37585
+ // Flipping only one of the two leaves the cutover half-applied.
37586
+ defaultValue: true,
37542
37587
  // ENG-9227: there are NO client readers of this key left. (dashboard)/
37543
37588
  // layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
37544
37589
  // passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
37590
+ // "ONCE" is about the chrome, not the key: components/brand-stamp.tsx is a
37591
+ // SECOND, independent server read of `ninjafy-brand` (CodeRabbit, #5385).
37592
+ // Both are listed in the note above because both carry an outage fallback
37593
+ // that has to move together — reading this paragraph alone would leave you
37594
+ // updating one of the two.
37545
37595
  // ENG-8858 did that for the THEME; ENG-9227 finished the job for the
37546
37596
  // WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
37547
37597
  // "augmented.team" to "ninjafy" after hydration.
@@ -37563,24 +37613,38 @@ var FLAG_REGISTRY = [
37563
37613
  },
37564
37614
  {
37565
37615
  key: "ninjafy-logo",
37566
- description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It exists so candidate logo treatments can be put in front of a human and compared in the running console, rather than judged from a static export. lockup = mark and wordmark together, both in the brand blue \u2014 exactly what ENG-9298 shipped, so an unset flag is a no-op. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork). wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
37616
+ description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It existed so candidate logo treatments could be put in front of a human and compared in the running console, rather than judged from a static export. That comparison has happened: ENG-9830 cutover step 1 chose TWO-TONE, so that is now the default and an unset flag renders it. NOTE this reverses the property this description used to claim \u2014 an unset flag was a no-op while the default was lockup (what ENG-9298 shipped), and that is deliberately given up at cutover: the default MOVING is the change. The key survives only until cutover step 2 hardcodes the chosen treatment and deletes it. lockup = mark and wordmark together, both in the brand blue \u2014 what ENG-9298 shipped. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork); the chosen one. wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
37567
37617
  flagType: "enum",
37568
37618
  allowedValues: ["lockup", "two-tone", "wordmark"],
37569
- // Declared safe value is `lockup` what ENG-9298 shipped. This flag has no
37570
- // "off" direction: it selects among presentations that are all live-safe, so
37571
- // fail-safe here means "the treatment currently in production", not the
37619
+ // ENG-9830 (cutover step 1): `lockup` -> `two-tone`. The treatments existed
37620
+ // so they could be compared in the running console rather than judged from a
37621
+ // static export (ENG-9365); that comparison has now happened and `two-tone`
37622
+ // — the mark in brand blue with the wordmark in the neutral text colour — is
37623
+ // the chosen treatment. It becomes the hardcoded one when this key is
37624
+ // removed at cutover step 2.
37625
+ //
37626
+ // The rule this value obeys is unchanged: this flag has no "off" direction,
37627
+ // so fail-safe means "the treatment currently in production", not the
37572
37628
  // narrowest one. A flag-DB read error, an archived row, or a stored value
37573
37629
  // outside allowedValues all resolve here (evaluate.ts normalizeFlagValue),
37574
- // which is what makes an experimental value impossible to strand.
37575
- defaultValue: "lockup",
37630
+ // which is what makes an experimental value impossible to strand. Only which
37631
+ // treatment holds that position has moved.
37632
+ //
37633
+ // Same split as ninjafy-brand above: this governs the API evaluation path
37634
+ // only. The webapp's outage fallback is the argument passed at the
37635
+ // (dashboard)/layout.tsx call site, which carries 'two-tone' to match.
37636
+ defaultValue: "two-tone",
37576
37637
  // Same trap as ninjafy-brand, and it bites harder here because there are no
37577
37638
  // client readers of this key AT ALL — it is server-resolved by construction.
37578
37639
  // `public` does NOT mean "read by a browser"; it means "readable through the
37579
37640
  // public map". getPublicEnumFlagServer resolves against the same GET /flags
37580
37641
  // endpoint the browser uses, and that route filters to listPublicFlagKeys()
37581
37642
  // (routes/flags.ts). Drop `public` and the key is simply absent from the map,
37582
- // so the helper falls through to its default and returns `lockup` FOREVER
37583
- // the dropdown appearing to do nothing, with no error raised anywhere.
37643
+ // so the helper falls through to the fallback passed at that call site and
37644
+ // returns it FOREVER — the dropdown appearing to do nothing, with no error
37645
+ // raised anywhere. Named as the call site rather than as a literal on
37646
+ // purpose: this comment said `lockup` for exactly as long as `lockup` was
37647
+ // the answer, and ENG-9830 flipping the winner is what made it a lie.
37584
37648
  public: true
37585
37649
  },
37586
37650
  {
package/dist/mcp/index.js CHANGED
@@ -27363,6 +27363,12 @@ var CURSOR_SHORTFALL_REASONS = [
27363
27363
  var NON_SHORTFALL_KEYS = new Set(CURSOR_ADVANCE_NON_SHORTFALL_KEYS);
27364
27364
  var KNOWN_REASONS = new Set(CURSOR_SHORTFALL_REASONS.filter((k) => !NON_SHORTFALL_KEYS.has(k)));
27365
27365
 
27366
+ // ../core/dist/direct-chat/agent-history-notices.js
27367
+ var AGENT_HISTORY_TURNS_ONLY_OR_FILTER = [
27368
+ "payload->>kind.is.null",
27369
+ `payload->>kind.in.("",${[...USER_FACING_NOTICE_KINDS].sort().join(",")})`
27370
+ ].join(",");
27371
+
27366
27372
  // ../core/dist/onboarding/state-machine.js
27367
27373
  var AREA_ORDER = [
27368
27374
  "framing",
@@ -29815,28 +29821,72 @@ var FLAG_REGISTRY = [
29815
29821
  },
29816
29822
  {
29817
29823
  key: "app-top-nav-shell",
29818
- description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). Ships dark.",
29824
+ description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). NO LONGER DARK: ENG-9831 cutover step 1 flipped the default to true, so an org with no override now gets the top-nav shell. The key survives only until cutover step 2 deletes it.",
29819
29825
  flagType: "boolean",
29820
- // Declared safe value is `false` = the current left sidebar. Fail-closed to the
29821
- // shell that ships today if the flag DB is unreachable.
29822
- defaultValue: false,
29823
- // Read CLIENT-SIDE by the dashboard layout + sidebar ("use client") via
29824
- // usePublicBooleanFlag, so the key must be in the browser-exposed public map.
29825
- // Additive layout gate not sensitive.
29826
+ // ENG-9831 (cutover step 1): flipped `false` -> `true`. The comment this
29827
+ // replaces called `false` the fail-closed direction — "the shell that ships
29828
+ // today". That was true while the sidebar was the shell that shipped; the
29829
+ // top nav is now the one being cut over to, so `false` points at the layout
29830
+ // being retired. The old rationale is recorded rather than deleted because
29831
+ // it was correct under a premise that has expired.
29832
+ //
29833
+ // NOTE the same split as ninjafy-brand below: this value is consulted by the
29834
+ // API evaluator when building the `GET /flags` map for an org with no
29835
+ // override. It is NOT the fallback the webapp uses on an outage — that is
29836
+ // the argument passed at the call site. Four call sites carry `true` to
29837
+ // match: (dashboard)/layout.tsx and the three legacy redirect hubs
29838
+ // (settings, team, organization/settings). Flipping the registry alone
29839
+ // leaves the cutover half-applied.
29840
+ defaultValue: true,
29841
+ // ENG-9831: NO LONGER read client-side. It was — via usePublicBooleanFlag in
29842
+ // sidebar.tsx (twice) and top-nav.tsx — and that is precisely what this
29843
+ // slice removed: a client hook starts at its default and corrects after an
29844
+ // async GET /flags, so the console painted the sidebar and then replaced the
29845
+ // WHOLE dashboard chrome after hydration. The ENG-8858/ENG-9227 flash at
29846
+ // layout scale. It is now resolved once server-side in (dashboard)/
29847
+ // layout.tsx and passed to both shells as a required prop.
29848
+ //
29849
+ // `public` MUST STAY ANYWAY — the same trap documented on ninjafy-brand.
29850
+ // getPublicBooleanFlagServer resolves against the very same public-filtered
29851
+ // `GET /flags` the browser uses (routes/flags.ts filters to
29852
+ // listPublicFlagKeys()). Drop `public` and the key is simply absent from the
29853
+ // map, the server helper falls through to its default forever, and every org
29854
+ // silently gets whatever that default happens to be with no error anywhere.
29855
+ // `public` means "readable through the public map", not "read by a browser".
29826
29856
  public: true
29827
29857
  },
29828
29858
  {
29829
29859
  key: "ninjafy-brand",
29830
- 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.',
29860
+ 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 fetchPublicFlags returns null 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. ENG-9830 CORRECTS what used to be written here ("those surfaces follow only when the stage-wide default flips"): they do not follow it at all. Two reasons. The value returned on a null fetch is the CALL SITE\u2019s passed argument, not this registry default; and six such pages (/login, /reset-password, /auth/confirm, the pre-redeem member-signin gate and the two public Slack-install terminal pages) do not mount <BrandStamp/> AT ALL \u2014 deliberately, enumerated with per-page reasons in eng-9266-brand-stamp-coverage.test.ts. They become branded when the flag is REMOVED at cutover step 2 and the palette moves into bare :root, with no read left to gate it. NO LONGER DARK: ENG-9830 cutover step 1 flipped the default to true, so an org with no override now sees Ninjafy. The key survives only until cutover step 2 deletes it.',
29831
29861
  flagType: "boolean",
29832
- // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
29833
- // fail-closed direction if the flag DB is unreachable we must show the brand
29834
- // that is currently live and contractually correct, never leak an unannounced
29835
- // rebrand to every customer at once.
29836
- defaultValue: false,
29862
+ // ENG-9830 (cutover step 1): flipped `false` -> `true`. The comment this
29863
+ // replaces argued `false` was the fail-closed direction "never leak an
29864
+ // UNANNOUNCED rebrand to every customer at once". That was correct for as
29865
+ // long as the rebrand was unannounced. It is not any more: Ninjafy is the
29866
+ // live, announced brand (ENG-9613 reconciled the console onto the master
29867
+ // assets while the flag was already ON in production), so `false` now points
29868
+ // at the brand we no longer ship. A flags outage that reverted a customer to
29869
+ // Augmented would be the incident, not the safe harbour.
29870
+ //
29871
+ // The old rationale is recorded rather than deleted because it is a correct
29872
+ // argument whose PREMISE expired — reading the change without it looks like
29873
+ // someone overriding a deliberate fail-closed default.
29874
+ //
29875
+ // NOTE this value is only consulted by the API evaluator when building the
29876
+ // `GET /flags` map for an org with no override. It is NOT the fallback the
29877
+ // webapp uses on an outage: `getPublicBooleanFlagServer(key, defaultValue)`
29878
+ // returns its own PASSED argument when the fetch fails, so the call sites in
29879
+ // (dashboard)/layout.tsx and components/brand-stamp.tsx carry `true` too.
29880
+ // Flipping only one of the two leaves the cutover half-applied.
29881
+ defaultValue: true,
29837
29882
  // ENG-9227: there are NO client readers of this key left. (dashboard)/
29838
29883
  // layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
29839
29884
  // passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
29885
+ // "ONCE" is about the chrome, not the key: components/brand-stamp.tsx is a
29886
+ // SECOND, independent server read of `ninjafy-brand` (CodeRabbit, #5385).
29887
+ // Both are listed in the note above because both carry an outage fallback
29888
+ // that has to move together — reading this paragraph alone would leave you
29889
+ // updating one of the two.
29840
29890
  // ENG-8858 did that for the THEME; ENG-9227 finished the job for the
29841
29891
  // WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
29842
29892
  // "augmented.team" to "ninjafy" after hydration.
@@ -29858,24 +29908,38 @@ var FLAG_REGISTRY = [
29858
29908
  },
29859
29909
  {
29860
29910
  key: "ninjafy-logo",
29861
- description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It exists so candidate logo treatments can be put in front of a human and compared in the running console, rather than judged from a static export. lockup = mark and wordmark together, both in the brand blue \u2014 exactly what ENG-9298 shipped, so an unset flag is a no-op. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork). wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
29911
+ description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It existed so candidate logo treatments could be put in front of a human and compared in the running console, rather than judged from a static export. That comparison has happened: ENG-9830 cutover step 1 chose TWO-TONE, so that is now the default and an unset flag renders it. NOTE this reverses the property this description used to claim \u2014 an unset flag was a no-op while the default was lockup (what ENG-9298 shipped), and that is deliberately given up at cutover: the default MOVING is the change. The key survives only until cutover step 2 hardcodes the chosen treatment and deletes it. lockup = mark and wordmark together, both in the brand blue \u2014 what ENG-9298 shipped. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork); the chosen one. wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
29862
29912
  flagType: "enum",
29863
29913
  allowedValues: ["lockup", "two-tone", "wordmark"],
29864
- // Declared safe value is `lockup` what ENG-9298 shipped. This flag has no
29865
- // "off" direction: it selects among presentations that are all live-safe, so
29866
- // fail-safe here means "the treatment currently in production", not the
29914
+ // ENG-9830 (cutover step 1): `lockup` -> `two-tone`. The treatments existed
29915
+ // so they could be compared in the running console rather than judged from a
29916
+ // static export (ENG-9365); that comparison has now happened and `two-tone`
29917
+ // — the mark in brand blue with the wordmark in the neutral text colour — is
29918
+ // the chosen treatment. It becomes the hardcoded one when this key is
29919
+ // removed at cutover step 2.
29920
+ //
29921
+ // The rule this value obeys is unchanged: this flag has no "off" direction,
29922
+ // so fail-safe means "the treatment currently in production", not the
29867
29923
  // narrowest one. A flag-DB read error, an archived row, or a stored value
29868
29924
  // outside allowedValues all resolve here (evaluate.ts normalizeFlagValue),
29869
- // which is what makes an experimental value impossible to strand.
29870
- defaultValue: "lockup",
29925
+ // which is what makes an experimental value impossible to strand. Only which
29926
+ // treatment holds that position has moved.
29927
+ //
29928
+ // Same split as ninjafy-brand above: this governs the API evaluation path
29929
+ // only. The webapp's outage fallback is the argument passed at the
29930
+ // (dashboard)/layout.tsx call site, which carries 'two-tone' to match.
29931
+ defaultValue: "two-tone",
29871
29932
  // Same trap as ninjafy-brand, and it bites harder here because there are no
29872
29933
  // client readers of this key AT ALL — it is server-resolved by construction.
29873
29934
  // `public` does NOT mean "read by a browser"; it means "readable through the
29874
29935
  // public map". getPublicEnumFlagServer resolves against the same GET /flags
29875
29936
  // endpoint the browser uses, and that route filters to listPublicFlagKeys()
29876
29937
  // (routes/flags.ts). Drop `public` and the key is simply absent from the map,
29877
- // so the helper falls through to its default and returns `lockup` FOREVER
29878
- // the dropdown appearing to do nothing, with no error raised anywhere.
29938
+ // so the helper falls through to the fallback passed at that call site and
29939
+ // returns it FOREVER — the dropdown appearing to do nothing, with no error
29940
+ // raised anywhere. Named as the call site rather than as a literal on
29941
+ // purpose: this comment said `lockup` for exactly as long as `lockup` was
29942
+ // the answer, and ENG-9830 flipping the winner is what made it a lie.
29879
29943
  public: true
29880
29944
  },
29881
29945
  {
@@ -41494,6 +41494,12 @@ var CURSOR_SHORTFALL_REASONS = [
41494
41494
  var NON_SHORTFALL_KEYS = new Set(CURSOR_ADVANCE_NON_SHORTFALL_KEYS);
41495
41495
  var KNOWN_REASONS = new Set(CURSOR_SHORTFALL_REASONS.filter((k) => !NON_SHORTFALL_KEYS.has(k)));
41496
41496
 
41497
+ // ../core/dist/direct-chat/agent-history-notices.js
41498
+ var AGENT_HISTORY_TURNS_ONLY_OR_FILTER = [
41499
+ "payload->>kind.is.null",
41500
+ `payload->>kind.in.("",${[...USER_FACING_NOTICE_KINDS].sort().join(",")})`
41501
+ ].join(",");
41502
+
41497
41503
  // ../core/dist/onboarding/state-machine.js
41498
41504
  var AREA_ORDER = [
41499
41505
  "framing",
@@ -43743,28 +43749,72 @@ var FLAG_REGISTRY = [
43743
43749
  },
43744
43750
  {
43745
43751
  key: "app-top-nav-shell",
43746
- description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). Ships dark.",
43752
+ description: "Global top-navigation shell redesign (ENG-8478 epic / ENG-8486). When ON, the dashboard chrome replaces the left sidebar with a top navigation bar (team switcher \xB7 destinations \xB7 command palette + account) so the left side is free for an agent-scoped rail; when OFF, the existing left sidebar is unchanged. Additive layout gate \u2014 no data-model change. Composes independently with agent-unified-screen (the two flags are reconciled in ENG-8488). NO LONGER DARK: ENG-9831 cutover step 1 flipped the default to true, so an org with no override now gets the top-nav shell. The key survives only until cutover step 2 deletes it.",
43747
43753
  flagType: "boolean",
43748
- // Declared safe value is `false` = the current left sidebar. Fail-closed to the
43749
- // shell that ships today if the flag DB is unreachable.
43750
- defaultValue: false,
43751
- // Read CLIENT-SIDE by the dashboard layout + sidebar ("use client") via
43752
- // usePublicBooleanFlag, so the key must be in the browser-exposed public map.
43753
- // Additive layout gate not sensitive.
43754
+ // ENG-9831 (cutover step 1): flipped `false` -> `true`. The comment this
43755
+ // replaces called `false` the fail-closed direction — "the shell that ships
43756
+ // today". That was true while the sidebar was the shell that shipped; the
43757
+ // top nav is now the one being cut over to, so `false` points at the layout
43758
+ // being retired. The old rationale is recorded rather than deleted because
43759
+ // it was correct under a premise that has expired.
43760
+ //
43761
+ // NOTE the same split as ninjafy-brand below: this value is consulted by the
43762
+ // API evaluator when building the `GET /flags` map for an org with no
43763
+ // override. It is NOT the fallback the webapp uses on an outage — that is
43764
+ // the argument passed at the call site. Four call sites carry `true` to
43765
+ // match: (dashboard)/layout.tsx and the three legacy redirect hubs
43766
+ // (settings, team, organization/settings). Flipping the registry alone
43767
+ // leaves the cutover half-applied.
43768
+ defaultValue: true,
43769
+ // ENG-9831: NO LONGER read client-side. It was — via usePublicBooleanFlag in
43770
+ // sidebar.tsx (twice) and top-nav.tsx — and that is precisely what this
43771
+ // slice removed: a client hook starts at its default and corrects after an
43772
+ // async GET /flags, so the console painted the sidebar and then replaced the
43773
+ // WHOLE dashboard chrome after hydration. The ENG-8858/ENG-9227 flash at
43774
+ // layout scale. It is now resolved once server-side in (dashboard)/
43775
+ // layout.tsx and passed to both shells as a required prop.
43776
+ //
43777
+ // `public` MUST STAY ANYWAY — the same trap documented on ninjafy-brand.
43778
+ // getPublicBooleanFlagServer resolves against the very same public-filtered
43779
+ // `GET /flags` the browser uses (routes/flags.ts filters to
43780
+ // listPublicFlagKeys()). Drop `public` and the key is simply absent from the
43781
+ // map, the server helper falls through to its default forever, and every org
43782
+ // silently gets whatever that default happens to be with no error anywhere.
43783
+ // `public` means "readable through the public map", not "read by a browser".
43754
43784
  public: true
43755
43785
  },
43756
43786
  {
43757
43787
  key: "ninjafy-brand",
43758
- 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.',
43788
+ 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 fetchPublicFlags returns null 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. ENG-9830 CORRECTS what used to be written here ("those surfaces follow only when the stage-wide default flips"): they do not follow it at all. Two reasons. The value returned on a null fetch is the CALL SITE\u2019s passed argument, not this registry default; and six such pages (/login, /reset-password, /auth/confirm, the pre-redeem member-signin gate and the two public Slack-install terminal pages) do not mount <BrandStamp/> AT ALL \u2014 deliberately, enumerated with per-page reasons in eng-9266-brand-stamp-coverage.test.ts. They become branded when the flag is REMOVED at cutover step 2 and the palette moves into bare :root, with no read left to gate it. NO LONGER DARK: ENG-9830 cutover step 1 flipped the default to true, so an org with no override now sees Ninjafy. The key survives only until cutover step 2 deletes it.',
43759
43789
  flagType: "boolean",
43760
- // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
43761
- // fail-closed direction if the flag DB is unreachable we must show the brand
43762
- // that is currently live and contractually correct, never leak an unannounced
43763
- // rebrand to every customer at once.
43764
- defaultValue: false,
43790
+ // ENG-9830 (cutover step 1): flipped `false` -> `true`. The comment this
43791
+ // replaces argued `false` was the fail-closed direction "never leak an
43792
+ // UNANNOUNCED rebrand to every customer at once". That was correct for as
43793
+ // long as the rebrand was unannounced. It is not any more: Ninjafy is the
43794
+ // live, announced brand (ENG-9613 reconciled the console onto the master
43795
+ // assets while the flag was already ON in production), so `false` now points
43796
+ // at the brand we no longer ship. A flags outage that reverted a customer to
43797
+ // Augmented would be the incident, not the safe harbour.
43798
+ //
43799
+ // The old rationale is recorded rather than deleted because it is a correct
43800
+ // argument whose PREMISE expired — reading the change without it looks like
43801
+ // someone overriding a deliberate fail-closed default.
43802
+ //
43803
+ // NOTE this value is only consulted by the API evaluator when building the
43804
+ // `GET /flags` map for an org with no override. It is NOT the fallback the
43805
+ // webapp uses on an outage: `getPublicBooleanFlagServer(key, defaultValue)`
43806
+ // returns its own PASSED argument when the fetch fails, so the call sites in
43807
+ // (dashboard)/layout.tsx and components/brand-stamp.tsx carry `true` too.
43808
+ // Flipping only one of the two leaves the cutover half-applied.
43809
+ defaultValue: true,
43765
43810
  // ENG-9227: there are NO client readers of this key left. (dashboard)/
43766
43811
  // layout.tsx resolves it ONCE server-side via getPublicBooleanFlagServer and
43767
43812
  // passes the boolean down to sidebar.tsx and top-nav.tsx as a required prop.
43813
+ // "ONCE" is about the chrome, not the key: components/brand-stamp.tsx is a
43814
+ // SECOND, independent server read of `ninjafy-brand` (CodeRabbit, #5385).
43815
+ // Both are listed in the note above because both carry an outage fallback
43816
+ // that has to move together — reading this paragraph alone would leave you
43817
+ // updating one of the two.
43768
43818
  // ENG-8858 did that for the THEME; ENG-9227 finished the job for the
43769
43819
  // WORDMARK, which had kept usePublicBooleanFlag and so kept swapping
43770
43820
  // "augmented.team" to "ninjafy" after hydration.
@@ -43786,24 +43836,38 @@ var FLAG_REGISTRY = [
43786
43836
  },
43787
43837
  {
43788
43838
  key: "ninjafy-logo",
43789
- description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It exists so candidate logo treatments can be put in front of a human and compared in the running console, rather than judged from a static export. lockup = mark and wordmark together, both in the brand blue \u2014 exactly what ENG-9298 shipped, so an unset flag is a no-op. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork). wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
43839
+ description: "Which Ninjafy logo TREATMENT the console chrome renders (ENG-9365). This is a SUB-SELECTION under ninjafy-brand, not a second brand gate: it is consulted only once ninjafy-brand has already resolved ON, so with the brand off the value is inert and the half-branded state that flag's description calls unreachable stays unreachable. ninjafy-brand remains the single kill switch for the rebrand. It existed so candidate logo treatments could be put in front of a human and compared in the running console, rather than judged from a static export. That comparison has happened: ENG-9830 cutover step 1 chose TWO-TONE, so that is now the default and an unset flag renders it. NOTE this reverses the property this description used to claim \u2014 an unset flag was a no-op while the default was lockup (what ENG-9298 shipped), and that is deliberately given up at cutover: the default MOVING is the change. The key survives only until cutover step 2 hardcodes the chosen treatment and deletes it. lockup = mark and wordmark together, both in the brand blue \u2014 what ENG-9298 shipped. two-tone = the mark in brand blue with the wordmark in the neutral text colour (the treatment of the blue-icon/black-wordmark artwork); the chosen one. wordmark = the word alone, no mark. Every treatment is composed from the ENG-9298 geometry \u2014 ENG-9312 pins that the lockup's blue path IS the mark subpath followed by the wordmark subpaths, byte for byte \u2014 so a new treatment is a composition, never a fresh trace, and the three drawings cannot drift apart. Surfaces are the dashboard sidebar and the top nav. The top nav shows the WORDMARK portion at every width whatever the treatment, because ENG-9342 decided that deliberately (a full lockup at the header's icon height crowds the team switcher on a phone) \u2014 so a treatment changes the word's colour there, never whether a mark appears. The admin sidebar (ENG-9323) is NOT wired to this key and always renders the mark; widening it is a separate decision. Resolved SERVER-SIDE in (dashboard)/layout.tsx via getPublicEnumFlagServer, beside ninjafy-brand and in the same single GET /flags round trip, then passed down as a required prop. That is not a style preference: the client hook starts at its default and updates after an async fetch, so a client read would paint one logo and swap it after hydration \u2014 the exact flash ENG-9227 removed from the wordmark and ENG-8858 removed from the palette. Org and team are the useful pilot grains, as for ninjafy-brand: the single consumer evaluates in the active-org cookie scope, so a host- or agent-scoped override row is accepted by the table but never reached by the console.",
43790
43840
  flagType: "enum",
43791
43841
  allowedValues: ["lockup", "two-tone", "wordmark"],
43792
- // Declared safe value is `lockup` what ENG-9298 shipped. This flag has no
43793
- // "off" direction: it selects among presentations that are all live-safe, so
43794
- // fail-safe here means "the treatment currently in production", not the
43842
+ // ENG-9830 (cutover step 1): `lockup` -> `two-tone`. The treatments existed
43843
+ // so they could be compared in the running console rather than judged from a
43844
+ // static export (ENG-9365); that comparison has now happened and `two-tone`
43845
+ // — the mark in brand blue with the wordmark in the neutral text colour — is
43846
+ // the chosen treatment. It becomes the hardcoded one when this key is
43847
+ // removed at cutover step 2.
43848
+ //
43849
+ // The rule this value obeys is unchanged: this flag has no "off" direction,
43850
+ // so fail-safe means "the treatment currently in production", not the
43795
43851
  // narrowest one. A flag-DB read error, an archived row, or a stored value
43796
43852
  // outside allowedValues all resolve here (evaluate.ts normalizeFlagValue),
43797
- // which is what makes an experimental value impossible to strand.
43798
- defaultValue: "lockup",
43853
+ // which is what makes an experimental value impossible to strand. Only which
43854
+ // treatment holds that position has moved.
43855
+ //
43856
+ // Same split as ninjafy-brand above: this governs the API evaluation path
43857
+ // only. The webapp's outage fallback is the argument passed at the
43858
+ // (dashboard)/layout.tsx call site, which carries 'two-tone' to match.
43859
+ defaultValue: "two-tone",
43799
43860
  // Same trap as ninjafy-brand, and it bites harder here because there are no
43800
43861
  // client readers of this key AT ALL — it is server-resolved by construction.
43801
43862
  // `public` does NOT mean "read by a browser"; it means "readable through the
43802
43863
  // public map". getPublicEnumFlagServer resolves against the same GET /flags
43803
43864
  // endpoint the browser uses, and that route filters to listPublicFlagKeys()
43804
43865
  // (routes/flags.ts). Drop `public` and the key is simply absent from the map,
43805
- // so the helper falls through to its default and returns `lockup` FOREVER
43806
- // the dropdown appearing to do nothing, with no error raised anywhere.
43866
+ // so the helper falls through to the fallback passed at that call site and
43867
+ // returns it FOREVER — the dropdown appearing to do nothing, with no error
43868
+ // raised anywhere. Named as the call site rather than as a literal on
43869
+ // purpose: this comment said `lockup` for exactly as long as `lockup` was
43870
+ // the answer, and ENG-9830 flipping the winner is what made it a lie.
43807
43871
  public: true
43808
43872
  },
43809
43873
  {