@axiom-lattice/core 2.1.37 → 2.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +986 -91
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1031 -137
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -109,6 +109,7 @@ __export(index_exports, {
|
|
|
109
109
|
createQueryTablesListTool: () => createQueryTablesListTool,
|
|
110
110
|
createTeamMiddleware: () => createTeamMiddleware,
|
|
111
111
|
createTeammateTools: () => createTeammateTools,
|
|
112
|
+
createWidgetMiddleware: () => createWidgetMiddleware,
|
|
112
113
|
decrypt: () => decrypt,
|
|
113
114
|
describeCronExpression: () => describeCronExpression,
|
|
114
115
|
embeddingsLatticeManager: () => embeddingsLatticeManager,
|
|
@@ -829,11 +830,11 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
829
830
|
* @param key Lattice键名
|
|
830
831
|
* @param tool 已有的StructuredTool实例
|
|
831
832
|
*/
|
|
832
|
-
registerExistingTool(key,
|
|
833
|
+
registerExistingTool(key, tool50) {
|
|
833
834
|
const config = {
|
|
834
|
-
name:
|
|
835
|
-
description:
|
|
836
|
-
schema:
|
|
835
|
+
name: tool50.name,
|
|
836
|
+
description: tool50.description,
|
|
837
|
+
schema: tool50.schema,
|
|
837
838
|
// StructuredTool的schema已经是Zod兼容的
|
|
838
839
|
needUserApprove: false
|
|
839
840
|
// MCP工具默认不需要用户批准
|
|
@@ -841,7 +842,7 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
841
842
|
const toolLattice = {
|
|
842
843
|
key,
|
|
843
844
|
config,
|
|
844
|
-
client:
|
|
845
|
+
client: tool50
|
|
845
846
|
};
|
|
846
847
|
this.register(key, toolLattice);
|
|
847
848
|
}
|
|
@@ -867,7 +868,7 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
867
868
|
};
|
|
868
869
|
var toolLatticeManager = ToolLatticeManager.getInstance();
|
|
869
870
|
var registerToolLattice = (key, config, executor) => toolLatticeManager.registerLattice(key, config, executor);
|
|
870
|
-
var registerExistingTool = (key,
|
|
871
|
+
var registerExistingTool = (key, tool50) => toolLatticeManager.registerExistingTool(key, tool50);
|
|
871
872
|
var getToolLattice = (key) => toolLatticeManager.getToolLattice(key);
|
|
872
873
|
var getToolDefinition = (key) => toolLatticeManager.getToolDefinition(key);
|
|
873
874
|
var getToolClient = (key) => toolLatticeManager.getToolClient(key);
|
|
@@ -4811,7 +4812,7 @@ var createReactAgentSchema = (schema) => {
|
|
|
4811
4812
|
};
|
|
4812
4813
|
|
|
4813
4814
|
// src/agent_lattice/builders/ReActAgentGraphBuilder.ts
|
|
4814
|
-
var
|
|
4815
|
+
var import_langchain51 = require("langchain");
|
|
4815
4816
|
|
|
4816
4817
|
// src/deep_agent_new/backends/sandboxFiles.ts
|
|
4817
4818
|
var import_sandbox2 = require("@agent-infra/sandbox");
|
|
@@ -8373,6 +8374,896 @@ function createAskUserClarifyMiddleware() {
|
|
|
8373
8374
|
});
|
|
8374
8375
|
}
|
|
8375
8376
|
|
|
8377
|
+
// src/middlewares/widgetMiddleware.ts
|
|
8378
|
+
var import_langchain50 = require("langchain");
|
|
8379
|
+
|
|
8380
|
+
// src/tool_lattice/widget/loadGuidelines.ts
|
|
8381
|
+
var import_langchain48 = require("langchain");
|
|
8382
|
+
var import_zod47 = require("zod");
|
|
8383
|
+
|
|
8384
|
+
// src/middlewares/guidelines/index.ts
|
|
8385
|
+
var CORE = `# Imagine \u2014 Visual Creation Suite
|
|
8386
|
+
|
|
8387
|
+
## Modules
|
|
8388
|
+
Call load_guidelines again with the modules parameter to load detailed guidance:
|
|
8389
|
+
- \`diagram\` \u2014 SVG flowcharts, structural diagrams, illustrative diagrams
|
|
8390
|
+
- \`mockup\` \u2014 UI mockups, forms, cards, dashboards
|
|
8391
|
+
- \`interactive\` \u2014 interactive explainers with controls
|
|
8392
|
+
- \`chart\` \u2014 charts and data analysis (includes Chart.js)
|
|
8393
|
+
- \`art\` \u2014 illustration and generative art
|
|
8394
|
+
Pick the closest fit. The module includes all relevant design guidance.
|
|
8395
|
+
|
|
8396
|
+
**Complexity budget \u2014 hard limits:**
|
|
8397
|
+
- Box subtitles: \u22645 words. Detail goes in click-through (\`sendPrompt\`) or the prose below \u2014 not the box.
|
|
8398
|
+
- Colors: \u22642 ramps per diagram. If colors encode meaning (states, tiers), add a 1-line legend. Otherwise use one neutral ramp.
|
|
8399
|
+
- Horizontal tier: \u22644 boxes at full width (~140px each). 5+ boxes \u2192 shrink to \u2264110px OR wrap to 2 rows OR split into overview + detail diagrams.
|
|
8400
|
+
|
|
8401
|
+
If you catch yourself writing "click to learn more" in prose, the diagram itself must ACTUALLY be sparse. Don't promise brevity then front-load everything.
|
|
8402
|
+
|
|
8403
|
+
You create rich visual content \u2014 SVG diagrams/illustrations and HTML interactive widgets \u2014 that renders inline in conversation. The best output feels like a natural extension of the chat.
|
|
8404
|
+
|
|
8405
|
+
## Core Design System
|
|
8406
|
+
|
|
8407
|
+
These rules apply to ALL use cases.
|
|
8408
|
+
|
|
8409
|
+
### Philosophy
|
|
8410
|
+
- **Seamless**: Users shouldn't notice where claude.ai ends and your widget begins.
|
|
8411
|
+
- **Flat**: No gradients, mesh backgrounds, noise textures, or decorative effects. Clean flat surfaces.
|
|
8412
|
+
- **Compact**: Show the essential inline. Explain the rest in text.
|
|
8413
|
+
- **Text goes in your response, visuals go in the tool** \u2014 All explanatory text, descriptions, introductions, and summaries must be written as normal response text OUTSIDE the tool call. The tool output should contain ONLY the visual element (diagram, chart, interactive widget). Never put paragraphs of explanation, section headings, or descriptive prose inside the HTML/SVG. If the user asks "explain X", write the explanation in your response and use the tool only for the visual that accompanies it. The user's font settings only apply to your response text, not to text inside the widget.
|
|
8414
|
+
|
|
8415
|
+
### Streaming
|
|
8416
|
+
Output streams token-by-token. Structure code so useful content appears early.
|
|
8417
|
+
- **HTML**: \`<style>\` (short) \u2192 content HTML \u2192 \`<script>\` last.
|
|
8418
|
+
- **SVG**: \`<defs>\` (markers) \u2192 visual elements immediately.
|
|
8419
|
+
- Prefer inline \`style="..."\` over \`<style>\` blocks \u2014 inputs/controls must look correct mid-stream.
|
|
8420
|
+
- Keep \`<style>\` under ~15 lines. Interactive widgets with inputs and sliders need more style rules \u2014 that's fine, but don't bloat with decorative CSS.
|
|
8421
|
+
- Gradients, shadows, and blur flash during streaming DOM diffs. Use solid flat fills instead.
|
|
8422
|
+
|
|
8423
|
+
### Rules
|
|
8424
|
+
- No \`<!-- comments -->\` or \`/* comments */\` (waste tokens, break streaming)
|
|
8425
|
+
- No font-size below 11px
|
|
8426
|
+
- No emoji \u2014 use CSS shapes or SVG paths
|
|
8427
|
+
- No gradients, drop shadows, blur, glow, or neon effects
|
|
8428
|
+
- No dark/colored backgrounds on outer containers (transparent only \u2014 host provides the bg)
|
|
8429
|
+
- **Typography**: The default font is Anthropic Sans. For the rare editorial/blockquote moment, use \`font-family: var(--font-serif)\`.
|
|
8430
|
+
- **Headings**: h1 = 22px, h2 = 18px, h3 = 16px \u2014 all \`font-weight: 500\`. Heading color is pre-set to \`var(--color-text-primary)\` \u2014 don't override it. Body text = 16px, weight 400, \`line-height: 1.7\`. **Two weights only: 400 regular, 500 bold.** Never use 600 or 700 \u2014 they look heavy against the host UI.
|
|
8431
|
+
- **Sentence case** always. Never Title Case, never ALL CAPS. This applies everywhere including SVG text labels and diagram headings.
|
|
8432
|
+
- **No mid-sentence bolding**, including in your response text around the tool call. Entity names, class names, function names go in \`code style\` not **bold**. Bold is for headings and labels only.
|
|
8433
|
+
- The widget container is \`display: block; width: 100%\`. Your HTML fills it naturally \u2014 no wrapper div needed. Just start with your content directly. If you want vertical breathing room, add \`padding: 1rem 0\` on your first element.
|
|
8434
|
+
- Never use \`position: fixed\` \u2014 the iframe viewport sizes itself to your in-flow content height, so fixed-positioned elements (modals, overlays, tooltips) collapse it to \`min-height: 100px\`. For modal/overlay mockups: wrap everything in a normal-flow \`<div style="min-height: 400px; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center;">\` and put the modal inside \u2014 it's a faux viewport that actually contributes layout height.
|
|
8435
|
+
- No DOCTYPE, \`<html>\`, \`<head>\`, or \`<body>\` \u2014 just content fragments.
|
|
8436
|
+
- When placing text on a colored background (badges, pills, cards, tags), use the darkest shade from that same color family for the text \u2014 never plain black or generic gray.
|
|
8437
|
+
- **Corners**: use \`border-radius: var(--border-radius-md)\` (or \`-lg\` for cards) in HTML. In SVG, \`rx="4"\` is the default \u2014 larger values make pills, use only when you mean a pill.
|
|
8438
|
+
- **No rounded corners on single-sided borders** \u2014 if using \`border-left\` or \`border-top\` accents, set \`border-radius: 0\`. Rounded corners only work with full borders on all sides.
|
|
8439
|
+
- **No titles or prose inside the tool output** \u2014 see Philosophy above.
|
|
8440
|
+
- **Icon sizing**: When using emoji or inline SVG icons, explicitly set \`font-size: 16px\` for emoji or \`width: 16px; height: 16px\` for SVG icons. Never let icons inherit the container's font size \u2014 they will render too large. For larger decorative icons, use 24px max.
|
|
8441
|
+
- No tabs, carousels, or \`display: none\` sections during streaming \u2014 hidden content streams invisibly. Show all content stacked vertically. (Post-streaming JS-driven steppers are fine \u2014 see Illustrative/Interactive sections.)
|
|
8442
|
+
- No nested scrolling \u2014 auto-fit height.
|
|
8443
|
+
- Scripts execute after streaming \u2014 load libraries via \`<script src="https://cdnjs.cloudflare.com/ajax/libs/...">\` (UMD globals), then use the global in a plain \`<script>\` that follows.
|
|
8444
|
+
- **CDN allowlist (CSP-enforced)**: external resources may ONLY load from \`cdnjs.cloudflare.com\`, \`esm.sh\`, \`cdn.jsdelivr.net\`, \`unpkg.com\`. All other origins are blocked by the sandbox \u2014 the request silently fails.
|
|
8445
|
+
|
|
8446
|
+
### CSS Variables
|
|
8447
|
+
**Backgrounds**: \`--color-background-primary\` (white), \`-secondary\` (surfaces), \`-tertiary\` (page bg), \`-info\`, \`-danger\`, \`-success\`, \`-warning\`
|
|
8448
|
+
**Text**: \`--color-text-primary\` (black), \`-secondary\` (muted), \`-tertiary\` (hints), \`-info\`, \`-danger\`, \`-success\`, \`-warning\`
|
|
8449
|
+
**Borders**: \`--color-border-tertiary\` (0.15\u03B1, default), \`-secondary\` (0.3\u03B1, hover), \`-primary\` (0.4\u03B1), semantic \`-info/-danger/-success/-warning\`
|
|
8450
|
+
**Typography**: \`--font-sans\`, \`--font-serif\`, \`--font-mono\`
|
|
8451
|
+
**Layout**: \`--border-radius-md\` (8px), \`--border-radius-lg\` (12px \u2014 preferred for most components), \`--border-radius-xl\` (16px)
|
|
8452
|
+
All auto-adapt to light/dark mode. For custom colors in HTML, use CSS variables.
|
|
8453
|
+
|
|
8454
|
+
**Dark mode is mandatory** \u2014 every color must work in both modes:
|
|
8455
|
+
- In SVG: use the pre-built color classes (\`c-blue\`, \`c-teal\`, \`c-amber\`, etc.) for colored nodes \u2014 they handle light/dark mode automatically. Never write \`<style>\` blocks for colors.
|
|
8456
|
+
- In SVG: every \`<text>\` element needs a class (\`t\`, \`ts\`, \`th\`) \u2014 never omit fill or use \`fill="inherit"\`. Inside a \`c-{color}\` parent, text classes auto-adjust to the ramp.
|
|
8457
|
+
- In HTML: always use CSS variables (--color-text-primary, --color-text-secondary) for text. Never hardcode colors like color: #333 \u2014 invisible in dark mode.
|
|
8458
|
+
- Mental test: if the background were near-black, would every text element still be readable?
|
|
8459
|
+
|
|
8460
|
+
### sendPrompt(text)
|
|
8461
|
+
A global function that sends a message to chat as if the user typed it. Use it when the user's next step benefits from Claude thinking. Handle filtering, sorting, toggling, and calculations in JS instead.
|
|
8462
|
+
|
|
8463
|
+
### Links
|
|
8464
|
+
\`<a href="https://...">\` just works \u2014 clicks are intercepted and open the host's link-confirmation dialog. Or call \`openLink(url)\` directly.
|
|
8465
|
+
|
|
8466
|
+
## When nothing fits
|
|
8467
|
+
Pick the closest use case below and adapt. When nothing fits cleanly:
|
|
8468
|
+
- Default to editorial layout if the content is explanatory
|
|
8469
|
+
- Default to card layout if the content is a bounded object
|
|
8470
|
+
- All core design system rules still apply
|
|
8471
|
+
- Use \`sendPrompt()\` for any action that benefits from Claude thinking`;
|
|
8472
|
+
var ART_AND_ILLUSTRATION = `## Art and illustration
|
|
8473
|
+
*"Draw me a sunset" / "Create a geometric pattern"*
|
|
8474
|
+
|
|
8475
|
+
Use \`imagine_svg\`. Same technical rules (viewBox, safe area) but the aesthetic is different:
|
|
8476
|
+
- Fill the canvas \u2014 art should feel rich, not sparse
|
|
8477
|
+
- Bold colors: mix \`--color-text-*\` categories for variety (info blue, success green, warning amber)
|
|
8478
|
+
- Art is the one place custom \`<style>\` color blocks are fine \u2014 freestyle colors, \`prefers-color-scheme\` for dark mode variants if you want them
|
|
8479
|
+
- Layer overlapping opaque shapes for depth
|
|
8480
|
+
- Organic forms with \`<path>\` curves, \`<ellipse>\`, \`<circle>\`
|
|
8481
|
+
- Texture via repetition (parallel lines, dots, hatching) not raster effects
|
|
8482
|
+
- Geometric patterns with \`<g transform="rotate()">\` for radial symmetry`;
|
|
8483
|
+
var CHARTS_CHART_JS = `## Charts (Chart.js)
|
|
8484
|
+
\`\`\`html
|
|
8485
|
+
<div style="position: relative; width: 100%; height: 300px;">
|
|
8486
|
+
<canvas id="myChart"></canvas>
|
|
8487
|
+
</div>
|
|
8488
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js" onload="initChart()"></script>
|
|
8489
|
+
<script>
|
|
8490
|
+
function initChart() {
|
|
8491
|
+
new Chart(document.getElementById('myChart'), {
|
|
8492
|
+
type: 'bar',
|
|
8493
|
+
data: { labels: ['Q1','Q2','Q3','Q4'], datasets: [{ label: 'Revenue', data: [12,19,8,15] }] },
|
|
8494
|
+
options: { responsive: true, maintainAspectRatio: false }
|
|
8495
|
+
});
|
|
8496
|
+
}
|
|
8497
|
+
if (window.Chart) initChart();
|
|
8498
|
+
</script>
|
|
8499
|
+
\`\`\`
|
|
8500
|
+
|
|
8501
|
+
**Chart.js rules**:
|
|
8502
|
+
- Canvas cannot resolve CSS variables. Use hardcoded hex or Chart.js defaults.
|
|
8503
|
+
- Wrap \`<canvas>\` in \`<div>\` with explicit \`height\` and \`position: relative\`.
|
|
8504
|
+
- **Canvas sizing**: set height ONLY on the wrapper div, never on the canvas element itself. Use position: relative on the wrapper and responsive: true, maintainAspectRatio: false in Chart.js options. Never set CSS height directly on canvas \u2014 this causes wrong dimensions, especially for horizontal bar charts.
|
|
8505
|
+
- For horizontal bar charts: wrapper div height should be at least (number_of_bars * 40) + 80 pixels.
|
|
8506
|
+
- Load UMD build via \`<script src="https://cdnjs.cloudflare.com/ajax/libs/...">\` \u2014 sets \`window.Chart\` global. Follow with plain \`<script>\` (no \`type="module"\`).
|
|
8507
|
+
- **Script load ordering**: CDN scripts may not be loaded when the next \`<script>\` runs (especially during streaming). Always use \`onload="initChart()"\` on the CDN script tag, define your chart init in a named function, and add \`if (window.Chart) initChart();\` as a fallback at the end of your inline script. This guarantees charts render regardless of load order.
|
|
8508
|
+
- Multiple charts: use unique IDs (\`myChart1\`, \`myChart2\`). Each gets its own canvas+div pair.
|
|
8509
|
+
- For bubble and scatter charts: bubble radii extend past their center points, so points near axis boundaries get clipped. Pad the scale range \u2014 set \`scales.y.min\` and \`scales.y.max\` ~10% beyond your data range (same for x). Or use \`layout: { padding: 20 }\` as a blunt fallback.
|
|
8510
|
+
- Chart.js auto-skips x-axis labels when they'd overlap. If you have \u226412 categories and need all labels visible (waterfall, monthly series), set \`scales.x.ticks: { autoSkip: false, maxRotation: 45 }\` \u2014 missing labels make bars unidentifiable.
|
|
8511
|
+
|
|
8512
|
+
**Number formatting**: negative values are \`-$5M\` not \`$-5M\` \u2014 sign before currency symbol. Use a formatter: \`(v) => (v < 0 ? '-' : '') + '$' + Math.abs(v) + 'M'\`.
|
|
8513
|
+
|
|
8514
|
+
**Legends** \u2014 always disable Chart.js default and build custom HTML. The default uses round dots and no values; custom HTML gives small squares, tight spacing, and percentages:
|
|
8515
|
+
|
|
8516
|
+
\`\`\`js
|
|
8517
|
+
plugins: { legend: { display: false } }
|
|
8518
|
+
\`\`\`
|
|
8519
|
+
|
|
8520
|
+
\`\`\`html
|
|
8521
|
+
<div style="display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 8px; font-size: 12px; color: var(--color-text-secondary);">
|
|
8522
|
+
<span style="display: flex; align-items: center; gap: 4px;"><span style="width: 10px; height: 10px; border-radius: 2px; background: #3266ad;"></span>Chrome 65%</span>
|
|
8523
|
+
<span style="display: flex; align-items: center; gap: 4px;"><span style="width: 10px; height: 10px; border-radius: 2px; background: #73726c;"></span>Safari 18%</span>
|
|
8524
|
+
</div>
|
|
8525
|
+
\`\`\`
|
|
8526
|
+
|
|
8527
|
+
Include the value/percentage in each label when the data is categorical (pie, donut, single-series bar). Position the legend above the chart (\`margin-bottom\`) or below (\`margin-top\`) \u2014 not inside the canvas.
|
|
8528
|
+
|
|
8529
|
+
**Dashboard layout** \u2014 wrap summary numbers in metric cards (see UI fragment) above the chart. Chart canvas flows below without a card wrapper. Use \`sendPrompt()\` for drill-down: \`sendPrompt('Break down Q4 by region')\`.`;
|
|
8530
|
+
var COLOR_PALETTE = `## Color palette
|
|
8531
|
+
|
|
8532
|
+
9 color ramps, each with 7 stops from lightest to darkest. 50 = lightest fill, 100-200 = light fills, 400 = mid tones, 600 = strong/border, 800-900 = text on light fills.
|
|
8533
|
+
|
|
8534
|
+
| Class | Ramp | 50 (lightest) | 100 | 200 | 400 | 600 | 800 | 900 (darkest) |
|
|
8535
|
+
|-------|------|------|-----|-----|-----|-----|-----|------|
|
|
8536
|
+
| \`c-purple\` | Purple | #EEEDFE | #CECBF6 | #AFA9EC | #7F77DD | #534AB7 | #3C3489 | #26215C |
|
|
8537
|
+
| \`c-teal\` | Teal | #E1F5EE | #9FE1CB | #5DCAA5 | #1D9E75 | #0F6E56 | #085041 | #04342C |
|
|
8538
|
+
| \`c-coral\` | Coral | #FAECE7 | #F5C4B3 | #F0997B | #D85A30 | #993C1D | #712B13 | #4A1B0C |
|
|
8539
|
+
| \`c-pink\` | Pink | #FBEAF0 | #F4C0D1 | #ED93B1 | #D4537E | #993556 | #72243E | #4B1528 |
|
|
8540
|
+
| \`c-gray\` | Gray | #F1EFE8 | #D3D1C7 | #B4B2A9 | #888780 | #5F5E5A | #444441 | #2C2C2A |
|
|
8541
|
+
| \`c-blue\` | Blue | #E6F1FB | #B5D4F4 | #85B7EB | #378ADD | #185FA5 | #0C447C | #042C53 |
|
|
8542
|
+
| \`c-green\` | Green | #EAF3DE | #C0DD97 | #97C459 | #639922 | #3B6D11 | #27500A | #173404 |
|
|
8543
|
+
| \`c-amber\` | Amber | #FAEEDA | #FAC775 | #EF9F27 | #BA7517 | #854F0B | #633806 | #412402 |
|
|
8544
|
+
| \`c-red\` | Red | #FCEBEB | #F7C1C1 | #F09595 | #E24B4A | #A32D2D | #791F1F | #501313 |
|
|
8545
|
+
|
|
8546
|
+
**How to assign colors**: Color should encode meaning, not sequence. Don't cycle through colors like a rainbow (step 1 = blue, step 2 = amber, step 3 = red...). Instead:
|
|
8547
|
+
- Group nodes by **category** \u2014 all nodes of the same type share one color. E.g. in a vaccine diagram: all immune cells = purple, all pathogens = coral, all outcomes = teal.
|
|
8548
|
+
- For illustrative diagrams, map colors to **physical properties** \u2014 warm ramps for heat/energy, cool for cold/calm, green for organic, gray for structural/inert.
|
|
8549
|
+
- Use **gray for neutral/structural** nodes (start, end, generic steps).
|
|
8550
|
+
- Use **2-3 colors per diagram**, not 6+. More colors = more visual noise. A diagram with gray + purple + teal is cleaner than one using every ramp.
|
|
8551
|
+
- **Prefer purple, teal, coral, pink** for general diagram categories. Reserve blue, green, amber, and red for cases where the node genuinely represents an informational, success, warning, or error concept \u2014 those colors carry strong semantic connotations from UI conventions. (Exception: illustrative diagrams may use blue/amber/red freely when they map to physical properties like temperature or pressure.)
|
|
8552
|
+
|
|
8553
|
+
**Text on colored backgrounds:** Always use the 800 or 900 stop from the same ramp as the fill. Never use black, gray, or --color-text-primary on colored fills. **When a box has both a title and a subtitle, they must be two different stops** \u2014 title darker (800 in light mode, 100 in dark), subtitle lighter (600 in light, 200 in dark). Same stop for both reads flat; the weight difference alone isn't enough. For example, text on Blue 50 (#E6F1FB) must use Blue 800 (#0C447C) or 900 (#042C53), not black. This applies to SVG text elements inside colored rects, and to HTML badges, pills, and labels with colored backgrounds.
|
|
8554
|
+
|
|
8555
|
+
**Light/dark mode quick pick** \u2014 use only stops from the table, never off-table hex values:
|
|
8556
|
+
- **Light mode**: 50 fill + 600 stroke + **800 title / 600 subtitle**
|
|
8557
|
+
- **Dark mode**: 800 fill + 200 stroke + **100 title / 200 subtitle**
|
|
8558
|
+
- Apply \`c-{ramp}\` to a \`<g>\` wrapping shape+text, or directly to a \`<rect>\`/\`<circle>\`/\`<ellipse>\`. Never to \`<path>\` \u2014 paths don't get ramp fill. For colored connector strokes use inline \`stroke="#..."\` (any mid-ramp hex works in both modes). Dark mode is automatic for ramp classes. Available: c-gray, c-blue, c-red, c-amber, c-green, c-teal, c-purple, c-coral, c-pink.
|
|
8559
|
+
|
|
8560
|
+
For status/semantic meaning in UI (success, warning, danger) use CSS variables. For categorical coloring in both diagrams and UI, use these ramps.`;
|
|
8561
|
+
var DIAGRAM_TYPES = `## Diagram types
|
|
8562
|
+
*"Explain how compound interest works" / "How does a process scheduler work"*
|
|
8563
|
+
|
|
8564
|
+
**Two rules that cause most diagram failures \u2014 check these before writing each arrow and each box:**
|
|
8565
|
+
1. **Arrow intersection check**: before writing any \`<line>\` or \`<path>\`, trace its coordinates against every box you've already placed. If the line crosses any rect's interior (not just its source/target), it will visibly slash through that box \u2014 use an L-shaped \`<path>\` detour instead. This applies to arrows crossing labels too.
|
|
8566
|
+
2. **Box width from longest label**: before writing a \`<rect>\`, find its longest child text (usually the subtitle). \`rect_width = max(title_chars \xD7 8, subtitle_chars \xD7 7) + 24\`. A 100px-wide box holds at most a 10-char subtitle. If your subtitle is "Files, APIs, streams" (20 chars), the box needs 164px minimum \u2014 100px will visibly overflow.
|
|
8567
|
+
|
|
8568
|
+
**Tier packing:** Compute total width BEFORE placing. Example \u2014 4 pub/sub consumer boxes:
|
|
8569
|
+
- WRONG: x=40,160,260,360 w=160 \u2192 40-60px overlaps (4\xD7160=640 > 480 available)
|
|
8570
|
+
- RIGHT: x=50,200,350,500 w=130 gap=20 \u2192 fits (4\xD7130 + 3\xD720 = 580 \u2264 590 safe width; right edge at 630 \u2264 640)
|
|
8571
|
+
Work bottom-up for trees: size leaf tier first, parent width \u2265 sum of children.
|
|
8572
|
+
|
|
8573
|
+
**Diagrams are the hardest use case** \u2014 they have the highest failure rate due to precise coordinate math. Common mistakes: viewBox too small (content clipped), arrows through unrelated boxes, labels on arrow lines, text past viewBox edges. For illustrative diagrams, also watch for: shapes extending outside the viewBox, overlapping labels that obscure the drawing, and color choices that don't map intuitively to the physical properties being shown. Double-check coordinates before finalizing.
|
|
8574
|
+
|
|
8575
|
+
Use \`imagine_svg\` for diagrams. The widget automatically wraps SVG output in a card.
|
|
8576
|
+
|
|
8577
|
+
**Pick the right diagram type.** The decision is about *intent*, not subject matter. Ask: is the user trying to *document* this, or *understand* it?
|
|
8578
|
+
|
|
8579
|
+
**Reference diagrams** \u2014 the user wants a map they can point at. Precision matters more than feeling. Boxes, labels, arrows, containment. These are the diagrams you'd find in documentation.
|
|
8580
|
+
- **Flowchart** \u2014 steps in sequence, decisions branching, data transforming. Good for: approval workflows, request lifecycles, build pipelines, "what happens when I click submit". Trigger phrases: *"walk me through the process"*, *"what are the steps"*, *"what's the flow"*.
|
|
8581
|
+
- **Structural diagram** \u2014 things inside other things. Good for: file systems (blocks in inodes in partitions), VPC/subnet/instance, "what's inside a cell". Trigger phrases: *"what's the architecture"*, *"how is this organised"*, *"where does X live"*.
|
|
8582
|
+
|
|
8583
|
+
**Intuition diagrams** \u2014 the user wants to *feel* how something works. The goal isn't a correct map, it's the right mental model. These should look nothing like a flowchart. The subject doesn't need a physical form \u2014 it needs a *visual metaphor*.
|
|
8584
|
+
- **Illustrative diagram** \u2014 draw the mechanism. Physical things get cross-sections (water heaters, engines, lungs). Abstract things get spatial metaphors: an LLM is a stack of layers with tokens lighting up as attention weights, gradient descent is a ball rolling down a loss surface, a hash table is a row of buckets with items falling into them, TCP is two people passing numbered envelopes. Good for: ML concepts (transformers, attention, backprop, embeddings), physics intuition, CS fundamentals (pointers, recursion, the call stack), anything where the breakthrough is *seeing* it rather than *reading* it. Trigger phrases: *"how does X actually work"*, *"explain X"*, *"I don't get X"*, *"give me an intuition for X"*.
|
|
8585
|
+
|
|
8586
|
+
**Route on the verb, not the noun.** Same subject, different diagram depending on what was asked:
|
|
8587
|
+
|
|
8588
|
+
| User says | Type | What to draw |
|
|
8589
|
+
|---|---|---|
|
|
8590
|
+
| "how do LLMs work" | **Illustrative** | Token row, stacked layer slabs, attention threads glowing warm between tokens. Go interactive if you can. |
|
|
8591
|
+
| "transformer architecture" | Structural | Labelled boxes: embedding, attention heads, FFN, layer norm. |
|
|
8592
|
+
| "how does attention work" | **Illustrative** | One query token, a fan of lines to every key, line opacity = weight. |
|
|
8593
|
+
| "how does gradient descent work" | **Illustrative** | Contour surface, a ball, a trail of steps. Slider for learning rate. |
|
|
8594
|
+
| "what are the training steps" | Flowchart | Forward \u2192 loss \u2192 backward \u2192 update. Boxes and arrows. |
|
|
8595
|
+
| "how does TCP work" | **Illustrative** | Two endpoints, numbered packets in flight, an ACK returning. |
|
|
8596
|
+
| "TCP handshake sequence" | Flowchart | SYN \u2192 SYN-ACK \u2192 ACK. Three boxes. |
|
|
8597
|
+
| "explain the Krebs cycle" / "how does the event loop work" | **HTML stepper** | Click through stages. Never a ring. |
|
|
8598
|
+
| "how does a hash map work" | **Illustrative** | Key falling through a funnel into one of N buckets. |
|
|
8599
|
+
| "draw the database schema" / "show me the ERD" | **mermaid.js** | \`erDiagram\` syntax. Not SVG. |
|
|
8600
|
+
|
|
8601
|
+
The illustrative route is the default for *"how does X work"* with no further qualification. It is the more ambitious choice \u2014 don't chicken out into a flowchart because it feels safer. Claude draws these well.
|
|
8602
|
+
|
|
8603
|
+
Don't mix families in one diagram. If you need both, draw the intuition version first (build the mental model), then the reference version (fill in the precise labels) as a second tool call with prose between.
|
|
8604
|
+
|
|
8605
|
+
**For complex topics, use multiple SVG calls** \u2014 break the explanation into a series of smaller diagrams rather than one dense diagram. Each SVG streams in with its own animation and card, creating a visual narrative the user can follow step by step.
|
|
8606
|
+
|
|
8607
|
+
**Always add prose between diagrams** \u2014 never stack multiple SVG calls back-to-back without text. Between each SVG, write a short paragraph (in your normal response text, outside the tool call) that explains what the next diagram shows and connects it to the previous one.
|
|
8608
|
+
|
|
8609
|
+
**Promise only what you deliver** \u2014 if your response text says "here are three diagrams", you must include all three tool calls. Never promise a follow-up diagram and omit it. If you can only fit one diagram, adjust your text to match. One complete diagram is better than three promised and one delivered.
|
|
8610
|
+
|
|
8611
|
+
#### Flowchart
|
|
8612
|
+
|
|
8613
|
+
For sequential processes, cause-and-effect, decision trees.
|
|
8614
|
+
|
|
8615
|
+
**Planning**: Size boxes to fit their text generously. At 14px sans-serif, each character is ~8px wide \u2014 a label like "Load Balancer" (13 chars) needs a rect at least 140px wide. When in doubt, make boxes wider and leave more space between them. Cramped diagrams are the most common failure mode.
|
|
8616
|
+
|
|
8617
|
+
**Special characters are wider**: Chemical formulas (C\u2086H\u2081\u2082O\u2086), math notation (\u2211, \u222B, \u221A), subscripts/superscripts via <tspan> with dy/baseline-shift, and Unicode symbols all render wider than plain Latin characters. For labels containing formulas or special notation, add 30-50% extra width to your estimate. When in doubt, make the box wider \u2014 overflow looks worse than extra padding.
|
|
8618
|
+
|
|
8619
|
+
**Spacing**: 60px minimum between boxes, 24px padding inside boxes, 12px between text and edges. Leave 10px gap between arrowheads and box edges. Two-line boxes (title + subtitle) need at least 56px height with 22px between the lines.
|
|
8620
|
+
|
|
8621
|
+
**Vertical text placement**: Every \`<text>\` inside a box needs \`dominant-baseline="central"\`, with y set to the *centre* of the slot it sits in. Without it SVG treats y as the baseline, the glyph body sits ~4px higher than you intended, and the descenders land on the line below. Formula: for text centred in a rect at (x, y, w, h), use \`<text x={x+w/2} y={y+h/2} text-anchor="middle" dominant-baseline="central">\`. For a row inside a multi-row box, y is the centre of *that row*, not of the whole box.
|
|
8622
|
+
|
|
8623
|
+
**Layout**: Prefer single-direction flows (all top-down or all left-right). Keep diagrams simple \u2014 max 4-5 nodes per diagram. The widget is narrow (~680px) so complex layouts break.
|
|
8624
|
+
|
|
8625
|
+
**When the prompt itself is over budget**: if the user lists 6+ components ("draw me auth, products, orders, payments, gateway, queue"), don't draw all of them in one pass \u2014 you'll get overlapping boxes and arrows through text, every time. Decompose: (1) a stripped overview with the boxes only and at most one or two arrows showing the main flow \u2014 no fan-outs, no N-to-N meshes; (2) then one diagram per interesting sub-flow ("here's what happens when an order is placed", "here's the auth handshake"), each with 3-4 nodes and room to breathe. Count the nouns before you draw. The user asked for completeness \u2014 give it to them across several diagrams, not crammed into one.
|
|
8626
|
+
|
|
8627
|
+
**Cycles don't get drawn as rings.** If the last stage feeds back into the first (Krebs cycle, event loop, GC mark-and-sweep, TCP retransmit), your instinct is to place the stages around a circle. Don't. Every spacing rule in this spec is Cartesian \u2014 there is no collision check for "input box orbits outside stage box on a ring". You will get satellite boxes overlapping the stages they feed, labels sitting on the dashed circle, and tangential arrows that point nowhere. The ring is decoration; the loop is conveyed by the return arrow.
|
|
8628
|
+
|
|
8629
|
+
Build a stepper in \`imagine_html\`. One panel per stage, dots or pills showing position (\u25CF \u25CB \u25CB), Next wraps from the last stage back to the first \u2014 that's the loop. Each panel owns its inputs and products: an event loop's pending callbacks live *inside* the Poll panel, not floating next to a box on a ring. Nothing collides because nothing shares the canvas. Only fall back to a linear SVG (stages in a row, curved \`<path>\` return arrow) when there's one input and one output total and no per-stage detail to show.
|
|
8630
|
+
|
|
8631
|
+
**Feedback loops in linear flows:** Don't draw a physical arrow traversing the layout (it fights the flow direction and clips edges). Instead:
|
|
8632
|
+
- Small \`\u21BB\` glyph + text near the cycle point: \`<text>\u21BB returns to start</text>\`
|
|
8633
|
+
- Or restructure the whole diagram as a circle if the cycle IS the point
|
|
8634
|
+
|
|
8635
|
+
**Arrows:** A line from A to B must not cross any other box or label. If the direct path crosses something, route around with an L-bend: \`<path d="M x1 y1 L x1 ymid L x2 ymid L x2 y2"/>\`. Place arrow labels in clear space, not on the midpoint.
|
|
8636
|
+
|
|
8637
|
+
Keep all nodes the same height when they have the same content type (e.g. all single-line boxes = 44px, all two-line boxes = 56px).
|
|
8638
|
+
|
|
8639
|
+
**Flowchart components** \u2014 use these patterns consistently:
|
|
8640
|
+
|
|
8641
|
+
*Single-line node* (44px tall): title only. The \`c-blue\` class sets fill, stroke, and text colors for both light and dark mode automatically \u2014 no \`<style>\` block needed.
|
|
8642
|
+
\`\`\`svg
|
|
8643
|
+
<g class="node c-blue" onclick="sendPrompt('Tell me more about T-cells')">
|
|
8644
|
+
<rect x="100" y="20" width="180" height="44" rx="8" stroke-width="0.5"/>
|
|
8645
|
+
<text class="th" x="190" y="42" text-anchor="middle" dominant-baseline="central">T-cells</text>
|
|
8646
|
+
</g>
|
|
8647
|
+
\`\`\`
|
|
8648
|
+
|
|
8649
|
+
*Two-line node* (56px tall): bold title + muted subtitle.
|
|
8650
|
+
\`\`\`svg
|
|
8651
|
+
<g class="node c-blue" onclick="sendPrompt('Tell me more about dendritic cells')">
|
|
8652
|
+
<rect x="100" y="20" width="200" height="56" rx="8" stroke-width="0.5"/>
|
|
8653
|
+
<text class="th" x="200" y="38" text-anchor="middle" dominant-baseline="central">Dendritic cells</text>
|
|
8654
|
+
<text class="ts" x="200" y="56" text-anchor="middle" dominant-baseline="central">Detect foreign antigens</text>
|
|
8655
|
+
</g>
|
|
8656
|
+
\`\`\`
|
|
8657
|
+
|
|
8658
|
+
*Connector* (no label \u2014 meaning is clear from source + target):
|
|
8659
|
+
\`\`\`svg
|
|
8660
|
+
<line x1="200" y1="76" x2="200" y2="120" class="arr" marker-end="url(#arrow)"/>
|
|
8661
|
+
\`\`\`
|
|
8662
|
+
|
|
8663
|
+
*Neutral node* (gray, for start/end/generic steps): use \`class="box"\` for auto-themed fill/stroke, and default text classes.
|
|
8664
|
+
|
|
8665
|
+
Make all nodes clickable by default \u2014 wrap in \`<g class="node" onclick="sendPrompt('...')">\`. The hover effect is built in.
|
|
8666
|
+
|
|
8667
|
+
#### Structural diagram
|
|
8668
|
+
|
|
8669
|
+
For concepts where physical or logical containment matters \u2014 things inside other things.
|
|
8670
|
+
|
|
8671
|
+
**When to use**: The explanation depends on *where* processes happen. Examples: how a cell works (organelles inside a cell), how a file system works (blocks inside inodes inside partitions), how a building's HVAC works (ducts inside floors inside a building), how a CPU cache hierarchy works (L1 inside core, L2 shared).
|
|
8672
|
+
|
|
8673
|
+
**Core idea**: Large rounded rects are containers. Smaller rects inside them are regions or sub-structures. Text labels describe what happens in each region. Arrows show flow between regions or from external inputs/outputs.
|
|
8674
|
+
|
|
8675
|
+
**Container rules**:
|
|
8676
|
+
- Outermost container: large rounded rect, rx=20-24, lightest fill (50 stop), 0.5px stroke (600 stop). Label at top-left inside, 14px bold.
|
|
8677
|
+
- Inner regions: medium rounded rects, rx=8-12, next shade fill (100-200 stop). Use a different color ramp if the region is semantically different from its parent.
|
|
8678
|
+
- 20px minimum padding inside every container \u2014 text and inner regions must not touch the container edges.
|
|
8679
|
+
- Max 2-3 nesting levels. Deeper nesting gets unreadable at 680px width.
|
|
8680
|
+
|
|
8681
|
+
**Layout**:
|
|
8682
|
+
- Place inner regions side by side within the container, with 16px+ gap between them.
|
|
8683
|
+
- External inputs (sunlight, water, data, requests) sit outside the container with arrows pointing in.
|
|
8684
|
+
- External outputs sit outside with arrows pointing out.
|
|
8685
|
+
- Keep external labels short \u2014 one word or a short phrase. Details go in the prose between diagrams.
|
|
8686
|
+
|
|
8687
|
+
**What goes inside regions**: Text only \u2014 the region name (14px bold) and a short description of what happens there (12px). Don't put flowchart-style boxes inside regions. Don't draw illustrations or icons inside.
|
|
8688
|
+
|
|
8689
|
+
**Structural container example** (library branch with two side-by-side regions, an internal labeled arrow, and an external input). ViewBox 700x320, horizontal layout, color classes handle both light and dark mode \u2014 no \`<style>\` block:
|
|
8690
|
+
\`\`\`svg
|
|
8691
|
+
<defs>
|
|
8692
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
8693
|
+
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8694
|
+
</marker>
|
|
8695
|
+
</defs>
|
|
8696
|
+
<!-- Outer container -->
|
|
8697
|
+
<g class="c-green">
|
|
8698
|
+
<rect x="120" y="30" width="560" height="260" rx="20" stroke-width="0.5"/>
|
|
8699
|
+
<text class="th" x="400" y="62" text-anchor="middle">Library branch</text>
|
|
8700
|
+
<text class="ts" x="400" y="80" text-anchor="middle">Main floor</text>
|
|
8701
|
+
</g>
|
|
8702
|
+
<!-- Inner: Circulation desk -->
|
|
8703
|
+
<g class="c-teal">
|
|
8704
|
+
<rect x="150" y="100" width="220" height="160" rx="12" stroke-width="0.5"/>
|
|
8705
|
+
<text class="th" x="260" y="130" text-anchor="middle">Circulation desk</text>
|
|
8706
|
+
<text class="ts" x="260" y="148" text-anchor="middle">Checkouts, returns</text>
|
|
8707
|
+
</g>
|
|
8708
|
+
<!-- Inner: Reading room -->
|
|
8709
|
+
<g class="c-amber">
|
|
8710
|
+
<rect x="450" y="100" width="210" height="160" rx="12" stroke-width="0.5"/>
|
|
8711
|
+
<text class="th" x="555" y="130" text-anchor="middle">Reading room</text>
|
|
8712
|
+
<text class="ts" x="555" y="148" text-anchor="middle">Seating, reference</text>
|
|
8713
|
+
</g>
|
|
8714
|
+
<!-- Arrow between inner boxes with label -->
|
|
8715
|
+
<text class="ts" x="410" y="175" text-anchor="middle">Books</text>
|
|
8716
|
+
<line x1="370" y1="185" x2="448" y2="185" class="arr" marker-end="url(#arrow)"/>
|
|
8717
|
+
<!-- External input: New acq. \u2014 text vertically aligned with arrow -->
|
|
8718
|
+
<text class="ts" x="40" y="185" text-anchor="middle">New acq.</text>
|
|
8719
|
+
<line x1="75" y1="185" x2="118" y2="185" class="arr" marker-end="url(#arrow)"/>
|
|
8720
|
+
\`\`\`
|
|
8721
|
+
|
|
8722
|
+
**Color in structural diagrams**: Nested regions need distinct ramps \u2014 \`c-{ramp}\` classes resolve to fixed fill/stroke stops, so the same class on parent and child gives identical fills and flattens the hierarchy. Pick a *related* ramp for inner structures (e.g. Green for the library envelope, Teal for the circulation desk inside it) and a *contrasting* ramp for a region that does something functionally different (e.g. Amber for the reading room). This keeps the diagram scannable \u2014 you can see at a glance which parts are related.
|
|
8723
|
+
|
|
8724
|
+
**Database schemas / ERDs \u2014 use mermaid.js, not SVG.** A schema table is a header plus N field rows plus typed columns plus crow's-foot connectors. That is a text-layout problem and hand-placing it in SVG fails the same way every time. mermaid.js \`erDiagram\` does layout, cardinality, and connector routing for free. ERDs only; everything else stays in SVG.
|
|
8725
|
+
|
|
8726
|
+
\`\`\`
|
|
8727
|
+
erDiagram
|
|
8728
|
+
USERS ||--o{ POSTS : writes
|
|
8729
|
+
POSTS ||--o{ COMMENTS : has
|
|
8730
|
+
USERS {
|
|
8731
|
+
uuid id PK
|
|
8732
|
+
string email
|
|
8733
|
+
timestamp created_at
|
|
8734
|
+
}
|
|
8735
|
+
POSTS {
|
|
8736
|
+
uuid id PK
|
|
8737
|
+
uuid user_id FK
|
|
8738
|
+
string title
|
|
8739
|
+
}
|
|
8740
|
+
\`\`\`
|
|
8741
|
+
|
|
8742
|
+
Use \`imagine_html\` for ERDs. Import and initialize in a \`<script type="module">\`. The host CSS re-styles mermaid's output to match the design system \u2014 keep the init block exactly as shown (fontFamily + fontSize are used for layout measurement; deviate and text clips). After rendering, replace sharp-cornered entity \`<path>\` elements with rounded \`<rect rx="8">\` to match the design system, and strip borders from attribute rows (only the outer container and header row keep visible borders \u2014 alternating fill colors separate the rows):
|
|
8743
|
+
\`\`\`html
|
|
8744
|
+
<style>
|
|
8745
|
+
#erd svg.erDiagram .divider path { stroke-opacity: 0.5; }
|
|
8746
|
+
#erd svg.erDiagram .row-rect-odd path,
|
|
8747
|
+
#erd svg.erDiagram .row-rect-odd rect,
|
|
8748
|
+
#erd svg.erDiagram .row-rect-even path,
|
|
8749
|
+
#erd svg.erDiagram .row-rect-even rect { stroke: none !important; }
|
|
8750
|
+
</style>
|
|
8751
|
+
<div id="erd"></div>
|
|
8752
|
+
<script type="module">
|
|
8753
|
+
import mermaid from 'https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
8754
|
+
const dark = matchMedia('(prefers-color-scheme: dark)').matches;
|
|
8755
|
+
await document.fonts.ready;
|
|
8756
|
+
mermaid.initialize({
|
|
8757
|
+
startOnLoad: false,
|
|
8758
|
+
theme: 'base',
|
|
8759
|
+
fontFamily: '"Anthropic Sans", sans-serif',
|
|
8760
|
+
themeVariables: {
|
|
8761
|
+
darkMode: dark,
|
|
8762
|
+
fontSize: '13px',
|
|
8763
|
+
fontFamily: '"Anthropic Sans", sans-serif',
|
|
8764
|
+
lineColor: dark ? '#9c9a92' : '#73726c',
|
|
8765
|
+
textColor: dark ? '#c2c0b6' : '#3d3d3a',
|
|
8766
|
+
},
|
|
8767
|
+
});
|
|
8768
|
+
const { svg } = await mermaid.render('erd-svg', \`erDiagram
|
|
8769
|
+
USERS ||--o{ POSTS : writes
|
|
8770
|
+
POSTS ||--o{ COMMENTS : has\`);
|
|
8771
|
+
document.getElementById('erd').innerHTML = svg;
|
|
8772
|
+
|
|
8773
|
+
// Round only the outermost entity box corners (not internal row stripes)
|
|
8774
|
+
document.querySelectorAll('#erd svg.erDiagram .node').forEach(node => {
|
|
8775
|
+
const firstPath = node.querySelector('path[d]');
|
|
8776
|
+
if (!firstPath) return;
|
|
8777
|
+
const d = firstPath.getAttribute('d');
|
|
8778
|
+
const nums = d.match(/-?[\\d.]+/g)?.map(Number);
|
|
8779
|
+
if (!nums || nums.length < 8) return;
|
|
8780
|
+
const xs = [nums[0], nums[2], nums[4], nums[6]];
|
|
8781
|
+
const ys = [nums[1], nums[3], nums[5], nums[7]];
|
|
8782
|
+
const x = Math.min(...xs), y = Math.min(...ys);
|
|
8783
|
+
const w = Math.max(...xs) - x, h = Math.max(...ys) - y;
|
|
8784
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
8785
|
+
rect.setAttribute('x', x); rect.setAttribute('y', y);
|
|
8786
|
+
rect.setAttribute('width', w); rect.setAttribute('height', h);
|
|
8787
|
+
rect.setAttribute('rx', '8');
|
|
8788
|
+
for (const a of ['fill', 'stroke', 'stroke-width', 'class', 'style']) {
|
|
8789
|
+
if (firstPath.hasAttribute(a)) rect.setAttribute(a, firstPath.getAttribute(a));
|
|
8790
|
+
}
|
|
8791
|
+
firstPath.replaceWith(rect);
|
|
8792
|
+
});
|
|
8793
|
+
|
|
8794
|
+
// Strip borders from attribute rows (mermaid v11: .row-rect-odd / .row-rect-even)
|
|
8795
|
+
document.querySelectorAll('#erd svg.erDiagram .row-rect-odd path, #erd svg.erDiagram .row-rect-even path').forEach(p => {
|
|
8796
|
+
p.setAttribute('stroke', 'none');
|
|
8797
|
+
});
|
|
8798
|
+
</script>
|
|
8799
|
+
\`\`\`
|
|
8800
|
+
|
|
8801
|
+
Works identically for \`classDiagram\` \u2014 swap the diagram source; init stays the same.
|
|
8802
|
+
|
|
8803
|
+
#### Illustrative diagram
|
|
8804
|
+
|
|
8805
|
+
For building *intuition*. The subject might be physical (an engine, a lung) or completely abstract (attention, recursion, gradient descent) \u2014 what matters is that a spatial drawing conveys the mechanism better than labelled boxes would. These are the diagrams that make someone go "oh, *that's* what it's doing."
|
|
8806
|
+
|
|
8807
|
+
**Two flavours, same rules:**
|
|
8808
|
+
- **Physical subjects** get drawn as simplified versions of themselves. Cross-sections, cutaways, schematics. A water heater is a tank with a burner underneath. A lung is a branching tree in a cavity. You're drawing *the thing*, stylised.
|
|
8809
|
+
- **Abstract subjects** get drawn as *spatial metaphors*. You're inventing a shape for something that doesn't have one \u2014 but the shape should make the mechanism obvious. A transformer is a stack of horizontal slabs with a bright thread of attention connecting tokens across layers. A hash function is a funnel scattering items into a row of buckets. The call stack is literally a stack of frames growing and shrinking. Embeddings are dots clustering in space. The metaphor *is* the explanation.
|
|
8810
|
+
|
|
8811
|
+
This is the most ambitious diagram type and the one Claude is best at. Lean into it. Use colour for intensity (a hot attention weight glows amber, a cold one stays gray). Use repetition for scale (many small circles = many parameters).
|
|
8812
|
+
|
|
8813
|
+
**Prefer interactive over static.** A static cross-section is a good answer; a cross-section you can *operate* is a great one. The decision rule: if the real-world system has a control, give the diagram that control. A water heater has a thermostat \u2014 so give the user a slider that shifts the hot/cold boundary, a toggle that fires the burner and animates convection currents. An LLM has input tokens \u2014 let the user click one and watch the attention weights re-fan. A cache has a hit rate \u2014 let them drag it and watch latency change. Reach for \`imagine_html\` with inline SVG first; only fall back to static \`imagine_svg\` when there's genuinely nothing to twiddle.
|
|
8814
|
+
|
|
8815
|
+
**When NOT to use**: The user is asking for a *reference*, not an *intuition*. "What are the components of a transformer" wants labelled boxes \u2014 that's a structural diagram. "Walk me through our CI pipeline" wants sequential steps \u2014 that's a flowchart. Also skip this when the metaphor would be arbitrary rather than revealing: drawing "the cloud" as a cloud shape or "microservices" as little houses doesn't teach anything about how they work. If the drawing doesn't make the *mechanism* clearer, don't draw it.
|
|
8816
|
+
|
|
8817
|
+
**Fidelity ceiling**: These are schematics, not illustrations. Every shape should read at a glance. If a \`<path>\` needs more than ~6 segments to draw, simplify it. A tank is a rounded rect, not a B\xE9zier portrait of a tank. A flame is three triangles, not a fire. Recognisable silhouette beats accurate contour every time \u2014 if you find yourself carefully tracing an outline, you're overshooting.
|
|
8818
|
+
|
|
8819
|
+
**Core principle**: Draw the mechanism, not a diagram *about* the mechanism. Spatial arrangement carries the meaning; labels annotate. A good illustrative diagram works with the labels removed.
|
|
8820
|
+
|
|
8821
|
+
**What changes from flowchart/structural rules**:
|
|
8822
|
+
|
|
8823
|
+
- **Shapes are freeform.** Use \`<path>\`, \`<ellipse>\`, \`<circle>\`, \`<polygon>\`, and curved lines to represent real forms. A water tank is a tall rect with rounded bottom. A heart valve is a pair of curved paths. A circuit trace is a thin polyline. You are not limited to rounded rects.
|
|
8824
|
+
- **Layout follows the subject's geometry**, not a grid. If the thing is tall and narrow (a water heater, a thermometer), the diagram is tall and narrow. If it's wide and flat (a PCB, a geological cross-section), the diagram is wide. Let the subject dictate proportions within the 680px viewBox width.
|
|
8825
|
+
- **Color encodes intensity**, not category. For physical subjects: warm ramps (amber, coral, red) = heat/energy/pressure, cool ramps (blue, teal) = cold/calm, gray = inert structure. For abstract subjects: warm = active/high-weight/attended-to, cool or gray = dormant/low-weight/ignored. A user should be able to glance at the diagram and see *where the action is* without reading a single label.
|
|
8826
|
+
- **Layering and overlap are encouraged \u2014 for shapes.** Unlike flowcharts where boxes must never overlap, illustrative diagrams can layer shapes for depth \u2014 a pipe entering a tank, attention lines fanning through layers, insulation wrapping a chamber. Use z-ordering (later in source = on top) deliberately.
|
|
8827
|
+
- **Text is the exception \u2014 never let a stroke cross it.** The overlap permission is for shapes only. Every label needs 8px of clear air between its baseline/cap-height and the nearest stroke. Don't solve this with a background rect \u2014 solve it by *placing the text somewhere else*. Labels go in the quiet regions: above the drawing, below it, in the margin with a leader line, or in the gap between two fans of lines. If there is no quiet region, the drawing is too dense \u2014 remove something or split into two diagrams.
|
|
8828
|
+
- **Small shape-based indicators are allowed** when they communicate physical state. Triangles for flames. Circles for bubbles or particles. Wavy lines for steam or heat radiation. Parallel lines for vibration. These aren't decoration \u2014 they tell the user what's happening physically. Keep them simple: basic SVG primitives, not detailed illustrations.
|
|
8829
|
+
- **One gradient per diagram is permitted** \u2014 the only exception to the global no-gradients rule \u2014 and only to show a *continuous* physical property across a region (temperature stratification in a tank, pressure drop along a pipe, concentration in a solution). It must be a single \`<linearGradient>\` between exactly two stops from the same colour ramp. No radial gradients, no multi-stop fades, no gradient-as-aesthetic. If two stacked flat-fill rects communicate the same thing, do that instead.
|
|
8830
|
+
- **Animation is permitted for interactive HTML versions.** Use CSS \`@keyframes\` animating only \`transform\` and \`opacity\`. Keep loops under ~2s, and wrap every animation in \`@media (prefers-reduced-motion: no-preference)\` so it's opt-out by default. Animations should show how the system *behaves* \u2014 convection current, rotation, flow \u2014 not just move for the sake of moving. No physics engines or heavy libraries.
|
|
8831
|
+
|
|
8832
|
+
All core rules still apply (viewBox 680px, dark mode mandatory, 14/12px text, pre-built classes, arrow marker, clickable nodes).
|
|
8833
|
+
|
|
8834
|
+
**Label placement**:
|
|
8835
|
+
- Place labels *outside* the drawn object when possible, with a thin leader line (0.5px dashed, \`var(--t)\` stroke) pointing to the relevant part. This keeps the illustration uncluttered.
|
|
8836
|
+
- For large internal zones (like temperature regions in a tank), labels can sit inside if there's ample clear space \u2014 minimum 20px from any edge.
|
|
8837
|
+
- External labels sit in the margin area or above/below the object. **Pick one side for labels and put them all there** \u2014 at 680px wide you don't have room for a drawing *and* label columns on both sides. Reserve at least 140px of horizontal margin on the label side. Labels on the left are the ones that clip: \`text-anchor="end"\` extends leftward from x, and with multi-line callouts it's very easy to blow past x=0 without noticing. Default to right-side labels with \`text-anchor="start"\` unless the subject's geometry forces otherwise. Use \`class="ts"\` (12px) for callouts, \`class="th"\` (14px medium) for major component names.
|
|
8838
|
+
|
|
8839
|
+
**Composition approach**:
|
|
8840
|
+
1. Start with the main object's silhouette \u2014 the largest shape, centered in the viewBox.
|
|
8841
|
+
2. Add internal structure: chambers, pipes, membranes, mechanical parts.
|
|
8842
|
+
3. Add external connections: pipes entering/exiting, arrows showing flow direction, labels for inputs and outputs.
|
|
8843
|
+
4. Add state indicators last: color fills showing temperature/pressure/concentration, small animated elements showing movement or energy.
|
|
8844
|
+
5. Leave generous whitespace around the object for labels \u2014 don't crowd annotations against the viewBox edges.
|
|
8845
|
+
|
|
8846
|
+
**Static vs interactive**: Static cutaways and cross-sections work best as pure \`imagine_svg\`. If the diagram benefits from controls \u2014 a slider that changes a temperature zone, buttons toggling between operating states, live readouts \u2014 use \`imagine_html\` with inline SVG for the drawing and HTML controls around it.
|
|
8847
|
+
|
|
8848
|
+
**Illustrative diagram example** \u2014 interactive water heater cross-section with vivid physical-realism colors, animated convection currents, and controls. Uses \`imagine_html\` with inline SVG: a thermostat slider shifts the hot/cold gradient boundary, a heating toggle animates flames on/off and transitions convection to paused. viewBox is 680x560; tank occupies x=180..440, leaving 140px+ of right margin for labels. Smooth convection paths use \`stroke-dasharray:5 5\` at ~1.6s for a gentle flow feel. A warm-glow overlay on the hot zone pulses subtly when heating is on. Flame shapes use warm gradient fills and clean opacity transitions. Labels sit along the right margin with leader lines.
|
|
8849
|
+
\`\`\`html
|
|
8850
|
+
<style>
|
|
8851
|
+
@keyframes conv { to { stroke-dashoffset: -20; } }
|
|
8852
|
+
@keyframes flicker { 0%,100%{opacity:1} 50%{opacity:.82} }
|
|
8853
|
+
@keyframes glow { 0%,100%{opacity:.3} 50%{opacity:.6} }
|
|
8854
|
+
.conv { stroke-dasharray:5 5; animation: conv var(--dur,1.6s) linear infinite; transition: opacity .5s; }
|
|
8855
|
+
.conv.off { opacity:0; animation-play-state:paused; }
|
|
8856
|
+
#flames path { transition: opacity .5s; }
|
|
8857
|
+
#flames.off path { opacity:0; animation:none; }
|
|
8858
|
+
#flames path:nth-child(odd) { animation: flicker .6s ease-in-out infinite; }
|
|
8859
|
+
#flames path:nth-child(even) { animation: flicker .8s ease-in-out infinite .15s; }
|
|
8860
|
+
#warm-glow { animation: glow 3s ease-in-out infinite; transition: opacity .5s; }
|
|
8861
|
+
#warm-glow.off { opacity:0; animation:none; }
|
|
8862
|
+
.toggle-track { position:relative;width:32px;height:18px;background:var(--color-border-secondary);border-radius:9px;transition:background .2s;display:inline-block; }
|
|
8863
|
+
.toggle-track:has(input:checked) { background:var(--color-text-info); }
|
|
8864
|
+
#heat-toggle:checked + span { transform:translateX(14px); }
|
|
8865
|
+
</style>
|
|
8866
|
+
<svg width="100%" viewBox="0 0 680 560">
|
|
8867
|
+
<defs>
|
|
8868
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker>
|
|
8869
|
+
<linearGradient id="tg" x1="0" y1="0" x2="0" y2="1">
|
|
8870
|
+
<stop id="gh" offset="40%" stop-color="#E8593C" stop-opacity="0.45"/>
|
|
8871
|
+
<stop id="gc" offset="40%" stop-color="#3B8BD4" stop-opacity="0.4"/>
|
|
8872
|
+
</linearGradient>
|
|
8873
|
+
<linearGradient id="fg1" x1="0" y1="1" x2="0" y2="0"><stop offset="0%" stop-color="#E85D24"/><stop offset="60%" stop-color="#F2A623"/><stop offset="100%" stop-color="#FCDE5A"/></linearGradient>
|
|
8874
|
+
<linearGradient id="fg2" x1="0" y1="1" x2="0" y2="0"><stop offset="0%" stop-color="#D14520"/><stop offset="50%" stop-color="#EF8B2C"/><stop offset="100%" stop-color="#F9CB42"/></linearGradient>
|
|
8875
|
+
<linearGradient id="pipe-h" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#D05538" stop-opacity=".25"/><stop offset="100%" stop-color="#D05538" stop-opacity=".08"/></linearGradient>
|
|
8876
|
+
<linearGradient id="pipe-c" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#3B8BD4" stop-opacity=".25"/><stop offset="100%" stop-color="#3B8BD4" stop-opacity=".08"/></linearGradient>
|
|
8877
|
+
<clipPath id="tc"><rect x="180" y="55" width="260" height="390" rx="14"/></clipPath>
|
|
8878
|
+
</defs>
|
|
8879
|
+
<!-- Tank fill -->
|
|
8880
|
+
<g clip-path="url(#tc)"><rect x="180" y="55" width="260" height="390" fill="url(#tg)"/></g>
|
|
8881
|
+
<!-- Warm glow overlay (pulses when heating) -->
|
|
8882
|
+
<g clip-path="url(#tc)"><rect id="warm-glow" x="180" y="55" width="260" height="160" fill="#E8593C" opacity=".3"/></g>
|
|
8883
|
+
<!-- Tank shell (double stroke for solidity) -->
|
|
8884
|
+
<rect x="180" y="55" width="260" height="390" rx="14" fill="none" stroke="var(--t)" stroke-width="2.5" opacity=".25"/>
|
|
8885
|
+
<rect x="180" y="55" width="260" height="390" rx="14" fill="none" stroke="var(--t)" stroke-width="1"/>
|
|
8886
|
+
<!-- Hot pipe out (top right) -->
|
|
8887
|
+
<rect x="370" y="14" width="16" height="50" rx="4" fill="url(#pipe-h)"/>
|
|
8888
|
+
<path d="M378 14V55" stroke="var(--t)" stroke-width="3" stroke-linecap="round" fill="none"/>
|
|
8889
|
+
<!-- Cold pipe in + dip tube (top left) -->
|
|
8890
|
+
<rect x="234" y="14" width="16" height="50" rx="4" fill="url(#pipe-c)"/>
|
|
8891
|
+
<path d="M242 14V55" stroke="var(--t)" stroke-width="3" stroke-linecap="round" fill="none"/>
|
|
8892
|
+
<path d="M242 55V395" stroke="var(--t)" stroke-width="2.5" stroke-linecap="round" fill="none" opacity=".5"/>
|
|
8893
|
+
<!-- Convection currents (curved paths at different speeds) -->
|
|
8894
|
+
<path class="conv" style="--dur:1.6s" fill="none" stroke="#D05538" stroke-width="1" opacity=".5" d="M350 380C355 320,365 240,358 140Q355 110,340 100"/>
|
|
8895
|
+
<path class="conv" style="--dur:2.1s" fill="none" stroke="#C04828" stroke-width=".8" opacity=".35" d="M300 390C308 340,320 260,315 170Q312 130,298 115"/>
|
|
8896
|
+
<path class="conv" style="--dur:2.6s" fill="none" stroke="#B05535" stroke-width=".7" opacity=".3" d="M380 370C382 310,388 230,382 150Q378 120,365 110"/>
|
|
8897
|
+
<!-- Burner bar -->
|
|
8898
|
+
<rect x="188" y="454" width="244" height="5" rx="2" fill="var(--t)" opacity=".6"/>
|
|
8899
|
+
<rect x="220" y="462" width="180" height="6" rx="3" fill="var(--t)" opacity=".3"/>
|
|
8900
|
+
<!-- Flames (gradient-filled organic shapes) -->
|
|
8901
|
+
<g id="flames">
|
|
8902
|
+
<path d="M240,454Q248,430 252,438Q256,424 260,454Z" fill="url(#fg1)"/>
|
|
8903
|
+
<path d="M278,454Q285,426 290,434Q295,418 300,454Z" fill="url(#fg2)"/>
|
|
8904
|
+
<path d="M320,454Q328,428 333,436Q338,420 342,454Z" fill="url(#fg1)"/>
|
|
8905
|
+
<path d="M360,454Q367,430 371,438Q375,422 380,454Z" fill="url(#fg2)"/>
|
|
8906
|
+
<path d="M398,454Q404,434 408,440Q412,428 416,454Z" fill="url(#fg1)"/>
|
|
8907
|
+
</g>
|
|
8908
|
+
<!-- Labels (right margin) -->
|
|
8909
|
+
<g class="node" onclick="sendPrompt('How does hot water exit the tank?')">
|
|
8910
|
+
<line class="leader" x1="386" y1="34" x2="468" y2="70"/><circle cx="386" cy="34" r="2" fill="var(--t)"/>
|
|
8911
|
+
<text class="ts" x="474" y="74">Hot water outlet</text></g>
|
|
8912
|
+
<g class="node" onclick="sendPrompt('How does the cold water inlet work?')">
|
|
8913
|
+
<line class="leader" x1="250" y1="34" x2="468" y2="140"/><circle cx="250" cy="34" r="2" fill="var(--t)"/>
|
|
8914
|
+
<text class="ts" x="474" y="144">Cold water inlet</text></g>
|
|
8915
|
+
<g class="node" onclick="sendPrompt('What does the dip tube do?')">
|
|
8916
|
+
<line class="leader" x1="250" y1="260" x2="468" y2="220"/><circle cx="250" cy="260" r="2" fill="var(--t)"/>
|
|
8917
|
+
<text class="ts" x="474" y="224">Dip tube</text></g>
|
|
8918
|
+
<g class="node" onclick="sendPrompt('What does the thermostat control?')">
|
|
8919
|
+
<line class="leader" x1="440" y1="250" x2="468" y2="300"/><circle cx="440" cy="250" r="2" fill="var(--t)"/>
|
|
8920
|
+
<text class="ts" x="474" y="304">Thermostat</text></g>
|
|
8921
|
+
<g class="node" onclick="sendPrompt('What material is the tank made of?')">
|
|
8922
|
+
<line class="leader" x1="440" y1="380" x2="468" y2="380"/><circle cx="440" cy="380" r="2" fill="var(--t)"/>
|
|
8923
|
+
<text class="ts" x="474" y="384">Tank wall</text></g>
|
|
8924
|
+
<g class="node" onclick="sendPrompt('How does the gas burner heat water?')">
|
|
8925
|
+
<line class="leader" x1="432" y1="454" x2="468" y2="454"/><circle cx="432" cy="454" r="2" fill="var(--t)"/>
|
|
8926
|
+
<text class="ts" x="474" y="458">Heating element</text></g>
|
|
8927
|
+
</svg>
|
|
8928
|
+
<div style="display:flex;align-items:center;gap:16px;margin:12px 0 0;font-size:13px;color:var(--color-text-secondary)">
|
|
8929
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none">
|
|
8930
|
+
<span class="toggle-track">
|
|
8931
|
+
<input type="checkbox" id="heat-toggle" checked onchange="toggleHeat(this.checked)" style="position:absolute;opacity:0;width:100%;height:100%;cursor:pointer;margin:0">
|
|
8932
|
+
<span style="position:absolute;top:2px;left:2px;width:14px;height:14px;background:#fff;border-radius:50%;transition:transform .2s;pointer-events:none"></span>
|
|
8933
|
+
</span>
|
|
8934
|
+
Heating
|
|
8935
|
+
</label>
|
|
8936
|
+
<span>Thermostat</span>
|
|
8937
|
+
<input type="range" id="temp-slider" min="10" max="90" value="40" style="flex:1" oninput="setTemp(this.value)">
|
|
8938
|
+
<span id="temp-label" style="min-width:36px;text-align:right">40%</span>
|
|
8939
|
+
</div>
|
|
8940
|
+
<script>
|
|
8941
|
+
function setTemp(v) {
|
|
8942
|
+
document.getElementById('gh').setAttribute('offset', v+'%');
|
|
8943
|
+
document.getElementById('gc').setAttribute('offset', v+'%');
|
|
8944
|
+
document.getElementById('temp-label').textContent = v+'%';
|
|
8945
|
+
}
|
|
8946
|
+
function toggleHeat(on) {
|
|
8947
|
+
document.getElementById('flames').classList.toggle('off', !on);
|
|
8948
|
+
document.getElementById('warm-glow').classList.toggle('off', !on);
|
|
8949
|
+
document.querySelectorAll('.conv').forEach(p => p.classList.toggle('off', !on));
|
|
8950
|
+
}
|
|
8951
|
+
</script>
|
|
8952
|
+
\`\`\`
|
|
8953
|
+
|
|
8954
|
+
**Illustrative example \u2014 abstract subject** (attention in a transformer). Same rules, no physical object. A row of tokens at the bottom, one query token highlighted, weight-scaled lines fanning to every other token. Caption sits below the fan \u2014 clear of every stroke \u2014 not inside it.
|
|
8955
|
+
\`\`\`svg
|
|
8956
|
+
<rect class="c-purple" x="60" y="40" width="560" height="26" rx="6" stroke-width="0.5"/>
|
|
8957
|
+
<rect class="c-purple" x="60" y="80" width="560" height="26" rx="6" stroke-width="0.5"/>
|
|
8958
|
+
<rect class="c-purple" x="60" y="120" width="560" height="26" rx="6" stroke-width="0.5"/>
|
|
8959
|
+
<text class="ts" x="72" y="57" >Layer 3</text>
|
|
8960
|
+
<text class="ts" x="72" y="97" >Layer 2</text>
|
|
8961
|
+
<text class="ts" x="72" y="137">Layer 1</text>
|
|
8962
|
+
|
|
8963
|
+
<line stroke="#EF9F27" stroke-linecap="round" x1="340" y1="230" x2="116" y2="146" stroke-width="1" opacity="0.25"/>
|
|
8964
|
+
<line stroke="#EF9F27" stroke-linecap="round" x1="340" y1="230" x2="228" y2="146" stroke-width="1.5" opacity="0.4"/>
|
|
8965
|
+
<line stroke="#EF9F27" stroke-linecap="round" x1="340" y1="230" x2="340" y2="146" stroke-width="4" opacity="1.0"/>
|
|
8966
|
+
<line stroke="#EF9F27" stroke-linecap="round" x1="340" y1="230" x2="452" y2="146" stroke-width="2.5" opacity="0.7"/>
|
|
8967
|
+
<line stroke="#EF9F27" stroke-linecap="round" x1="340" y1="230" x2="564" y2="146" stroke-width="1" opacity="0.2"/>
|
|
8968
|
+
|
|
8969
|
+
<g class="node" onclick="sendPrompt('What do the attention weights mean?')">
|
|
8970
|
+
<rect class="c-gray" x="80" y="230" width="72" height="36" rx="6" stroke-width="0.5"/>
|
|
8971
|
+
<rect class="c-gray" x="192" y="230" width="72" height="36" rx="6" stroke-width="0.5"/>
|
|
8972
|
+
<rect class="c-amber" x="304" y="230" width="72" height="36" rx="6" stroke-width="1"/>
|
|
8973
|
+
<rect class="c-gray" x="416" y="230" width="72" height="36" rx="6" stroke-width="0.5"/>
|
|
8974
|
+
<rect class="c-gray" x="528" y="230" width="72" height="36" rx="6" stroke-width="0.5"/>
|
|
8975
|
+
<text class="ts" x="116" y="252" text-anchor="middle">the</text>
|
|
8976
|
+
<text class="ts" x="228" y="252" text-anchor="middle">cat</text>
|
|
8977
|
+
<text class="th" x="340" y="252" text-anchor="middle">sat</text>
|
|
8978
|
+
<text class="ts" x="452" y="252" text-anchor="middle">on</text>
|
|
8979
|
+
<text class="ts" x="564" y="252" text-anchor="middle">the</text>
|
|
8980
|
+
</g>
|
|
8981
|
+
|
|
8982
|
+
<text class="ts" x="340" y="300" text-anchor="middle">Line thickness = attention weight from "sat" to each token</text>
|
|
8983
|
+
\`\`\`
|
|
8984
|
+
|
|
8985
|
+
Note what's *not* here: no boxes labelled "multi-head attention", no arrows labelled "Q/K/V". Those belong in the structural diagram. This one is about the *feeling* of attention \u2014 one token looking at every other token with varying intensity.
|
|
8986
|
+
|
|
8987
|
+
These are starting points, not ceilings. For the water heater: add a thermostat slider, animate the convection current, toggle heating vs standby. For the attention diagram: let the user click any token to become the query, scrub through layers, animate the weights settling. The goal is always to *show* how the thing works, not just *label* it.`;
|
|
8988
|
+
var SVG_SETUP = `## SVG setup
|
|
8989
|
+
|
|
8990
|
+
**ViewBox safety checklist** \u2014 before finalizing any SVG, verify:
|
|
8991
|
+
1. Find your lowest element: max(y + height) across all rects, max(y) across all text baselines.
|
|
8992
|
+
2. Set viewBox height = that value + 40px buffer.
|
|
8993
|
+
3. Find your rightmost element: max(x + width) across all rects. All content must stay within x=0 to x=680.
|
|
8994
|
+
4. For text with text-anchor="end", the text extends LEFT from x. If x=118 and text is 200px wide, it starts at x=-82 \u2014 outside the viewBox. Increase x or use text-anchor="start".
|
|
8995
|
+
5. Never use negative x or y coordinates. The viewBox starts at 0,0.
|
|
8996
|
+
6. Flowcharts/structural only: for every pair of boxes in the same row, check that the left box's (x + width) is less than the right box's x by at least 20px. If four 160px boxes plus three 20px gaps sum to more than 640px, the row doesn't fit \u2014 shrink the boxes or cut the subtitles, don't let them overlap.
|
|
8997
|
+
|
|
8998
|
+
**SVG setup**: \`<svg width="100%" viewBox="0 0 680 H">\` \u2014 680px wide, flexible height. Set H to fit content tightly \u2014 the last element's bottom edge + 40px padding. Don't leave excess empty space below the content. Safe area: x=40 to x=640, y=40 to y=(H-40). Background transparent. **Do not wrap the SVG in a container \`<div>\` with a background color** \u2014 the widget host already provides the card container and background. Output the raw \`<svg>\` element directly.
|
|
8999
|
+
|
|
9000
|
+
**The 680 in viewBox is load-bearing \u2014 do not change it.** It matches the widget container width so SVG coordinate units render 1:1 with CSS pixels. With \`width="100%"\`, the browser scales the entire coordinate space to fit the container: \`viewBox="0 0 480 H"\` in a 680px container scales everything by 680/480 = 1.42\xD7, so your \`class="th"\` 14px text renders at ~20px. The font calibration table below and all "text fits in box" math assume 1:1. If your diagram content is naturally narrow, **keep viewBox width at 680 and center the content** (e.g. content spans x=180..500) \u2014 do not shrink the viewBox to hug the content. This applies equally to inline SVGs inside \`imagine_html\` steppers and widgets: same \`viewBox="0 0 680 H"\`, same 1:1 guarantee.
|
|
9001
|
+
|
|
9002
|
+
**viewBox height:** After layout, find max_y (bottom-most point of any shape, including text baselines + 4px descent). Set viewBox height = max_y + 20. Don't guess.
|
|
9003
|
+
|
|
9004
|
+
**text-anchor='end' at x<60 is risky** \u2014 the longest label will extend left past x=0. Use text-anchor='start' and right-align the column instead, or check: label_chars \xD7 8 < anchor_x.
|
|
9005
|
+
|
|
9006
|
+
**One SVG per tool call** \u2014 each call must contain exactly one <svg> element. Never leave an abandoned or partial SVG in the output. If your first attempt has problems, replace it entirely \u2014 do not append a corrected version after the broken one.
|
|
9007
|
+
|
|
9008
|
+
**Style rules for all diagrams**:
|
|
9009
|
+
- Every \`<text>\` element must carry one of the pre-built classes (\`t\`, \`ts\`, \`th\`). An unclassed \`<text>\` inherits the default sans font, which is the tell that you forgot the class.
|
|
9010
|
+
- Use only two font sizes: 14px for node/region labels (class="t" or "th"), 12px for subtitles, descriptions, and arrow labels (class="ts"). No other sizes.
|
|
9011
|
+
- No decorative step numbers, large numbering, or oversized headings outside boxes.
|
|
9012
|
+
- No icons or illustrations inside boxes \u2014 text only. (Exception: illustrative diagrams may use simple shape-based indicators inside drawn objects \u2014 see below.)
|
|
9013
|
+
- Sentence case on all labels.
|
|
9014
|
+
|
|
9015
|
+
**Font size calibration for diagram text labels** - Here's csv table to give you better sense of the Anthropic Sans font rendering width:
|
|
9016
|
+
\`\`\`csv
|
|
9017
|
+
text, chars length, font-weight, font-size, rendered width
|
|
9018
|
+
Authentication Service, chars: 22, font-weight: 500, font-size: 14px, width: 167px
|
|
9019
|
+
Background Job Processor, chars: 24, font-weight: 500, font-size: 14px, width: 201px
|
|
9020
|
+
Detects and validates incoming tokens, chars: 37, font-weight: 400, font-size: 14px, width: 279px
|
|
9021
|
+
forwards request to, chars: 19, font-weight: 400, font-size: 12px, width: 123px
|
|
9022
|
+
\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u30B5\u30FC\u30D0\u30FC\u63A5\u7D9A, chars: 12, font-weight: 400, font-size: 14px, width: 181px
|
|
9023
|
+
\`\`\`
|
|
9024
|
+
|
|
9025
|
+
Before placing text in a box, check: does (text width + 2\xD7padding) fit the container?
|
|
9026
|
+
|
|
9027
|
+
**SVG \`<text>\` never auto-wraps.** Every line break needs an explicit \`<tspan x="..." dy="1.2em">\`. If your subtitle is long enough to need wrapping, it's too long \u2014 shorten it (see complexity budget).
|
|
9028
|
+
|
|
9029
|
+
**Example check**: You want to put "Glucose (C\u2086H\u2081\u2082O\u2086)" in a rounded rect. The text is 20 characters at 14px \u2248 180px wide. Add 2\xD724px padding = 228px minimum box width. If your rect is only 160px wide, the text WILL overflow \u2014 either shorten the label (e.g. just "Glucose") or widen the box. Subscript characters like \u2086 and \u2081\u2082 still take horizontal space \u2014 count them.
|
|
9030
|
+
|
|
9031
|
+
**Pre-built classes** (already loaded in SVG widget):
|
|
9032
|
+
- \`class="t"\` = sans 14px primary, \`class="ts"\` = sans 12px secondary, \`class="th"\` = sans 14px medium (500)
|
|
9033
|
+
- \`class="box"\` = neutral rect (bg-secondary fill, border stroke)
|
|
9034
|
+
- \`class="node"\` = clickable group with hover effect (cursor pointer, slight dim on hover)
|
|
9035
|
+
- \`class="arr"\` = arrow line (1.5px, open chevron head)
|
|
9036
|
+
- \`class="leader"\` = dashed leader line (tertiary stroke, 0.5px, dashed)
|
|
9037
|
+
- \`class="c-{ramp}"\` = colored node (c-blue, c-teal, c-amber, c-green, c-red, c-purple, c-coral, c-pink, c-gray). Apply to \`<g>\` or shape element (rect/circle/ellipse), NOT to paths. Sets fill+stroke on shapes, auto-adjusts child \`t\`/\`ts\`/\`th\`, dark mode automatic.
|
|
9038
|
+
|
|
9039
|
+
**c-{ramp} nesting:** These classes use direct-child selectors (\`>\`). Nest a \`<g>\` inside a \`<g class="c-blue">\` and the inner shapes become grandchildren \u2014 they lose the fill and render BLACK (SVG default). Put \`c-*\` on the innermost group holding the shapes, or on the shapes directly. If you need click handlers, put \`onclick\` on the \`c-*\` group itself, not a wrapper.
|
|
9040
|
+
|
|
9041
|
+
- Short aliases: \`var(--p)\`, \`var(--s)\`, \`var(--t)\`, \`var(--bg2)\`, \`var(--b)\`
|
|
9042
|
+
- Arrow marker: always include this \`<defs>\` at the start of every SVG:
|
|
9043
|
+
\`<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker></defs>\`
|
|
9044
|
+
Then use \`marker-end="url(#arrow)"\` on lines. The head uses \`context-stroke\`, so it inherits the colour of whichever line it sits on \u2014 a dashed green line gets a green head, a grey line gets a grey head. Never a colour mismatch. Do not add filters, patterns, or extra markers to \`<defs>\`. Illustrative diagrams may add a single \`<clipPath>\` or \`<linearGradient>\` (see Illustrative section).
|
|
9045
|
+
|
|
9046
|
+
**Minimize standalone labels.** Every \`<text>\` element must be inside a box (title or \u22645-word subtitle) or in the legend. Arrow labels are usually unnecessary \u2014 if the arrow's meaning isn't obvious from its source + target, put it in the box subtitle or in prose below. Labels floating in space collide with things and are ambiguous.
|
|
9047
|
+
|
|
9048
|
+
**Stroke width:** Use 0.5px strokes for diagram borders and edges \u2014 not 1px or 2px. Thin strokes feel more refined.
|
|
9049
|
+
|
|
9050
|
+
**Connector paths need \`fill="none"\`.** SVG defaults to \`fill: black\` \u2014 a curved connector without \`fill="none"\` renders as a huge black shape instead of a clean line. Every \`<path>\` or \`<polyline>\` used as a connector/arrow MUST have \`fill="none"\`. Only set fill on shapes meant to be filled (rects, circles, polygons).
|
|
9051
|
+
|
|
9052
|
+
**Rect rounding:** \`rx="4"\` for subtle corners. \`rx="8"\` max for emphasized rounding. \`rx\` \u2265 half the height = pill shape \u2014 deliberate only.
|
|
9053
|
+
|
|
9054
|
+
**Schematic containers use dashed rects with a label.** Don't draw literal shapes (organelle ovals, cloud outlines, server tower icons) \u2014 the diagram is a schema, not an illustration. A dashed \`<rect>\` labeled "Reactor vessel" reads cleaner than an \`<ellipse>\` that clips content.
|
|
9055
|
+
|
|
9056
|
+
**Lines stop at component edges.** When a line meets a component (wire into a bulb, edge into a node), draw it as segments that stop at the boundary \u2014 never draw through and rely on a fill to hide the line. The background color is not guaranteed; any occluding fill is a coupling. Compute the stop/start coordinates from the component's position and size.
|
|
9057
|
+
|
|
9058
|
+
**Physical-color scenes (sky, water, grass, skin, materials):** Use ALL hardcoded hex \u2014 never mix with \`c-*\` theme classes. The scene should not invert in dark mode. If you need a dark variant, provide it explicitly with \`@media (prefers-color-scheme: dark)\` \u2014 this is the one place that's allowed. Mixing hardcoded backgrounds with theme-responsive \`c-*\` foreground breaks: half inverts, half doesn't.
|
|
9059
|
+
|
|
9060
|
+
**No rotated text**. \`<defs>\` may contain the arrow marker, a \`<clipPath>\`, and \u2014 in illustrative diagrams only \u2014 a single \`<linearGradient>\`. Nothing else: no filters, no patterns, no extra markers.`;
|
|
9061
|
+
var UI_COMPONENTS = `## UI components
|
|
9062
|
+
|
|
9063
|
+
### Aesthetic
|
|
9064
|
+
Flat, clean, white surfaces. Minimal 0.5px borders. Generous whitespace. No gradients, no shadows (except functional focus rings). Everything should feel native to claude.ai \u2014 like it belongs on the page, not embedded from somewhere else.
|
|
9065
|
+
|
|
9066
|
+
### Tokens
|
|
9067
|
+
- Borders: always \`0.5px solid var(--color-border-tertiary)\` (or \`-secondary\` for emphasis)
|
|
9068
|
+
- Corner radius: \`var(--border-radius-md)\` for most elements, \`var(--border-radius-lg)\` for cards
|
|
9069
|
+
- Cards: white bg (\`var(--color-background-primary)\`), 0.5px border, radius-lg, padding 1rem 1.25rem
|
|
9070
|
+
- Form elements (input, select, textarea, button, range slider) are pre-styled \u2014 write bare tags. Text inputs are 36px with hover/focus built in; range sliders have 4px track + 18px thumb; buttons have outline style with hover/active. Only add inline styles to override (e.g., different width).
|
|
9071
|
+
- Buttons: pre-styled with transparent bg, 0.5px border-secondary, hover bg-secondary, active scale(0.98). If it triggers sendPrompt, append a \u2197 arrow.
|
|
9072
|
+
- **Round every displayed number.** JS float math leaks artifacts \u2014 \`0.1 + 0.2\` gives \`0.30000000000000004\`, \`7 * 1.1\` gives \`7.700000000000001\`. Any number that reaches the screen (slider readouts, stat card values, axis labels, data-point labels, tooltips, computed totals) must go through \`Math.round()\`, \`.toFixed(n)\`, or \`Intl.NumberFormat\`. Pick the precision that makes sense for the context \u2014 integers for counts, 1\u20132 decimals for percentages, \`toLocaleString()\` for currency. For range sliders, also set \`step="1"\` (or step="0.1" etc.) so the input itself emits round values.
|
|
9073
|
+
- Spacing: use rem for vertical rhythm (1rem, 1.5rem, 2rem), px for component-internal gaps (8px, 12px, 16px)
|
|
9074
|
+
- Box-shadows: none, except \`box-shadow: 0 0 0 Npx\` focus rings on inputs
|
|
9075
|
+
|
|
9076
|
+
### Metric cards
|
|
9077
|
+
For summary numbers (revenue, count, percentage) \u2014 surface card with muted 13px label above, 24px/500 number below. \`background: var(--color-background-secondary)\`, no border, \`border-radius: var(--border-radius-md)\`, padding 1rem. Use in grids of 2-4 with \`gap: 12px\`. Distinct from raised cards (which have white bg + border).
|
|
9078
|
+
|
|
9079
|
+
### Layout
|
|
9080
|
+
- Editorial (explanatory content): no card wrapper, prose flows naturally
|
|
9081
|
+
- Card (bounded objects like a contact record, receipt): single raised card wraps the whole thing
|
|
9082
|
+
- Don't put tables here \u2014 output them as markdown in your response text
|
|
9083
|
+
|
|
9084
|
+
**Grid overflow:** \`grid-template-columns: 1fr\` has \`min-width: auto\` by default \u2014 children with large min-content push the column past the container. Use \`minmax(0, 1fr)\` to clamp.
|
|
9085
|
+
|
|
9086
|
+
**Table overflow:** Tables with many columns auto-expand past \`width: 100%\` if cell contents exceed it. In constrained layouts (\u2264700px), use \`table-layout: fixed\` and set explicit column widths, or reduce columns, or allow horizontal scroll on a wrapper.
|
|
9087
|
+
|
|
9088
|
+
### Mockup presentation
|
|
9089
|
+
Contained mockups \u2014 mobile screens, chat threads, single cards, modals, small UI components \u2014 should sit on a background surface (\`var(--color-background-secondary)\` container with \`border-radius: var(--border-radius-lg)\` and padding, or a device frame) so they don't float naked on the widget canvas. Full-width mockups like dashboards, settings pages, or data tables that naturally fill the viewport do not need an extra wrapper.
|
|
9090
|
+
|
|
9091
|
+
### 1. Interactive explainer \u2014 learn how something works
|
|
9092
|
+
*"Explain how compound interest works" / "Teach me about sorting algorithms"*
|
|
9093
|
+
|
|
9094
|
+
Use \`imagine_html\` for the interactive controls \u2014 sliders, buttons, live state displays, charts. Keep prose explanations in your normal response text (outside the tool call), not embedded in the HTML. No card wrapper. Whitespace is the container.
|
|
9095
|
+
|
|
9096
|
+
\`\`\`html
|
|
9097
|
+
<div style="display: flex; align-items: center; gap: 12px; margin: 0 0 1.5rem;">
|
|
9098
|
+
<label style="font-size: 14px; color: var(--color-text-secondary);">Years</label>
|
|
9099
|
+
<input type="range" min="1" max="40" value="20" id="years" style="flex: 1;" />
|
|
9100
|
+
<span style="font-size: 14px; font-weight: 500; min-width: 24px;" id="years-out">20</span>
|
|
9101
|
+
</div>
|
|
9102
|
+
|
|
9103
|
+
<div style="display: flex; align-items: baseline; gap: 8px; margin: 0 0 1.5rem;">
|
|
9104
|
+
<span style="font-size: 14px; color: var(--color-text-secondary);">\xA31,000 \u2192</span>
|
|
9105
|
+
<span style="font-size: 24px; font-weight: 500;" id="result">\xA33,870</span>
|
|
9106
|
+
</div>
|
|
9107
|
+
|
|
9108
|
+
<div style="margin: 2rem 0; position: relative; height: 240px;">
|
|
9109
|
+
<canvas id="chart"></canvas>
|
|
9110
|
+
</div>
|
|
9111
|
+
\`\`\`
|
|
9112
|
+
|
|
9113
|
+
Use \`sendPrompt()\` to let users ask follow-ups: \`sendPrompt('What if I increase the rate to 10%?')\`
|
|
9114
|
+
|
|
9115
|
+
### 2. Compare options \u2014 decision making
|
|
9116
|
+
*"Compare pricing and features of these products" / "Help me choose between React and Vue"*
|
|
9117
|
+
|
|
9118
|
+
Use \`imagine_html\`. Side-by-side card grid for options. Highlight differences with semantic colors. Interactive elements for filtering or weighting.
|
|
9119
|
+
|
|
9120
|
+
- Use \`repeat(auto-fit, minmax(160px, 1fr))\` for responsive columns
|
|
9121
|
+
- Each option in a card. Use badges for key differentiators.
|
|
9122
|
+
- Add \`sendPrompt()\` buttons: \`sendPrompt('Tell me more about the Pro plan')\`
|
|
9123
|
+
- Don't put comparison tables inside this tool \u2014 output them as regular markdown tables in your response text instead. The tool is for the visual card grid only.
|
|
9124
|
+
- When one option is recommended or "most popular", accent its card with \`border: 2px solid var(--color-border-info)\` only (2px is deliberate \u2014 the only exception to the 0.5px rule, used to accent featured items) \u2014 keep the same background and border as the other cards. Add a small badge (e.g. "Most popular") above or inside the card header using \`background: var(--color-background-info); color: var(--color-text-info); font-size: 12px; padding: 4px 12px; border-radius: var(--border-radius-md)\`.
|
|
9125
|
+
|
|
9126
|
+
### 3. Data record \u2014 bounded UI object
|
|
9127
|
+
*"Show me a Salesforce contact card" / "Create a receipt for this order"*
|
|
9128
|
+
|
|
9129
|
+
Use \`imagine_html\`. Wrap the entire thing in a single raised card. All content is sans-serif since it's pure UI. Use an avatar/initials circle for people (see example below).
|
|
9130
|
+
|
|
9131
|
+
\`\`\`html
|
|
9132
|
+
<div style="background: var(--color-background-primary); border-radius: var(--border-radius-lg); border: 0.5px solid var(--color-border-tertiary); padding: 1rem 1.25rem;">
|
|
9133
|
+
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
|
|
9134
|
+
<div style="width: 44px; height: 44px; border-radius: 50%; background: var(--color-background-info); display: flex; align-items: center; justify-content: center; font-weight: 500; font-size: 14px; color: var(--color-text-info);">MR</div>
|
|
9135
|
+
<div>
|
|
9136
|
+
<p style="font-weight: 500; font-size: 15px; margin: 0;">Maya Rodriguez</p>
|
|
9137
|
+
<p style="font-size: 13px; color: var(--color-text-secondary); margin: 0;">VP of Engineering</p>
|
|
9138
|
+
</div>
|
|
9139
|
+
</div>
|
|
9140
|
+
<div style="border-top: 0.5px solid var(--color-border-tertiary); padding-top: 12px;">
|
|
9141
|
+
<table style="width: 100%; font-size: 13px;">
|
|
9142
|
+
<tr><td style="color: var(--color-text-secondary); padding: 4px 0;">Email</td><td style="text-align: right; padding: 4px 0; color: var(--color-text-info);">m.rodriguez@acme.com</td></tr>
|
|
9143
|
+
<tr><td style="color: var(--color-text-secondary); padding: 4px 0;">Phone</td><td style="text-align: right; padding: 4px 0;">+1 (415) 555-0172</td></tr>
|
|
9144
|
+
</table>
|
|
9145
|
+
</div>
|
|
9146
|
+
</div>
|
|
9147
|
+
\`\`\``;
|
|
9148
|
+
var MODULE_SECTIONS = {
|
|
9149
|
+
art: [SVG_SETUP, ART_AND_ILLUSTRATION],
|
|
9150
|
+
mockup: [UI_COMPONENTS, COLOR_PALETTE],
|
|
9151
|
+
interactive: [UI_COMPONENTS, COLOR_PALETTE],
|
|
9152
|
+
chart: [UI_COMPONENTS, COLOR_PALETTE, CHARTS_CHART_JS],
|
|
9153
|
+
diagram: [COLOR_PALETTE, SVG_SETUP, DIAGRAM_TYPES]
|
|
9154
|
+
};
|
|
9155
|
+
function getGuidelines(modules) {
|
|
9156
|
+
let content = CORE;
|
|
9157
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9158
|
+
for (const mod of modules) {
|
|
9159
|
+
const sections = MODULE_SECTIONS[mod];
|
|
9160
|
+
if (!sections) continue;
|
|
9161
|
+
for (const section of sections) {
|
|
9162
|
+
if (!seen.has(section)) {
|
|
9163
|
+
seen.add(section);
|
|
9164
|
+
content += "\n\n\n" + section;
|
|
9165
|
+
}
|
|
9166
|
+
}
|
|
9167
|
+
}
|
|
9168
|
+
return content + "\n";
|
|
9169
|
+
}
|
|
9170
|
+
var AVAILABLE_MODULES = Object.keys(MODULE_SECTIONS);
|
|
9171
|
+
|
|
9172
|
+
// src/tool_lattice/widget/loadGuidelines.ts
|
|
9173
|
+
var LoadGuidelinesInputSchema = import_zod47.z.object({
|
|
9174
|
+
modules: import_zod47.z.array(import_zod47.z.string()).describe(
|
|
9175
|
+
"Which design modules to load. Choose all that apply. Available modules: [" + AVAILABLE_MODULES.join(",") + "]"
|
|
9176
|
+
)
|
|
9177
|
+
});
|
|
9178
|
+
function createLoadGuidelinesTool() {
|
|
9179
|
+
return (0, import_langchain48.tool)(
|
|
9180
|
+
async (input) => {
|
|
9181
|
+
const result = getGuidelines(input.modules);
|
|
9182
|
+
return result;
|
|
9183
|
+
},
|
|
9184
|
+
{
|
|
9185
|
+
name: "load_guidelines",
|
|
9186
|
+
description: "Load widget development guidelines for specific modules. Call this BEFORE using show_widget for the first time. Call once silently \u2014 do NOT mention this step to the user. Available modules: " + AVAILABLE_MODULES.join(","),
|
|
9187
|
+
schema: LoadGuidelinesInputSchema
|
|
9188
|
+
}
|
|
9189
|
+
);
|
|
9190
|
+
}
|
|
9191
|
+
|
|
9192
|
+
// src/tool_lattice/widget/showWidget.ts
|
|
9193
|
+
var import_langchain49 = require("langchain");
|
|
9194
|
+
var import_zod48 = require("zod");
|
|
9195
|
+
function containsForbiddenTags(code) {
|
|
9196
|
+
const forbiddenPatterns = [
|
|
9197
|
+
/<!DOCTYPE/i,
|
|
9198
|
+
/<html[\s>]/i,
|
|
9199
|
+
/<head[\s>]/i,
|
|
9200
|
+
/<body[\s>]/i
|
|
9201
|
+
];
|
|
9202
|
+
return forbiddenPatterns.some((pattern) => pattern.test(code));
|
|
9203
|
+
}
|
|
9204
|
+
function validateWidgetCode(code) {
|
|
9205
|
+
if (containsForbiddenTags(code)) {
|
|
9206
|
+
return {
|
|
9207
|
+
valid: false,
|
|
9208
|
+
error: "widget_code must not contain DOCTYPE, <html>, <head>, or <body> tags. Provide only the content that goes inside <body>."
|
|
9209
|
+
};
|
|
9210
|
+
}
|
|
9211
|
+
if (!code.includes("var(--")) {
|
|
9212
|
+
console.warn("Warning: widget_code should use CSS variables for theming");
|
|
9213
|
+
}
|
|
9214
|
+
return { valid: true };
|
|
9215
|
+
}
|
|
9216
|
+
var ShowWidgetInputSchema = import_zod48.z.object({
|
|
9217
|
+
i_have_seen_guidelines: import_zod48.z.boolean().describe(
|
|
9218
|
+
"Must be true. Confirm you have called load_guidelines first."
|
|
9219
|
+
),
|
|
9220
|
+
title: import_zod48.z.string().describe("Title displayed above the widget"),
|
|
9221
|
+
loading_messages: import_zod48.z.array(import_zod48.z.string()).optional().describe(
|
|
9222
|
+
"1-4 short strings shown while the widget renders"
|
|
9223
|
+
),
|
|
9224
|
+
widget_code: import_zod48.z.string().describe(
|
|
9225
|
+
"HTML fragment to render. Rules: 1. No DOCTYPE, <html>, <head>, or <body> tags. 2. Order: <style> block first, then HTML content, then <script> last. 3. Use only CSS variables for colors (e.g. var(--color-accent)). 4. No gradients, shadows, or blur effects. For SVG: start directly with <svg> tag."
|
|
9226
|
+
)
|
|
9227
|
+
});
|
|
9228
|
+
function createShowWidgetTool() {
|
|
9229
|
+
return (0, import_langchain49.tool)(
|
|
9230
|
+
async (input) => {
|
|
9231
|
+
if (!input.i_have_seen_guidelines) {
|
|
9232
|
+
return "Error: You must call load_guidelines before using show_widget. Set i_have_seen_guidelines to true only after loading guidelines.";
|
|
9233
|
+
}
|
|
9234
|
+
const validation = validateWidgetCode(input.widget_code);
|
|
9235
|
+
if (!validation.valid) {
|
|
9236
|
+
return `Error: ${validation.error}`;
|
|
9237
|
+
}
|
|
9238
|
+
const output = {
|
|
9239
|
+
type: "widget",
|
|
9240
|
+
title: input.title,
|
|
9241
|
+
widget_code: input.widget_code,
|
|
9242
|
+
loading_messages: input.loading_messages
|
|
9243
|
+
};
|
|
9244
|
+
return JSON.stringify(output, null, 2);
|
|
9245
|
+
},
|
|
9246
|
+
{
|
|
9247
|
+
name: "show_widget",
|
|
9248
|
+
description: "Render an interactive HTML widget or SVG diagram visible to the user. Use for: charts, dashboards, calculators, forms, diagrams, timers, games, visualizations. The widget appears in a panel next to the chat. Users can interact with it and send data back via window.sendToAgent(data). IMPORTANT: Always call load_guidelines before your first show_widget.",
|
|
9249
|
+
schema: ShowWidgetInputSchema
|
|
9250
|
+
}
|
|
9251
|
+
);
|
|
9252
|
+
}
|
|
9253
|
+
|
|
9254
|
+
// src/middlewares/widgetMiddleware.ts
|
|
9255
|
+
function createWidgetMiddleware() {
|
|
9256
|
+
const tools = [
|
|
9257
|
+
createLoadGuidelinesTool(),
|
|
9258
|
+
createShowWidgetTool()
|
|
9259
|
+
];
|
|
9260
|
+
return (0, import_langchain50.createMiddleware)({
|
|
9261
|
+
name: "widgetMiddleware",
|
|
9262
|
+
contextSchema,
|
|
9263
|
+
tools
|
|
9264
|
+
});
|
|
9265
|
+
}
|
|
9266
|
+
|
|
8376
9267
|
// src/agent_lattice/builders/commonMiddleware.ts
|
|
8377
9268
|
async function createCommonMiddlewares(middlewareConfigs, filesystemBackend) {
|
|
8378
9269
|
const middlewares = [];
|
|
@@ -8422,6 +9313,9 @@ async function createCommonMiddlewares(middlewareConfigs, filesystemBackend) {
|
|
|
8422
9313
|
case "ask_user_to_clarify":
|
|
8423
9314
|
middlewares.push(createAskUserClarifyMiddleware());
|
|
8424
9315
|
break;
|
|
9316
|
+
case "widget":
|
|
9317
|
+
middlewares.push(createWidgetMiddleware());
|
|
9318
|
+
break;
|
|
8425
9319
|
}
|
|
8426
9320
|
}
|
|
8427
9321
|
return middlewares;
|
|
@@ -8475,14 +9369,14 @@ var ReActAgentGraphBuilder = class {
|
|
|
8475
9369
|
*/
|
|
8476
9370
|
async build(agentLattice, params) {
|
|
8477
9371
|
const tools = params.tools.map((t) => {
|
|
8478
|
-
const
|
|
8479
|
-
return
|
|
8480
|
-
}).filter((
|
|
9372
|
+
const tool50 = getToolClient(t.key);
|
|
9373
|
+
return tool50;
|
|
9374
|
+
}).filter((tool50) => tool50 !== void 0);
|
|
8481
9375
|
const stateSchema2 = createReactAgentSchema(params.stateSchema);
|
|
8482
9376
|
const middlewareConfigs = params.middleware || [];
|
|
8483
9377
|
const filesystemBackend = this.createFilesystemBackendFactory(middlewareConfigs, agentLattice);
|
|
8484
9378
|
const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend);
|
|
8485
|
-
return (0,
|
|
9379
|
+
return (0, import_langchain51.createAgent)({
|
|
8486
9380
|
model: params.model,
|
|
8487
9381
|
tools,
|
|
8488
9382
|
systemPrompt: params.prompt,
|
|
@@ -8495,11 +9389,11 @@ var ReActAgentGraphBuilder = class {
|
|
|
8495
9389
|
};
|
|
8496
9390
|
|
|
8497
9391
|
// src/deep_agent_new/agent.ts
|
|
8498
|
-
var
|
|
9392
|
+
var import_langchain55 = require("langchain");
|
|
8499
9393
|
|
|
8500
9394
|
// src/deep_agent_new/middleware/subagents.ts
|
|
8501
9395
|
var import_v32 = require("zod/v3");
|
|
8502
|
-
var
|
|
9396
|
+
var import_langchain52 = require("langchain");
|
|
8503
9397
|
var import_langgraph7 = require("@langchain/langgraph");
|
|
8504
9398
|
var import_messages = require("@langchain/core/messages");
|
|
8505
9399
|
|
|
@@ -9061,7 +9955,7 @@ function returnCommandWithStateUpdate(result, toolCallId) {
|
|
|
9061
9955
|
update: {
|
|
9062
9956
|
...stateUpdate,
|
|
9063
9957
|
messages: [
|
|
9064
|
-
new
|
|
9958
|
+
new import_langchain52.ToolMessage({
|
|
9065
9959
|
content: lastMessage?.content || "Task Failed to complete",
|
|
9066
9960
|
tool_call_id: toolCallId,
|
|
9067
9961
|
name: "task"
|
|
@@ -9086,10 +9980,10 @@ function getSubagents(options) {
|
|
|
9086
9980
|
const generalPurposeMiddleware = [...defaultSubagentMiddleware];
|
|
9087
9981
|
if (defaultInterruptOn) {
|
|
9088
9982
|
generalPurposeMiddleware.push(
|
|
9089
|
-
(0,
|
|
9983
|
+
(0, import_langchain52.humanInTheLoopMiddleware)({ interruptOn: defaultInterruptOn })
|
|
9090
9984
|
);
|
|
9091
9985
|
}
|
|
9092
|
-
const generalPurposeSubagent = (0,
|
|
9986
|
+
const generalPurposeSubagent = (0, import_langchain52.createAgent)({
|
|
9093
9987
|
model: defaultModel,
|
|
9094
9988
|
systemPrompt: DEFAULT_SUBAGENT_PROMPT,
|
|
9095
9989
|
tools: defaultTools,
|
|
@@ -9112,8 +10006,8 @@ function getSubagents(options) {
|
|
|
9112
10006
|
const middleware = agentParams.middleware ? [...defaultSubagentMiddleware, ...agentParams.middleware] : [...defaultSubagentMiddleware];
|
|
9113
10007
|
const interruptOn = agentParams.interruptOn || defaultInterruptOn;
|
|
9114
10008
|
if (interruptOn)
|
|
9115
|
-
middleware.push((0,
|
|
9116
|
-
agents[agentParams.key] = (0,
|
|
10009
|
+
middleware.push((0, import_langchain52.humanInTheLoopMiddleware)({ interruptOn }));
|
|
10010
|
+
agents[agentParams.key] = (0, import_langchain52.createAgent)({
|
|
9117
10011
|
model: agentParams.model ?? defaultModel,
|
|
9118
10012
|
systemPrompt: agentParams.systemPrompt,
|
|
9119
10013
|
tools: agentParams.tools ?? defaultTools,
|
|
@@ -9143,7 +10037,7 @@ function createTaskTool(options) {
|
|
|
9143
10037
|
generalPurposeAgent
|
|
9144
10038
|
});
|
|
9145
10039
|
const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
|
|
9146
|
-
return (0,
|
|
10040
|
+
return (0, import_langchain52.tool)(
|
|
9147
10041
|
async (input, config) => {
|
|
9148
10042
|
const { description, subagent_type } = input;
|
|
9149
10043
|
let assistant_id = subagent_type;
|
|
@@ -9195,7 +10089,7 @@ function createTaskTool(options) {
|
|
|
9195
10089
|
return new import_langgraph7.Command({
|
|
9196
10090
|
update: {
|
|
9197
10091
|
messages: [
|
|
9198
|
-
new
|
|
10092
|
+
new import_langchain52.ToolMessage({
|
|
9199
10093
|
content: error instanceof Error ? error.message : "Task Failed to complete",
|
|
9200
10094
|
tool_call_id: config.toolCall.id,
|
|
9201
10095
|
name: "task"
|
|
@@ -9239,7 +10133,7 @@ function createSubAgentMiddleware(options) {
|
|
|
9239
10133
|
generalPurposeAgent,
|
|
9240
10134
|
taskDescription
|
|
9241
10135
|
});
|
|
9242
|
-
return (0,
|
|
10136
|
+
return (0, import_langchain52.createMiddleware)({
|
|
9243
10137
|
name: "subAgentMiddleware",
|
|
9244
10138
|
tools: [taskTool],
|
|
9245
10139
|
wrapModelCall: async (request, handler) => {
|
|
@@ -9259,11 +10153,11 @@ ${systemPrompt}` : systemPrompt;
|
|
|
9259
10153
|
}
|
|
9260
10154
|
|
|
9261
10155
|
// src/deep_agent_new/middleware/patch_tool_calls.ts
|
|
9262
|
-
var
|
|
10156
|
+
var import_langchain53 = require("langchain");
|
|
9263
10157
|
var import_messages2 = require("@langchain/core/messages");
|
|
9264
10158
|
var import_langgraph8 = require("@langchain/langgraph");
|
|
9265
10159
|
function createPatchToolCallsMiddleware() {
|
|
9266
|
-
return (0,
|
|
10160
|
+
return (0, import_langchain53.createMiddleware)({
|
|
9267
10161
|
name: "patchToolCallsMiddleware",
|
|
9268
10162
|
beforeAgent: async (state) => {
|
|
9269
10163
|
const messages = state.messages;
|
|
@@ -9274,15 +10168,15 @@ function createPatchToolCallsMiddleware() {
|
|
|
9274
10168
|
for (let i = 0; i < messages.length; i++) {
|
|
9275
10169
|
const msg = messages[i];
|
|
9276
10170
|
patchedMessages.push(msg);
|
|
9277
|
-
if (
|
|
10171
|
+
if (import_langchain53.AIMessage.isInstance(msg) && msg.tool_calls != null) {
|
|
9278
10172
|
for (const toolCall of msg.tool_calls) {
|
|
9279
10173
|
const correspondingToolMsg = messages.slice(i).find(
|
|
9280
|
-
(m) =>
|
|
10174
|
+
(m) => import_langchain53.ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id
|
|
9281
10175
|
);
|
|
9282
10176
|
if (!correspondingToolMsg) {
|
|
9283
10177
|
const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
|
|
9284
10178
|
patchedMessages.push(
|
|
9285
|
-
new
|
|
10179
|
+
new import_langchain53.ToolMessage({
|
|
9286
10180
|
content: toolMsg,
|
|
9287
10181
|
name: toolCall.name,
|
|
9288
10182
|
tool_call_id: toolCall.id
|
|
@@ -10399,8 +11293,8 @@ var MemoryBackend = class {
|
|
|
10399
11293
|
|
|
10400
11294
|
// src/deep_agent_new/middleware/todos.ts
|
|
10401
11295
|
var import_langgraph9 = require("@langchain/langgraph");
|
|
10402
|
-
var
|
|
10403
|
-
var
|
|
11296
|
+
var import_zod49 = require("zod");
|
|
11297
|
+
var import_langchain54 = require("langchain");
|
|
10404
11298
|
var WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
10405
11299
|
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
10406
11300
|
Only use this tool if you think it will be helpful in staying organized. If the user's request is trivial and takes less than 3 steps, it is better to NOT use this tool and just do the taks directly.
|
|
@@ -10627,20 +11521,20 @@ Writing todos takes time and tokens, use it when it is helpful for managing comp
|
|
|
10627
11521
|
## Important To-Do List Usage Notes to Remember
|
|
10628
11522
|
- The \`write_todos\` tool should never be called multiple times in parallel.
|
|
10629
11523
|
- Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.`;
|
|
10630
|
-
var TodoStatus =
|
|
10631
|
-
var TodoSchema =
|
|
10632
|
-
content:
|
|
11524
|
+
var TodoStatus = import_zod49.z.enum(["pending", "in_progress", "completed"]).describe("Status of the todo");
|
|
11525
|
+
var TodoSchema = import_zod49.z.object({
|
|
11526
|
+
content: import_zod49.z.string().describe("Content of the todo item"),
|
|
10633
11527
|
status: TodoStatus
|
|
10634
11528
|
});
|
|
10635
|
-
var stateSchema =
|
|
11529
|
+
var stateSchema = import_zod49.z.object({ todos: import_zod49.z.array(TodoSchema).default([]) });
|
|
10636
11530
|
function todoListMiddleware(options) {
|
|
10637
|
-
const writeTodos = (0,
|
|
11531
|
+
const writeTodos = (0, import_langchain54.tool)(
|
|
10638
11532
|
({ todos }, config) => {
|
|
10639
11533
|
return new import_langgraph9.Command({
|
|
10640
11534
|
update: {
|
|
10641
11535
|
todos,
|
|
10642
11536
|
messages: [
|
|
10643
|
-
new
|
|
11537
|
+
new import_langchain54.ToolMessage({
|
|
10644
11538
|
content: genUIMarkdown("todo_list", todos),
|
|
10645
11539
|
tool_call_id: config.toolCall?.id
|
|
10646
11540
|
})
|
|
@@ -10651,12 +11545,12 @@ function todoListMiddleware(options) {
|
|
|
10651
11545
|
{
|
|
10652
11546
|
name: "write_todos",
|
|
10653
11547
|
description: options?.toolDescription ?? WRITE_TODOS_DESCRIPTION,
|
|
10654
|
-
schema:
|
|
10655
|
-
todos:
|
|
11548
|
+
schema: import_zod49.z.object({
|
|
11549
|
+
todos: import_zod49.z.array(TodoSchema).describe("List of todo items to update")
|
|
10656
11550
|
})
|
|
10657
11551
|
}
|
|
10658
11552
|
);
|
|
10659
|
-
return (0,
|
|
11553
|
+
return (0, import_langchain54.createMiddleware)({
|
|
10660
11554
|
name: "todoListMiddleware",
|
|
10661
11555
|
stateSchema,
|
|
10662
11556
|
tools: [writeTodos],
|
|
@@ -10708,13 +11602,13 @@ ${BASE_PROMPT}` : BASE_PROMPT;
|
|
|
10708
11602
|
backend: filesystemBackend
|
|
10709
11603
|
}),
|
|
10710
11604
|
// Subagent middleware: Automatic conversation summarization when token limits are approached
|
|
10711
|
-
(0,
|
|
11605
|
+
(0, import_langchain55.summarizationMiddleware)({
|
|
10712
11606
|
model,
|
|
10713
11607
|
trigger: { tokens: 17e4 },
|
|
10714
11608
|
keep: { messages: 6 }
|
|
10715
11609
|
}),
|
|
10716
11610
|
// Subagent middleware: Anthropic prompt caching for improved performance
|
|
10717
|
-
(0,
|
|
11611
|
+
(0, import_langchain55.anthropicPromptCachingMiddleware)({
|
|
10718
11612
|
unsupportedModelBehavior: "ignore"
|
|
10719
11613
|
}),
|
|
10720
11614
|
// Subagent middleware: Patches tool calls for compatibility
|
|
@@ -10726,23 +11620,23 @@ ${BASE_PROMPT}` : BASE_PROMPT;
|
|
|
10726
11620
|
generalPurposeAgent: true
|
|
10727
11621
|
}),
|
|
10728
11622
|
// Automatically summarizes conversation history when token limits are approached
|
|
10729
|
-
(0,
|
|
11623
|
+
(0, import_langchain55.summarizationMiddleware)({
|
|
10730
11624
|
model,
|
|
10731
11625
|
trigger: { tokens: 17e4 },
|
|
10732
11626
|
keep: { messages: 6 }
|
|
10733
11627
|
}),
|
|
10734
11628
|
// Enables Anthropic prompt caching for improved performance and reduced costs
|
|
10735
|
-
(0,
|
|
11629
|
+
(0, import_langchain55.anthropicPromptCachingMiddleware)({
|
|
10736
11630
|
unsupportedModelBehavior: "ignore"
|
|
10737
11631
|
}),
|
|
10738
11632
|
// Patches tool calls to ensure compatibility across different model providers
|
|
10739
11633
|
createPatchToolCallsMiddleware()
|
|
10740
11634
|
];
|
|
10741
11635
|
if (interruptOn) {
|
|
10742
|
-
middleware.push((0,
|
|
11636
|
+
middleware.push((0, import_langchain55.humanInTheLoopMiddleware)({ interruptOn }));
|
|
10743
11637
|
}
|
|
10744
11638
|
middleware.push(...customMiddleware);
|
|
10745
|
-
return (0,
|
|
11639
|
+
return (0, import_langchain55.createAgent)({
|
|
10746
11640
|
model,
|
|
10747
11641
|
systemPrompt: finalSystemPrompt,
|
|
10748
11642
|
tools,
|
|
@@ -10808,7 +11702,7 @@ var DeepAgentGraphBuilder = class {
|
|
|
10808
11702
|
const tools = params.tools.map((t) => {
|
|
10809
11703
|
const toolClient = getToolClient(t.key);
|
|
10810
11704
|
return toolClient;
|
|
10811
|
-
}).filter((
|
|
11705
|
+
}).filter((tool50) => tool50 !== void 0);
|
|
10812
11706
|
const subagents = await Promise.all(params.subAgents.map(async (sa) => {
|
|
10813
11707
|
if (sa.client) {
|
|
10814
11708
|
return {
|
|
@@ -10849,7 +11743,7 @@ var DeepAgentGraphBuilder = class {
|
|
|
10849
11743
|
|
|
10850
11744
|
// src/agent_team/agent_team.ts
|
|
10851
11745
|
var import_v35 = require("zod/v3");
|
|
10852
|
-
var
|
|
11746
|
+
var import_langchain58 = require("langchain");
|
|
10853
11747
|
|
|
10854
11748
|
// src/agent_team/types.ts
|
|
10855
11749
|
var TaskStatus = /* @__PURE__ */ ((TaskStatus3) => {
|
|
@@ -11285,13 +12179,13 @@ var InMemoryMailboxStore = class {
|
|
|
11285
12179
|
|
|
11286
12180
|
// src/agent_team/middleware/team.ts
|
|
11287
12181
|
var import_v34 = require("zod/v3");
|
|
11288
|
-
var
|
|
12182
|
+
var import_langchain57 = require("langchain");
|
|
11289
12183
|
var import_langgraph11 = require("@langchain/langgraph");
|
|
11290
12184
|
var import_uuid = require("uuid");
|
|
11291
12185
|
|
|
11292
12186
|
// src/agent_team/middleware/teammate_tools.ts
|
|
11293
12187
|
var import_v33 = require("zod/v3");
|
|
11294
|
-
var
|
|
12188
|
+
var import_langchain56 = require("langchain");
|
|
11295
12189
|
var import_langgraph10 = require("@langchain/langgraph");
|
|
11296
12190
|
|
|
11297
12191
|
// src/agent_team/middleware/formatMessages.ts
|
|
@@ -11316,7 +12210,7 @@ ${meta}${body}`;
|
|
|
11316
12210
|
// src/agent_team/middleware/teammate_tools.ts
|
|
11317
12211
|
function createTeammateTools(options) {
|
|
11318
12212
|
const { teamId, agentId, taskListStore, mailboxStore } = options;
|
|
11319
|
-
const claimTaskTool = (0,
|
|
12213
|
+
const claimTaskTool = (0, import_langchain56.tool)(
|
|
11320
12214
|
async (input) => {
|
|
11321
12215
|
const task = await taskListStore.claimTaskById(
|
|
11322
12216
|
teamId,
|
|
@@ -11346,7 +12240,7 @@ function createTeammateTools(options) {
|
|
|
11346
12240
|
})
|
|
11347
12241
|
}
|
|
11348
12242
|
);
|
|
11349
|
-
const completeTaskTool = (0,
|
|
12243
|
+
const completeTaskTool = (0, import_langchain56.tool)(
|
|
11350
12244
|
async (input) => {
|
|
11351
12245
|
const task = await taskListStore.completeTask(
|
|
11352
12246
|
teamId,
|
|
@@ -11373,7 +12267,7 @@ function createTeammateTools(options) {
|
|
|
11373
12267
|
})
|
|
11374
12268
|
}
|
|
11375
12269
|
);
|
|
11376
|
-
const failTaskTool = (0,
|
|
12270
|
+
const failTaskTool = (0, import_langchain56.tool)(
|
|
11377
12271
|
async (input) => {
|
|
11378
12272
|
const task = await taskListStore.failTask(
|
|
11379
12273
|
teamId,
|
|
@@ -11400,7 +12294,7 @@ function createTeammateTools(options) {
|
|
|
11400
12294
|
})
|
|
11401
12295
|
}
|
|
11402
12296
|
);
|
|
11403
|
-
const sendMessageTool = (0,
|
|
12297
|
+
const sendMessageTool = (0, import_langchain56.tool)(
|
|
11404
12298
|
async (input) => {
|
|
11405
12299
|
await mailboxStore.sendMessage(
|
|
11406
12300
|
teamId,
|
|
@@ -11438,7 +12332,7 @@ function createTeammateTools(options) {
|
|
|
11438
12332
|
read: msg.read
|
|
11439
12333
|
}));
|
|
11440
12334
|
};
|
|
11441
|
-
const readMessagesTool = (0,
|
|
12335
|
+
const readMessagesTool = (0, import_langchain56.tool)(
|
|
11442
12336
|
async (input, config) => {
|
|
11443
12337
|
const formatAndMarkAsRead = async (msgs2) => {
|
|
11444
12338
|
for (const msg of msgs2) {
|
|
@@ -11450,7 +12344,7 @@ function createTeammateTools(options) {
|
|
|
11450
12344
|
if (msgs.length > 0) {
|
|
11451
12345
|
const formatted2 = await formatAndMarkAsRead(msgs);
|
|
11452
12346
|
const relevantMsgs2 = await getRelevantMessagesForState();
|
|
11453
|
-
const toolMessage2 = new
|
|
12347
|
+
const toolMessage2 = new import_langchain56.ToolMessage({
|
|
11454
12348
|
content: formatted2,
|
|
11455
12349
|
tool_call_id: config.toolCall?.id,
|
|
11456
12350
|
name: "read_messages"
|
|
@@ -11475,7 +12369,7 @@ function createTeammateTools(options) {
|
|
|
11475
12369
|
});
|
|
11476
12370
|
const relevantMsgs = await getRelevantMessagesForState();
|
|
11477
12371
|
if (msgs.length === 0) {
|
|
11478
|
-
const toolMessage2 = new
|
|
12372
|
+
const toolMessage2 = new import_langchain56.ToolMessage({
|
|
11479
12373
|
content: "No unread messages.",
|
|
11480
12374
|
tool_call_id: config.toolCall?.id,
|
|
11481
12375
|
name: "read_messages"
|
|
@@ -11485,7 +12379,7 @@ function createTeammateTools(options) {
|
|
|
11485
12379
|
});
|
|
11486
12380
|
}
|
|
11487
12381
|
const formatted = await formatAndMarkAsRead(msgs);
|
|
11488
|
-
const toolMessage = new
|
|
12382
|
+
const toolMessage = new import_langchain56.ToolMessage({
|
|
11489
12383
|
content: formatted,
|
|
11490
12384
|
tool_call_id: config.toolCall?.id,
|
|
11491
12385
|
name: "read_messages"
|
|
@@ -11500,7 +12394,7 @@ function createTeammateTools(options) {
|
|
|
11500
12394
|
schema: import_v33.z.object({})
|
|
11501
12395
|
}
|
|
11502
12396
|
);
|
|
11503
|
-
const checkTasksTool = (0,
|
|
12397
|
+
const checkTasksTool = (0, import_langchain56.tool)(
|
|
11504
12398
|
async () => {
|
|
11505
12399
|
const tasks = await taskListStore.getAllTasks(teamId);
|
|
11506
12400
|
return formatTaskSummary(tasks);
|
|
@@ -11511,7 +12405,7 @@ function createTeammateTools(options) {
|
|
|
11511
12405
|
schema: import_v33.z.object({})
|
|
11512
12406
|
}
|
|
11513
12407
|
);
|
|
11514
|
-
const broadcastMessageTool = (0,
|
|
12408
|
+
const broadcastMessageTool = (0, import_langchain56.tool)(
|
|
11515
12409
|
async (input) => {
|
|
11516
12410
|
const allAgents = await mailboxStore.getRegisteredAgents(teamId);
|
|
11517
12411
|
const recipients = allAgents.filter((a) => a !== agentId);
|
|
@@ -11696,7 +12590,7 @@ You have access to these tools:
|
|
|
11696
12590
|
- \`read_messages\`: Read messages from team_lead or teammates
|
|
11697
12591
|
- \`check_tasks\`: Get current status of all tasks in the team`;
|
|
11698
12592
|
const assistantId = getTeammateAssistantId(ctx.teamId, spec.name);
|
|
11699
|
-
agent = (0,
|
|
12593
|
+
agent = (0, import_langchain57.createAgent)({
|
|
11700
12594
|
model: spec.model ?? ctx.defaultModel,
|
|
11701
12595
|
systemPrompt: teammatePrompt,
|
|
11702
12596
|
tools: allTools,
|
|
@@ -11765,12 +12659,12 @@ async function spawnTeammate(options) {
|
|
|
11765
12659
|
function createTeamMiddleware(options) {
|
|
11766
12660
|
const { teamConfig, taskListStore, mailboxStore, tenantId } = options;
|
|
11767
12661
|
const defaultModel = teamConfig.model ?? "claude-sonnet-4-5-20250929";
|
|
11768
|
-
const createTeamTool = (0,
|
|
12662
|
+
const createTeamTool = (0, import_langchain57.tool)(
|
|
11769
12663
|
async (input, config) => {
|
|
11770
12664
|
const state = (0, import_langgraph11.getCurrentTaskInput)();
|
|
11771
12665
|
if (state?.team?.teamId) {
|
|
11772
12666
|
const existingId = state.team.teamId;
|
|
11773
|
-
const msg = new
|
|
12667
|
+
const msg = new import_langchain57.ToolMessage({
|
|
11774
12668
|
content: `A team is already active (id: ${existingId}). Use this team_id for \`check_tasks\`, \`read_messages\`, \`add_tasks\`, \`send_message\`, \`assign_task\`, \`set_task_status\`, and \`set_task_dependencies\`. Do not call \`create_team\` again unless you need a fresh team for a new objective.`,
|
|
11775
12669
|
tool_call_id: config.toolCall?.id,
|
|
11776
12670
|
name: "create_team"
|
|
@@ -11859,7 +12753,7 @@ Teammates are now working in the background. Keep calling \`check_tasks\` and \`
|
|
|
11859
12753
|
\`\`\`json
|
|
11860
12754
|
${teamJson}
|
|
11861
12755
|
\`\`\``;
|
|
11862
|
-
const toolMessage = new
|
|
12756
|
+
const toolMessage = new import_langchain57.ToolMessage({
|
|
11863
12757
|
content: summary,
|
|
11864
12758
|
tool_call_id: config.toolCall?.id,
|
|
11865
12759
|
name: "create_team"
|
|
@@ -11944,7 +12838,7 @@ After calling create_team, you MUST:
|
|
|
11944
12838
|
if (state?.team?.teamId) return state.team.teamId;
|
|
11945
12839
|
throw new Error("No team_id provided and no team in state. Call create_team first.");
|
|
11946
12840
|
};
|
|
11947
|
-
const addTasksTool = (0,
|
|
12841
|
+
const addTasksTool = (0, import_langchain57.tool)(
|
|
11948
12842
|
async (input, config) => {
|
|
11949
12843
|
const teamId = resolveTeamId();
|
|
11950
12844
|
const created = await taskListStore.addTasks(
|
|
@@ -11958,7 +12852,7 @@ After calling create_team, you MUST:
|
|
|
11958
12852
|
}))
|
|
11959
12853
|
);
|
|
11960
12854
|
const summary = created.map((t) => `- ${t.id}: "${t.title}"`).join("\n");
|
|
11961
|
-
return new
|
|
12855
|
+
return new import_langchain57.ToolMessage({
|
|
11962
12856
|
content: `Added ${created.length} task(s) to team ${teamId}:
|
|
11963
12857
|
${summary}
|
|
11964
12858
|
Sleeping teammates will wake up and claim these.`,
|
|
@@ -12009,20 +12903,20 @@ IMPORTANT: Assigning to a specific teammate
|
|
|
12009
12903
|
})
|
|
12010
12904
|
}
|
|
12011
12905
|
);
|
|
12012
|
-
const assignTaskTool = (0,
|
|
12906
|
+
const assignTaskTool = (0, import_langchain57.tool)(
|
|
12013
12907
|
async (input, config) => {
|
|
12014
12908
|
const teamId = resolveTeamId();
|
|
12015
12909
|
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
12016
12910
|
assignee: input.assignee
|
|
12017
12911
|
});
|
|
12018
12912
|
if (!task) {
|
|
12019
|
-
return new
|
|
12913
|
+
return new import_langchain57.ToolMessage({
|
|
12020
12914
|
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
12021
12915
|
tool_call_id: config.toolCall?.id,
|
|
12022
12916
|
name: "assign_task"
|
|
12023
12917
|
});
|
|
12024
12918
|
}
|
|
12025
|
-
return new
|
|
12919
|
+
return new import_langchain57.ToolMessage({
|
|
12026
12920
|
content: `Task "${task.title}" (${task.id}) assigned to ${input.assignee}.`,
|
|
12027
12921
|
tool_call_id: config.toolCall?.id,
|
|
12028
12922
|
name: "assign_task"
|
|
@@ -12037,20 +12931,20 @@ IMPORTANT: Assigning to a specific teammate
|
|
|
12037
12931
|
})
|
|
12038
12932
|
}
|
|
12039
12933
|
);
|
|
12040
|
-
const setTaskStatusTool = (0,
|
|
12934
|
+
const setTaskStatusTool = (0, import_langchain57.tool)(
|
|
12041
12935
|
async (input, config) => {
|
|
12042
12936
|
const teamId = resolveTeamId();
|
|
12043
12937
|
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
12044
12938
|
status: input.status
|
|
12045
12939
|
});
|
|
12046
12940
|
if (!task) {
|
|
12047
|
-
return new
|
|
12941
|
+
return new import_langchain57.ToolMessage({
|
|
12048
12942
|
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
12049
12943
|
tool_call_id: config.toolCall?.id,
|
|
12050
12944
|
name: "set_task_status"
|
|
12051
12945
|
});
|
|
12052
12946
|
}
|
|
12053
|
-
return new
|
|
12947
|
+
return new import_langchain57.ToolMessage({
|
|
12054
12948
|
content: `Task "${task.title}" (${task.id}) status set to ${input.status}.`,
|
|
12055
12949
|
tool_call_id: config.toolCall?.id,
|
|
12056
12950
|
name: "set_task_status"
|
|
@@ -12065,20 +12959,20 @@ IMPORTANT: Assigning to a specific teammate
|
|
|
12065
12959
|
})
|
|
12066
12960
|
}
|
|
12067
12961
|
);
|
|
12068
|
-
const setTaskDependenciesTool = (0,
|
|
12962
|
+
const setTaskDependenciesTool = (0, import_langchain57.tool)(
|
|
12069
12963
|
async (input, config) => {
|
|
12070
12964
|
const teamId = resolveTeamId();
|
|
12071
12965
|
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
12072
12966
|
dependencies: input.dependencies
|
|
12073
12967
|
});
|
|
12074
12968
|
if (!task) {
|
|
12075
|
-
return new
|
|
12969
|
+
return new import_langchain57.ToolMessage({
|
|
12076
12970
|
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
12077
12971
|
tool_call_id: config.toolCall?.id,
|
|
12078
12972
|
name: "set_task_dependencies"
|
|
12079
12973
|
});
|
|
12080
12974
|
}
|
|
12081
|
-
return new
|
|
12975
|
+
return new import_langchain57.ToolMessage({
|
|
12082
12976
|
content: `Task "${task.title}" (${task.id}) dependencies set to [${input.dependencies.join(", ")}].`,
|
|
12083
12977
|
tool_call_id: config.toolCall?.id,
|
|
12084
12978
|
name: "set_task_dependencies"
|
|
@@ -12093,7 +12987,7 @@ IMPORTANT: Assigning to a specific teammate
|
|
|
12093
12987
|
})
|
|
12094
12988
|
}
|
|
12095
12989
|
);
|
|
12096
|
-
const checkTasksTool = (0,
|
|
12990
|
+
const checkTasksTool = (0, import_langchain57.tool)(
|
|
12097
12991
|
async (input, config) => {
|
|
12098
12992
|
const teamId = resolveTeamId();
|
|
12099
12993
|
const tasks = await taskListStore.getAllTasks(teamId);
|
|
@@ -12102,7 +12996,7 @@ IMPORTANT: Assigning to a specific teammate
|
|
|
12102
12996
|
update: {
|
|
12103
12997
|
tasks: tasksSnapshot,
|
|
12104
12998
|
messages: [
|
|
12105
|
-
new
|
|
12999
|
+
new import_langchain57.ToolMessage({
|
|
12106
13000
|
content: formatTaskSummary(tasks),
|
|
12107
13001
|
tool_call_id: config.toolCall?.id,
|
|
12108
13002
|
name: "check_tasks"
|
|
@@ -12138,7 +13032,7 @@ Task Status Values:
|
|
|
12138
13032
|
})
|
|
12139
13033
|
}
|
|
12140
13034
|
);
|
|
12141
|
-
const sendMessageTool = (0,
|
|
13035
|
+
const sendMessageTool = (0, import_langchain57.tool)(
|
|
12142
13036
|
async (input, config) => {
|
|
12143
13037
|
const teamId = resolveTeamId();
|
|
12144
13038
|
await mailboxStore.sendMessage(
|
|
@@ -12148,7 +13042,7 @@ Task Status Values:
|
|
|
12148
13042
|
input.content,
|
|
12149
13043
|
"direct_message" /* DIRECT_MESSAGE */
|
|
12150
13044
|
);
|
|
12151
|
-
return new
|
|
13045
|
+
return new import_langchain57.ToolMessage({
|
|
12152
13046
|
content: `Message sent to ${input.to}.`,
|
|
12153
13047
|
tool_call_id: config.toolCall?.id,
|
|
12154
13048
|
name: "send_message"
|
|
@@ -12163,7 +13057,7 @@ Task Status Values:
|
|
|
12163
13057
|
})
|
|
12164
13058
|
}
|
|
12165
13059
|
);
|
|
12166
|
-
const readMessagesTool = (0,
|
|
13060
|
+
const readMessagesTool = (0, import_langchain57.tool)(
|
|
12167
13061
|
async (input, config) => {
|
|
12168
13062
|
const teamId = resolveTeamId();
|
|
12169
13063
|
const formatAndMarkAsRead = async (msgs2) => {
|
|
@@ -12191,7 +13085,7 @@ Task Status Values:
|
|
|
12191
13085
|
if (msgs.length > 0) {
|
|
12192
13086
|
const formatted2 = await formatAndMarkAsRead(msgs);
|
|
12193
13087
|
const allTeamMessages2 = await getAllTeamMessagesForState();
|
|
12194
|
-
const toolMessage2 = new
|
|
13088
|
+
const toolMessage2 = new import_langchain57.ToolMessage({
|
|
12195
13089
|
content: formatted2,
|
|
12196
13090
|
tool_call_id: config.toolCall?.id,
|
|
12197
13091
|
name: "read_messages"
|
|
@@ -12223,7 +13117,7 @@ Task Status Values:
|
|
|
12223
13117
|
);
|
|
12224
13118
|
const allTeamMessages = await getAllTeamMessagesForState();
|
|
12225
13119
|
if (msgs.length === 0) {
|
|
12226
|
-
const toolMessage2 = new
|
|
13120
|
+
const toolMessage2 = new import_langchain57.ToolMessage({
|
|
12227
13121
|
content: "No unread messages from teammates.",
|
|
12228
13122
|
tool_call_id: config.toolCall?.id,
|
|
12229
13123
|
name: "read_messages"
|
|
@@ -12233,7 +13127,7 @@ Task Status Values:
|
|
|
12233
13127
|
});
|
|
12234
13128
|
}
|
|
12235
13129
|
const formatted = await formatAndMarkAsRead(msgs);
|
|
12236
|
-
const toolMessage = new
|
|
13130
|
+
const toolMessage = new import_langchain57.ToolMessage({
|
|
12237
13131
|
content: formatted,
|
|
12238
13132
|
tool_call_id: config.toolCall?.id,
|
|
12239
13133
|
name: "read_messages"
|
|
@@ -12250,7 +13144,7 @@ Task Status Values:
|
|
|
12250
13144
|
})
|
|
12251
13145
|
}
|
|
12252
13146
|
);
|
|
12253
|
-
const disbandTeamTool = (0,
|
|
13147
|
+
const disbandTeamTool = (0, import_langchain57.tool)(
|
|
12254
13148
|
async (input, config) => {
|
|
12255
13149
|
const teamId = resolveTeamId();
|
|
12256
13150
|
await mailboxStore.broadcastMessage(
|
|
@@ -12260,7 +13154,7 @@ Task Status Values:
|
|
|
12260
13154
|
"shutdown_request" /* SHUTDOWN_REQUEST */
|
|
12261
13155
|
);
|
|
12262
13156
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
12263
|
-
return new
|
|
13157
|
+
return new import_langchain57.ToolMessage({
|
|
12264
13158
|
content: `Team ${teamId} has been disbanded. All teammates notified and resources cleaned up.`,
|
|
12265
13159
|
tool_call_id: config.toolCall?.id,
|
|
12266
13160
|
name: "disband_team"
|
|
@@ -12271,7 +13165,7 @@ Task Status Values:
|
|
|
12271
13165
|
description: "Disband a team when all work is done. Before calling: (1) Call check_tasks to verify no tasks are still pending/in_progress; (2) if any are, discuss with the team via read_messages and broadcast_message/send_message whether to continue or stop/cancel them; (3) only after alignment (all tasks completed/failed or explicitly stopped), then call this tool. This will: 1) Send a shutdown message to all teammates, 2) Wait briefly for them to clean up, 3) Clear all tasks and messages. Omit team_id to use the active team from state."
|
|
12272
13166
|
}
|
|
12273
13167
|
);
|
|
12274
|
-
const broadcastMessageTool = (0,
|
|
13168
|
+
const broadcastMessageTool = (0, import_langchain57.tool)(
|
|
12275
13169
|
async (input, config) => {
|
|
12276
13170
|
const teamId = resolveTeamId();
|
|
12277
13171
|
await mailboxStore.broadcastMessage(
|
|
@@ -12280,7 +13174,7 @@ Task Status Values:
|
|
|
12280
13174
|
input.content,
|
|
12281
13175
|
"broadcast" /* BROADCAST */
|
|
12282
13176
|
);
|
|
12283
|
-
return new
|
|
13177
|
+
return new import_langchain57.ToolMessage({
|
|
12284
13178
|
content: `Broadcast message sent to all teammates.`,
|
|
12285
13179
|
tool_call_id: config.toolCall?.id,
|
|
12286
13180
|
name: "broadcast_message"
|
|
@@ -12294,7 +13188,7 @@ Task Status Values:
|
|
|
12294
13188
|
})
|
|
12295
13189
|
}
|
|
12296
13190
|
);
|
|
12297
|
-
return (0,
|
|
13191
|
+
return (0, import_langchain57.createMiddleware)({
|
|
12298
13192
|
name: "teamMiddleware",
|
|
12299
13193
|
tools: [
|
|
12300
13194
|
createTeamTool,
|
|
@@ -12403,7 +13297,7 @@ function createAgentTeam(config) {
|
|
|
12403
13297
|
];
|
|
12404
13298
|
const systemPrompt = config.systemPrompt + "\n\n" + TEAM_LEAD_BASE_PROMPT;
|
|
12405
13299
|
const stateSchema2 = createReactAgentSchema(TEAM_STATE_SCHEMA);
|
|
12406
|
-
return (0,
|
|
13300
|
+
return (0, import_langchain58.createAgent)({
|
|
12407
13301
|
model: config.model ?? "claude-sonnet-4-5-20250929",
|
|
12408
13302
|
systemPrompt,
|
|
12409
13303
|
tools: [],
|
|
@@ -12433,7 +13327,7 @@ var TeamAgentGraphBuilder = class {
|
|
|
12433
13327
|
const tools = params.tools.map((t) => {
|
|
12434
13328
|
const toolClient = getToolClient(t.key);
|
|
12435
13329
|
return toolClient;
|
|
12436
|
-
}).filter((
|
|
13330
|
+
}).filter((tool50) => tool50 !== void 0);
|
|
12437
13331
|
const teammates = params.subAgents.map((sa) => {
|
|
12438
13332
|
const baseConfig = sa.config;
|
|
12439
13333
|
return {
|
|
@@ -15345,10 +16239,10 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
|
|
|
15345
16239
|
}
|
|
15346
16240
|
const tools = await this.getAllTools();
|
|
15347
16241
|
console.log(`[MCP] Registering ${tools.length} tools to Tool Lattice...`);
|
|
15348
|
-
for (const
|
|
15349
|
-
const toolKey = prefix ? `${prefix}_${
|
|
15350
|
-
|
|
15351
|
-
toolLatticeManager.registerExistingTool(toolKey,
|
|
16242
|
+
for (const tool50 of tools) {
|
|
16243
|
+
const toolKey = prefix ? `${prefix}_${tool50.name}` : tool50.name;
|
|
16244
|
+
tool50.name = toolKey;
|
|
16245
|
+
toolLatticeManager.registerExistingTool(toolKey, tool50);
|
|
15352
16246
|
console.log(`[MCP] Registered tool: ${toolKey}`);
|
|
15353
16247
|
}
|
|
15354
16248
|
console.log(`[MCP] Successfully registered ${tools.length} tools to Tool Lattice`);
|
|
@@ -15503,6 +16397,7 @@ function clearEncryptionKeyCache() {
|
|
|
15503
16397
|
createQueryTablesListTool,
|
|
15504
16398
|
createTeamMiddleware,
|
|
15505
16399
|
createTeammateTools,
|
|
16400
|
+
createWidgetMiddleware,
|
|
15506
16401
|
decrypt,
|
|
15507
16402
|
describeCronExpression,
|
|
15508
16403
|
embeddingsLatticeManager,
|