@agent-native/core 0.84.53 → 0.84.55

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 (83) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/skills.ts +34 -16
  5. package/corpus/core/src/client/AssistantChat.tsx +198 -146
  6. package/corpus/core/src/client/agent-chat-adapter.ts +14 -4
  7. package/corpus/core/src/client/blocks/library/diagram.config.ts +13 -2
  8. package/corpus/core/src/client/blocks/library/diagram.tsx +27 -3
  9. package/corpus/core/src/client/blocks/library/server-specs.ts +2 -2
  10. package/corpus/core/src/client/blocks/library/wireframe.config.ts +7 -1
  11. package/corpus/core/src/client/blocks/library/wireframe.tsx +42 -3
  12. package/corpus/core/src/client/blocks/types.ts +9 -0
  13. package/corpus/core/src/client/chat/message-components.tsx +6 -2
  14. package/corpus/core/src/client/sse-event-processor.ts +11 -4
  15. package/corpus/core/src/client/use-agent-engine-configured.ts +54 -12
  16. package/corpus/core/src/server/agent-chat-plugin.ts +9 -5
  17. package/corpus/core/src/styles/agent-native.css +44 -0
  18. package/corpus/core/src/styles/blocks.css +18 -0
  19. package/corpus/templates/plan/.agents/skills/visual-plan/references/document-quality.md +10 -6
  20. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +20 -9
  21. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +4 -1
  22. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +20 -9
  23. package/corpus/templates/plan/actions/create-ui-plan.ts +1 -1
  24. package/corpus/templates/plan/actions/create-visual-plan.ts +1 -1
  25. package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
  26. package/corpus/templates/plan/actions/get-plan-blocks.ts +2 -0
  27. package/corpus/templates/plan/app/components/plan/planBlocks.tsx +2 -0
  28. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +38 -4
  29. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +18 -0
  30. package/corpus/templates/plan/app/global.css +1 -2
  31. package/corpus/templates/plan/changelog/2026-07-02-plan-chat-keeps-the-composer-anchored-when-connect-ai-appears.md +6 -0
  32. package/corpus/templates/plan/changelog/2026-07-02-wireframe-and-diagram-blocks-can-now-show-or-hide-their-outer-frame.md +6 -0
  33. package/corpus/templates/plan/server/plan-mdx.ts +3 -0
  34. package/corpus/templates/plan/shared/plan-content.ts +8 -0
  35. package/dist/cli/skills.d.ts +3 -3
  36. package/dist/cli/skills.d.ts.map +1 -1
  37. package/dist/cli/skills.js +34 -16
  38. package/dist/cli/skills.js.map +1 -1
  39. package/dist/client/AssistantChat.d.ts.map +1 -1
  40. package/dist/client/AssistantChat.js +65 -45
  41. package/dist/client/AssistantChat.js.map +1 -1
  42. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  43. package/dist/client/agent-chat-adapter.js +12 -2
  44. package/dist/client/agent-chat-adapter.js.map +1 -1
  45. package/dist/client/blocks/library/diagram.config.d.ts +3 -1
  46. package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
  47. package/dist/client/blocks/library/diagram.config.js +10 -1
  48. package/dist/client/blocks/library/diagram.config.js.map +1 -1
  49. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  50. package/dist/client/blocks/library/diagram.js +13 -7
  51. package/dist/client/blocks/library/diagram.js.map +1 -1
  52. package/dist/client/blocks/library/server-specs.js +2 -2
  53. package/dist/client/blocks/library/server-specs.js.map +1 -1
  54. package/dist/client/blocks/library/wireframe.config.d.ts +3 -1
  55. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -1
  56. package/dist/client/blocks/library/wireframe.config.js +4 -0
  57. package/dist/client/blocks/library/wireframe.config.js.map +1 -1
  58. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  59. package/dist/client/blocks/library/wireframe.js +20 -9
  60. package/dist/client/blocks/library/wireframe.js.map +1 -1
  61. package/dist/client/blocks/types.d.ts +8 -0
  62. package/dist/client/blocks/types.d.ts.map +1 -1
  63. package/dist/client/blocks/types.js.map +1 -1
  64. package/dist/client/chat/message-components.d.ts.map +1 -1
  65. package/dist/client/chat/message-components.js +6 -2
  66. package/dist/client/chat/message-components.js.map +1 -1
  67. package/dist/client/sse-event-processor.d.ts.map +1 -1
  68. package/dist/client/sse-event-processor.js +9 -4
  69. package/dist/client/sse-event-processor.js.map +1 -1
  70. package/dist/client/use-agent-engine-configured.d.ts +10 -1
  71. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  72. package/dist/client/use-agent-engine-configured.js +35 -10
  73. package/dist/client/use-agent-engine-configured.js.map +1 -1
  74. package/dist/collab/routes.d.ts +1 -1
  75. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  76. package/dist/notifications/routes.d.ts +3 -3
  77. package/dist/observability/routes.d.ts +4 -4
  78. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  79. package/dist/server/agent-chat-plugin.js +9 -2
  80. package/dist/server/agent-chat-plugin.js.map +1 -1
  81. package/dist/styles/agent-native.css +44 -0
  82. package/dist/styles/blocks.css +18 -0
  83. package/package.json +1 -1
