privateboard 0.1.6 → 0.1.8
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/cli.js +700 -17
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
- package/public/adjourn-overlay.css +194 -0
- package/public/agent-profile.js +14 -0
- package/public/app.js +862 -741
- package/public/bento.html +1396 -0
- package/public/home.html +389 -17
- package/public/index.html +211 -152
- package/public/magazine.html +1266 -0
- package/public/newspaper.html +1770 -0
- package/public/report.html +666 -55
- package/public/typing-sfx.js +158 -0
- package/public/user-settings.css +156 -19
- package/public/user-settings.js +109 -4
package/dist/cli.js
CHANGED
|
@@ -376,6 +376,14 @@ var init_usage_daily = __esm({
|
|
|
376
376
|
}
|
|
377
377
|
});
|
|
378
378
|
|
|
379
|
+
// src/storage/migrations/025_brief_mode.sql
|
|
380
|
+
var brief_mode_default;
|
|
381
|
+
var init_brief_mode = __esm({
|
|
382
|
+
"src/storage/migrations/025_brief_mode.sql"() {
|
|
383
|
+
brief_mode_default = "-- 025_brief_mode \xB7 add `mode` column to briefs.\n--\n-- Briefs now come in two flavors:\n-- \xB7 'research-note' (default \xB7 the existing markdown-body research\n-- dossier with composer-picked components and a spine-rendered\n-- report.html view)\n-- \xB7 'bento' (new \xB7 a single-page infographic with fixed structure \xB7\n-- header band, 3-milestone timeline, ranked-bars / verification /\n-- talking-points sidebars, conclusion band \xB7 rendered by bento.html\n-- from the BentoScaffold JSON persisted in body_json)\n--\n-- Legacy rows pre-dating bento are research-note by definition; the\n-- DEFAULT clause backfills them. The column is NOT NULL so the\n-- pipeline can branch on it without an `?? \"research-note\"` fallback\n-- at every read site.\n--\n-- For 'bento' rows the body_md column is empty (or carries a plain-\n-- text fallback) and body_json holds the structured BentoScaffold; the\n-- spine / components_json / composer_rationale columns are unused (the\n-- bento has fixed shape, the composer doesn't run for it).\n\nALTER TABLE briefs ADD COLUMN mode TEXT NOT NULL DEFAULT 'research-note';\n";
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
379
387
|
// src/storage/db.ts
|
|
380
388
|
var db_exports = {};
|
|
381
389
|
__export(db_exports, {
|
|
@@ -457,6 +465,7 @@ var init_db = __esm({
|
|
|
457
465
|
init_intensity_brutal_to_terse();
|
|
458
466
|
init_brief_assets();
|
|
459
467
|
init_usage_daily();
|
|
468
|
+
init_brief_mode();
|
|
460
469
|
MIGRATIONS = [
|
|
461
470
|
{ name: "001_init.sql", sql: init_default },
|
|
462
471
|
{ name: "002_default_opus.sql", sql: default_opus_default },
|
|
@@ -481,7 +490,8 @@ var init_db = __esm({
|
|
|
481
490
|
{ name: "021_notes.sql", sql: notes_default },
|
|
482
491
|
{ name: "022_intensity_brutal_to_terse.sql", sql: intensity_brutal_to_terse_default },
|
|
483
492
|
{ name: "023_brief_assets.sql", sql: brief_assets_default },
|
|
484
|
-
{ name: "024_usage_daily.sql", sql: usage_daily_default }
|
|
493
|
+
{ name: "024_usage_daily.sql", sql: usage_daily_default },
|
|
494
|
+
{ name: "025_brief_mode.sql", sql: brief_mode_default }
|
|
485
495
|
];
|
|
486
496
|
_db = null;
|
|
487
497
|
}
|
|
@@ -5413,6 +5423,424 @@ ${lines}`;
|
|
|
5413
5423
|
}
|
|
5414
5424
|
];
|
|
5415
5425
|
}
|
|
5426
|
+
var BENTO_SYSTEM = [
|
|
5427
|
+
`You are the chair of a boardroom session. You produce a SINGLE-PAGE INFOGRAPHIC report \u2014 a structured "bento box" that compresses the room's discussion into a one-screen visual brief. Not a memo. Not a research note. A poster.`,
|
|
5428
|
+
"",
|
|
5429
|
+
"## What a bento is for",
|
|
5430
|
+
"",
|
|
5431
|
+
"Bento is for the moment AFTER the discussion when the user wants to forward the takeaway to someone in 60 seconds. Not the analysis itself \xB7 the answer. Not the room's debate \xB7 the conclusion the room reached. Not all the evidence \xB7 the 3 most load-bearing claims with their numerics.",
|
|
5432
|
+
"",
|
|
5433
|
+
"Lossy is feature, not bug. If you can't compress to 3 milestones + a one-line conclusion, you didn't pick the load-bearing pieces. Compression is the work.",
|
|
5434
|
+
"",
|
|
5435
|
+
"## The 8 slots you fill (output is JSON only)",
|
|
5436
|
+
"",
|
|
5437
|
+
'1. **title** \xB7 the takeaway in claim form \xB7 serif headline \xB7 \u2264 110 chars \xB7 ONE sentence that names what the room concluded. Quantified is stronger ("X cuts cost 10\xD7" beats "X is meaningful").',
|
|
5438
|
+
"",
|
|
5439
|
+
"2. **kicker** \xB7 1 italic sentence \xB7 \u2264 200 chars \xB7 the angle / what's new about this conclusion. Reads like a magazine deck under the headline.",
|
|
5440
|
+
"",
|
|
5441
|
+
'3. **source** \xB7 \u2264 80 chars \xB7 attribution / context. Format: "From {chair name} \xB7 {date or horizon}" or "{room subject short} \xB7 {date}". Mono small caps register; auto-filled if you skip.',
|
|
5442
|
+
"",
|
|
5443
|
+
"4. **milestones** \xB7 EXACTLY 3 cards in the LEFT timeline. Each card has:",
|
|
5444
|
+
' \xB7 `period` \xB7 time / phase tag \xB7 \u2264 24 chars \xB7 "2025H2" / "Q1 2026" / "Phase 2" / "Top finding" / "Step 1". Choose the lens (chronological, ranked, or sequential) that fits this room.',
|
|
5445
|
+
" \xB7 `title` \xB7 \u2264 60 chars \xB7 the milestone's name in claim form.",
|
|
5446
|
+
" \xB7 `body` \xB7 2-3 sentences \xB7 \u2264 220 chars \xB7 what happened / will happen / why it matters.",
|
|
5447
|
+
' \xB7 `callout` \xB7 \u2264 12 chars \xB7 the metric / multiplier / count that anchors this card visually. Examples: "-10\xD7", "2000\u4E07\u9897", "$120M ARR", "T+90". Empty string when no clean numeric exists for this milestone.',
|
|
5448
|
+
" \xB7 `tags` \xB7 0-4 short chips \xB7 \u2264 16 chars each \xB7 entity names, owners, domains. Render as small rounded chips beside the body.",
|
|
5449
|
+
"",
|
|
5450
|
+
" Pick the 3 most load-bearing pieces of the discussion. NOT the 3 most-mentioned. The 3 that, taken together, produce the takeaway from \xA71.",
|
|
5451
|
+
"",
|
|
5452
|
+
"5. **rankedBars** \xB7 OPTIONAL \xB7 top-right card \xB7 3-5 ranked entries with normalised ratio bars. Pick this slot when the room produced quantitative comparisons (e.g. competitor TAM, model latencies, milestone costs). Each entry: `label` (\u2264 40), `value` (\u2264 20, the displayed number), `ratio` (0-1 normalised for bar width \u2014 divide by the largest entry). Set to `null` when the room had no real ranked-numeric material.",
|
|
5453
|
+
"",
|
|
5454
|
+
'6. **verification** \xB7 OPTIONAL \xB7 mid-right card \xB7 3-5 single-sentence bullets \xB7 \u2264 140 chars each \xB7 the validation signals that, if observed, would confirm the takeaway. Maps from convergence + leading-indicator material. The card title is your call (default "What we\'d verify" / "\u9A8C\u8BC1\u7EBF\u7D22"). Set to `null` when the room raised no clean signals to watch.',
|
|
5455
|
+
"",
|
|
5456
|
+
'7. **talkingPoints** \xB7 ALWAYS rendered \xB7 bottom-right card \xB7 3-5 elevator-pitch sentences \xB7 \u2264 120 chars each \xB7 what a reader could literally say to brief a colleague verbally. Imperative, declarative, no hedging. Maps from recommendations + bottom line, collapsed to single declarative sentences. Default title "How to say this" / "\u53E3\u64AD\u63D0\u7EB2".',
|
|
5457
|
+
"",
|
|
5458
|
+
"8. **conclusion** \xB7 bottom band \xB7 \u2264 100 chars \xB7 ONE sentence \xB7 the takeaway compressed further than the title. The reader walks away with this single line.",
|
|
5459
|
+
"",
|
|
5460
|
+
'Plus optional **flow** \xB7 2-4 short nodes joined by arrows at render time \xB7 for transformations the room argued ("before \u2192 after" / "10\xD7 \u2192 10\xD7" / "weak \u2192 defensible"). Set to `null` when no clean transformation arc exists.',
|
|
5461
|
+
"",
|
|
5462
|
+
"Plus auto **footerTag** \xB7 \u2264 80 chars \xB7 short caption mono caps \xB7 room subject + horizon. Auto-filled if you skip.",
|
|
5463
|
+
"",
|
|
5464
|
+
"## Routing the SIGNALS block into bento slots",
|
|
5465
|
+
"",
|
|
5466
|
+
"The SIGNALS block carries each director's extracted material with kind prefixes (`[claim]`, `[evidence\xB7data]`, `[risk]`, etc.). Route them into bento slots:",
|
|
5467
|
+
" \xB7 **milestones** \u2190 the 3 strongest `[claim]` + `[evidence\xB7data]` pairs \xB7 pair each claim with its supporting datapoint when one exists; that becomes the card's `body` + `callout`.",
|
|
5468
|
+
" \xB7 **rankedBars** \u2190 `[evidence\xB7data]` entries that are numeric AND comparable (the room mentioned multiple options / competitors / sizes / dates as ranked numerics).",
|
|
5469
|
+
' \xB7 **verification** \u2190 `[evidence\xB7data]` + `[claim]` material that READS as something to monitor ("if X stays above Y, the call holds").',
|
|
5470
|
+
" \xB7 **talkingPoints** \u2190 `[action]` + `[claim\xB7confidence:high]` material distilled to imperative single-sentence form.",
|
|
5471
|
+
" \xB7 **conclusion** \u2190 compressed restatement of the room's anchor (Bottom Line / Thesis equivalent).",
|
|
5472
|
+
" \xB7 **flow** \u2190 when the room argued a transformation (X becomes Y) or a multi-step path, distill it to 2-4 nodes.",
|
|
5473
|
+
"",
|
|
5474
|
+
"## Output format",
|
|
5475
|
+
"",
|
|
5476
|
+
"Strict JSON inside a fenced ```json code block. No prose outside the block. The shape is fixed:",
|
|
5477
|
+
"",
|
|
5478
|
+
"```json",
|
|
5479
|
+
"{",
|
|
5480
|
+
' "title": "Sentence-form takeaway with a quantified element when one fits.",',
|
|
5481
|
+
' "kicker": "1-sentence italic deck explaining the angle.",',
|
|
5482
|
+
' "source": "From {chair name} \xB7 {date or horizon}",',
|
|
5483
|
+
' "milestones": [',
|
|
5484
|
+
' { "period": "2025H2", "title": "Milestone name", "body": "2-3 sentences.", "callout": "-10\xD7", "tags": ["AWS", "GCP"] },',
|
|
5485
|
+
' { "period": "Q1 2026", "title": "...", "body": "...", "callout": "...", "tags": [] },',
|
|
5486
|
+
' { "period": "2026H2", "title": "...", "body": "...", "callout": "...", "tags": [] }',
|
|
5487
|
+
" ],",
|
|
5488
|
+
' "rankedBars": {',
|
|
5489
|
+
' "title": "By the numbers",',
|
|
5490
|
+
' "entries": [',
|
|
5491
|
+
' { "label": "Hopper", "value": "1.0\xD7", "ratio": 1.0 },',
|
|
5492
|
+
' { "label": "Blackwell", "value": "0.1\xD7", "ratio": 0.1 },',
|
|
5493
|
+
' { "label": "Rubin", "value": "0.01\xD7", "ratio": 0.01 }',
|
|
5494
|
+
" ]",
|
|
5495
|
+
" },",
|
|
5496
|
+
' "verification": {',
|
|
5497
|
+
` "title": "What we'd verify",`,
|
|
5498
|
+
' "bullets": [',
|
|
5499
|
+
' "Single sentence verification signal #1.",',
|
|
5500
|
+
' "Single sentence verification signal #2.",',
|
|
5501
|
+
' "Single sentence verification signal #3."',
|
|
5502
|
+
" ]",
|
|
5503
|
+
" },",
|
|
5504
|
+
' "talkingPoints": {',
|
|
5505
|
+
' "title": "How to say this",',
|
|
5506
|
+
' "bullets": [',
|
|
5507
|
+
' "First elevator-pitch sentence.",',
|
|
5508
|
+
' "Second elevator-pitch sentence.",',
|
|
5509
|
+
' "Third elevator-pitch sentence."',
|
|
5510
|
+
" ]",
|
|
5511
|
+
" },",
|
|
5512
|
+
' "conclusion": "One-line takeaway \xB7 \u2264 100 chars.",',
|
|
5513
|
+
' "flow": { "nodes": ["Hopper", "Blackwell", "Rubin"], "caption": "Two-stage cost step-down" },',
|
|
5514
|
+
' "footerTag": "Q4 update \xB7 2025H2 \u2192 2026H2"',
|
|
5515
|
+
"}",
|
|
5516
|
+
"```",
|
|
5517
|
+
"",
|
|
5518
|
+
"Constraints:",
|
|
5519
|
+
'\xB7 Title MUST be claim-style (state the takeaway, not the topic). "Three commitments that change the trajectory" not "Analysis of strategic options".',
|
|
5520
|
+
"\xB7 Milestones MUST be 3. Pad with the most-mentioned claim if the room only surfaced 2 strong points; trim to the 3 most load-bearing if the room surfaced more.",
|
|
5521
|
+
'\xB7 `callout` field carries ONE numeric or unit \xB7 no English plus number combinations ("$120M ARR" OK; "makes $120M" not OK).',
|
|
5522
|
+
"\xB7 `talkingPoints` is mandatory \xB7 if the room had no recommendations, distil the bottom-line claim into 3 ways a colleague could quote it.",
|
|
5523
|
+
"\xB7 No markdown formatting inside string fields. No bullet characters. No headings. Plain prose only \u2014 the renderer adds visual structure."
|
|
5524
|
+
].join("\n");
|
|
5525
|
+
function buildBentoMessages(opts) {
|
|
5526
|
+
const { room, members, perDirectorSignals, language } = opts;
|
|
5527
|
+
const memberList = members.map((a) => `${a.id} \xB7 ${a.name} (${a.handle}) \u2014 ${a.roleTag}`).join("\n \xB7 ");
|
|
5528
|
+
const signalsBlock = perDirectorSignals.map((d) => {
|
|
5529
|
+
if (!d.signals.length) return `[${d.directorId}] ${d.directorName} \u2014 (no signals)`;
|
|
5530
|
+
const lines = d.signals.map((s, i) => ` \xB7 ${d.directorId}#${i} [${s.lens}] ${s.text}`).join("\n");
|
|
5531
|
+
return `[${d.directorId}] ${d.directorName}
|
|
5532
|
+
${lines}`;
|
|
5533
|
+
}).join("\n\n");
|
|
5534
|
+
const supplementBlock = opts.supplement && opts.supplement.trim() ? [
|
|
5535
|
+
``,
|
|
5536
|
+
`\u2500\u2500\u2500 SUPPLEMENTARY PERSPECTIVE FROM USER \u2500\u2500\u2500`,
|
|
5537
|
+
``,
|
|
5538
|
+
`The user has asked you to additionally consider this angle when building the bento. Surface it in the most fitting slot (most often as one of the milestones, occasionally as a verification bullet or a talking point).`,
|
|
5539
|
+
``,
|
|
5540
|
+
opts.supplement.trim(),
|
|
5541
|
+
``,
|
|
5542
|
+
`\u2500\u2500\u2500 END SUPPLEMENT \u2500\u2500\u2500`
|
|
5543
|
+
].join("\n") : "";
|
|
5544
|
+
return [
|
|
5545
|
+
{
|
|
5546
|
+
role: "system",
|
|
5547
|
+
content: [BENTO_SYSTEM, "", languageInstruction(language)].join("\n")
|
|
5548
|
+
},
|
|
5549
|
+
{
|
|
5550
|
+
role: "user",
|
|
5551
|
+
content: [
|
|
5552
|
+
`ROOM #${room.number} \xB7 ${room.name}`,
|
|
5553
|
+
`Subject: ${room.subject}`,
|
|
5554
|
+
``,
|
|
5555
|
+
`Directors:`,
|
|
5556
|
+
` \xB7 ${memberList}`,
|
|
5557
|
+
``,
|
|
5558
|
+
`\u2500\u2500\u2500 SIGNALS \u2500\u2500\u2500`,
|
|
5559
|
+
``,
|
|
5560
|
+
signalsBlock || "(no signals extracted)",
|
|
5561
|
+
``,
|
|
5562
|
+
`\u2500\u2500\u2500 END SIGNALS \u2500\u2500\u2500`,
|
|
5563
|
+
supplementBlock,
|
|
5564
|
+
``,
|
|
5565
|
+
`Produce the bento now. JSON only.`
|
|
5566
|
+
].join("\n")
|
|
5567
|
+
}
|
|
5568
|
+
];
|
|
5569
|
+
}
|
|
5570
|
+
var MAGAZINE_SYSTEM = [
|
|
5571
|
+
'You are the chair of a boardroom session. You produce a MAGAZINE-SPREAD report \u2014 an editorial single-page layout that opens like a magazine cover, lays out a numbered card grid of takeaways, walks through a 3-step setup band, and closes with a high-contrast "why this matters" pull-list. Not a memo. Not a research note. A magazine.',
|
|
5572
|
+
"",
|
|
5573
|
+
"## What a magazine spread is for",
|
|
5574
|
+
"",
|
|
5575
|
+
"Magazine is for the moment when the user wants to share the takeaway as a *piece of editorial content* \u2014 something a reader could scroll on a feed. Not the analysis itself \xB7 the cover line + the numbered tactics + the setup recipe. Not the room's debate \xB7 the published version a stranger could understand cold.",
|
|
5576
|
+
"",
|
|
5577
|
+
'Editorial register \xB7 magazines lead with personality, not with hedging. Headlines are claim-style, decks have voice, talking points read like "5 tactics that actually work" rather than "considerations". You are writing the cover spread, not the white paper.',
|
|
5578
|
+
"",
|
|
5579
|
+
"## The 8 slots you fill (output is JSON only)",
|
|
5580
|
+
"",
|
|
5581
|
+
'1. **title** \xB7 the cover headline \xB7 serif display \xB7 \u2264 110 chars \xB7 ONE sentence in claim form. Magazine covers state the takeaway with confidence \u2014 quantified or named ("How {chair} {verb} {object}" / "X is the operating system for Y" / "Three commitments that change the trajectory").',
|
|
5582
|
+
"",
|
|
5583
|
+
"2. **kicker** \xB7 the cover deck \xB7 \u2264 200 chars \xB7 1 sentence under the headline. Non-italic register \xB7 subtitle voice. Names the angle / what's new.",
|
|
5584
|
+
"",
|
|
5585
|
+
'3. **source** \xB7 \u2264 80 chars \xB7 the masthead byline \xB7 "From {chair name} \xB7 {date}" / "Issue 01 \xB7 {date}" / "{chair name} presents". Mono small caps register; auto-filled if you skip.',
|
|
5586
|
+
"",
|
|
5587
|
+
`4. **milestones** \xB7 EXACTLY 3 cards \xB7 these become the magazine's middle band \u2014 a "how to set this up in 10 minutes" 3-step recipe. Each card has:`,
|
|
5588
|
+
' \xB7 `period` \xB7 short step label \xB7 \u2264 24 chars \xB7 "Step 1" / "\u51C6\u5907" / "Phase 2" / "First". Imperative or sequential.',
|
|
5589
|
+
' \xB7 `title` \xB7 \u2264 60 chars \xB7 what to do at this step. Imperative voice ("Set up environment" / "\u51C6\u5907\u73AF\u5883").',
|
|
5590
|
+
" \xB7 `body` \xB7 2 sentences \xB7 \u2264 220 chars \xB7 the concrete instruction \xB7 how a reader actually does this step.",
|
|
5591
|
+
" \xB7 `callout` \xB7 \u2264 12 chars \xB7 optional anchor numeric \xB7 usually empty in magazine mode (the layout doesn't lean on big numbers in this band).",
|
|
5592
|
+
" \xB7 `tags` \xB7 empty array (`[]`) \xB7 the magazine layout doesn't render tags on the setup band.",
|
|
5593
|
+
"",
|
|
5594
|
+
"5. **rankedBars** \xB7 OPTIONAL \xB7 3-5 ranked entries. Renders only when the room produced a clean ranking. Set to `null` when there's no real ranked-numeric material.",
|
|
5595
|
+
"",
|
|
5596
|
+
`6. **verification** \xB7 MANDATORY for magazine \xB7 these become the dark closing band's "why this matters" pull-list. Provide 4 bullets \xB7 \u2264 140 chars each \xB7 each bullet is a SHORT REASON the takeaway matters \xB7 phrased as a stand-alone declaration ("Saves time \xB7 routine work compresses to minutes" / "Highly personalized \xB7 tailored to your context"). Title is your call (default "Why this matters" / "\u4E3A\u4EC0\u4E48\u8FD9\u5F88\u5F3A\u5927").`,
|
|
5597
|
+
"",
|
|
5598
|
+
`7. **talkingPoints** \xB7 MANDATORY \xB7 5 numbered cards \xB7 \u2264 120 chars each \xB7 these become the magazine's hero card grid \xB7 the "5 tactics" feature. EACH BULLET MUST BEGIN WITH A SHORT PHRASE (the card's title \u2014 what the renderer extracts as the card headline) FOLLOWED BY " \xB7 " (a middle-dot with spaces) AND THEN THE BODY SENTENCE. Example format: "Weekly check-in \xB7 Run /weekly check-in to track key metrics across a personal dashboard." The renderer splits on " \xB7 " to extract title + body. If you can't fit 5, output as many as you have \u22653.`,
|
|
5599
|
+
"",
|
|
5600
|
+
' Title side \u2264 24 chars \xB7 body side \u2264 100 chars. Imperative voice in the body ("Run X" / "\u4F7F\u7528 X"). Imperative for English magazine voice; in Chinese, lead the body with the verb ("\u4F7F\u7528\u2026" / "\u8FD0\u884C\u2026" / "\u901A\u8FC7\u2026").',
|
|
5601
|
+
"",
|
|
5602
|
+
` Default talkingPoints title \xB7 "5 tactics" / "5 \u4E2A\u4F8B\u5B50\u770B\u660E\u767D" / "5 ways to use this" \xB7 the LARGE outline numeral on the magazine cover is derived from this section's count.`,
|
|
5603
|
+
"",
|
|
5604
|
+
"8. **conclusion** \xB7 \u2264 100 chars \xB7 ONE sentence \xB7 the cover-line reinforcement. The reader walks away with this single line \xB7 usually a short imperative or claim restatement.",
|
|
5605
|
+
"",
|
|
5606
|
+
"Plus optional **flow** \xB7 usually `null` in magazine mode \xB7 only fill when the room argued a clean transformation arc.",
|
|
5607
|
+
"",
|
|
5608
|
+
"Plus auto **footerTag** \xB7 \u2264 80 chars \xB7 masthead-style caption \xB7 auto-filled if you skip.",
|
|
5609
|
+
"",
|
|
5610
|
+
"## Routing the SIGNALS block into magazine slots",
|
|
5611
|
+
"",
|
|
5612
|
+
" \xB7 **title** \u2190 the strongest claim \xB7 phrased as a magazine cover headline.",
|
|
5613
|
+
" \xB7 **kicker** \u2190 the supporting deck \xB7 \u2264 1 sentence \xB7 what's new about this conclusion.",
|
|
5614
|
+
" \xB7 **milestones** \u2190 if the room argued a setup / how-to flow, route the 3 most important steps. If the room didn't produce a clean recipe, distill the 3 most actionable items into imperative steps.",
|
|
5615
|
+
' \xB7 **talkingPoints** \u2190 the 5 strongest action / claim entries \xB7 each rewritten in "Title \xB7 Body" form (split with the middle dot + spaces).',
|
|
5616
|
+
" \xB7 **verification** \u2190 4 reasons the takeaway matters \xB7 drawn from the room's evidence + bottom-line material \xB7 phrased as standalone declarations.",
|
|
5617
|
+
" \xB7 **conclusion** \u2190 compressed restatement of the room's anchor.",
|
|
5618
|
+
"",
|
|
5619
|
+
"## Output format",
|
|
5620
|
+
"",
|
|
5621
|
+
"Strict JSON inside a fenced ```json code block. No prose outside the block. The shape is fixed (same as bento mode):",
|
|
5622
|
+
"",
|
|
5623
|
+
"```json",
|
|
5624
|
+
"{",
|
|
5625
|
+
' "title": "Cover-line takeaway in claim form.",',
|
|
5626
|
+
' "kicker": "1-sentence deck explaining the angle.",',
|
|
5627
|
+
' "source": "From {chair name} \xB7 {date}",',
|
|
5628
|
+
' "milestones": [',
|
|
5629
|
+
' { "period": "Step 1", "title": "Set up environment", "body": "Install the tooling and create the workspace folder.", "callout": "", "tags": [] },',
|
|
5630
|
+
' { "period": "Step 2", "title": "Initialize and configure", "body": "Run the init command and follow the prompts to wire up your slash commands.", "callout": "", "tags": [] },',
|
|
5631
|
+
' { "period": "Step 3", "title": "Run and customize", "body": "Run weekly or daily, customize prompts to your context.", "callout": "", "tags": [] }',
|
|
5632
|
+
" ],",
|
|
5633
|
+
' "rankedBars": null,',
|
|
5634
|
+
' "verification": {',
|
|
5635
|
+
' "title": "Why this matters",',
|
|
5636
|
+
' "bullets": [',
|
|
5637
|
+
' "Saves time \xB7 routine work compresses to minutes a week.",',
|
|
5638
|
+
' "Highly personalized \xB7 tailored to the context you provide.",',
|
|
5639
|
+
' "Beyond coding \xB7 use cases extend well past development tasks.",',
|
|
5640
|
+
' "Infinite extensibility \xB7 spin up more agents to fit any new need."',
|
|
5641
|
+
" ]",
|
|
5642
|
+
" },",
|
|
5643
|
+
' "talkingPoints": {',
|
|
5644
|
+
' "title": "5 tactics",',
|
|
5645
|
+
' "bullets": [',
|
|
5646
|
+
' "Weekly check-in \xB7 Run /weekly check-in to track key metrics on a personal dashboard.",',
|
|
5647
|
+
' "Daily journal \xB7 Run /daily check-in to journal accomplishments and feelings.",',
|
|
5648
|
+
' "Content research \xB7 Use /newsletter researcher to draft your own briefs in your voice.",',
|
|
5649
|
+
' "Brain-dump analyzer \xB7 Run /brain dump analysis on raw notes to surface a mind-map.",',
|
|
5650
|
+
' "Daily brief \xB7 Use /daily brief for a tailored news round-up by your interests."',
|
|
5651
|
+
" ]",
|
|
5652
|
+
" },",
|
|
5653
|
+
' "conclusion": "One-line takeaway \xB7 \u2264 100 chars.",',
|
|
5654
|
+
' "flow": null,',
|
|
5655
|
+
' "footerTag": "Issue 01 \xB7 {date}"',
|
|
5656
|
+
"}",
|
|
5657
|
+
"```",
|
|
5658
|
+
"",
|
|
5659
|
+
"Constraints:",
|
|
5660
|
+
`\xB7 Title MUST be cover-style (a magazine wouldn't run "Analysis of strategic options" \u2014 it would run "How X built the operating system for Y"). State the takeaway, not the topic.`,
|
|
5661
|
+
`\xB7 talkingPoints bullets MUST follow "Title \xB7 Body" with the middle dot + spaces \xB7 the renderer's split is exact.`,
|
|
5662
|
+
"\xB7 Provide 4 verification bullets when the room has the material; 3 acceptable as a floor.",
|
|
5663
|
+
"\xB7 No markdown formatting inside string fields. No bullet characters. No headings. Plain prose only \u2014 the renderer adds visual structure."
|
|
5664
|
+
].join("\n");
|
|
5665
|
+
function buildMagazineMessages(opts) {
|
|
5666
|
+
const { room, members, perDirectorSignals, language } = opts;
|
|
5667
|
+
const memberList = members.map((a) => `${a.id} \xB7 ${a.name} (${a.handle}) \u2014 ${a.roleTag}`).join("\n \xB7 ");
|
|
5668
|
+
const signalsBlock = perDirectorSignals.map((d) => {
|
|
5669
|
+
if (!d.signals.length) return `[${d.directorId}] ${d.directorName} \u2014 (no signals)`;
|
|
5670
|
+
const lines = d.signals.map((s, i) => ` \xB7 ${d.directorId}#${i} [${s.lens}] ${s.text}`).join("\n");
|
|
5671
|
+
return `[${d.directorId}] ${d.directorName}
|
|
5672
|
+
${lines}`;
|
|
5673
|
+
}).join("\n\n");
|
|
5674
|
+
const supplementBlock = opts.supplement && opts.supplement.trim() ? [
|
|
5675
|
+
``,
|
|
5676
|
+
`\u2500\u2500\u2500 SUPPLEMENTARY PERSPECTIVE FROM USER \u2500\u2500\u2500`,
|
|
5677
|
+
``,
|
|
5678
|
+
`The user has asked you to additionally consider this angle when building the magazine. Surface it in the most fitting slot (most often as one of the talking points or verification bullets).`,
|
|
5679
|
+
``,
|
|
5680
|
+
opts.supplement.trim(),
|
|
5681
|
+
``,
|
|
5682
|
+
`\u2500\u2500\u2500 END SUPPLEMENT \u2500\u2500\u2500`
|
|
5683
|
+
].join("\n") : "";
|
|
5684
|
+
return [
|
|
5685
|
+
{
|
|
5686
|
+
role: "system",
|
|
5687
|
+
content: [MAGAZINE_SYSTEM, "", languageInstruction(language)].join("\n")
|
|
5688
|
+
},
|
|
5689
|
+
{
|
|
5690
|
+
role: "user",
|
|
5691
|
+
content: [
|
|
5692
|
+
`ROOM #${room.number} \xB7 ${room.name}`,
|
|
5693
|
+
`Subject: ${room.subject}`,
|
|
5694
|
+
``,
|
|
5695
|
+
`Directors:`,
|
|
5696
|
+
` \xB7 ${memberList}`,
|
|
5697
|
+
``,
|
|
5698
|
+
`\u2500\u2500\u2500 SIGNALS \u2500\u2500\u2500`,
|
|
5699
|
+
``,
|
|
5700
|
+
signalsBlock || "(no signals extracted)",
|
|
5701
|
+
``,
|
|
5702
|
+
`\u2500\u2500\u2500 END SIGNALS \u2500\u2500\u2500`,
|
|
5703
|
+
supplementBlock,
|
|
5704
|
+
``,
|
|
5705
|
+
`Produce the magazine spread now. JSON only.`
|
|
5706
|
+
].join("\n")
|
|
5707
|
+
}
|
|
5708
|
+
];
|
|
5709
|
+
}
|
|
5710
|
+
var NEWSPAPER_SYSTEM = [
|
|
5711
|
+
"You are the chair of a boardroom session. You produce a NEWSPAPER FRONT-PAGE report \u2014 a broadsheet single-page layout with a banner masthead, a full-width front-page headline, and a 3-column editorial spread with sidebar callouts. Not a memo. Not a magazine. A NEWSPAPER.",
|
|
5712
|
+
"",
|
|
5713
|
+
"## Voice",
|
|
5714
|
+
"",
|
|
5715
|
+
'Front-page journalism. Headlines are declarative, present-tense, claim-form ("BOARD COMMITS TO TWO-TRACK RELEASE" \u2014 not "An analysis of release strategy" or "What the board decided"). Body prose is lead-paragraph editorial: each paragraph carries one claim with its supporting evidence, sentences are short, transitions are crisp, hedging is minimal.',
|
|
5716
|
+
"",
|
|
5717
|
+
"Newspaper voice is more formal than magazine voice and more confident than research-note voice. Imagine the front page of a serious broadsheet \xB7 The Wall Street Journal, The Financial Times, The Economist if it ran a daily.",
|
|
5718
|
+
"",
|
|
5719
|
+
"## The 8 slots you fill (output is JSON only)",
|
|
5720
|
+
"",
|
|
5721
|
+
'1. **title** \xB7 the front-page banner headline \xB7 \u2264 110 chars \xB7 ALL-CAPS-ABLE claim (the renderer applies uppercase) \xB7 NOT a question, NOT a label, a CLAIM. Examples: "BOARD COMMITS TO TWO-TRACK RELEASE", "MARKETS BRACE FOR Q4 RESET", "REGULATORS MOVE AGAINST DARK PATTERNS".',
|
|
5722
|
+
"",
|
|
5723
|
+
"2. **kicker** \xB7 the subheading deck \xB7 \u2264 200 chars \xB7 1 sentence under the headline \xB7 expands the claim with the angle / what's new.",
|
|
5724
|
+
"",
|
|
5725
|
+
'3. **source** \xB7 masthead byline \xB7 \u2264 80 chars \xB7 "From the desk of {chair name} \xB7 {date}" or similar. Mono small caps register; auto-filled if you skip.',
|
|
5726
|
+
"",
|
|
5727
|
+
"4. **milestones** \xB7 EXACTLY 3 column-stories. Each milestone IS one of the newspaper's 3 main columns. Each card has:",
|
|
5728
|
+
' \xB7 `period` \xB7 column section label \xB7 \u2264 24 chars \xB7 "TOP STORY" / "MARKETS" / "POLICY" / "OPS" / "OPINION". Section-banner register. ALL-CAPS-ABLE.',
|
|
5729
|
+
" \xB7 `title` \xB7 column subheading \xB7 \u2264 60 chars \xB7 the column's hook. Question or claim form OK.",
|
|
5730
|
+
" \xB7 `body` \xB7 4-7 sentences \xB7 \u2264 420 chars \xB7 LONGER than other modes since this fills a full editorial column. Lead-paragraph style: open with the claim, support with evidence, close with the so-what. Present tense, declarative.",
|
|
5731
|
+
" \xB7 `callout` \xB7 usually empty \xB7 the layout doesn't lean on big-number callouts in this pattern.",
|
|
5732
|
+
" \xB7 `tags` \xB7 empty array.",
|
|
5733
|
+
"",
|
|
5734
|
+
'5. **rankedBars** \xB7 OPTIONAL \xB7 top-right "image slot" \xB7 3-5 ranked entries painted as a small editorial chart. Set null when the room has no clean ranked-numeric material.',
|
|
5735
|
+
"",
|
|
5736
|
+
'6. **verification** \xB7 MANDATORY \xB7 these become the bottom-left "MORE HEADINGS" stacked sidebar \xB7 3-5 entries \xB7 each \u2264 180 chars \xB7 phrased as "Heading: body sentence." \u2014 use a colon as separator (the renderer splits on it for typography). Each entry is a SHORT NEWS ITEM that supports or qualifies the front-page claim.',
|
|
5737
|
+
"",
|
|
5738
|
+
"7. **talkingPoints** \xB7 MANDATORY \xB7 3-5 quotable lines \xB7 \u2264 140 chars each \xB7 these become the bottom editorial column's paragraphs \xB7 imperative or declarative, no hedging. Each is a self-contained line a reader could pull-quote.",
|
|
5739
|
+
"",
|
|
5740
|
+
'8. **conclusion** \xB7 the front-page "BOTTOM LINE" inverted callout \xB7 \u2264 100 chars \xB7 ONE sentence \xB7 the takeaway compressed to a quote. The reader walks away with this single line.',
|
|
5741
|
+
"",
|
|
5742
|
+
"Plus optional **flow** \xB7 usually `null` in newspaper mode \xB7 only fill when the room argued a clean transformation arc.",
|
|
5743
|
+
"",
|
|
5744
|
+
"Plus auto **footerTag** \xB7 \u2264 80 chars \xB7 masthead-style date caption \xB7 auto-filled if you skip.",
|
|
5745
|
+
"",
|
|
5746
|
+
"## Routing the SIGNALS block into newspaper slots",
|
|
5747
|
+
"",
|
|
5748
|
+
" \xB7 **title** \u2190 the strongest claim \xB7 phrased as a front-page headline (declarative, claim-form).",
|
|
5749
|
+
" \xB7 **kicker** \u2190 the supporting deck \xB7 \u2264 1 sentence \xB7 what's new about this conclusion.",
|
|
5750
|
+
" \xB7 **milestones** \u2190 the 3 most load-bearing columns of the discussion \xB7 each milestone gets a section banner (TOP STORY / MARKETS / etc.) + a column-length editorial body.",
|
|
5751
|
+
` \xB7 **verification** \u2190 the room's secondary findings \xB7 each phrased as "Heading: body." with a colon separator.`,
|
|
5752
|
+
" \xB7 **talkingPoints** \u2190 the room's actionable conclusions \xB7 3-5 quotable lines.",
|
|
5753
|
+
" \xB7 **conclusion** \u2190 the IMPORTANT-DETAILS callout \xB7 the room's bottom line in claim form.",
|
|
5754
|
+
"",
|
|
5755
|
+
"## Output format",
|
|
5756
|
+
"",
|
|
5757
|
+
"Strict JSON inside a fenced ```json code block. No prose outside the block. The shape is fixed (same as bento mode):",
|
|
5758
|
+
"",
|
|
5759
|
+
"```json",
|
|
5760
|
+
"{",
|
|
5761
|
+
' "title": "Banner headline in claim form",',
|
|
5762
|
+
' "kicker": "1-sentence subdeck explaining the angle.",',
|
|
5763
|
+
' "source": "From the desk of {chair name} \xB7 {date}",',
|
|
5764
|
+
' "milestones": [',
|
|
5765
|
+
' { "period": "TOP STORY", "title": "What the board decided", "body": "4-7 sentence editorial column body explaining the lead story with evidence and so-what.", "callout": "", "tags": [] },',
|
|
5766
|
+
' { "period": "MARKETS", "title": "Why the market is reading this", "body": "4-7 sentence editorial column body covering the second angle.", "callout": "", "tags": [] },',
|
|
5767
|
+
' { "period": "POLICY", "title": "Open questions for the regulator", "body": "4-7 sentence editorial column body covering the third angle.", "callout": "", "tags": [] }',
|
|
5768
|
+
" ],",
|
|
5769
|
+
' "rankedBars": null,',
|
|
5770
|
+
' "verification": {',
|
|
5771
|
+
' "title": "More headings",',
|
|
5772
|
+
' "bullets": [',
|
|
5773
|
+
` "Q4 outlook: Three commitments anchor the next quarter's plan.",`,
|
|
5774
|
+
' "Pricing: The pilot programme survives, but caps are tightening.",',
|
|
5775
|
+
' "Hiring: Two new senior roles open by next board meeting.",',
|
|
5776
|
+
' "Risk: Compliance review remains the gating constraint."',
|
|
5777
|
+
" ]",
|
|
5778
|
+
" },",
|
|
5779
|
+
' "talkingPoints": {',
|
|
5780
|
+
' "title": "From the editorial",',
|
|
5781
|
+
' "bullets": [',
|
|
5782
|
+
' "First quotable editorial line that names the takeaway.",',
|
|
5783
|
+
' "Second quotable line that addresses the obvious objection.",',
|
|
5784
|
+
' "Third quotable line that sets the next-step expectation."',
|
|
5785
|
+
" ]",
|
|
5786
|
+
" },",
|
|
5787
|
+
' "conclusion": "One-sentence bottom-line takeaway \xB7 \u2264 100 chars.",',
|
|
5788
|
+
' "flow": null,',
|
|
5789
|
+
' "footerTag": "{date} \xB7 Edition 01"',
|
|
5790
|
+
"}",
|
|
5791
|
+
"```",
|
|
5792
|
+
"",
|
|
5793
|
+
"Constraints:",
|
|
5794
|
+
"\xB7 Title MUST be declarative claim-form (newspaper headlines DO NOT ask questions on the front page \xB7 they STATE).",
|
|
5795
|
+
"\xB7 Milestones bodies are LONGER than other modes (4-7 sentences) \xB7 this is a column, not a card.",
|
|
5796
|
+
'\xB7 Verification bullets MUST follow "Heading: body." with a colon separator \xB7 the renderer splits on it.',
|
|
5797
|
+
"\xB7 No markdown formatting inside string fields. No bullet characters. No headings. Plain prose only \u2014 the renderer adds visual structure."
|
|
5798
|
+
].join("\n");
|
|
5799
|
+
function buildNewspaperMessages(opts) {
|
|
5800
|
+
const { room, members, perDirectorSignals, language } = opts;
|
|
5801
|
+
const memberList = members.map((a) => `${a.id} \xB7 ${a.name} (${a.handle}) \u2014 ${a.roleTag}`).join("\n \xB7 ");
|
|
5802
|
+
const signalsBlock = perDirectorSignals.map((d) => {
|
|
5803
|
+
if (!d.signals.length) return `[${d.directorId}] ${d.directorName} \u2014 (no signals)`;
|
|
5804
|
+
const lines = d.signals.map((s, i) => ` \xB7 ${d.directorId}#${i} [${s.lens}] ${s.text}`).join("\n");
|
|
5805
|
+
return `[${d.directorId}] ${d.directorName}
|
|
5806
|
+
${lines}`;
|
|
5807
|
+
}).join("\n\n");
|
|
5808
|
+
const supplementBlock = opts.supplement && opts.supplement.trim() ? [
|
|
5809
|
+
``,
|
|
5810
|
+
`\u2500\u2500\u2500 SUPPLEMENTARY PERSPECTIVE FROM USER \u2500\u2500\u2500`,
|
|
5811
|
+
``,
|
|
5812
|
+
`The user has asked you to additionally consider this angle when building the newspaper. Surface it in the most fitting slot (most often as one of the 3 milestone columns or as a verification headline).`,
|
|
5813
|
+
``,
|
|
5814
|
+
opts.supplement.trim(),
|
|
5815
|
+
``,
|
|
5816
|
+
`\u2500\u2500\u2500 END SUPPLEMENT \u2500\u2500\u2500`
|
|
5817
|
+
].join("\n") : "";
|
|
5818
|
+
return [
|
|
5819
|
+
{
|
|
5820
|
+
role: "system",
|
|
5821
|
+
content: [NEWSPAPER_SYSTEM, "", languageInstruction(language)].join("\n")
|
|
5822
|
+
},
|
|
5823
|
+
{
|
|
5824
|
+
role: "user",
|
|
5825
|
+
content: [
|
|
5826
|
+
`ROOM #${room.number} \xB7 ${room.name}`,
|
|
5827
|
+
`Subject: ${room.subject}`,
|
|
5828
|
+
``,
|
|
5829
|
+
`Directors:`,
|
|
5830
|
+
` \xB7 ${memberList}`,
|
|
5831
|
+
``,
|
|
5832
|
+
`\u2500\u2500\u2500 SIGNALS \u2500\u2500\u2500`,
|
|
5833
|
+
``,
|
|
5834
|
+
signalsBlock || "(no signals extracted)",
|
|
5835
|
+
``,
|
|
5836
|
+
`\u2500\u2500\u2500 END SIGNALS \u2500\u2500\u2500`,
|
|
5837
|
+
supplementBlock,
|
|
5838
|
+
``,
|
|
5839
|
+
`Produce the newspaper front page now. JSON only.`
|
|
5840
|
+
].join("\n")
|
|
5841
|
+
}
|
|
5842
|
+
];
|
|
5843
|
+
}
|
|
5416
5844
|
var WRITE_SYSTEM = [
|
|
5417
5845
|
"You are the chair of a boardroom session. You have a structured scaffold. Write the final report in markdown \u2014 a McKinsey-grade research note that makes the multi-director thinking visible. Pyramid principle, MECE, action-oriented.",
|
|
5418
5846
|
"",
|
|
@@ -7720,6 +8148,126 @@ function parseScaffold(raw, fallbackTitle, fallbackOriginalQuestion) {
|
|
|
7720
8148
|
openQuestions: parseOpenQuestions(parsed.openQuestions)
|
|
7721
8149
|
};
|
|
7722
8150
|
}
|
|
8151
|
+
function parseBento(raw, fallbackTitle, fallbackSource, fallbackFooterTag) {
|
|
8152
|
+
const parsed = extractJson3(raw);
|
|
8153
|
+
if (!parsed) return null;
|
|
8154
|
+
const title = clipString(stringField(parsed.title) || fallbackTitle, 110);
|
|
8155
|
+
if (!title) return null;
|
|
8156
|
+
const kicker = clipString(stringField(parsed.kicker), 200);
|
|
8157
|
+
const source = clipString(stringField(parsed.source) || fallbackSource, 80);
|
|
8158
|
+
const milestones = parseBentoMilestones(parsed.milestones);
|
|
8159
|
+
if (milestones.length === 0) return null;
|
|
8160
|
+
while (milestones.length < 3) {
|
|
8161
|
+
milestones.push({ period: "", title: "", body: "", callout: "", tags: [] });
|
|
8162
|
+
}
|
|
8163
|
+
if (milestones.length > 3) milestones.length = 3;
|
|
8164
|
+
const rankedBars = parseBentoRankedBars(parsed.rankedBars);
|
|
8165
|
+
const verification = parseBentoVerification(parsed.verification);
|
|
8166
|
+
const talkingPoints = parseBentoTalkingPoints(parsed.talkingPoints);
|
|
8167
|
+
const conclusion = clipString(stringField(parsed.conclusion), 100);
|
|
8168
|
+
const flow = parseBentoFlow(parsed.flow);
|
|
8169
|
+
const footerTag = clipString(stringField(parsed.footerTag) || fallbackFooterTag, 80);
|
|
8170
|
+
return {
|
|
8171
|
+
title,
|
|
8172
|
+
kicker,
|
|
8173
|
+
source,
|
|
8174
|
+
milestones,
|
|
8175
|
+
rankedBars,
|
|
8176
|
+
verification,
|
|
8177
|
+
talkingPoints,
|
|
8178
|
+
conclusion,
|
|
8179
|
+
flow,
|
|
8180
|
+
footerTag
|
|
8181
|
+
};
|
|
8182
|
+
}
|
|
8183
|
+
function stringField(v) {
|
|
8184
|
+
return typeof v === "string" ? v.trim() : "";
|
|
8185
|
+
}
|
|
8186
|
+
function clipString(s, max) {
|
|
8187
|
+
if (s.length <= max) return s;
|
|
8188
|
+
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
8189
|
+
}
|
|
8190
|
+
function parseBentoMilestones(raw) {
|
|
8191
|
+
if (!Array.isArray(raw)) return [];
|
|
8192
|
+
const out = [];
|
|
8193
|
+
for (const m of raw) {
|
|
8194
|
+
if (!m || typeof m !== "object") continue;
|
|
8195
|
+
const o = m;
|
|
8196
|
+
const title = clipString(stringField(o.title), 60);
|
|
8197
|
+
const body = clipString(stringField(o.body), 220);
|
|
8198
|
+
if (!title || !body) continue;
|
|
8199
|
+
const period = clipString(stringField(o.period), 24);
|
|
8200
|
+
const callout = clipString(stringField(o.callout), 12);
|
|
8201
|
+
const tagsRaw = Array.isArray(o.tags) ? o.tags : [];
|
|
8202
|
+
const tags = tagsRaw.map((t) => typeof t === "string" ? clipString(t.trim(), 16) : "").filter(Boolean).slice(0, 4);
|
|
8203
|
+
out.push({ period, title, body, callout, tags });
|
|
8204
|
+
if (out.length >= 3) break;
|
|
8205
|
+
}
|
|
8206
|
+
return out;
|
|
8207
|
+
}
|
|
8208
|
+
function parseBentoRankedBars(raw) {
|
|
8209
|
+
if (!raw || typeof raw !== "object") return null;
|
|
8210
|
+
const o = raw;
|
|
8211
|
+
const title = clipString(stringField(o.title), 40);
|
|
8212
|
+
if (!title) return null;
|
|
8213
|
+
const entriesRaw = Array.isArray(o.entries) ? o.entries : [];
|
|
8214
|
+
const entries = [];
|
|
8215
|
+
for (const e of entriesRaw) {
|
|
8216
|
+
if (!e || typeof e !== "object") continue;
|
|
8217
|
+
const eo = e;
|
|
8218
|
+
const label = clipString(stringField(eo.label), 40);
|
|
8219
|
+
const value = clipString(stringField(eo.value), 20);
|
|
8220
|
+
if (!label || !value) continue;
|
|
8221
|
+
const ratioRaw = typeof eo.ratio === "number" ? eo.ratio : 0;
|
|
8222
|
+
const ratio = Math.max(0, Math.min(1, Number.isFinite(ratioRaw) ? ratioRaw : 0));
|
|
8223
|
+
entries.push({ label, value, ratio });
|
|
8224
|
+
if (entries.length >= 5) break;
|
|
8225
|
+
}
|
|
8226
|
+
if (entries.length < 2) return null;
|
|
8227
|
+
return { title, entries };
|
|
8228
|
+
}
|
|
8229
|
+
function parseBentoVerification(raw) {
|
|
8230
|
+
if (!raw || typeof raw !== "object") return null;
|
|
8231
|
+
const o = raw;
|
|
8232
|
+
const title = clipString(stringField(o.title), 40);
|
|
8233
|
+
if (!title) return null;
|
|
8234
|
+
const bullets = parseBentoBullets(o.bullets, 140, 5);
|
|
8235
|
+
if (bullets.length === 0) return null;
|
|
8236
|
+
return { title, bullets };
|
|
8237
|
+
}
|
|
8238
|
+
function parseBentoTalkingPoints(raw) {
|
|
8239
|
+
if (!raw || typeof raw !== "object") {
|
|
8240
|
+
return { title: "How to say this", bullets: [] };
|
|
8241
|
+
}
|
|
8242
|
+
const o = raw;
|
|
8243
|
+
const title = clipString(stringField(o.title), 40) || "How to say this";
|
|
8244
|
+
const bullets = parseBentoBullets(o.bullets, 120, 5);
|
|
8245
|
+
return { title, bullets };
|
|
8246
|
+
}
|
|
8247
|
+
function parseBentoBullets(raw, maxChars, maxCount) {
|
|
8248
|
+
if (!Array.isArray(raw)) return [];
|
|
8249
|
+
const out = [];
|
|
8250
|
+
for (const b of raw) {
|
|
8251
|
+
const s = typeof b === "string" ? clipString(b.trim(), maxChars) : "";
|
|
8252
|
+
if (s) out.push(s);
|
|
8253
|
+
if (out.length >= maxCount) break;
|
|
8254
|
+
}
|
|
8255
|
+
return out;
|
|
8256
|
+
}
|
|
8257
|
+
function parseBentoFlow(raw) {
|
|
8258
|
+
if (!raw || typeof raw !== "object") return null;
|
|
8259
|
+
const o = raw;
|
|
8260
|
+
const nodesRaw = Array.isArray(o.nodes) ? o.nodes : [];
|
|
8261
|
+
const nodes = [];
|
|
8262
|
+
for (const n of nodesRaw) {
|
|
8263
|
+
const s = typeof n === "string" ? clipString(n.trim(), 24) : "";
|
|
8264
|
+
if (s) nodes.push(s);
|
|
8265
|
+
if (nodes.length >= 4) break;
|
|
8266
|
+
}
|
|
8267
|
+
if (nodes.length < 2) return null;
|
|
8268
|
+
const caption = clipString(stringField(o.caption), 60);
|
|
8269
|
+
return caption ? { nodes, caption } : { nodes };
|
|
8270
|
+
}
|
|
7723
8271
|
function parseAppendices(raw) {
|
|
7724
8272
|
if (!Array.isArray(raw)) return null;
|
|
7725
8273
|
const out = [];
|
|
@@ -8580,7 +9128,7 @@ function recoverStuckClarifyRooms() {
|
|
|
8580
9128
|
|
|
8581
9129
|
// src/storage/briefs.ts
|
|
8582
9130
|
init_db();
|
|
8583
|
-
var COLS2 = "id, room_id, style, title, body_md, body_json, supplement, spine, components_json, composer_rationale, subject_type, house_style, assets_json, created_at";
|
|
9131
|
+
var COLS2 = "id, room_id, style, title, body_md, body_json, supplement, spine, components_json, composer_rationale, subject_type, house_style, assets_json, mode, created_at";
|
|
8584
9132
|
function parseAssets(json) {
|
|
8585
9133
|
if (!json) return null;
|
|
8586
9134
|
try {
|
|
@@ -8761,6 +9309,7 @@ function mapRow7(row) {
|
|
|
8761
9309
|
subjectType: row.subject_type,
|
|
8762
9310
|
houseStyle: row.house_style || "boardroom-default",
|
|
8763
9311
|
assets: parseAssets(row.assets_json),
|
|
9312
|
+
mode: row.mode === "bento" || row.mode === "magazine" || row.mode === "newspaper" ? row.mode : "research-note",
|
|
8764
9313
|
createdAt: row.created_at
|
|
8765
9314
|
};
|
|
8766
9315
|
}
|
|
@@ -8780,7 +9329,7 @@ function listAllBriefs() {
|
|
|
8780
9329
|
const rows = getDb().prepare(
|
|
8781
9330
|
`SELECT b.id, b.room_id, b.style, b.title, b.body_md, b.body_json,
|
|
8782
9331
|
b.supplement, b.spine, b.components_json,
|
|
8783
|
-
b.composer_rationale, b.subject_type, b.house_style, b.assets_json, b.created_at,
|
|
9332
|
+
b.composer_rationale, b.subject_type, b.house_style, b.assets_json, b.mode, b.created_at,
|
|
8784
9333
|
r.name AS room_name, r.subject AS room_subject,
|
|
8785
9334
|
r.number AS room_number, r.status AS room_status
|
|
8786
9335
|
FROM briefs b
|
|
@@ -8806,8 +9355,9 @@ function insertBrief(b) {
|
|
|
8806
9355
|
const composerRationale = b.composerRationale && b.composerRationale.trim() ? b.composerRationale.trim() : null;
|
|
8807
9356
|
const subjectType = b.subjectType && b.subjectType.trim() ? b.subjectType.trim() : null;
|
|
8808
9357
|
const houseStyle = b.houseStyle && b.houseStyle.trim() ? b.houseStyle.trim() : "boardroom-default";
|
|
9358
|
+
const mode = b.mode === "bento" || b.mode === "magazine" || b.mode === "newspaper" ? b.mode : "research-note";
|
|
8809
9359
|
db.prepare(
|
|
8810
|
-
`INSERT INTO briefs (${COLS2}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
9360
|
+
`INSERT INTO briefs (${COLS2}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8811
9361
|
).run(
|
|
8812
9362
|
id,
|
|
8813
9363
|
b.roomId,
|
|
@@ -8823,6 +9373,7 @@ function insertBrief(b) {
|
|
|
8823
9373
|
houseStyle,
|
|
8824
9374
|
null,
|
|
8825
9375
|
// assets_json · filled later by updateBriefAssets
|
|
9376
|
+
mode,
|
|
8826
9377
|
now
|
|
8827
9378
|
);
|
|
8828
9379
|
return getBrief(id);
|
|
@@ -8868,12 +9419,22 @@ function updateBriefBody(id, bodyMd, title) {
|
|
|
8868
9419
|
function setBriefTitle(id, title) {
|
|
8869
9420
|
getDb().prepare("UPDATE briefs SET title = ? WHERE id = ?").run(title, id);
|
|
8870
9421
|
}
|
|
9422
|
+
function updateBriefBodyJson(id, bodyJson, title) {
|
|
9423
|
+
const json = JSON.stringify(bodyJson);
|
|
9424
|
+
if (title !== void 0) {
|
|
9425
|
+
getDb().prepare("UPDATE briefs SET body_json = ?, title = ? WHERE id = ?").run(json, title, id);
|
|
9426
|
+
} else {
|
|
9427
|
+
getDb().prepare("UPDATE briefs SET body_json = ? WHERE id = ?").run(json, id);
|
|
9428
|
+
}
|
|
9429
|
+
}
|
|
8871
9430
|
function deleteBrief(id) {
|
|
8872
9431
|
const r = getDb().prepare("DELETE FROM briefs WHERE id = ?").run(id);
|
|
8873
9432
|
return r.changes > 0;
|
|
8874
9433
|
}
|
|
8875
9434
|
function countBriefs() {
|
|
8876
|
-
const row = getDb().prepare(
|
|
9435
|
+
const row = getDb().prepare(
|
|
9436
|
+
"SELECT COUNT(*) AS c FROM briefs WHERE (body_md IS NOT NULL AND TRIM(body_md) != '') OR (body_json IS NOT NULL AND TRIM(body_json) != '' AND TRIM(body_json) != 'null')"
|
|
9437
|
+
).get();
|
|
8877
9438
|
return row.c ?? 0;
|
|
8878
9439
|
}
|
|
8879
9440
|
|
|
@@ -8982,6 +9543,7 @@ function abortBriefGeneration(briefId) {
|
|
|
8982
9543
|
async function generateBrief(opts) {
|
|
8983
9544
|
const { roomId } = opts;
|
|
8984
9545
|
const style = opts.style ?? "mckinsey";
|
|
9546
|
+
const mode = opts.mode === "bento" || opts.mode === "magazine" || opts.mode === "newspaper" ? opts.mode : "research-note";
|
|
8985
9547
|
const room = getRoom(roomId);
|
|
8986
9548
|
if (!room) throw new Error(`room not found: ${roomId}`);
|
|
8987
9549
|
const memberRows = listRoomMembers(roomId);
|
|
@@ -8992,7 +9554,8 @@ async function generateBrief(opts) {
|
|
|
8992
9554
|
style,
|
|
8993
9555
|
title: room.subject,
|
|
8994
9556
|
bodyMd: "",
|
|
8995
|
-
supplement: opts.supplement
|
|
9557
|
+
supplement: opts.supplement,
|
|
9558
|
+
mode
|
|
8996
9559
|
});
|
|
8997
9560
|
const chairForState = getChairAgent();
|
|
8998
9561
|
const inferredLang = /[一-鿿]/.test(room.subject || "") ? "zh" : "en";
|
|
@@ -9011,6 +9574,7 @@ async function generateBrief(opts) {
|
|
|
9011
9574
|
briefId: placeholder.id,
|
|
9012
9575
|
roomId,
|
|
9013
9576
|
style,
|
|
9577
|
+
mode,
|
|
9014
9578
|
members,
|
|
9015
9579
|
transcript,
|
|
9016
9580
|
room,
|
|
@@ -9154,7 +9718,7 @@ async function runPipeline(args) {
|
|
|
9154
9718
|
roomBus.emit(roomId, {
|
|
9155
9719
|
type: "config-event",
|
|
9156
9720
|
kind: "brief-started",
|
|
9157
|
-
payload: { briefId, style, chairName, language },
|
|
9721
|
+
payload: { briefId, style, chairName, language, mode: args.mode },
|
|
9158
9722
|
createdAt: Date.now()
|
|
9159
9723
|
});
|
|
9160
9724
|
let buf = "";
|
|
@@ -9247,6 +9811,26 @@ async function runPipeline(args) {
|
|
|
9247
9811
|
`
|
|
9248
9812
|
);
|
|
9249
9813
|
}
|
|
9814
|
+
if (args.mode === "bento" || args.mode === "magazine" || args.mode === "newspaper") {
|
|
9815
|
+
const ok = await runBentoStage({
|
|
9816
|
+
roomId,
|
|
9817
|
+
briefId,
|
|
9818
|
+
chair,
|
|
9819
|
+
chairId,
|
|
9820
|
+
room,
|
|
9821
|
+
members,
|
|
9822
|
+
perDirectorSignals,
|
|
9823
|
+
language,
|
|
9824
|
+
supplement,
|
|
9825
|
+
mode: args.mode,
|
|
9826
|
+
signal: args.signal
|
|
9827
|
+
});
|
|
9828
|
+
if (!ok) {
|
|
9829
|
+
const label = args.mode === "magazine" ? "Magazine" : args.mode === "newspaper" ? "Newspaper" : "Bento";
|
|
9830
|
+
pipelineError = `${label} writer couldn't structure this room (3 retries failed). Try regenerating, or shorten the conversation.`;
|
|
9831
|
+
}
|
|
9832
|
+
return;
|
|
9833
|
+
}
|
|
9250
9834
|
const stage1ActualSec = (Date.now() - stage1StartedAt) / 1e3;
|
|
9251
9835
|
const stage1PredictedMid = (stage1Eta.lo + stage1Eta.hi) / 2;
|
|
9252
9836
|
let calibration = stage1PredictedMid > 0.5 ? stage1ActualSec / stage1PredictedMid : 1;
|
|
@@ -9675,6 +10259,90 @@ async function runStage2(args) {
|
|
|
9675
10259
|
}
|
|
9676
10260
|
return null;
|
|
9677
10261
|
}
|
|
10262
|
+
async function runBentoStage(args) {
|
|
10263
|
+
const totalSignals = args.perDirectorSignals.reduce(
|
|
10264
|
+
(acc, d) => acc + d.signals.length,
|
|
10265
|
+
0
|
|
10266
|
+
);
|
|
10267
|
+
if (totalSignals === 0) return false;
|
|
10268
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10269
|
+
const chairName = args.chair?.name || "Chair";
|
|
10270
|
+
const fallbackSource = `From ${chairName} \xB7 ${today}`;
|
|
10271
|
+
const subjectShort = (args.room.subject || "").slice(0, 60);
|
|
10272
|
+
const fallbackFooterTag = subjectShort ? `${subjectShort} \xB7 ${today}` : today;
|
|
10273
|
+
const buildMessages = args.mode === "magazine" ? buildMagazineMessages : args.mode === "newspaper" ? buildNewspaperMessages : buildBentoMessages;
|
|
10274
|
+
const messages = buildMessages({
|
|
10275
|
+
chair: args.chair,
|
|
10276
|
+
room: args.room,
|
|
10277
|
+
members: args.members,
|
|
10278
|
+
perDirectorSignals: args.perDirectorSignals,
|
|
10279
|
+
language: args.language,
|
|
10280
|
+
supplement: args.supplement,
|
|
10281
|
+
fallbackSource,
|
|
10282
|
+
fallbackFooterTag
|
|
10283
|
+
});
|
|
10284
|
+
emitStage(args.roomId, args.briefId, "write", "active", void 0, void 0, { lo: 8, hi: 24 });
|
|
10285
|
+
for (const modelV of stageFlagshipList()) {
|
|
10286
|
+
if (!isModelV(modelV)) continue;
|
|
10287
|
+
for (let attempt = 0; attempt < STAGE_2_RETRIES; attempt++) {
|
|
10288
|
+
try {
|
|
10289
|
+
let buf = "";
|
|
10290
|
+
let totalTokens = 0;
|
|
10291
|
+
for await (const chunk of callLLMStream({
|
|
10292
|
+
modelV,
|
|
10293
|
+
messages,
|
|
10294
|
+
temperature: STAGE_2_TEMPERATURES[attempt] ?? 0.6,
|
|
10295
|
+
maxTokens: 8e3,
|
|
10296
|
+
signal: args.signal
|
|
10297
|
+
})) {
|
|
10298
|
+
if (chunk.type === "text") {
|
|
10299
|
+
buf += chunk.delta;
|
|
10300
|
+
roomBus.emit(args.roomId, {
|
|
10301
|
+
type: "config-event",
|
|
10302
|
+
kind: "brief-token",
|
|
10303
|
+
payload: { briefId: args.briefId, delta: chunk.delta },
|
|
10304
|
+
createdAt: Date.now()
|
|
10305
|
+
});
|
|
10306
|
+
} else if (chunk.type === "usage") {
|
|
10307
|
+
totalTokens = chunk.totalTokens;
|
|
10308
|
+
} else if (chunk.type === "error") {
|
|
10309
|
+
throw new Error(chunk.message);
|
|
10310
|
+
}
|
|
10311
|
+
}
|
|
10312
|
+
if (totalTokens > 0) billChair(args.chairId, { totalTokens });
|
|
10313
|
+
const bento = parseBento(buf, args.room.subject, fallbackSource, fallbackFooterTag);
|
|
10314
|
+
if (bento) {
|
|
10315
|
+
if (attempt > 0) {
|
|
10316
|
+
process.stderr.write(
|
|
10317
|
+
`[brief.${args.mode}] ${modelV} succeeded on retry ${attempt + 1}
|
|
10318
|
+
`
|
|
10319
|
+
);
|
|
10320
|
+
}
|
|
10321
|
+
updateBriefBodyJson(args.briefId, bento, bento.title);
|
|
10322
|
+
emitStage(args.roomId, args.briefId, "write", "done");
|
|
10323
|
+
roomBus.emit(args.roomId, {
|
|
10324
|
+
type: "config-event",
|
|
10325
|
+
kind: "brief-final",
|
|
10326
|
+
payload: { briefId: args.briefId, title: bento.title, modelV },
|
|
10327
|
+
createdAt: Date.now()
|
|
10328
|
+
});
|
|
10329
|
+
return true;
|
|
10330
|
+
}
|
|
10331
|
+
process.stderr.write(
|
|
10332
|
+
`[brief.${args.mode}] ${modelV} attempt ${attempt + 1}/${STAGE_2_RETRIES} produced unparseable bento
|
|
10333
|
+
`
|
|
10334
|
+
);
|
|
10335
|
+
} catch (e) {
|
|
10336
|
+
if (args.signal?.aborted) throw e;
|
|
10337
|
+
process.stderr.write(
|
|
10338
|
+
`[brief.${args.mode}] ${modelV} attempt ${attempt + 1}/${STAGE_2_RETRIES} failed: ${e instanceof Error ? e.message : String(e)}
|
|
10339
|
+
`
|
|
10340
|
+
);
|
|
10341
|
+
}
|
|
10342
|
+
}
|
|
10343
|
+
}
|
|
10344
|
+
return false;
|
|
10345
|
+
}
|
|
9678
10346
|
function synthesizeDirectorPerspectivesFallback(perDirectorSignals, members) {
|
|
9679
10347
|
const memberById = new Map(members.map((m) => [m.id, m]));
|
|
9680
10348
|
const VALID_LENSES = /* @__PURE__ */ new Set([
|
|
@@ -9869,10 +10537,17 @@ function buildMethodologyFooter(args) {
|
|
|
9869
10537
|
|
|
9870
10538
|
// src/routes/briefs.ts
|
|
9871
10539
|
init_paths();
|
|
10540
|
+
function briefHasBody(b) {
|
|
10541
|
+
if (b.mode === "bento" || b.mode === "magazine" || b.mode === "newspaper") {
|
|
10542
|
+
const j = b.bodyJson;
|
|
10543
|
+
return !!(j && typeof j === "object" && typeof j.title === "string" && j.title.length > 0);
|
|
10544
|
+
}
|
|
10545
|
+
return !!(b.bodyMd && b.bodyMd.trim().length > 0);
|
|
10546
|
+
}
|
|
9872
10547
|
function briefsRouter() {
|
|
9873
10548
|
const r = new Hono3();
|
|
9874
10549
|
r.get("/", (c) => {
|
|
9875
|
-
const briefs = listAllBriefs().filter((b) => !isBriefGenerating(b.id) && b
|
|
10550
|
+
const briefs = listAllBriefs().filter((b) => !isBriefGenerating(b.id) && briefHasBody(b));
|
|
9876
10551
|
return c.json({ briefs });
|
|
9877
10552
|
});
|
|
9878
10553
|
r.get("/count", (c) => {
|
|
@@ -9887,7 +10562,7 @@ function briefsRouter() {
|
|
|
9887
10562
|
const id = c.req.param("id");
|
|
9888
10563
|
const b = getBrief(id);
|
|
9889
10564
|
if (!b) return c.json({ error: "not found" }, 404);
|
|
9890
|
-
const hasBody =
|
|
10565
|
+
const hasBody = briefHasBody(b);
|
|
9891
10566
|
const generating = isBriefGenerating(id);
|
|
9892
10567
|
const completed = hasBody && !generating;
|
|
9893
10568
|
const state = generating ? getBriefGenerationState(id) : null;
|
|
@@ -14147,13 +14822,13 @@ function resolvePickerModel() {
|
|
|
14147
14822
|
var TARGET_CAST_SIZE = 3;
|
|
14148
14823
|
var LENS_AXES = ["dissent", "rigor", "empathy", "pattern_recall"];
|
|
14149
14824
|
var LENS_THRESHOLD = 7;
|
|
14150
|
-
function
|
|
14825
|
+
function clipString2(s, max) {
|
|
14151
14826
|
if (s.length <= max) return s;
|
|
14152
14827
|
return s.slice(0, max).trim();
|
|
14153
14828
|
}
|
|
14154
14829
|
function describeDirector(a, recentCount) {
|
|
14155
14830
|
const ability = a.ability ? Object.entries(a.ability).filter(([, v]) => typeof v === "number" && v >= LENS_THRESHOLD).map(([k, v]) => `${k}:${v}`).join(",") : "";
|
|
14156
|
-
const bio =
|
|
14831
|
+
const bio = clipString2(a.bio || "", 140).replace(/\s+/g, " ");
|
|
14157
14832
|
const tag = (a.roleTag || "director").toLowerCase();
|
|
14158
14833
|
const recencyTag = recentCount > 0 ? ` \xB7 [seen ${recentCount}/5 recent rooms]` : ` \xB7 [unseen recently]`;
|
|
14159
14834
|
return `- ${a.handle} \xB7 ${a.name} \xB7 ${tag} \xB7 "${bio}"${ability ? ` \xB7 strong on { ${ability} }` : ""}${recencyTag}`;
|
|
@@ -14359,7 +15034,7 @@ async function pickDirectors(opts) {
|
|
|
14359
15034
|
const agent = byHandle.get(handle);
|
|
14360
15035
|
if (!agent) continue;
|
|
14361
15036
|
if (llmPicks.find((x) => x.agent.id === agent.id)) continue;
|
|
14362
|
-
const reason = typeof p.reason === "string" ?
|
|
15037
|
+
const reason = typeof p.reason === "string" ? clipString2(p.reason.trim(), 80) : "";
|
|
14363
15038
|
llmPicks.push({ agent, reason });
|
|
14364
15039
|
if (llmPicks.length >= TARGET_CAST_SIZE) break;
|
|
14365
15040
|
}
|
|
@@ -14374,7 +15049,7 @@ async function pickDirectors(opts) {
|
|
|
14374
15049
|
}
|
|
14375
15050
|
const reasonsByAgent = new Map(llmPicks.map((p) => [p.agent.id, p.reason]));
|
|
14376
15051
|
const adjusted = enforceDiversity(llmPicks.map((p) => p.agent), candidates);
|
|
14377
|
-
const rationale = typeof parsed.rationale === "string" ?
|
|
15052
|
+
const rationale = typeof parsed.rationale === "string" ? clipString2(parsed.rationale.trim(), 120) : "covers complementary lenses";
|
|
14378
15053
|
return {
|
|
14379
15054
|
picks: adjusted.map((a) => ({
|
|
14380
15055
|
agentId: a.id,
|
|
@@ -15092,6 +15767,7 @@ function roomsRouter() {
|
|
|
15092
15767
|
const explicit = typeof b.style === "string" && b.style ? b.style : null;
|
|
15093
15768
|
const fromRoom = room.briefStyle && room.briefStyle !== "auto" ? room.briefStyle : null;
|
|
15094
15769
|
const style = explicit || fromRoom || "mckinsey";
|
|
15770
|
+
const mode = b.mode === "bento" || b.mode === "magazine" || b.mode === "newspaper" ? b.mode : "research-note";
|
|
15095
15771
|
abortRoom(id);
|
|
15096
15772
|
const adjournedAt = Date.now();
|
|
15097
15773
|
setRoomStatus(id, "adjourned", { adjournedAt });
|
|
@@ -15123,7 +15799,7 @@ function roomsRouter() {
|
|
|
15123
15799
|
}
|
|
15124
15800
|
let briefId = null;
|
|
15125
15801
|
try {
|
|
15126
|
-
const result = await generateBrief({ roomId: id, style });
|
|
15802
|
+
const result = await generateBrief({ roomId: id, style, mode });
|
|
15127
15803
|
briefId = result.briefId;
|
|
15128
15804
|
} catch (e) {
|
|
15129
15805
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -15172,11 +15848,13 @@ function roomsRouter() {
|
|
|
15172
15848
|
const explicit = typeof b.style === "string" && b.style ? b.style : null;
|
|
15173
15849
|
const fromRoom = room.briefStyle && room.briefStyle !== "auto" ? room.briefStyle : null;
|
|
15174
15850
|
const style = explicit || fromRoom || "mckinsey";
|
|
15851
|
+
const mode = b.mode === "bento" || b.mode === "magazine" || b.mode === "newspaper" ? b.mode : "research-note";
|
|
15175
15852
|
try {
|
|
15176
15853
|
const result = await generateBrief({
|
|
15177
15854
|
roomId: id,
|
|
15178
15855
|
style,
|
|
15179
|
-
supplement: supplement || void 0
|
|
15856
|
+
supplement: supplement || void 0,
|
|
15857
|
+
mode
|
|
15180
15858
|
});
|
|
15181
15859
|
return c.json({ briefId: result.briefId, status: "generating" });
|
|
15182
15860
|
} catch (e) {
|
|
@@ -15335,6 +16013,11 @@ function usageRouter() {
|
|
|
15335
16013
|
|
|
15336
16014
|
// src/server.ts
|
|
15337
16015
|
init_paths();
|
|
16016
|
+
|
|
16017
|
+
// src/version.ts
|
|
16018
|
+
var VERSION = "0.1.8";
|
|
16019
|
+
|
|
16020
|
+
// src/server.ts
|
|
15338
16021
|
function createApp() {
|
|
15339
16022
|
const app = new Hono10();
|
|
15340
16023
|
const dir = publicDir();
|
|
@@ -15358,8 +16041,9 @@ Build the package or check that public/ is bundled alongside dist/.`
|
|
|
15358
16041
|
});
|
|
15359
16042
|
app.get(
|
|
15360
16043
|
"/api/health",
|
|
15361
|
-
(c) => c.json({ ok: true, version:
|
|
16044
|
+
(c) => c.json({ ok: true, version: VERSION, time: (/* @__PURE__ */ new Date()).toISOString() })
|
|
15362
16045
|
);
|
|
16046
|
+
app.get("/api/version", (c) => c.json({ version: VERSION }));
|
|
15363
16047
|
app.get("/api/system/migrations", async (c) => {
|
|
15364
16048
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
15365
16049
|
try {
|
|
@@ -15426,7 +16110,6 @@ function isPortFree(port) {
|
|
|
15426
16110
|
}
|
|
15427
16111
|
|
|
15428
16112
|
// src/cli.ts
|
|
15429
|
-
var VERSION = "0.1.2";
|
|
15430
16113
|
async function main() {
|
|
15431
16114
|
const program = new Command().name("privateboard").description("PrivateBoard \xB7 your private board meeting, on call. Local-first, multi-agent thinking.").version(VERSION).option("-p, --port <n>", "port to listen on (default: auto-detect from 3030)").option("--host <h>", "host to bind", "127.0.0.1").option("--no-open", "don't open the browser automatically");
|
|
15432
16115
|
program.parse();
|