@@ -89,6 +89,7 @@ function HtmlDiagram({
89
89
  const ref = useRef<HTMLDivElement>(null);
90
90
  const isDark = useIsDark();
91
91
  const style = useWireframeStyle();
92
+ const showFrame = resolveVisualFrame(data.frame, ctx);
92
93
  const scopeId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
93
94
  // Sanitize author HTML/CSS at the render point (defense-in-depth against
94
95
  // stored XSS). Self-contained in core via the shared block sanitizer (DOM-based
@@ -114,6 +115,7 @@ function HtmlDiagram({
114
115
  dir={ctx.textDirection}
115
116
  data-theme={isDark ? "dark" : "light"}
116
117
  data-style={style}
118
+ data-frame={showFrame ? "show" : "hide"}
117
119
  data-text-direction={ctx.textDirection}
118
120
  data-plan-diagram-scope={scopeId}
119
121
  >
@@ -126,7 +128,7 @@ function HtmlDiagram({
126
128
  <RoughOverlay
127
129
  scopeRef={ref}
128
130
  enabled={style === "sketchy"}
129
- drawFrame={false}
131
+ drawFrame={showFrame}
130
132
  selector={DIAGRAM_ROUGH_SELECTOR}
131
133
  />
132
134
  {data.caption && !compact && (
@@ -138,6 +140,15 @@ function HtmlDiagram({
138
140
  );
139
141
  }
140
142
 
143
+ function resolveVisualFrame(
144
+ frame: DiagramData["frame"],
145
+ ctx: BlockRenderContext,
146
+ ): boolean {
147
+ const resolved =
148
+ frame && frame !== "auto" ? frame : (ctx.visualFrame ?? "show");
149
+ return resolved !== "hide";
150
+ }
151
+
141
152
  /* -------------------------------------------------------------------------- */
142
153
  /* Legacy node-graph paths */
143
154
  /* -------------------------------------------------------------------------- */
@@ -186,11 +197,13 @@ function PositionedDiagram({
186
197
  compact,
187
198
  markerId,
188
199
  direction,
200
+ showFrame,
189
201
  }: {
190
202
  data: DiagramData;
191
203
  compact?: boolean;
192
204
  markerId: string;
193
205
  direction?: BlockRenderContext["textDirection"];
206
+ showFrame: boolean;
194
207
  }) {
195
208
  const nodes = (data.nodes ?? []).map((node) => ({
196
209
  ...node,
@@ -228,7 +241,10 @@ function PositionedDiagram({
228
241
 
229
242
  return (
230
243
  <div
231
- className="plan-sketch rounded-[16px] border border-border bg-muted p-5"
244
+ className={cn(
245
+ "plan-sketch",
246
+ showFrame ? "rounded-[16px] border border-border bg-muted p-5" : "p-0",
247
+ )}
232
248
  dir={direction}
233
249
  data-text-direction={direction}
234
250
  >
@@ -426,10 +442,12 @@ function SequenceDiagram({
426
442
  data,
427
443
  compact,
428
444
  direction,
445
+ showFrame,
429
446
  }: {
430
447
  data: DiagramData;
431
448
  compact?: boolean;
432
449
  direction?: BlockRenderContext["textDirection"];
450
+ showFrame: boolean;
433
451
  }) {
434
452
  const edges = data.edges ?? [];
435
453
  const nodes = orderDiagramNodes(data.nodes ?? [], edges);
@@ -443,7 +461,10 @@ function SequenceDiagram({
443
461
  const visualNodes = direction === "rtl" ? [...nodes].reverse() : nodes;
444
462
  return (
445
463
  <div
446
- className="plan-sketch rounded-[16px] border border-border bg-muted p-5"
464
+ className={cn(
465
+ "plan-sketch",
466
+ showFrame ? "rounded-[16px] border border-border bg-muted p-5" : "p-0",
467
+ )}
447
468
  dir={direction}
448
469
  data-text-direction={direction}
449
470
  >
@@ -524,6 +545,7 @@ function DiagramBody({
524
545
  compact?: boolean;
525
546
  }) {
526
547
  const markerId = useId().replace(/:/g, "");
548
+ const showFrame = resolveVisualFrame(data.frame, ctx);
527
549
  if (data.html?.trim()) {
528
550
  return <HtmlDiagram data={data} ctx={ctx} compact={compact} />;
529
551
  }
@@ -534,6 +556,7 @@ function DiagramBody({
534
556
  compact={compact}
535
557
  markerId={markerId}
536
558
  direction={ctx.textDirection}
559
+ showFrame={showFrame}
537
560
  />
538
561
  );
539
562
  }
@@ -542,6 +565,7 @@ function DiagramBody({
542
565
  data={data}
543
566
  compact={compact}
544
567
  direction={ctx.textDirection}
568
+ showFrame={showFrame}
545
569
  />
546
570
  );
547
571
  }
@@ -211,7 +211,7 @@ export const libraryBlockConfigs: BlockSpec<any>[] = [
211
211
  placement: ["block"],
212
212
  label: "Diagram",
213
213
  description:
214
- "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
214
+ "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Set frame to show for standalone artifacts that need containment, hide when surrounding docs/canvas chrome already supplies the boundary. Legacy nodes/edges are only for simple previews.",
215
215
  }),
216
216
  defineBlock<WireframeData>({
217
217
  type: "wireframe",
@@ -221,7 +221,7 @@ export const libraryBlockConfigs: BlockSpec<any>[] = [
221
221
  placement: ["block"],
222
222
  label: "Wireframe",
223
223
  description:
224
- "A sketch wireframe of one screen built from kit primitives (or an HTML mockup), rendered in a chosen surface frame (desktop/mobile/popover/panel/browser).",
224
+ "A sketch wireframe of one screen built from kit primitives (or an HTML mockup), rendered in a chosen surface (desktop/mobile/popover/panel/browser). Set frame to show for standalone screens and recap comparisons, hide only when surrounding chrome already supplies the boundary.",
225
225
  }),
226
226
  defineBlock<MermaidData>({
227
227
  type: "mermaid",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { prop, attributeValue } from "../mdx.js";
4
- import type { BlockMdxConfig } from "../types.js";
4
+ import type { BlockMdxConfig, BlockVisualFrame } from "../types.js";
5
5
 
6
6
  /**
7
7
  * Pure (React-free) part of the shared `wireframe` block: its data shape, zod
@@ -138,6 +138,8 @@ export interface WireframeData {
138
138
  /** `design` renders full-fidelity branded HTML/CSS instead of a sketch. */
139
139
  renderMode?: WireframeRenderMode;
140
140
  caption?: string;
141
+ /** Outer surface frame. `auto` lets the host choose the right default. */
142
+ frame?: BlockVisualFrame;
141
143
  /**
142
144
  * Neutral, textless loading register. The renderer drops borders, the sketch
143
145
  * outline, and color, rendering soft placeholder geometry only.
@@ -205,6 +207,7 @@ const elNameSchema = z.enum(
205
207
  WIREFRAME_EL_NAMES as [WireframeElName, ...WireframeElName[]],
206
208
  );
207
209
  const idSchema = z.string().trim().min(1).max(120);
210
+ const visualFrameSchema = z.enum(["auto", "show", "hide"]);
208
211
 
209
212
  /**
210
213
  * Reject full-document HTML (html/head/body/script/style tags) in the `html` /
@@ -281,6 +284,7 @@ export const wireframeSchema = z
281
284
  surface: z.enum(["desktop", "mobile", "popover", "panel", "browser"]),
282
285
  renderMode: z.enum(["wireframe", "design"]).optional(),
283
286
  caption: z.string().trim().max(400).optional(),
287
+ frame: visualFrameSchema.optional(),
284
288
  skeleton: z.boolean().optional(),
285
289
  html: z
286
290
  .string()
@@ -387,6 +391,7 @@ function serializeScreen(data: WireframeData): string {
387
391
  prop("surface", data.surface),
388
392
  prop("renderMode", data.renderMode),
389
393
  prop("caption", data.caption),
394
+ prop("frame", data.frame),
390
395
  prop("html", data.html),
391
396
  prop("css", data.css),
392
397
  prop("skeleton", data.skeleton),
@@ -494,6 +499,7 @@ function parseScreen(node: WireframeMdxNode, idContext: string): WireframeData {
494
499
  (stringAttr(node, "surface") as WireframeData["surface"]) ?? "desktop",
495
500
  renderMode: stringAttr(node, "renderMode") as WireframeData["renderMode"],
496
501
  caption: stringAttr(node, "caption"),
502
+ frame: stringAttr(node, "frame") as WireframeData["frame"],
497
503
  html: requiredStringAttr(node, "html"),
498
504
  css: requiredStringAttr(node, "css"),
499
505
  skeleton: boolAttr(node, "skeleton"),
@@ -113,6 +113,7 @@ function ArtboardFrame({
113
113
  skeleton,
114
114
  renderMode,
115
115
  roughOverlay = true,
116
+ showFrame = true,
116
117
  selector,
117
118
  caption,
118
119
  render,
@@ -129,6 +130,7 @@ function ArtboardFrame({
129
130
  skeleton?: boolean;
130
131
  renderMode?: "wireframe" | "design";
131
132
  roughOverlay?: boolean;
133
+ showFrame?: boolean;
132
134
  selector: string;
133
135
  caption?: string;
134
136
  render: (ctx: {
@@ -164,6 +166,9 @@ function ArtboardFrame({
164
166
  const designMode = renderMode === "design";
165
167
  const sketchy = !designMode && style === "sketchy" && !skeleton;
166
168
  const roughEnabled = sketchy && roughOverlay;
169
+ const frameBorder = skeleton
170
+ ? "var(--plan-placeholder-line, var(--plan-line, hsl(var(--border))))"
171
+ : "var(--plan-line, hsl(var(--border)))";
167
172
 
168
173
  useEffect(() => {
169
174
  const element = fitRef.current;
@@ -231,6 +236,7 @@ function ArtboardFrame({
231
236
  ref={ref}
232
237
  className="plan-kit-artboard relative"
233
238
  data-rough-scope="wireframe"
239
+ data-frame={showFrame ? "show" : "hide"}
234
240
  style={{
235
241
  width,
236
242
  // Auto-height by default (content-driven, floored at `minHeight`);
@@ -259,10 +265,19 @@ function ArtboardFrame({
259
265
  >
260
266
  {render({ theme, style })}
261
267
  </div>
268
+ {!roughEnabled && showFrame && (
269
+ <div
270
+ className="pointer-events-none absolute inset-0"
271
+ style={{
272
+ borderRadius: preset.radius,
273
+ border: `1.5px solid ${frameBorder}`,
274
+ }}
275
+ />
276
+ )}
262
277
  <RoughOverlay
263
278
  scopeRef={ref}
264
279
  enabled={roughEnabled}
265
- drawFrame={false}
280
+ drawFrame={showFrame}
266
281
  frameRadius={preset.radius}
267
282
  selector={selector}
268
283
  />
@@ -313,10 +328,12 @@ function WireframeStyleToggleButton() {
313
328
  function HtmlArtboard({
314
329
  data,
315
330
  ctx: _ctx,
331
+ showFrame,
316
332
  compact,
317
333
  }: {
318
334
  data: WireframeData;
319
335
  ctx: BlockRenderContext;
336
+ showFrame: boolean;
320
337
  compact?: boolean;
321
338
  }) {
322
339
  const renderMode = data.renderMode ?? "wireframe";
@@ -350,6 +367,7 @@ function HtmlArtboard({
350
367
  compact={compact}
351
368
  skeleton={data.skeleton}
352
369
  renderMode={renderMode}
370
+ showFrame={showFrame}
353
371
  selector={HTML_ROUGH_SELECTOR}
354
372
  caption={data.caption}
355
373
  render={({ theme, style }) => (
@@ -357,6 +375,7 @@ function HtmlArtboard({
357
375
  className="plan-html-frame"
358
376
  data-theme={theme}
359
377
  data-style={style}
378
+ data-frame={showFrame ? "show" : "hide"}
360
379
  data-render-mode={renderMode}
361
380
  data-plan-design-scope={scopeId}
362
381
  data-skeleton={data.skeleton ? "true" : undefined}
@@ -378,9 +397,11 @@ function HtmlArtboard({
378
397
 
379
398
  function KitArtboard({
380
399
  data,
400
+ showFrame,
381
401
  compact,
382
402
  }: {
383
403
  data: WireframeData;
404
+ showFrame: boolean;
384
405
  compact?: boolean;
385
406
  }) {
386
407
  return (
@@ -388,6 +409,7 @@ function KitArtboard({
388
409
  surface={data.surface}
389
410
  compact={compact}
390
411
  skeleton={data.skeleton}
412
+ showFrame={showFrame}
391
413
  selector="[data-rough]"
392
414
  caption={data.caption}
393
415
  render={({ theme, style }) => (
@@ -430,10 +452,27 @@ function WireframeSurfaceView({
430
452
  ctx: BlockRenderContext;
431
453
  compact?: boolean;
432
454
  }) {
455
+ const showFrame = resolveVisualFrame(data.frame, ctx);
433
456
  if (isHtmlData(data)) {
434
- return <HtmlArtboard data={data} ctx={ctx} compact={compact} />;
457
+ return (
458
+ <HtmlArtboard
459
+ data={data}
460
+ ctx={ctx}
461
+ showFrame={showFrame}
462
+ compact={compact}
463
+ />
464
+ );
435
465
  }
436
- return <KitArtboard data={data} compact={compact} />;
466
+ return <KitArtboard data={data} showFrame={showFrame} compact={compact} />;
467
+ }
468
+
469
+ function resolveVisualFrame(
470
+ frame: WireframeData["frame"],
471
+ ctx: BlockRenderContext,
472
+ ): boolean {
473
+ const resolved =
474
+ frame && frame !== "auto" ? frame : (ctx.visualFrame ?? "show");
475
+ return resolved !== "hide";
437
476
  }
438
477
 
439
478
  /* -------------------------------------------------------------------------- */
@@ -19,6 +19,9 @@ import type { ZodType } from "zod";
19
19
  /** Where a block can be placed in a document. */
20
20
  export type BlockPlacement = "block" | "inline";
21
21
 
22
+ /** How visual blocks decide whether to draw an outer surface frame. */
23
+ export type BlockVisualFrame = "auto" | "show" | "hide";
24
+
22
25
  /**
23
26
  * A serialized MDX/NFM attribute value before the shared `prop()` encoder runs.
24
27
  * `prop()` decides string-vs-JSON encoding; this is just the value domain.
@@ -96,6 +99,12 @@ export interface BlockRenderContext {
96
99
  dialect?: "gfm" | "nfm";
97
100
  /** Document text direction inferred by the host app. */
98
101
  textDirection?: "ltr" | "rtl";
102
+ /**
103
+ * Host default for visual wireframe/diagram frames when block data leaves
104
+ * `frame` unset or set to `auto`. Plans/recaps usually show a frame; docs can
105
+ * hide it by default and let individual blocks opt back in.
106
+ */
107
+ visualFrame?: Exclude<BlockVisualFrame, "auto">;
99
108
  /** Resolve an asset id → displayable URL. */
100
109
  resolveAssetSrc?: (assetId: string) => string | undefined;
101
110
  /** Open the shared asset picker (returns the chosen asset). */
@@ -75,7 +75,11 @@ const PENDING_SELECTION_KEY = "pending-selection-context";
75
75
  // ─── displayableUserMessageText ───────────────────────────────────────────────
76
76
 
77
77
  export function displayableUserMessageText(text: string): string {
78
- return text.replace(/<context>[\s\S]*?<\/context>\n?/g, "").trim();
78
+ return text
79
+ .replace(/<context\b[^>]*>[\s\S]*?<\/context>\n?/gi, "")
80
+ .replace(/<context\b[^>]*>[\s\S]*$/gi, "")
81
+ .replace(/<\/context>/gi, "")
82
+ .trim();
79
83
  }
80
84
 
81
85
  // ─── Message timestamp helpers ────────────────────────────────────────────────
@@ -214,7 +218,7 @@ export function SelectionAttachedPill() {
214
218
  if (length === null || length === 0) return null;
215
219
 
216
220
  return (
217
- <div className="shrink-0 px-3 pt-1.5 -mb-1">
221
+ <div className="agent-selection-attached-pill shrink-0 px-3 pt-1.5 -mb-1">
218
222
  <div className="inline-flex items-center gap-1.5 rounded-full border border-border bg-muted/50 px-2 py-0.5 text-[11px] text-muted-foreground">
219
223
  <IconQuote size={11} />
220
224
  <span>{length.toLocaleString()} chars of selection attached</span>
@@ -601,6 +601,15 @@ function dispatchActivityClear(tabId: string | undefined) {
601
601
  );
602
602
  }
603
603
 
604
+ function dispatchMissingApiKey(tabId: string | undefined) {
605
+ if (typeof window === "undefined") return;
606
+ window.dispatchEvent(
607
+ new CustomEvent("agent-chat:missing-api-key", {
608
+ detail: { tabId },
609
+ }),
610
+ );
611
+ }
612
+
604
613
  function pendingToolNames(content: ContentPart[]): {
605
614
  activity: string[];
606
615
  running: string[];
@@ -1056,7 +1065,7 @@ export function processEvent(
1056
1065
  errorCode,
1057
1066
  };
1058
1067
  if (typeof window !== "undefined") {
1059
- window.dispatchEvent(new Event("agent-chat:missing-api-key"));
1068
+ dispatchMissingApiKey(tabId);
1060
1069
  window.dispatchEvent(
1061
1070
  new CustomEvent("agent-chat:run-error", {
1062
1071
  detail: { ...runError, tabId },
@@ -1156,9 +1165,7 @@ export function processEvent(
1156
1165
  }
1157
1166
  const normalized = normalizeChatError(errMsg, ev.errorCode);
1158
1167
  if (isMissingCredentialText(errMsg, ev.errorCode)) {
1159
- if (typeof window !== "undefined") {
1160
- window.dispatchEvent(new Event("agent-chat:missing-api-key"));
1161
- }
1168
+ dispatchMissingApiKey(tabId);
1162
1169
  }
1163
1170
  const runError = {
1164
1171
  message: normalized.message,
@@ -15,10 +15,20 @@ export interface UseAgentEngineConfiguredResult {
15
15
  }
16
16
 
17
17
  export interface FetchAgentEngineConfiguredStateOptions {
18
+ /**
19
+ * Legacy hint from explicit missing-key stream events. Kept for API
20
+ * compatibility, but missing state still requires authoritative status
21
+ * responses so transient endpoint failures do not clobber connected state.
22
+ */
18
23
  missingFallback?: boolean;
19
24
  timeoutMs?: number;
20
25
  }
21
26
 
27
+ export interface UseAgentEngineConfiguredOptions {
28
+ tabId?: string | null;
29
+ threadId?: string | null;
30
+ }
31
+
22
32
  const DEFAULT_STATUS_CHECK_TIMEOUT_MS = 2500;
23
33
 
24
34
  async function fetchStatusJson(
@@ -60,6 +70,27 @@ function hasConfiguredFlag(value: unknown): value is { configured: boolean } {
60
70
  );
61
71
  }
62
72
 
73
+ function missingKeyEventMatchesScope(
74
+ event: Event,
75
+ options: UseAgentEngineConfiguredOptions | undefined,
76
+ ): boolean {
77
+ const detail = (event as CustomEvent).detail as
78
+ | { tabId?: unknown; threadId?: unknown }
79
+ | undefined;
80
+ const eventTabId = typeof detail?.tabId === "string" ? detail.tabId : null;
81
+ const eventThreadId =
82
+ typeof detail?.threadId === "string" ? detail.threadId : null;
83
+ if (!eventTabId && !eventThreadId) return true;
84
+
85
+ const tabId = options?.tabId ?? null;
86
+ const threadId = options?.threadId ?? null;
87
+ if (!tabId && !threadId) return true;
88
+ return (
89
+ (eventTabId != null && eventTabId === tabId) ||
90
+ (eventThreadId != null && eventThreadId === threadId)
91
+ );
92
+ }
93
+
63
94
  export async function fetchAgentEngineConfiguredState(
64
95
  enabled = true,
65
96
  options?: FetchAgentEngineConfiguredStateOptions,
@@ -76,22 +107,31 @@ export async function fetchAgentEngineConfiguredState(
76
107
  fetchStatusJson("/_agent-native/agent-engine/status", timeoutMs),
77
108
  ]);
78
109
 
79
- // All three failed — likely a flaky network; keep the caller in unknown
80
- // unless this check is reacting to an explicit missing-key stream event.
110
+ // All three failed — likely a flaky network; keep the caller in unknown.
111
+ // Even an explicit missing-key stream event should not pin the composer into
112
+ // setup without a fresh authoritative status response.
81
113
  if (envKeys == null && builderStatus == null && engineStatus == null) {
82
- return options?.missingFallback ? "missing" : "unknown";
114
+ return "unknown";
83
115
  }
84
116
 
85
- const keys = (envKeys ?? []) as Array<{
86
- key: string;
87
- configured: boolean;
88
- }>;
117
+ const envKeysKnown = Array.isArray(envKeys);
118
+ const builderStatusKnown = hasConfiguredFlag(builderStatus);
119
+ const engineStatusKnown = hasConfiguredFlag(engineStatus);
120
+ const keys = envKeysKnown
121
+ ? (envKeys as Array<{
122
+ key: string;
123
+ configured: boolean;
124
+ }>)
125
+ : [];
89
126
  const llmKeys = keys.filter((k) => PROVIDER_ENV_VAR_SET.has(k.key));
90
127
  const anyConfigured =
91
128
  llmKeys.some((k) => k.configured) ||
92
- (hasConfiguredFlag(builderStatus) && builderStatus.configured) ||
93
- (hasConfiguredFlag(engineStatus) && engineStatus.configured);
94
- return anyConfigured ? "configured" : "missing";
129
+ (builderStatusKnown && builderStatus.configured) ||
130
+ (engineStatusKnown && engineStatus.configured);
131
+ if (anyConfigured) return "configured";
132
+ return envKeysKnown && builderStatusKnown && engineStatusKnown
133
+ ? "missing"
134
+ : "unknown";
95
135
  }
96
136
 
97
137
  /**
@@ -103,6 +143,7 @@ export async function fetchAgentEngineConfiguredState(
103
143
  */
104
144
  export function useAgentEngineConfigured(
105
145
  enabled = true,
146
+ options?: UseAgentEngineConfiguredOptions,
106
147
  ): UseAgentEngineConfiguredResult {
107
148
  const [state, setState] = useState<AgentEngineConfiguredState>("unknown");
108
149
 
@@ -119,7 +160,8 @@ export function useAgentEngineConfigured(
119
160
  const onConfiguredChanged = () => {
120
161
  void check();
121
162
  };
122
- const onMissing = () => {
163
+ const onMissing = (event: Event) => {
164
+ if (!missingKeyEventMatchesScope(event, options)) return;
123
165
  if (!enabled) {
124
166
  setState("configured");
125
167
  return;
@@ -143,7 +185,7 @@ export function useAgentEngineConfigured(
143
185
  );
144
186
  window.removeEventListener("agent-chat:missing-api-key", onMissing);
145
187
  };
146
- }, [enabled]);
188
+ }, [enabled, options?.tabId, options?.threadId]);
147
189
 
148
190
  return { missing: state === "missing", state };
149
191
  }
@@ -7124,11 +7124,15 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7124
7124
  setResponseStatus(event, 400);
7125
7125
  return { error: "message is required" };
7126
7126
  }
7127
- // Strip mention markup: @[Name|type] @Name
7128
- const cleanMessage = message.replace(
7129
- /@\[([^\]|]+)\|[^\]]*\]/g,
7130
- "@$1",
7131
- );
7127
+ // Strip hidden context and mention markup before title generation.
7128
+ // Fallback titles are often direct truncations, so never let injected
7129
+ // prompt context become a visible tab label.
7130
+ const cleanMessage = message
7131
+ .replace(/<context\b[^>]*>[\s\S]*?<\/context>\n?/gi, "")
7132
+ .replace(/<context\b[^>]*>[\s\S]*$/gi, "")
7133
+ .replace(/<\/context>/gi, "")
7134
+ .replace(/@\[([^\]|]+)\|[^\]]*\]/g, "@$1")
7135
+ .trim();
7132
7136
  // Mirror the chat-run resolution so BYO-key users have title
7133
7137
  // generation billed to their own key instead of the platform key.
7134
7138
  const { getOwnerActiveApiKey } =
@@ -682,6 +682,23 @@
682
682
  line-height: 1.75rem;
683
683
  }
684
684
 
685
+ .agent-composer-stack {
686
+ position: relative;
687
+ flex-shrink: 0;
688
+ width: 100%;
689
+ }
690
+
691
+ .agent-composer-stack[data-agent-composer-adjacent-ui="true"],
692
+ .agent-composer-stack:has(.agent-selection-attached-pill) {
693
+ display: flex;
694
+ flex-direction: column;
695
+ gap: 0.5rem;
696
+ }
697
+
698
+ .agent-composer-setup-card {
699
+ width: 100%;
700
+ }
701
+
685
702
  [data-agent-empty-state="centered"] {
686
703
  justify-content: center;
687
704
  }
@@ -691,6 +708,33 @@
691
708
  overflow: visible;
692
709
  }
693
710
 
711
+ [data-agent-empty-state="centered"]
712
+ > .agent-composer-stack:not([data-agent-composer-adjacent-ui="true"]):not(
713
+ :has(.agent-selection-attached-pill)
714
+ )
715
+ > .agent-composer-setup-card {
716
+ position: absolute;
717
+ left: 0;
718
+ right: 0;
719
+ z-index: 3;
720
+ }
721
+
722
+ [data-agent-empty-state="centered"]
723
+ > .agent-composer-stack:not([data-agent-composer-adjacent-ui="true"]):not(
724
+ :has(.agent-selection-attached-pill)
725
+ )
726
+ > .agent-composer-setup-card[data-agent-composer-setup-position="above"] {
727
+ bottom: calc(100% + 0.5rem);
728
+ }
729
+
730
+ [data-agent-empty-state="centered"]
731
+ > .agent-composer-stack:not([data-agent-composer-adjacent-ui="true"]):not(
732
+ :has(.agent-selection-attached-pill)
733
+ )
734
+ > .agent-composer-setup-card[data-agent-composer-setup-position="below"] {
735
+ top: calc(100% + 0.5rem);
736
+ }
737
+
694
738
  [data-agent-empty-state="compact-setup"] > .agent-chat-scroll {
695
739
  flex: 0 0 auto;
696
740
  overflow: visible;
@@ -1290,6 +1290,20 @@
1290
1290
  line-height: 1.4;
1291
1291
  }
1292
1292
 
1293
+ .plan-diagram-frame[data-frame="show"] {
1294
+ border: 1px solid var(--wf-diagram-line);
1295
+ padding: 10px;
1296
+ }
1297
+
1298
+ .plan-diagram-frame[data-frame="show"][data-rough-ready] {
1299
+ border-color: transparent;
1300
+ }
1301
+
1302
+ .plan-diagram-frame[data-frame="hide"] {
1303
+ border: 0;
1304
+ background: transparent;
1305
+ }
1306
+
1293
1307
  .plan-diagram-frame[data-theme="dark"] {
1294
1308
  --wf-diagram-line: color-mix(in srgb, var(--wf-sketch) 72%, var(--wf-line));
1295
1309
  }
@@ -1312,6 +1326,10 @@
1312
1326
  direction: inherit;
1313
1327
  }
1314
1328
 
1329
+ .plan-diagram-frame[data-frame="show"] .plan-diagram-frame-content {
1330
+ padding: 0;
1331
+ }
1332
+
1315
1333
  .plan-diagram-frame-content
1316
1334
  > .diagram-panel:not([data-rough]):not([data-diagram-frame]) {
1317
1335
  --rough-skip: 1;
@@ -109,12 +109,16 @@ so you never emit a block the editor cannot render or round-trip:
109
109
  `--wf-paper`, `--wf-card`, `--wf-accent`, `--wf-accent-soft`, `--wf-warn`, and
110
110
  `--wf-ok`, and switch to Excalifont plus rough.js outlines in sketchy mode. Do not
111
111
  set `font-family` and do not hard-code hex, rgb, or hsl colors in diagram HTML
112
- or CSS. Leave room for the sketch font: keep labels short, give nodes generous
113
- width, and place boundary/annotation labels in unused space instead of over
114
- nodes; labels must not overlap nodes, connectors, or each other. For small
115
- text/SVG changes to an existing HTML diagram, use `patch-diagram-html` with a
116
- unique `find`/`replace` snippet instead of resending the whole `data.html`
117
- string. Use legacy `nodes` / `edges` only for small previews or truly
112
+ or CSS. Choose the outer `frame` intentionally: use `show` when the diagram
113
+ stands alone in a recap, comparison, or prose section; use `hide` when the
114
+ diagram sits inside docs chrome, columns, tabs, cards, a canvas surface, or
115
+ already has visible `.diagram-panel` / `.diagram-box` structure. Leave room
116
+ for the sketch font: keep labels short, give nodes generous width, and place
117
+ boundary/annotation labels in unused space instead of over nodes; labels must
118
+ not overlap nodes, connectors, or each other. For small text/SVG changes to an
119
+ existing HTML diagram, use `patch-diagram-html` with a unique
120
+ `find`/`replace` snippet instead of resending the whole `data.html` string.
121
+ Use legacy `nodes` / `edges` only for small previews or truly
118
122
  sequential flows. In architecture/code plans, prefer a repeated section rhythm:
119
123
  recommendation title, confidence and category badges, code-path evidence, a
120
124
  local before/after or current/target spatial diagram, then concise