@sonnechasser/ntrp 1.3.9 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +2616 -990
  2. package/dist/mcp/server.js +1155 -166
  3. package/package.json +3 -1
@@ -1369,6 +1369,26 @@ function buildSessionContextDoc(file, opts = {}) {
1369
1369
  if (file.strategist.origin) lines.push(`- Origin: ${file.strategist.origin}`);
1370
1370
  lines.push("");
1371
1371
  }
1372
+ if (file.think) {
1373
+ lines.push("## Think (in flight)");
1374
+ lines.push("");
1375
+ lines.push(`- Step: ${file.think.step}`);
1376
+ if (file.think.seed) lines.push(`- Seed: ${file.think.seed}`);
1377
+ if (file.think.origin) lines.push(`- Origin: ${file.think.origin}`);
1378
+ if (file.think.open_questions?.length) {
1379
+ lines.push("- Open questions:");
1380
+ for (const q of file.think.open_questions) lines.push(` - ${q}`);
1381
+ }
1382
+ if (file.think.challenged_assumptions?.length) {
1383
+ lines.push("- Challenged assumptions:");
1384
+ for (const a of file.think.challenged_assumptions) lines.push(` - ${a}`);
1385
+ }
1386
+ if (file.think.working_hypotheses?.length) {
1387
+ lines.push("- Working hypotheses:");
1388
+ for (const h of file.think.working_hypotheses) lines.push(` - ${h}`);
1389
+ }
1390
+ lines.push("");
1391
+ }
1372
1392
  lines.push("## Deliverables");
1373
1393
  lines.push("");
1374
1394
  if (file.deliverables && file.deliverables.length > 0) {
@@ -8090,6 +8110,7 @@ function initContext(oneShot, execution) {
8090
8110
  snapshot: { computeResult: null, divergences: [] },
8091
8111
  messages: [],
8092
8112
  conversation: [],
8113
+ thinkConversation: [],
8093
8114
  stage: "new",
8094
8115
  deliverables: [],
8095
8116
  analysis: defaultSessionAnalysis(),
@@ -8110,12 +8131,14 @@ function buildSessionFileSnapshot(ctx) {
8110
8131
  if (ctx.dataset) file.dataset = ctx.dataset;
8111
8132
  if (ctx.deliverables.length > 0) file.deliverables = ctx.deliverables;
8112
8133
  if (ctx.conversation.length > 0) file.thread = ctx.conversation;
8134
+ if ((ctx.thinkConversation?.length ?? 0) > 0) file.think_thread = ctx.thinkConversation;
8113
8135
  if (ctx.resumedFromId) file.resumed_from = ctx.resumedFromId;
8114
8136
  if (ctx.analysis) file.analysis = ctx.analysis;
8115
8137
  if (ctx.scope) file.scope = ctx.scope;
8116
8138
  if (ctx.attachments && ctx.attachments.length > 0) file.attachments = ctx.attachments;
8117
8139
  if (ctx.llm && Object.keys(ctx.llm).length > 0) file.llm = ctx.llm;
8118
8140
  if (ctx.strategistState) file.strategist = ctx.strategistState;
8141
+ if (ctx.thinkState) file.think = ctx.thinkState;
8119
8142
  if (ctx.pendingAsk) file.pending_ask = ctx.pendingAsk;
8120
8143
  return file;
8121
8144
  }
@@ -8242,6 +8265,9 @@ function loadSessionFile(id) {
8242
8265
  if (session.thread?.length) {
8243
8266
  session.thread = normalizeThread(session.thread);
8244
8267
  }
8268
+ if (session.think_thread?.length) {
8269
+ session.think_thread = normalizeThread(session.think_thread);
8270
+ }
8245
8271
  return session;
8246
8272
  } catch {
8247
8273
  return null;
@@ -8444,6 +8470,9 @@ async function finalizeSession(ctx, stage) {
8444
8470
  if (ctx.conversation.length > 0) {
8445
8471
  file.thread = ctx.conversation;
8446
8472
  }
8473
+ if ((ctx.thinkConversation?.length ?? 0) > 0) {
8474
+ file.think_thread = ctx.thinkConversation;
8475
+ }
8447
8476
  if (ctx.analysis) {
8448
8477
  file.analysis = ctx.analysis;
8449
8478
  }
@@ -8459,6 +8488,9 @@ async function finalizeSession(ctx, stage) {
8459
8488
  if (ctx.strategistState) {
8460
8489
  file.strategist = ctx.strategistState;
8461
8490
  }
8491
+ if (ctx.thinkState) {
8492
+ file.think = ctx.thinkState;
8493
+ }
8462
8494
  if (ctx.pendingAsk) {
8463
8495
  file.pending_ask = ctx.pendingAsk;
8464
8496
  }
@@ -8520,6 +8552,7 @@ function resetContextForSwitch(ctx, opts) {
8520
8552
  ctx.sessionName = opts.sessionName;
8521
8553
  ctx.messages = opts.messages;
8522
8554
  ctx.conversation = opts.conversation ?? [];
8555
+ ctx.thinkConversation = opts.thinkConversation ?? [];
8523
8556
  ctx.resumedFromId = opts.resumedFromId;
8524
8557
  ctx.resumedSessionSummary = opts.resumedSessionSummary;
8525
8558
  ctx.stage = opts.stage ?? "new";
@@ -8530,6 +8563,7 @@ function resetContextForSwitch(ctx, opts) {
8530
8563
  ctx.attachments = opts.attachments ?? [];
8531
8564
  ctx.llm = opts.llm;
8532
8565
  ctx.strategistState = opts.strategistState;
8566
+ ctx.thinkState = opts.thinkState;
8533
8567
  ctx.pendingAsk = opts.pendingAsk;
8534
8568
  ctx.gapAudit = void 0;
8535
8569
  ctx.deliverIntent = false;
@@ -10215,6 +10249,21 @@ Bare \`/deepdive\` runs the full onboarding tour. Type \`/deepdive guide\` to ju
10215
10249
  Type \`/deepdive handoff\` for the ship-to-Claude slide. Type \`/deepdive <metric>\` to jump to one metric card.
10216
10250
  Type \`/deepdive list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
10217
10251
  Live values overlay when an analysis exists.`
10252
+ },
10253
+ {
10254
+ name: "thinkwithme",
10255
+ raw: `---
10256
+ name: thinkwithme
10257
+ description: Socratic co-thinking channel \u2014 explore and pressure-test
10258
+ section: Navigation
10259
+ args: [topic]
10260
+ handler: ../commands/thinkwithme.ts
10261
+ ---
10262
+
10263
+ Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
10264
+ Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
10265
+ Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to ask \u203A.
10266
+ When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
10218
10267
  },
10219
10268
  {
10220
10269
  name: "status",
@@ -10400,6 +10449,7 @@ handler: ../commands/config.ts
10400
10449
  Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
10401
10450
  \`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
10402
10451
  \`llm-primary\` (default provider), \`llm-tier\`, \`llm-auto-failover\`,
10452
+ \`voice-personality\`, \`voice-roast\`,
10403
10453
  \`default-format\`, \`export-dir\`, \`ai-inbox-dir\` (or type \`/inbox set\`).
10404
10454
 
10405
10455
  Setting a provider key opens a hidden prompt and auto-discovers that provider's models.
@@ -10432,6 +10482,21 @@ handler: ../commands/tier.ts
10432
10482
  Set the quality and cost tier for this session. Agentic surfaces respect the tier.
10433
10483
  Some single-shot surfaces keep fixed defaults. Type \`/tier list\` to highlight the active stack.
10434
10484
  Add \`--default\` to persist to config.`
10485
+ },
10486
+ {
10487
+ name: "voice",
10488
+ raw: `---
10489
+ name: voice
10490
+ description: Set personality and roast (how NTRP talks)
10491
+ section: Settings
10492
+ args: [list|personality <level>|roast <level>|<personality> <roast>]
10493
+ handler: ../commands/voice.ts
10494
+ ---
10495
+
10496
+ Set how NTRP talks. Personality is robotic, composed, casual, or loose.
10497
+ Roast is light, medium, dark, or heavy. Communication only \u2014 scores and dollars do not change.
10498
+ Type \`/voice\` to pick from a numbered list (Enter keeps the current setting). Type \`/voice list\` to print the catalog. Type \`/voice casual heavy\` to set both.
10499
+ This writes to config immediately.`
10435
10500
  },
10436
10501
  {
10437
10502
  name: "model",
@@ -10599,7 +10664,7 @@ function formatCatalogLine(meta) {
10599
10664
  const note = DESTRUCTIVE_COMMAND_NOTES[meta.name] ? ` (${DESTRUCTIVE_COMMAND_NOTES[meta.name]})` : "";
10600
10665
  return `- /${meta.name}${args} \u2014 ${meta.description}${note}`;
10601
10666
  }
10602
- var ANALYST_FILE_NAME, ANALYST_FILE_MAX_CHARS, EXECUTION_BIAS_BLOCK, ANALYST_INSTINCT_BLOCK, SAFETY_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, GTM_ENGINEERING_BLOCK, PYRAMID_OUTPUT_BLOCK, USER_VISIBLE_STE_BLOCK, FINDINGS_SCHEMA_BLOCK;
10667
+ var ANALYST_FILE_NAME, ANALYST_FILE_MAX_CHARS, EXECUTION_BIAS_BLOCK, ANALYST_INSTINCT_BLOCK, SAFETY_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, GTM_ENGINEERING_BLOCK, PYRAMID_OUTPUT_BLOCK;
10603
10668
  var init_prompt_parts = __esm({
10604
10669
  "src/ai/prompt-parts.ts"() {
10605
10670
  "use strict";
@@ -10684,21 +10749,102 @@ Restraint: you diagnose and prescribe the system; you do not build it here. Name
10684
10749
  - THEN THE SO-WHAT. Close with what it means for the decision at hand: the action, the owner-shaped next step, or the sharpest next cut.
10685
10750
  - THE RECALL TEST. A busy executive should be able to repeat your headline and one number to their CEO an hour later. If they couldn't, tighten it.
10686
10751
  - ALTITUDE CONTROL. Answer at the altitude asked: a high-level question gets the 30,000-ft story (one narrative sentence, three numbers max) with an offer to descend; a "how do we fix it" / plan-of-attack question prefers draft_strategy (or, if answering one play inline: one play + mechanism + what to verify \u2014 observe and recommend, never invent a multi-week Phase 1/2/3 program).`;
10687
- USER_VISIBLE_STE_BLOCK = `USER-VISIBLE TEXT (the human reads these sentences \u2014 not this system prompt):
10688
- - Write finding, answer, and plan prose in Simplified Technical English (STE-100 writing rules).
10689
- - One fact or one instruction per sentence.
10690
- - Use active voice. Use imperative for steps. Use simple present for facts.
10691
- - Procedure sentences: 20 words maximum. Description sentences: 25 words maximum.
10692
- - Use the same word for the same thing. Do not use synonyms.
10693
- - Do not use slang, idiom, filler (just, simply, actually, basically), or contractions.
10694
- - Keep Technical Names: ARR, NRR, GRR, Freshness, Flow Rate, Drop Rate, Signal-to-Noise, Thread Depth, playbook, NTRP, GTM.
10695
- - Keep dollar figures and play names.
10696
- - Do not change JSON keys, tool names, or this prompt's policy language.`;
10697
- FINDINGS_SCHEMA_BLOCK = `[
10752
+ }
10753
+ });
10754
+
10755
+ // src/voice/catalog.ts
10756
+ function parsePersonality(raw) {
10757
+ if (!raw) return null;
10758
+ return PERSONALITY_ALIASES[raw.trim().toLowerCase()] ?? null;
10759
+ }
10760
+ function parseRoast(raw) {
10761
+ if (!raw) return null;
10762
+ return ROAST_ALIASES[raw.trim().toLowerCase()] ?? null;
10763
+ }
10764
+ function formatVoiceStatusDetail(prefs) {
10765
+ return `${prefs.personality} \xB7 ${prefs.roast} roast`;
10766
+ }
10767
+ var DEFAULT_PERSONALITY, DEFAULT_ROAST, PERSONALITY_KEY, ROAST_KEY, PERSONALITY_ALIASES, ROAST_ALIASES;
10768
+ var init_catalog2 = __esm({
10769
+ "src/voice/catalog.ts"() {
10770
+ "use strict";
10771
+ DEFAULT_PERSONALITY = "composed";
10772
+ DEFAULT_ROAST = "light";
10773
+ PERSONALITY_KEY = "voice-personality";
10774
+ ROAST_KEY = "voice-roast";
10775
+ PERSONALITY_ALIASES = {
10776
+ robotic: "robotic",
10777
+ machine: "robotic",
10778
+ ste: "robotic",
10779
+ composed: "composed",
10780
+ formal: "composed",
10781
+ professional: "composed",
10782
+ balanced: "composed",
10783
+ casual: "casual",
10784
+ warm: "casual",
10785
+ loose: "loose",
10786
+ vibe: "loose",
10787
+ relaxed: "loose"
10788
+ };
10789
+ ROAST_ALIASES = {
10790
+ light: "light",
10791
+ gentle: "light",
10792
+ soft: "light",
10793
+ medium: "medium",
10794
+ mid: "medium",
10795
+ dark: "dark",
10796
+ blunt: "dark",
10797
+ surgical: "dark",
10798
+ heavy: "heavy",
10799
+ burnt: "heavy",
10800
+ extra: "heavy",
10801
+ max: "heavy"
10802
+ };
10803
+ }
10804
+ });
10805
+
10806
+ // src/voice/prefs.ts
10807
+ function loadVoicePrefs() {
10808
+ return {
10809
+ personality: parsePersonality(getConfigValue(PERSONALITY_KEY)) ?? DEFAULT_PERSONALITY,
10810
+ roast: parseRoast(getConfigValue(ROAST_KEY)) ?? DEFAULT_ROAST
10811
+ };
10812
+ }
10813
+ var init_prefs = __esm({
10814
+ "src/voice/prefs.ts"() {
10815
+ "use strict";
10816
+ init_store();
10817
+ init_catalog2();
10818
+ }
10819
+ });
10820
+
10821
+ // src/voice/prompt.ts
10822
+ function buildUserVisibleProseBlock(prefs) {
10823
+ const voice2 = prefs ?? loadVoicePrefs();
10824
+ if (voice2.personality === "robotic") {
10825
+ return `${ROBOTIC_STE_BLOCK}
10826
+ VOICE (communication only \u2014 never change the numbers):
10827
+ - Personality: robotic.
10828
+ ${ROAST_RULES[voice2.roast]}`;
10829
+ }
10830
+ return `USER-VISIBLE TEXT (the human reads these sentences \u2014 not this system prompt):
10831
+ ${PERSONALITY_RULES[voice2.personality]}
10832
+ ${ROAST_RULES[voice2.roast]}
10833
+ ${SHARED_INVARIANTS}`;
10834
+ }
10835
+ function buildFindingsFindingHint(prefs) {
10836
+ const voice2 = prefs ?? loadVoicePrefs();
10837
+ const register = voice2.personality === "robotic" ? "STE-100 sentences. No contractions, slang, or filler." : voice2.personality === "composed" ? "Composed consultant sentences. Light contractions OK." : voice2.personality === "casual" ? "Casual consultant sentences. Warm, still short." : "Loose consultant sentences. Personality in the wrap, not the math.";
10838
+ const roast = voice2.roast === "light" ? "Light roast: what is working, then what to improve." : voice2.roast === "medium" ? "Medium roast: name the miss. No padding." : voice2.roast === "dark" ? "Dark roast: surgical. Verdict + dollar in one breath." : "Heavy roast: maximum roast after the number, never instead of it.";
10839
+ return `Pyramid-shaped, 2-3 sentences: (1) HEADLINE \u2014 verdict + dollar figure in one short sentence (\u226420 words); (2) EVIDENCE \u2014 the one or two numbers that prove it; (3) SO-WHAT \u2014 the consequence or the action. ${register} ${roast} Keep dollar figures and play names. Do not invent numbers. An executive should be able to repeat sentence 1 from memory.`;
10840
+ }
10841
+ function buildFindingsSchemaBlock(prefs) {
10842
+ const finding = buildFindingsFindingHint(prefs);
10843
+ return `[
10698
10844
  {
10699
10845
  "severity": "critical" | "warning" | "info",
10700
10846
  "segment": "segment name or 'Overall'",
10701
- "finding": "Pyramid-shaped, 2-3 STE-100 sentences: (1) HEADLINE \u2014 verdict + dollar figure in one short sentence (\u226420 words); (2) EVIDENCE \u2014 the one or two numbers that prove it; (3) SO-WHAT \u2014 the consequence or the action. No contractions, slang, or filler. An executive should be able to repeat sentence 1 from memory.",
10847
+ "finding": ${JSON.stringify(finding)},
10702
10848
  "vital_signs": {"vital_sign_name": score, ...},
10703
10849
  "entity_count": number_of_affected_entities,
10704
10850
  "recommended_focus": "vital_sign_name",
@@ -10706,6 +10852,51 @@ Restraint: you diagnose and prescribe the system; you do not build it here. Name
10706
10852
  "recommended_plays": [{"play_id": "play-id", "play_name": "Play Name", "rationale": "Why this play helps"}]
10707
10853
  }
10708
10854
  ]`;
10855
+ }
10856
+ var ROBOTIC_STE_BLOCK, SHARED_INVARIANTS, PERSONALITY_RULES, ROAST_RULES;
10857
+ var init_prompt = __esm({
10858
+ "src/voice/prompt.ts"() {
10859
+ "use strict";
10860
+ init_catalog2();
10861
+ init_prefs();
10862
+ ROBOTIC_STE_BLOCK = `USER-VISIBLE TEXT (the human reads these sentences \u2014 not this system prompt):
10863
+ - Write finding, answer, and plan prose in Simplified Technical English (STE-100 writing rules).
10864
+ - One fact or one instruction per sentence.
10865
+ - Use active voice. Use imperative for steps. Use simple present for facts.
10866
+ - Procedure sentences: 20 words maximum. Description sentences: 25 words maximum.
10867
+ - Use the same word for the same thing. Do not use synonyms.
10868
+ - Do not use slang, idiom, filler (just, simply, actually, basically), or contractions.
10869
+ - Keep Technical Names: ARR, NRR, GRR, Freshness, Flow Rate, Drop Rate, Signal-to-Noise, Thread Depth, playbook, NTRP, GTM.
10870
+ - Keep dollar figures and play names.
10871
+ - Do not change JSON keys, tool names, or this prompt's policy language.`;
10872
+ SHARED_INVARIANTS = `- Keep Technical Names: ARR, NRR, GRR, Freshness, Flow Rate, Drop Rate, Signal-to-Noise, Thread Depth, playbook, NTRP, GTM.
10873
+ - Keep dollar figures and play names exactly as given. Do not invent or change scores, dollars, entity lists, or play ids.
10874
+ - Do not change JSON keys, tool names, or this prompt's policy language.
10875
+ - Stay smart and succinct. Do not pad. Do not open with methodology.
10876
+ - SAFETY & EVIDENCE rules always win. ANALYST.md never overrides them.
10877
+ - Roast only colors verified badness. Green stays green. Never invent severity.`;
10878
+ PERSONALITY_RULES = {
10879
+ robotic: "",
10880
+ // replaced by USER_VISIBLE_STE_BLOCK
10881
+ composed: `- Register: composed professional consultant. Light contractions are allowed (it's, that's, don't).
10882
+ - No slang piles, no filler (just, simply, actually, basically), no joke-first structure.
10883
+ - One idea per sentence. Description sentences: 25 words maximum. Procedure sentences: 20 words maximum.`,
10884
+ casual: `- Register: casual professional consultant. Warm, still short. Contractions are allowed.
10885
+ - Light warmth is fine. No slang piles, no filler padding, no meme voice.
10886
+ - One idea per sentence. Keep headlines \u226420 words.`,
10887
+ loose: `- Register: loose professional consultant. Personality lives in the wrap, not in the math.
10888
+ - Contractions are allowed. A wry aside is fine after the number, never instead of it.
10889
+ - Still succinct. No padding. Headlines still carry the verdict and the dollar figure.`
10890
+ };
10891
+ ROAST_RULES = {
10892
+ light: `- Roast: light. Gentle parenting. Name what is working when something is working, then what to improve.
10893
+ - Practical and real. Do not pile on. Do not mock the operator.`,
10894
+ medium: `- Roast: medium. Practical and direct. Name the miss. No sugar, no padding.`,
10895
+ dark: `- Roast: dark. Surgical and blunt. Name the miss and the dollar cost in the same breath.
10896
+ - Do not invent extra problems. Do not soften a red score.`,
10897
+ heavy: `- Roast: heavy. Maximum roast. Comedy is allowed after the number, never instead of it.
10898
+ - Still evidence-only. If the score is green, do not roast it into red.`
10899
+ };
10709
10900
  }
10710
10901
  });
10711
10902
 
@@ -10853,10 +11044,10 @@ ${buildPlaybookBlock()}
10853
11044
  OUTPUT DOCTRINE (how findings are structured for recall):
10854
11045
  ${PYRAMID_OUTPUT_BLOCK}
10855
11046
 
10856
- ${USER_VISIBLE_STE_BLOCK}
11047
+ ${buildUserVisibleProseBlock()}
10857
11048
 
10858
11049
  Respond with JSON only \u2014 an array of finding objects:
10859
- ${FINDINGS_SCHEMA_BLOCK}
11050
+ ${buildFindingsSchemaBlock()}
10860
11051
 
10861
11052
  Rules:
10862
11053
  - Maximum 8 findings
@@ -10960,6 +11151,7 @@ var init_findings = __esm({
10960
11151
  init_repl_api();
10961
11152
  init_failover();
10962
11153
  init_prompt_parts();
11154
+ init_prompt();
10963
11155
  init_json_response();
10964
11156
  init_json_stream();
10965
11157
  init_playbook();
@@ -11041,13 +11233,21 @@ function buildFreshNlTools() {
11041
11233
  if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
11042
11234
  return tools2;
11043
11235
  }
11236
+ function buildThinkTools() {
11237
+ const tools2 = [
11238
+ ...AGENTIC_TOOLS,
11239
+ ...THINK_CHANNEL_TOOLS
11240
+ ];
11241
+ if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
11242
+ return tools2;
11243
+ }
11044
11244
  function allRegisteredToolSchemas() {
11045
- return [...AGENTIC_TOOLS, ...CONVERSATION_TOOLS, WEB_SEARCH_TOOL];
11245
+ return [...AGENTIC_TOOLS, ...CONVERSATION_TOOLS, ...THINK_CHANNEL_TOOLS, WEB_SEARCH_TOOL];
11046
11246
  }
11047
11247
  function getToolSchema(name) {
11048
11248
  return allRegisteredToolSchemas().find((t) => t.name === name);
11049
11249
  }
11050
- var WEB_SEARCH_TOOL, CONVERSATION_TOOLS, AGENTIC_TOOLS;
11250
+ var WEB_SEARCH_TOOL, CONVERSATION_TOOLS, THINK_CHANNEL_TOOLS, AGENTIC_TOOLS;
11051
11251
  var init_tool_schemas = __esm({
11052
11252
  "src/ai/tool-schemas.ts"() {
11053
11253
  "use strict";
@@ -11124,6 +11324,64 @@ var init_tool_schemas = __esm({
11124
11324
  }
11125
11325
  }
11126
11326
  ];
11327
+ THINK_CHANNEL_TOOLS = [
11328
+ {
11329
+ name: "draft_handoff",
11330
+ description: "Draft a handoff prompt combining analysis numbers and conversation thread.",
11331
+ parameters: {
11332
+ type: "object",
11333
+ properties: {
11334
+ target: {
11335
+ type: "string",
11336
+ enum: ["deck", "asana", "clay", "plan"],
11337
+ description: "Deliverable type. Defaults to plan."
11338
+ }
11339
+ }
11340
+ }
11341
+ },
11342
+ {
11343
+ name: "draft_strategy",
11344
+ description: "Hand off to the strategist brain from the think channel when the user is ready to commit to a plan. Do not improvise a multi-week roadmap inline \u2014 call this instead.",
11345
+ parameters: {
11346
+ type: "object",
11347
+ properties: {
11348
+ objective: {
11349
+ type: "string",
11350
+ maxLength: 2e3,
11351
+ description: "The measurable objective to plan toward, in the user's terms."
11352
+ }
11353
+ },
11354
+ required: ["objective"]
11355
+ }
11356
+ },
11357
+ {
11358
+ name: "update_think_scratch",
11359
+ description: "Update the think-channel working scratch: open questions, challenged assumptions, and working hypotheses. Pass only the arrays you want to replace; omitted fields stay unchanged. Call when the conversation advances a question, surfaces a challenged assumption, or forms a hypothesis.",
11360
+ parameters: {
11361
+ type: "object",
11362
+ properties: {
11363
+ open_questions: {
11364
+ type: "array",
11365
+ items: { type: "string", maxLength: 500 },
11366
+ maxItems: 12,
11367
+ description: "Current open questions (replaces the list when provided)."
11368
+ },
11369
+ challenged_assumptions: {
11370
+ type: "array",
11371
+ items: { type: "string", maxLength: 500 },
11372
+ maxItems: 12,
11373
+ description: "Assumptions that have been pressure-tested (replaces when provided)."
11374
+ },
11375
+ working_hypotheses: {
11376
+ type: "array",
11377
+ items: { type: "string", maxLength: 500 },
11378
+ maxItems: 12,
11379
+ description: "Working hypotheses under consideration (replaces when provided)."
11380
+ }
11381
+ }
11382
+ }
11383
+ }
11384
+ ];
11127
11385
  AGENTIC_TOOLS = [
11128
11386
  {
11129
11387
  name: "get_health_summary",
@@ -12892,6 +13150,15 @@ var init_layout = __esm({
12892
13150
  }
12893
13151
  });
12894
13152
 
13153
+ // src/whimsy/home-taglines.ts
13154
+ var HOME_TAGLINE_WHIMSY_CHANCE;
13155
+ var init_home_taglines = __esm({
13156
+ "src/whimsy/home-taglines.ts"() {
13157
+ "use strict";
13158
+ HOME_TAGLINE_WHIMSY_CHANCE = 1 / 8;
13159
+ }
13160
+ });
13161
+
12895
13162
  // src/ui/banner.ts
12896
13163
  import chalk4 from "chalk";
12897
13164
  var init_banner = __esm({
@@ -12899,6 +13166,8 @@ var init_banner = __esm({
12899
13166
  "use strict";
12900
13167
  init_theme();
12901
13168
  init_layout();
13169
+ init_home_taglines();
13170
+ init_home_taglines();
12902
13171
  }
12903
13172
  });
12904
13173
 
@@ -13241,8 +13510,8 @@ function resolveAskMultiInput(raw, options) {
13241
13510
  function resolveChooseInput(raw, choices, defaultValue) {
13242
13511
  if (choices.length === 0) return null;
13243
13512
  const defaultIdx = resolveChooseDefaultIndex(choices, defaultValue);
13244
- const pick2 = raw.trim() || String(defaultIdx + 1);
13245
- const n = Number(pick2);
13513
+ const pick3 = raw.trim() || String(defaultIdx + 1);
13514
+ const n = Number(pick3);
13246
13515
  if (Number.isInteger(n) && n >= 1 && n <= choices.length) {
13247
13516
  return choices[n - 1].value;
13248
13517
  }
@@ -13515,6 +13784,8 @@ function resolveRecommendedAction(ctx) {
13515
13784
  return { submit: "yes", hint: "yes" };
13516
13785
  case "strategize":
13517
13786
  return ctx.strategistState?.step === "objective_confirm" ? { submit: "yes", hint: "yes" } : null;
13787
+ case "think":
13788
+ return null;
13518
13789
  default:
13519
13790
  return null;
13520
13791
  }
@@ -13549,6 +13820,9 @@ function resolveConversationPhase(ctx) {
13549
13820
  if (ctx.strategistState && ctx.strategistState.step !== "awaiting_analysis" && ctx.strategistState.step !== "awaiting_connect") {
13550
13821
  return "strategize";
13551
13822
  }
13823
+ if (ctx.thinkState?.step === "active") {
13824
+ return "think";
13825
+ }
13552
13826
  if (isAnalysisReady(ctx)) return "explore";
13553
13827
  const scope = ctx.scope;
13554
13828
  if (scope?.confirmed_at) {
@@ -13573,6 +13847,8 @@ function formatPhaseLabel(phase) {
13573
13847
  return "setup";
13574
13848
  case "explore":
13575
13849
  return "ready to ask";
13850
+ case "think":
13851
+ return "thinking together";
13576
13852
  default:
13577
13853
  return phase.replace(/_/g, " ");
13578
13854
  }
@@ -13591,8 +13867,14 @@ function buildConversationPrompt(ctx) {
13591
13867
  const modeTag = mode === "brief" ? "brief" : "deep";
13592
13868
  const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
13593
13869
  const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 strategy after /connect") : "";
13870
+ const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 think after /connect") : "";
13594
13871
  const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
13595
- return paint("accent", `ask${scope} \u203A `) + chalk8.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + enterHint2 + " ";
13872
+ return paint("accent", `ask${scope} \u203A `) + chalk8.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
13873
+ }
13874
+ if (phase === "think") {
13875
+ const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
13876
+ const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
13877
+ return paint("accent", `think${scope} \u203A `) + chalk8.dim(stack) + enterHint2 + " ";
13596
13878
  }
13597
13879
  const enterHint = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
13598
13880
  return paint("accent", `${label.replace(" \u203A", "")}${scope} \u203A `) + enterHint;
@@ -13633,6 +13915,7 @@ var init_phase = __esm({
13633
13915
  awaiting_data: "data \u203A",
13634
13916
  compute: "\u2026",
13635
13917
  explore: "ask \u203A",
13918
+ think: "think \u203A",
13636
13919
  strategize: "strategy \u203A",
13637
13920
  deliver: "ship \u203A"
13638
13921
  };
@@ -13985,14 +14268,14 @@ ${ANALYST_INSTINCT_BLOCK}
13985
14268
  OUTPUT DOCTRINE (how findings are structured for recall):
13986
14269
  ${PYRAMID_OUTPUT_BLOCK}
13987
14270
 
13988
- ${USER_VISIBLE_STE_BLOCK}
14271
+ ${buildUserVisibleProseBlock()}
13989
14272
 
13990
14273
  Respond with JSON only \u2014 an array of finding objects:
13991
14274
  [
13992
14275
  {
13993
14276
  "severity": "critical" | "warning" | "info",
13994
14277
  "segment": "Overall",
13995
- "finding": "Pyramid-shaped, 2-3 STE-100 sentences: (1) HEADLINE \u2014 verdict + number (\u226420 words); (2) EVIDENCE \u2014 the figures that prove it, citing confidence when below 80; (3) SO-WHAT \u2014 the consequence or action. No contractions, slang, or filler.",
14278
+ "finding": ${JSON.stringify(buildFindingsFindingHint())},
13996
14279
  "vital_signs": {},
13997
14280
  "entity_count": 0,
13998
14281
  "recommended_focus": "nrr",
@@ -14070,6 +14353,7 @@ var init_metrics_findings = __esm({
14070
14353
  init_repl_api();
14071
14354
  init_failover();
14072
14355
  init_prompt_parts();
14356
+ init_prompt();
14073
14357
  init_playbook();
14074
14358
  init_metrics_benchmarks();
14075
14359
  init_profile();
@@ -14437,6 +14721,158 @@ var init_slides = __esm({
14437
14721
  }
14438
14722
  });
14439
14723
 
14724
+ // src/voice/copy.ts
14725
+ function voice(prefs) {
14726
+ return prefs ?? loadVoicePrefs();
14727
+ }
14728
+ function voiceKeylessHeadline(input, prefs) {
14729
+ const v = voice(prefs);
14730
+ const { label, dollarBit, score, status } = input;
14731
+ if (dollarBit) {
14732
+ return pick2(v, {
14733
+ robotic: `${label} \u2014 ${dollarBit} \u2014 is the most expensive problem to solve right now.`,
14734
+ composed: `${label} is the most expensive problem right now: ${dollarBit}.`,
14735
+ casual: `${label} is the expensive one \u2014 ${dollarBit}.`,
14736
+ loose: {
14737
+ light: `${label} is the expensive problem: ${dollarBit}.`,
14738
+ medium: `${label} is eating ${dollarBit}. Fix that first.`,
14739
+ dark: `${label} is eating ${dollarBit}. That is the miss.`,
14740
+ heavy: `${label} is eating ${dollarBit}. That is the fire.`
14741
+ }
14742
+ });
14743
+ }
14744
+ return pick2(v, {
14745
+ robotic: `${label} (score ${Math.round(score)}, ${status}) is the problem to fix first.`,
14746
+ composed: `${label} (score ${Math.round(score)}) is the problem to fix first.`,
14747
+ casual: `Start with ${label} \u2014 score ${Math.round(score)}, ${status}.`,
14748
+ loose: {
14749
+ light: `Start with ${label} (score ${Math.round(score)}).`,
14750
+ medium: `${label} at ${Math.round(score)} is the one to fix first.`,
14751
+ dark: `${label} at ${Math.round(score)} is the miss.`,
14752
+ heavy: `${label} at ${Math.round(score)} is the joke, and not the funny kind.`
14753
+ }
14754
+ });
14755
+ }
14756
+ function voiceKeylessGating(_label, _score, prefs) {
14757
+ const v = voice(prefs);
14758
+ return pick2(v, {
14759
+ robotic: `it bounds what you can trust downstream.`,
14760
+ composed: `it bounds what you can trust downstream.`,
14761
+ casual: `nothing downstream is trustworthy until this moves.`,
14762
+ loose: {
14763
+ light: `it caps what you can trust downstream.`,
14764
+ medium: `fix this before you trust anything downstream.`,
14765
+ dark: `everything downstream is a story until this moves.`,
14766
+ heavy: `everything downstream is fan fiction until this moves.`
14767
+ }
14768
+ });
14769
+ }
14770
+ function voiceKeylessGatingWrap(label, score, prefs) {
14771
+ const clause = voiceKeylessGating(label, score, prefs);
14772
+ return `(score ${Math.round(score)}) \u2014 ${clause}`;
14773
+ }
14774
+ function voiceKeylessAlsoLabel(prefs) {
14775
+ const v = voice(prefs);
14776
+ return pick2(v, {
14777
+ robotic: "Also on the board:",
14778
+ composed: "Also on the board:",
14779
+ casual: "Also in play:",
14780
+ loose: {
14781
+ light: "Also in play:",
14782
+ medium: "Also costing you:",
14783
+ dark: "Also on fire:",
14784
+ heavy: "Also embarrassing:"
14785
+ }
14786
+ });
14787
+ }
14788
+ function voiceKeylessNextLabel(prefs) {
14789
+ const v = voice(prefs);
14790
+ return pick2(v, {
14791
+ robotic: "Next after that: ",
14792
+ composed: "Next after that: ",
14793
+ casual: "Then: ",
14794
+ loose: "Then: "
14795
+ });
14796
+ }
14797
+ function voiceKeylessConnectCta(prefs) {
14798
+ const v = voice(prefs);
14799
+ return pick2(v, {
14800
+ robotic: `Press \u23CE to connect a key (/connect) for the why and the plan. NTRP will finish this question after you connect.`,
14801
+ composed: `Press \u23CE to connect a key (/connect) for the why and the plan. NTRP will finish this question after you connect.`,
14802
+ casual: `Press \u23CE or type /connect for the why and the plan. NTRP will finish this question after you connect.`,
14803
+ loose: `Press \u23CE or type /connect when you want the why. NTRP will finish this question after you connect.`
14804
+ });
14805
+ }
14806
+ function voiceGapReadyCta(prefs) {
14807
+ const v = voice(prefs);
14808
+ return pick2(v, {
14809
+ robotic: `Ready to compute. Press \u23CE or type "go ahead"`,
14810
+ composed: `Ready to compute. Press \u23CE or type "go ahead"`,
14811
+ casual: `Ready. Press \u23CE or type "go ahead"`,
14812
+ loose: `Ready. Press \u23CE or type "go ahead"`
14813
+ });
14814
+ }
14815
+ function voiceGapLoadCta(prefs) {
14816
+ const v = voice(prefs);
14817
+ return pick2(v, {
14818
+ robotic: `Load data. Paste a CSV path, or press \u23CE to use demo data`,
14819
+ composed: `Load data. Paste a CSV path, or press \u23CE to use demo data`,
14820
+ casual: `Load data \u2014 paste a CSV, or press \u23CE to use demo data`,
14821
+ loose: `No data yet. Paste a CSV, or press \u23CE to use demo data`
14822
+ });
14823
+ }
14824
+ function voiceHealthGloss(status, prefs) {
14825
+ const v = voice(prefs);
14826
+ if (status === "green") {
14827
+ return v.personality === "robotic" ? null : "holding";
14828
+ }
14829
+ if (status === "yellow") {
14830
+ return pick2(v, {
14831
+ robotic: "watch this",
14832
+ composed: "watch this",
14833
+ casual: "soft",
14834
+ loose: {
14835
+ light: "watch this",
14836
+ medium: "soft",
14837
+ dark: "thin ice",
14838
+ heavy: "limping"
14839
+ }
14840
+ });
14841
+ }
14842
+ return pick2(v, {
14843
+ robotic: "needs attention",
14844
+ composed: "needs attention",
14845
+ casual: "the leak",
14846
+ loose: {
14847
+ light: "needs attention",
14848
+ medium: "the leak",
14849
+ dark: "the miss",
14850
+ heavy: "this is the fire"
14851
+ }
14852
+ });
14853
+ }
14854
+ function voiceHandoffAddendum(prefs) {
14855
+ const v = voice(prefs);
14856
+ return [
14857
+ `Write in this voice: personality=${v.personality}, roast=${v.roast} (${formatVoiceStatusDetail(v)}).`,
14858
+ "Ground every recommendation in the specific numbers provided. Do not invent data.",
14859
+ v.personality === "robotic" ? "STE-100 for the prose you produce. No contractions." : "Stay smart and succinct. Personality in the wrap, not in the math.",
14860
+ v.roast === "light" ? "Light roast: name what is working, then what to improve." : v.roast === "medium" ? "Medium roast: name the miss. No padding." : v.roast === "dark" ? "Dark roast: surgical. Verdict and dollar in the same breath." : "Heavy roast: maximum roast after the number, never instead of it. Green stays green."
14861
+ ].join(" ");
14862
+ }
14863
+ function pick2(prefs, map) {
14864
+ const entry = map[prefs.personality];
14865
+ if (typeof entry === "string") return entry;
14866
+ return entry[prefs.roast];
14867
+ }
14868
+ var init_copy = __esm({
14869
+ "src/voice/copy.ts"() {
14870
+ "use strict";
14871
+ init_catalog2();
14872
+ init_prefs();
14873
+ }
14874
+ });
14875
+
14440
14876
  // src/output/terminal.ts
14441
14877
  import chalk13 from "chalk";
14442
14878
  import Table2 from "cli-table3";
@@ -14484,9 +14920,10 @@ function printVitalSignRow(vs) {
14484
14920
  function printHealthSummary(result, _pipelineMetrics) {
14485
14921
  const scoreStr = `${chalk13.bold(String(Math.round(result.overall_score)))}${chalk13.dim("/100")}`;
14486
14922
  const impact = result.total_value_at_risk != null && result.total_value_at_risk > 0 ? `${paint("success", formatCurrency(result.total_value_at_risk))} ${chalk13.dim("total at risk")}` : chalk13.dim("No dollar-weighted risk detected");
14923
+ const gloss = voiceHealthGloss(result.overall_status);
14487
14924
  const next = result.overall_status === "red" ? actionHint("Next:", "/playbook", "review recommended plays") : result.overall_status === "yellow" ? actionHint("Next:", "/diagnose --deep", "investigate the weak signal") : actionHint("Next:", "/report", "export the clean snapshot");
14488
14925
  printResultCard("Overall Health", [
14489
- `${chalk13.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}`,
14926
+ `${chalk13.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}${gloss ? chalk13.dim(` ${gloss}`) : ""}`,
14490
14927
  `${chalk13.dim("Held back by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`,
14491
14928
  `${chalk13.dim("Revenue")} ${impact}`,
14492
14929
  next
@@ -14498,8 +14935,9 @@ function printHealthSummary(result, _pipelineMetrics) {
14498
14935
  }
14499
14936
  function printHealthLine(result) {
14500
14937
  const score = `${chalk13.bold(String(Math.round(result.overall_score)))}${chalk13.dim("/100")}`;
14938
+ const gloss = voiceHealthGloss(result.overall_status);
14501
14939
  const parts = [
14502
- `${chalk13.dim("Health")} ${score} ${statusBadge(result.overall_status)}`,
14940
+ `${chalk13.dim("Health")} ${score} ${statusBadge(result.overall_status)}${gloss ? chalk13.dim(` ${gloss}`) : ""}`,
14503
14941
  `${chalk13.dim("held back by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`
14504
14942
  ];
14505
14943
  if (result.total_value_at_risk != null && result.total_value_at_risk > 0) {
@@ -14840,6 +15278,7 @@ var init_terminal = __esm({
14840
15278
  init_layout();
14841
15279
  init_llm_attribution();
14842
15280
  init_slides();
15281
+ init_copy();
14843
15282
  }
14844
15283
  });
14845
15284
 
@@ -16514,6 +16953,8 @@ function wrapForTarget(target, analysisBlock, conversationBlock, openQuestions,
16514
16953
  "",
16515
16954
  "Ground every recommendation in the specific numbers provided. Do not invent data.",
16516
16955
  "",
16956
+ voiceHandoffAddendum(),
16957
+ "",
16517
16958
  "---",
16518
16959
  "",
16519
16960
  analysisBlock,
@@ -16572,6 +17013,7 @@ var init_handoff_draft = __esm({
16572
17013
  init_profile();
16573
17014
  init_session_analysis();
16574
17015
  init_metric_explainers();
17016
+ init_copy();
16575
17017
  QUESTION_LEAD_RE = /^\s*(what|why|how|when|where|who|which|is|are|was|were|do|does|did|explain|tell me|help me understand)\b/i;
16576
17018
  SHIP_INTENT_RE = /\b(ship|export|deliver|write[- ]?up|board memo|action plan|turn (this|it|that) into|(draft|create|make|build|prepare|generate|send)\s+(me\s+)?(a\s+|the\s+)?hand[- ]?off|hand[- ]?off\s+(prompt|doc|document|plan))\b/i;
16577
17019
  }
@@ -16901,7 +17343,7 @@ ALTITUDE CONTRACT (the plan must work at every altitude a client reads it at):
16901
17343
  - 10,000 FT: each workstream's title + problem line is a delegation unit \u2014 a one-liner an owner could receive in Slack and know what they own, why it's theirs, and what number they move.
16902
17344
  - GROUND LEVEL: actions are Monday-morning prescriptive. You have built this before \u2014 read the linked play's full detail with get_play_detail and prescribe its known-good sequence adapted to THIS company's numbers and constraints. The first action of every workstream must be startable within 48 hours with no new tooling.
16903
17345
 
16904
- ${USER_VISIBLE_STE_BLOCK}
17346
+ ${buildUserVisibleProseBlock()}
16905
17347
 
16906
17348
  GTM ENGINEERING (plans install systems, not heroics):
16907
17349
  ${GTM_ENGINEERING_BLOCK}
@@ -16991,6 +17433,7 @@ var init_strategist_prompt = __esm({
16991
17433
  "src/ai/strategist-prompt.ts"() {
16992
17434
  "use strict";
16993
17435
  init_prompt_parts();
17436
+ init_prompt();
16994
17437
  STRATEGIST_PLAN_SCHEMA_BLOCK = `{
16995
17438
  "title": "Short plan name, e.g. 'Q4 Pipeline Recovery'",
16996
17439
  "objective": "The measurable destination, restated precisely",
@@ -18304,45 +18747,372 @@ var init_strategist_flow = __esm({
18304
18747
  }
18305
18748
  });
18306
18749
 
18307
- // src/conversation/gap-card.ts
18750
+ // src/services/think.ts
18751
+ var think_exports = {};
18752
+ __export(think_exports, {
18753
+ runThinkTurn: () => runThinkTurn
18754
+ });
18308
18755
  import chalk18 from "chalk";
18756
+ async function runThinkTurn(input, ctx) {
18757
+ assertReplAi(ctx);
18758
+ let snapshot = ctx.snapshot.computeResult;
18759
+ if (!snapshot) {
18760
+ const metricsFirst = prefersMetricsFirstContext(ctx);
18761
+ const spinner2 = makeSpinner(metricsFirst ? "Loading session context\u2026" : "Computing vital signs\u2026");
18762
+ try {
18763
+ snapshot = await computeFullHealth();
18764
+ ctx.snapshot.computeResult = snapshot;
18765
+ const divInput = snapshot.segments.map((s) => ({
18766
+ segmentId: s.segment.id,
18767
+ segmentName: s.segment.name,
18768
+ result: s.result
18769
+ }));
18770
+ ctx.snapshot.divergences = detectDivergences(snapshot.aggregate, divInput).divergences;
18771
+ spinner2.succeed(metricsFirst ? "Session context ready" : "Health snapshot ready");
18772
+ } catch (err) {
18773
+ spinner2.fail("Could not compute health snapshot");
18774
+ console.error(" " + chalk18.red(String(err.message ?? err)));
18775
+ console.log(" " + chalk18.dim("Run ") + paint("accent", "/new") + chalk18.dim(" \u2192 pick Demo to load sample data."));
18776
+ console.log();
18777
+ return;
18778
+ }
18779
+ }
18780
+ console.log();
18781
+ const memoryBlock = await buildMemoryBlock(input).catch(() => "");
18782
+ const spinner = makeSpinner("Thinking with you\u2026");
18783
+ let lastAnswer = "";
18784
+ let rawHistory = [];
18785
+ const toolsUsed = [];
18786
+ setAgentContext(ctx);
18787
+ try {
18788
+ const analysisBlock = buildAnalysisBlock(ctx);
18789
+ const conversationBlock = getConversationPhaseBlock(ctx);
18790
+ const bundle = await loadSessionAnalysisBundle();
18791
+ const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
18792
+ for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
18793
+ mode: "think",
18794
+ userQuestion: input,
18795
+ sessionContext: ctx.resumedSessionSummary,
18796
+ includeMetrics: true,
18797
+ analysisBlock,
18798
+ conversationBlock,
18799
+ sessionArtifact,
18800
+ priorMessages: ctx.thinkConversation,
18801
+ memoryBlock,
18802
+ ctx
18803
+ })) {
18804
+ switch (event.type) {
18805
+ case "tool_call":
18806
+ toolsUsed.push(event.name);
18807
+ spinner.text = `Querying ${event.name}\u2026`;
18808
+ break;
18809
+ case "thinking":
18810
+ spinner.stop();
18811
+ console.log(" " + chalk18.dim.italic(event.text));
18812
+ spinner.start("Thinking with you\u2026");
18813
+ break;
18814
+ case "answer":
18815
+ spinner.stop();
18816
+ lastAnswer = event.text;
18817
+ printMarkdown(event.text, { indent: 2 });
18818
+ break;
18819
+ case "finding":
18820
+ spinner.stop();
18821
+ printFindingInline(event.finding);
18822
+ break;
18823
+ case "done":
18824
+ spinner.stop();
18825
+ rawHistory = event.conversation_history;
18826
+ break;
18827
+ }
18828
+ }
18829
+ } catch (err) {
18830
+ spinner.fail("Error while thinking");
18831
+ console.error(" " + chalk18.red(String(err.message ?? err)));
18832
+ console.log();
18833
+ return;
18834
+ } finally {
18835
+ setAgentContext(null);
18836
+ }
18837
+ if (rawHistory.length > 0) {
18838
+ ctx.thinkConversation = distillThread(rawHistory);
18839
+ }
18840
+ if (!lastAnswer) {
18841
+ console.log(" " + chalk18.dim("(no answer returned)"));
18842
+ } else {
18843
+ recordMessage(ctx, "agent", lastAnswer);
18844
+ saveSessionState(ctx);
18845
+ creditNlAnswer(ctx, Math.floor(ctx.messages.length / 2));
18846
+ recordAnalysis({
18847
+ question: `[think] ${input}`,
18848
+ answer: lastAnswer,
18849
+ tools: toolsUsed,
18850
+ session_id: ctx.sessionId
18851
+ });
18852
+ ctx.lastExchange = { question: input, answer: lastAnswer };
18853
+ }
18854
+ console.log();
18855
+ return lastAnswer ? extractSummary(lastAnswer) : void 0;
18856
+ }
18857
+ function extractSummary(text) {
18858
+ const plain = text.replace(/#{1,6}\s+/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^[-*]\s+/gm, "").trim();
18859
+ const sentenceMatch = plain.match(/^(.+?[.!?])(?:\s|$)/);
18860
+ const sentence = sentenceMatch ? sentenceMatch[1] : plain.split("\n")[0];
18861
+ if (sentence.length <= 60) return sentence;
18862
+ return sentence.slice(0, 60).replace(/\s+\S*$/, "") + "\u2026";
18863
+ }
18864
+ function printFindingInline(finding) {
18865
+ const sev = finding.severity;
18866
+ console.log();
18867
+ console.log(
18868
+ " " + severityPaint(sev)(sev.toUpperCase()) + " " + (finding.finding ?? "").slice(0, 120)
18869
+ );
18870
+ }
18871
+ var init_think = __esm({
18872
+ "src/services/think.ts"() {
18873
+ "use strict";
18874
+ init_spinner();
18875
+ init_context2();
18876
+ init_phase();
18877
+ init_agent_context();
18878
+ init_agentic_loop();
18879
+ init_thread();
18880
+ init_store2();
18881
+ init_health_score();
18882
+ init_divergence();
18883
+ init_repl_api();
18884
+ init_theme();
18885
+ init_markdown();
18886
+ init_session_analysis();
18887
+ init_time_bank();
18888
+ }
18889
+ });
18890
+
18891
+ // src/conversation/think-flow.ts
18892
+ var think_flow_exports = {};
18893
+ __export(think_flow_exports, {
18894
+ clearThinkFlow: () => clearThinkFlow,
18895
+ extractThinkSeed: () => extractThinkSeed,
18896
+ handleThinkFlow: () => handleThinkFlow,
18897
+ isThinkIntent: () => isThinkIntent,
18898
+ queueThinkForAnalysis: () => queueThinkForAnalysis,
18899
+ resumeThinkAfterCompute: () => resumeThinkAfterCompute,
18900
+ resumeThinkAfterConnect: () => resumeThinkAfterConnect,
18901
+ startThinkFlow: () => startThinkFlow
18902
+ });
18903
+ import chalk19 from "chalk";
18904
+ function isThinkIntent(input) {
18905
+ const line = input.trim();
18906
+ if (!line) return false;
18907
+ if (isShipIntent(line)) return false;
18908
+ if (isStrategistIntent(line)) return false;
18909
+ return THINK_INTENT_RE.test(line);
18910
+ }
18911
+ function extractThinkSeed(input) {
18912
+ const cleaned = input.trim().replace(/^(hey|ok|okay|please|can you|could you|help me|let'?s|i want to|i'?d like to)\s+/i, "").replace(/^(think\s+with\s+me|pressure[- ]?test|dig\s+into|think\s+through)\s*(about|on|around|this|:)?\s*/i, "").replace(/^(what\s+am\s+i\s+missing\s*(about|on|with|here)?)\s*/i, "").trim();
18913
+ return cleaned.length >= 4 ? cleaned : input.trim();
18914
+ }
18915
+ function queueThinkForAnalysis(ctx, opts) {
18916
+ ctx.thinkState = {
18917
+ step: "awaiting_analysis",
18918
+ seed: opts.seed,
18919
+ origin: opts.origin,
18920
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
18921
+ };
18922
+ saveSessionState(ctx);
18923
+ console.log();
18924
+ console.log(
18925
+ " " + chalk19.dim("Think session queued. NTRP opens the channel after analysis.")
18926
+ );
18927
+ if (opts.origin !== "nl") {
18928
+ console.log(
18929
+ " " + chalk19.dim("Type what to look at. Paste a CSV path. Or type ") + chalk19.cyan("use demo data") + chalk19.dim(".")
18930
+ );
18931
+ }
18932
+ console.log();
18933
+ }
18934
+ function printChannelIntro(seed) {
18935
+ console.log();
18936
+ console.log(" " + paint("accent", "Think with me"));
18937
+ console.log(
18938
+ " " + chalk19.dim(
18939
+ "Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
18940
+ ) + chalk19.cyan("done") + chalk19.dim(" or ") + chalk19.cyan("cancel") + chalk19.dim(" to return to ask \u203A.")
18941
+ );
18942
+ if (seed) {
18943
+ console.log(" " + chalk19.dim("Seed: ") + seed);
18944
+ }
18945
+ console.log();
18946
+ }
18947
+ function armKeylessThink(ctx, opts) {
18948
+ ctx.thinkState = {
18949
+ step: "awaiting_connect",
18950
+ seed: opts.seed,
18951
+ origin: opts.origin,
18952
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
18953
+ };
18954
+ saveSessionState(ctx);
18955
+ console.log();
18956
+ console.log(" " + chalk19.dim("Think channel needs an AI key."));
18957
+ console.log(
18958
+ " " + chalk19.dim("Type ") + paint("accent", "/connect") + chalk19.dim(" and paste a key. Seed kept \u2014 the channel opens after connect.")
18959
+ );
18960
+ console.log();
18961
+ }
18962
+ async function startThinkFlow(ctx, opts) {
18963
+ if (!isAnalysisReady(ctx)) {
18964
+ queueThinkForAnalysis(ctx, opts);
18965
+ return "Think queued";
18966
+ }
18967
+ if (!canUseReplAi(ctx)) {
18968
+ armKeylessThink(ctx, opts);
18969
+ return "Think awaiting connect";
18970
+ }
18971
+ const seed = opts.seed?.trim() || void 0;
18972
+ ctx.thinkState = {
18973
+ step: "active",
18974
+ seed,
18975
+ origin: opts.origin,
18976
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
18977
+ open_questions: [],
18978
+ challenged_assumptions: [],
18979
+ working_hypotheses: []
18980
+ };
18981
+ if (ctx.thinkConversation.length === 0 && seed) {
18982
+ }
18983
+ saveSessionState(ctx);
18984
+ printChannelIntro(seed);
18985
+ recordMessage(ctx, "agent", seed ? `Think channel opened: ${seed}` : "Think channel opened");
18986
+ if (seed) {
18987
+ recordMessage(ctx, "user", seed);
18988
+ const { runThinkTurn: runThinkTurn2 } = await Promise.resolve().then(() => (init_think(), think_exports));
18989
+ await runThinkTurn2(seed, ctx);
18990
+ }
18991
+ return "Think channel open";
18992
+ }
18993
+ async function resumeThinkAfterCompute(ctx) {
18994
+ const state2 = ctx.thinkState;
18995
+ if (!state2 || state2.step !== "awaiting_analysis") return;
18996
+ if (ctx.strategistState && ctx.strategistState.step !== "awaiting_analysis" && ctx.strategistState.step !== "awaiting_connect") {
18997
+ return;
18998
+ }
18999
+ if (ctx.strategistState?.step === "awaiting_analysis") return;
19000
+ console.log();
19001
+ console.log(" " + paint("accent", "Analysis is ready. The think channel continues."));
19002
+ await startThinkFlow(ctx, {
19003
+ seed: state2.seed,
19004
+ origin: state2.origin ?? "nl"
19005
+ });
19006
+ }
19007
+ async function resumeThinkAfterConnect(ctx) {
19008
+ const state2 = ctx.thinkState;
19009
+ if (!state2 || state2.step !== "awaiting_connect") return false;
19010
+ if (!canUseReplAi(ctx)) return false;
19011
+ console.log();
19012
+ console.log(" " + paint("accent", "Key connected. Opening the think channel."));
19013
+ await startThinkFlow(ctx, {
19014
+ seed: state2.seed,
19015
+ origin: state2.origin ?? "nl"
19016
+ });
19017
+ return true;
19018
+ }
19019
+ function clearThinkFlow(ctx, reason) {
19020
+ ctx.thinkState = void 0;
19021
+ saveSessionState(ctx);
19022
+ if (reason === "handoff") return;
19023
+ console.log();
19024
+ console.log(
19025
+ " " + chalk19.dim(
19026
+ reason === "done" ? "Think channel closed. Back to ask \u203A." : "Think session cancelled. Continue exploration."
19027
+ )
19028
+ );
19029
+ console.log();
19030
+ }
19031
+ async function handleThinkFlow(input, ctx) {
19032
+ const state2 = ctx.thinkState;
19033
+ if (!state2 || state2.step !== "active") return;
19034
+ const line = input.trim();
19035
+ recordMessage(ctx, "user", line);
19036
+ if (CANCEL_RE2.test(line) || DONE_RE.test(line)) {
19037
+ clearThinkFlow(ctx, CANCEL_RE2.test(line) ? "cancel" : "done");
19038
+ recordMessage(ctx, "agent", "Think channel closed");
19039
+ return "Think closed";
19040
+ }
19041
+ if (isStrategistIntent(line)) {
19042
+ clearThinkFlow(ctx, "handoff");
19043
+ const summary = await startStrategistFlow(ctx, {
19044
+ seed: extractObjectiveSeed(line),
19045
+ origin: "nl"
19046
+ }) ?? void 0;
19047
+ return summary ?? "Handed off to strategist";
19048
+ }
19049
+ if (!canUseReplAi(ctx)) {
19050
+ armKeylessThink(ctx, { seed: state2.seed ?? line, origin: state2.origin ?? "nl" });
19051
+ return "Think awaiting connect";
19052
+ }
19053
+ const { runThinkTurn: runThinkTurn2 } = await Promise.resolve().then(() => (init_think(), think_exports));
19054
+ await runThinkTurn2(line, ctx);
19055
+ if (ctx.strategistState && ctx.strategistState.step !== "awaiting_analysis" && ctx.strategistState.step !== "awaiting_connect") {
19056
+ clearThinkFlow(ctx, "handoff");
19057
+ const { promptQueuedAiStrategist: promptQueuedAiStrategist2 } = await Promise.resolve().then(() => (init_strategist_flow(), strategist_flow_exports));
19058
+ promptQueuedAiStrategist2(ctx);
19059
+ }
19060
+ return "Think turn";
19061
+ }
19062
+ var THINK_INTENT_RE, CANCEL_RE2, DONE_RE;
19063
+ var init_think_flow = __esm({
19064
+ "src/conversation/think-flow.ts"() {
19065
+ "use strict";
19066
+ init_context2();
19067
+ init_repl_api();
19068
+ init_theme();
19069
+ init_handoff_draft();
19070
+ init_strategist_flow();
19071
+ THINK_INTENT_RE = /\b(think\s+with\s+me|pressure[- ]?test|what\s+am\s+i\s+missing|challenge\s+(my\s+)?(assumption|thinking|hypothesis)|let'?s\s+(dig|explore|think|pressure)|dig\s+into|steelman|devil'?s\s+advocate|think\s+through|brainstorm\s+(with\s+me|this)|socratic)\b/i;
19072
+ CANCEL_RE2 = /^(cancel|stop|quit|abort|never\s?mind|nevermind|forget it)\s*[.!]?\s*$/i;
19073
+ DONE_RE = /^(done|enough|enough for now|that'?s enough|leave|exit think)\s*[.!]?\s*$/i;
19074
+ }
19075
+ });
19076
+
19077
+ // src/conversation/gap-card.ts
19078
+ import chalk20 from "chalk";
18309
19079
  function printGapCard(audit, opts = {}) {
18310
19080
  console.log();
18311
19081
  if (opts.skipSatisfied) {
18312
19082
  if (audit.missing.length > 0) {
18313
- console.log(" " + chalk18.bold("Data check"));
19083
+ console.log(" " + chalk20.bold("Data check"));
18314
19084
  }
18315
19085
  } else if (audit.satisfied.length > 0) {
18316
19086
  const bits = audit.satisfied.map((item) => item.detail);
18317
19087
  console.log(
18318
- " " + chalk18.green("\u2713") + " " + chalk18.bold("Data check") + chalk18.dim(" \u2014 " + bits.join(" \xB7 "))
19088
+ " " + chalk20.green("\u2713") + " " + chalk20.bold("Data check") + chalk20.dim(" \u2014 " + bits.join(" \xB7 "))
18319
19089
  );
18320
19090
  } else {
18321
- console.log(" " + chalk18.bold("Data check"));
19091
+ console.log(" " + chalk20.bold("Data check"));
18322
19092
  }
18323
19093
  for (const item of audit.missing) {
18324
- console.log(" " + chalk18.red("\u2717") + " " + item.label + chalk18.dim(` \u2014 ${item.why}`));
18325
- console.log(" " + chalk18.dim(item.suggestion));
19094
+ console.log(" " + chalk20.red("\u2717") + " " + item.label + chalk20.dim(` \u2014 ${item.why}`));
19095
+ console.log(" " + chalk20.dim(item.suggestion));
18326
19096
  }
18327
19097
  if (audit.optional.length > 0) {
18328
19098
  const heads = audit.optional.map((item) => item.detail.split(" \u2014 ")[0] ?? item.detail);
18329
19099
  const joined = heads.join(" \xB7 ");
18330
19100
  if (joined.length <= 100) {
18331
- console.log(" " + chalk18.yellow("~") + " " + chalk18.dim(joined));
19101
+ console.log(" " + chalk20.yellow("~") + " " + chalk20.dim(joined));
18332
19102
  } else {
18333
19103
  for (const item of audit.optional) {
18334
- console.log(" " + chalk18.yellow("~") + " " + chalk18.dim(`${item.label}: ${item.detail}`));
19104
+ console.log(" " + chalk20.yellow("~") + " " + chalk20.dim(`${item.label}: ${item.detail}`));
18335
19105
  }
18336
19106
  }
18337
19107
  }
18338
19108
  console.log();
18339
19109
  if (audit.can_compute) {
18340
19110
  console.log(
18341
- " " + chalk18.dim("Ready to compute. Press ") + chalk18.cyan("\u23CE") + chalk18.dim(" or type ") + chalk18.cyan('"go ahead"')
19111
+ " " + chalk20.dim(voiceGapReadyCta())
18342
19112
  );
18343
19113
  } else if (audit.missing.length > 0) {
18344
19114
  console.log(
18345
- " " + chalk18.dim("Load data. Paste a CSV path, or press ") + chalk18.cyan("\u23CE") + chalk18.dim(" to ") + chalk18.cyan("use demo data")
19115
+ " " + chalk20.dim(voiceGapLoadCta())
18346
19116
  );
18347
19117
  }
18348
19118
  console.log();
@@ -18350,6 +19120,7 @@ function printGapCard(audit, opts = {}) {
18350
19120
  var init_gap_card = __esm({
18351
19121
  "src/conversation/gap-card.ts"() {
18352
19122
  "use strict";
19123
+ init_copy();
18353
19124
  }
18354
19125
  });
18355
19126
 
@@ -18359,7 +19130,7 @@ __export(keyless_ask_exports, {
18359
19130
  isKeylessVitalsAsk: () => isKeylessVitalsAsk,
18360
19131
  tryKeylessAskAnswer: () => tryKeylessAskAnswer
18361
19132
  });
18362
- import chalk19 from "chalk";
19133
+ import chalk21 from "chalk";
18363
19134
  function isKeylessVitalsAsk(input) {
18364
19135
  return KEYLESS_ASK_RE.test(input.trim());
18365
19136
  }
@@ -18405,39 +19176,42 @@ async function tryKeylessAskAnswer(ctx, input, opts = {}) {
18405
19176
  if (!primary) return false;
18406
19177
  const label = VITAL_SIGN_LABELS[primary.vital_sign] ?? primary.vital_sign;
18407
19178
  const dollarBit = primary.dollar_value != null && primary.dollar_value > 0 ? `${formatCurrency(primary.dollar_value)} ${primary.dollar_label ?? ""}`.trim() : null;
18408
- const headline = dollarBit ? `${label} \u2014 ${dollarBit} \u2014 is the most expensive problem to solve right now.` : `${label} (score ${Math.round(primary.score)}, ${primary.status}) is the problem to fix first.`;
19179
+ const headline = voiceKeylessHeadline({
19180
+ label,
19181
+ dollarBit,
19182
+ score: primary.score,
19183
+ status: primary.status
19184
+ });
18409
19185
  const runners = [...aggregate.vital_signs].filter((v) => v.vital_sign !== primary.vital_sign && (v.dollar_value ?? 0) > 0).sort((a, b) => (b.dollar_value ?? 0) - (a.dollar_value ?? 0)).slice(0, 2);
18410
19186
  console.log();
18411
- console.log(" " + chalk19.bold(headline));
19187
+ console.log(" " + chalk21.bold(headline));
18412
19188
  if (opts.fromResume) {
18413
19189
  if (runners.length > 0) {
18414
19190
  console.log(
18415
- " " + chalk19.dim("Next after that: ") + chalk19.dim(runners.map(formatRunnerBit).join(" \xB7 "))
19191
+ " " + chalk21.dim(voiceKeylessNextLabel()) + chalk21.dim(runners.map(formatRunnerBit).join(" \xB7 "))
18416
19192
  );
18417
19193
  }
18418
19194
  } else {
18419
19195
  console.log();
18420
19196
  if (gating && gating.vital_sign !== primary.vital_sign) {
18421
19197
  console.log(
18422
- " " + chalk19.dim("Gating vital: ") + paint("accent", VITAL_SIGN_LABELS[gating.vital_sign]) + chalk19.dim(` (score ${Math.round(gating.score)}) \u2014 it bounds what you can trust downstream.`)
19198
+ " " + chalk21.dim("Gating vital: ") + paint("accent", VITAL_SIGN_LABELS[gating.vital_sign]) + chalk21.dim(voiceKeylessGatingWrap(VITAL_SIGN_LABELS[gating.vital_sign], gating.score))
18423
19199
  );
18424
19200
  }
18425
19201
  if (runners.length > 0) {
18426
- console.log(" " + chalk19.dim("Also on the board:"));
19202
+ console.log(" " + chalk21.dim(voiceKeylessAlsoLabel()));
18427
19203
  for (const vs of runners) {
18428
- console.log(" " + chalk19.dim("\xB7 ") + formatVitalLine(vs));
19204
+ console.log(" " + chalk21.dim("\xB7 ") + formatVitalLine(vs));
18429
19205
  }
18430
19206
  }
18431
19207
  if (aggregate.total_value_at_risk != null && aggregate.total_value_at_risk > 0) {
18432
19208
  console.log(
18433
- " " + chalk19.dim("Total at risk: ") + chalk19.green(formatCurrency(aggregate.total_value_at_risk))
19209
+ " " + chalk21.dim("Total at risk: ") + chalk21.green(formatCurrency(aggregate.total_value_at_risk))
18434
19210
  );
18435
19211
  }
18436
19212
  }
18437
19213
  console.log();
18438
- console.log(
18439
- " " + chalk19.dim("Press ") + paint("accent", "\u23CE") + chalk19.dim(" to connect a key (") + paint("accent", "/connect") + chalk19.dim(") for the why and the plan. NTRP will finish this question after you connect.")
18440
- );
19214
+ console.log(" " + chalk21.dim(voiceKeylessConnectCta()));
18441
19215
  console.log();
18442
19216
  if (!opts.fromResume) {
18443
19217
  recordMessage(ctx, "user", input);
@@ -18454,12 +19228,13 @@ var init_keyless_ask = __esm({
18454
19228
  init_formatters();
18455
19229
  init_theme();
18456
19230
  init_health_score();
19231
+ init_copy();
18457
19232
  KEYLESS_ASK_RE = /\b(most expensive|biggest risk|expensive problem|what should (i|we) fix|fix first|biggest problem|largest risk|where (are we|do we) (bleed|leak|hurt))\b/i;
18458
19233
  }
18459
19234
  });
18460
19235
 
18461
19236
  // src/conversation/keyless-definitions.ts
18462
- import chalk20 from "chalk";
19237
+ import chalk22 from "chalk";
18463
19238
  function isPossessiveMetricAsk(input) {
18464
19239
  return POSSESSIVE_RE.test(input.trim());
18465
19240
  }
@@ -18512,9 +19287,9 @@ function tryKeylessDefinitionAnswer(ctx, input) {
18512
19287
  const bench = explainer.benchmarkHint?.(motion);
18513
19288
  console.log();
18514
19289
  console.log(
18515
- " " + sectionHeading(explainer.label) + chalk20.dim(` \xB7 ${explainer.kind === "vital" ? "vital sign" : "SaaS metric"}`)
19290
+ " " + sectionHeading(explainer.label) + chalk22.dim(` \xB7 ${explainer.kind === "vital" ? "vital sign" : "SaaS metric"}`)
18516
19291
  );
18517
- console.log(" " + chalk20.dim(explainer.tagline));
19292
+ console.log(" " + chalk22.dim(explainer.tagline));
18518
19293
  console.log();
18519
19294
  console.log(" " + bold("Meaning"));
18520
19295
  printWrapped2(explainer.meaning, " ");
@@ -18526,16 +19301,16 @@ function tryKeylessDefinitionAnswer(ctx, input) {
18526
19301
  }
18527
19302
  if (bench) {
18528
19303
  console.log();
18529
- console.log(" " + chalk20.dim(`Benchmark \xB7 ${bench}`));
19304
+ console.log(" " + chalk22.dim(`Benchmark \xB7 ${bench}`));
18530
19305
  }
18531
19306
  if (explainer.dollar_label) {
18532
19307
  console.log(
18533
- " " + chalk20.dim(`Dollar translation \xB7 ${explainer.dollar_label}`)
19308
+ " " + chalk22.dim(`Dollar translation \xB7 ${explainer.dollar_label}`)
18534
19309
  );
18535
19310
  }
18536
19311
  console.log();
18537
19312
  console.log(
18538
- " " + chalk20.dim("More: ") + paint("accent", `/deepdive ${explainer.id}`) + chalk20.dim(" \xB7 full tour: ") + paint("accent", "/deepdive")
19313
+ " " + chalk22.dim("More: ") + paint("accent", `/deepdive ${explainer.id}`) + chalk22.dim(" \xB7 full tour: ") + paint("accent", "/deepdive")
18539
19314
  );
18540
19315
  console.log();
18541
19316
  recordMessage(ctx, "user", input);
@@ -18565,7 +19340,7 @@ var init_keyless_definitions = __esm({
18565
19340
  });
18566
19341
 
18567
19342
  // src/conversation/orchestrator.ts
18568
- import chalk21 from "chalk";
19343
+ import chalk23 from "chalk";
18569
19344
  async function handleExploreWithoutKey(ctx, input) {
18570
19345
  if (isDefinitionAsk(input) && tryKeylessDefinitionAnswer(ctx, input)) {
18571
19346
  return;
@@ -18595,7 +19370,7 @@ async function handleExploreWithoutKey(ctx, input) {
18595
19370
  );
18596
19371
  if (ctx.pendingAsk) {
18597
19372
  console.log(
18598
- " " + chalk21.dim("Your question is stored. NTRP will answer it after you connect.")
19373
+ " " + chalk23.dim("Your question is stored. NTRP will answer it after you connect.")
18599
19374
  );
18600
19375
  }
18601
19376
  if (ctx.gapAudit) {
@@ -18610,12 +19385,12 @@ async function handleExploreWithoutKey(ctx, input) {
18610
19385
  return;
18611
19386
  }
18612
19387
  console.log();
18613
- console.log(" " + chalk21.yellow("No key is connected. Q&A stays off until you type ") + paint("accent", "/connect") + chalk21.yellow("."));
18614
- console.log(" " + chalk21.dim("These commands work without a key:"));
18615
- console.log(" " + paint("accent", "/deepdive") + chalk21.dim(" slides for numbers and how to use NTRP"));
18616
- console.log(" " + paint("accent", "/playbook") + chalk21.dim(" recommended plays from your computed vitals"));
18617
- console.log(" " + chalk21.cyan('"how should we fix this?"') + chalk21.dim(" a simple plan. No AI."));
18618
- console.log(" " + paint("accent", "/handoff") + chalk21.dim(" write this analysis for another tool"));
19388
+ console.log(" " + chalk23.yellow("No key is connected. Q&A stays off until you type ") + paint("accent", "/connect") + chalk23.yellow("."));
19389
+ console.log(" " + chalk23.dim("These commands work without a key:"));
19390
+ console.log(" " + paint("accent", "/deepdive") + chalk23.dim(" slides for numbers and how to use NTRP"));
19391
+ console.log(" " + paint("accent", "/playbook") + chalk23.dim(" recommended plays from your computed vitals"));
19392
+ console.log(" " + chalk23.cyan('"how should we fix this?"') + chalk23.dim(" a simple plan. No AI."));
19393
+ console.log(" " + paint("accent", "/handoff") + chalk23.dim(" write this analysis for another tool"));
18619
19394
  console.log();
18620
19395
  recordMessage(
18621
19396
  ctx,
@@ -19270,7 +20045,7 @@ var nl_exports = {};
19270
20045
  __export(nl_exports, {
19271
20046
  runNaturalLanguage: () => runNaturalLanguage
19272
20047
  });
19273
- import chalk22 from "chalk";
20048
+ import chalk24 from "chalk";
19274
20049
  async function runNaturalLanguage(input, ctx) {
19275
20050
  if (isSmokeProtocolTrigger(input)) {
19276
20051
  recordMessage(ctx, "user", input);
@@ -19282,10 +20057,10 @@ async function runNaturalLanguage(input, ctx) {
19282
20057
  printAnswer(result.answer);
19283
20058
  recordMessage(ctx, "agent", result.answer);
19284
20059
  console.log();
19285
- return extractSummary(result.answer);
20060
+ return extractSummary2(result.answer);
19286
20061
  } catch (err) {
19287
20062
  spinner2.fail("Smoke protocol failed");
19288
- console.error(" " + chalk22.red(String(err.message ?? err)));
20063
+ console.error(" " + chalk24.red(String(err.message ?? err)));
19289
20064
  console.log();
19290
20065
  return;
19291
20066
  }
@@ -19315,8 +20090,8 @@ async function runNaturalLanguage(input, ctx) {
19315
20090
  spinner2.succeed(metricsFirst ? "Session context ready" : "Health snapshot ready");
19316
20091
  } catch (err) {
19317
20092
  spinner2.fail("Could not compute health snapshot");
19318
- console.error(" " + chalk22.red(String(err.message ?? err)));
19319
- console.log(" " + chalk22.dim("Run ") + paint("accent", "/new") + chalk22.dim(" \u2192 pick Demo to load sample data."));
20093
+ console.error(" " + chalk24.red(String(err.message ?? err)));
20094
+ console.log(" " + chalk24.dim("Run ") + paint("accent", "/new") + chalk24.dim(" \u2192 pick Demo to load sample data."));
19320
20095
  console.log();
19321
20096
  return;
19322
20097
  }
@@ -19354,7 +20129,7 @@ async function runNaturalLanguage(input, ctx) {
19354
20129
  break;
19355
20130
  case "thinking":
19356
20131
  spinner.stop();
19357
- console.log(" " + chalk22.dim.italic(event.text));
20132
+ console.log(" " + chalk24.dim.italic(event.text));
19358
20133
  spinner.start("Thinking\u2026");
19359
20134
  break;
19360
20135
  case "answer":
@@ -19364,7 +20139,7 @@ async function runNaturalLanguage(input, ctx) {
19364
20139
  break;
19365
20140
  case "finding":
19366
20141
  spinner.stop();
19367
- printFindingInline(event.finding);
20142
+ printFindingInline2(event.finding);
19368
20143
  break;
19369
20144
  case "done":
19370
20145
  spinner.stop();
@@ -19374,7 +20149,7 @@ async function runNaturalLanguage(input, ctx) {
19374
20149
  }
19375
20150
  } catch (err) {
19376
20151
  spinner.fail("Error while investigating");
19377
- console.error(" " + chalk22.red(String(err.message ?? err)));
20152
+ console.error(" " + chalk24.red(String(err.message ?? err)));
19378
20153
  console.log();
19379
20154
  return;
19380
20155
  } finally {
@@ -19384,7 +20159,7 @@ async function runNaturalLanguage(input, ctx) {
19384
20159
  ctx.conversation = distillThread(rawHistory);
19385
20160
  }
19386
20161
  if (!lastAnswer) {
19387
- console.log(" " + chalk22.dim("(no answer returned)"));
20162
+ console.log(" " + chalk24.dim("(no answer returned)"));
19388
20163
  } else {
19389
20164
  recordMessage(ctx, "agent", lastAnswer);
19390
20165
  if (ctx.pendingAsk) {
@@ -19400,12 +20175,12 @@ async function runNaturalLanguage(input, ctx) {
19400
20175
  console.log();
19401
20176
  const { promptQueuedAiStrategist: promptQueuedAiStrategist2 } = await Promise.resolve().then(() => (init_strategist_flow(), strategist_flow_exports));
19402
20177
  promptQueuedAiStrategist2(ctx);
19403
- return lastAnswer ? extractSummary(lastAnswer) : void 0;
20178
+ return lastAnswer ? extractSummary2(lastAnswer) : void 0;
19404
20179
  }
19405
20180
  function printAnswer(text) {
19406
20181
  printMarkdown(text, { indent: 2 });
19407
20182
  }
19408
- function extractSummary(text) {
20183
+ function extractSummary2(text) {
19409
20184
  const plain = text.replace(/#{1,6}\s+/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^[-*]\s+/gm, "").trim();
19410
20185
  const sentenceMatch = plain.match(/^(.+?[.!?])(?:\s|$)/);
19411
20186
  const sentence = sentenceMatch ? sentenceMatch[1] : plain.split("\n")[0];
@@ -19413,16 +20188,16 @@ function extractSummary(text) {
19413
20188
  const truncated = sentence.slice(0, 60).replace(/\s+\S*$/, "");
19414
20189
  return truncated + "\u2026";
19415
20190
  }
19416
- function printFindingInline(finding) {
20191
+ function printFindingInline2(finding) {
19417
20192
  const sev = finding.severity;
19418
20193
  console.log();
19419
- console.log(" " + severityPaint(sev)(`[${sev}]`) + " " + chalk22.bold(finding.segment));
20194
+ console.log(" " + severityPaint(sev)(`[${sev}]`) + " " + chalk24.bold(finding.segment));
19420
20195
  printMarkdown(finding.finding, { indent: 2 });
19421
20196
  const play = finding.recommended_plays?.[0];
19422
- if (play) console.log(" " + chalk22.dim("\u2192 " + play.play_name + " \u2014 " + play.rationale));
20197
+ if (play) console.log(" " + chalk24.dim("\u2192 " + play.play_name + " \u2014 " + play.rationale));
19423
20198
  if (finding.recommended_focus) {
19424
20199
  console.log(
19425
- " " + chalk22.dim("How this works: ") + paint("accent", `/deepdive ${finding.recommended_focus}`)
20200
+ " " + chalk24.dim("How this works: ") + paint("accent", `/deepdive ${finding.recommended_focus}`)
19426
20201
  );
19427
20202
  }
19428
20203
  }
@@ -19458,12 +20233,12 @@ __export(demo_exports, {
19458
20233
  printDemoDisabled: () => printDemoDisabled,
19459
20234
  setDemoEnabled: () => setDemoEnabled
19460
20235
  });
19461
- import chalk23 from "chalk";
20236
+ import chalk25 from "chalk";
19462
20237
  function printDemoDisabled() {
19463
20238
  console.log();
19464
- console.log(" " + chalk23.red(DEMO_DISABLED_MESSAGE));
20239
+ console.log(" " + chalk25.red(DEMO_DISABLED_MESSAGE));
19465
20240
  console.log(
19466
- " " + chalk23.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk23.dim(".")
20241
+ " " + chalk25.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk25.dim(".")
19467
20242
  );
19468
20243
  console.log();
19469
20244
  }
@@ -22765,16 +23540,16 @@ var generate_exports = {};
22765
23540
  __export(generate_exports, {
22766
23541
  handler: () => handler2
22767
23542
  });
22768
- import chalk24 from "chalk";
23543
+ import chalk26 from "chalk";
22769
23544
  async function handler2(args, ctx) {
22770
23545
  const { flags } = parseArgs(args, ["list-scenarios", "regen-taxonomy", "brief"]);
22771
23546
  const quiet = ctx.execution.quiet;
22772
23547
  const brief = getBool(flags, "brief");
22773
23548
  if (getBool(flags, "list-scenarios")) {
22774
- console.log(chalk24.bold("\n Available Scenarios:\n"));
23549
+ console.log(chalk26.bold("\n Available Scenarios:\n"));
22775
23550
  for (const s of SCENARIO_LIST) {
22776
- console.log(` ${chalk24.cyan(s.key.padEnd(20))} ${s.label}`);
22777
- console.log(` ${chalk24.dim(" ".repeat(20))} ${s.description}
23551
+ console.log(` ${chalk26.cyan(s.key.padEnd(20))} ${s.label}`);
23552
+ console.log(` ${chalk26.dim(" ".repeat(20))} ${s.description}
22778
23553
  `);
22779
23554
  }
22780
23555
  return true;
@@ -22784,9 +23559,9 @@ async function handler2(args, ctx) {
22784
23559
  const skipProfile = getFalse(flags, "profile");
22785
23560
  if (!isProfileConfigured(profile) && !skipProfile) {
22786
23561
  console.error();
22787
- console.error(" " + chalk24.red("No company profile found."));
22788
- console.error(" " + chalk24.dim("Run ") + paint("accent", "/onboard") + chalk24.dim(" first for a richer demo,"));
22789
- console.error(" " + chalk24.dim("or pass ") + paint("accent", "--no-profile") + chalk24.dim(" to skip."));
23562
+ console.error(" " + chalk26.red("No company profile found."));
23563
+ console.error(" " + chalk26.dim("Run ") + paint("accent", "/onboard") + chalk26.dim(" first for a richer demo,"));
23564
+ console.error(" " + chalk26.dim("or pass ") + paint("accent", "--no-profile") + chalk26.dim(" to skip."));
22790
23565
  console.error();
22791
23566
  markFailure(ctx);
22792
23567
  return false;
@@ -22794,8 +23569,8 @@ async function handler2(args, ctx) {
22794
23569
  const explicitScenario = getString(flags, "scenario", "s");
22795
23570
  const resolvedScenario = resolveScenarioInput(explicitScenario);
22796
23571
  if (resolvedScenario === null) {
22797
- console.error(chalk24.red(` Unknown scenario: ${explicitScenario}`));
22798
- console.log(chalk24.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
23572
+ console.error(chalk26.red(` Unknown scenario: ${explicitScenario}`));
23573
+ console.log(chalk26.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
22799
23574
  markFailure(ctx);
22800
23575
  return false;
22801
23576
  }
@@ -22809,10 +23584,10 @@ async function handler2(args, ctx) {
22809
23584
  const s = getScenario(scenario);
22810
23585
  console.log();
22811
23586
  if (brief) {
22812
- console.log(" " + paint("accent", "\u2713 Demo: ") + bold(s.label) + chalk24.dim(" \u2014 " + s.hook));
23587
+ console.log(" " + paint("accent", "\u2713 Demo: ") + bold(s.label) + chalk26.dim(" \u2014 " + s.hook));
22813
23588
  } else {
22814
23589
  console.log(" " + paint("accent", "\u2713 Scenario: ") + bold(s.label));
22815
- console.log(" " + chalk24.dim(s.story));
23590
+ console.log(" " + chalk26.dim(s.story));
22816
23591
  console.log();
22817
23592
  }
22818
23593
  }
@@ -22842,18 +23617,18 @@ async function handler2(args, ctx) {
22842
23617
  if (brief) {
22843
23618
  spinner.succeed(`Demo loaded \u2014 ${briefCounts(result.counts)}`);
22844
23619
  } else {
22845
- spinner.succeed(`Generated demo data for "${chalk24.cyan(scenario)}" scenario`);
23620
+ spinner.succeed(`Generated demo data for "${chalk26.cyan(scenario)}" scenario`);
22846
23621
  console.log();
22847
23622
  printEntityCounts(result.counts);
22848
23623
  }
22849
23624
  }
22850
23625
  if (!quiet && !brief && ctx.analysis.primary !== "revenue_metrics") {
22851
- console.log(chalk24.dim("\n Run /diagnose to compute vital signs.\n"));
23626
+ console.log(chalk26.dim("\n Run /diagnose to compute vital signs.\n"));
22852
23627
  }
22853
23628
  }
22854
23629
  } catch (err) {
22855
23630
  if (spinner) spinner.fail("Generation failed");
22856
- console.error(chalk24.red(String(err)));
23631
+ console.error(chalk26.red(String(err)));
22857
23632
  markFailure(ctx);
22858
23633
  return false;
22859
23634
  }
@@ -22891,7 +23666,7 @@ async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
22891
23666
  return taxonomy;
22892
23667
  } catch (err) {
22893
23668
  spinner.fail("Couldn't build market taxonomy \u2014 using generic data pools");
22894
- console.log(" " + chalk24.dim(String(err.message ?? err)));
23669
+ console.log(" " + chalk26.dim(String(err.message ?? err)));
22895
23670
  return void 0;
22896
23671
  }
22897
23672
  }
@@ -22986,7 +23761,7 @@ __export(inbox_setup_exports, {
22986
23761
  reuseInboxFolderIfPresent: () => reuseInboxFolderIfPresent,
22987
23762
  shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
22988
23763
  });
22989
- import chalk25 from "chalk";
23764
+ import chalk27 from "chalk";
22990
23765
  import { existsSync as existsSync22 } from "fs";
22991
23766
  function markDemoOffered() {
22992
23767
  setConfigValue("ai-inbox-nudge-seen", "true");
@@ -23008,27 +23783,27 @@ function printSkipHint(beat) {
23008
23783
  const skillCmd = paint("accent", "/inbox skill");
23009
23784
  if (beat === "demo") {
23010
23785
  console.log(
23011
- " " + chalk25.dim("Skipped. NTRP will ask once when you load your own data. Or type ") + setCmd + chalk25.dim(" then ") + skillCmd
23786
+ " " + chalk27.dim("Skipped. NTRP will ask once when you load your own data. Or type ") + setCmd + chalk27.dim(" then ") + skillCmd
23012
23787
  );
23013
23788
  return;
23014
23789
  }
23015
23790
  console.log(
23016
- " " + chalk25.dim("Skipped. NTRP will not ask again. Type ") + setCmd + chalk25.dim(" then ") + skillCmd + chalk25.dim(" at any time.")
23791
+ " " + chalk27.dim("Skipped. NTRP will not ask again. Type ") + setCmd + chalk27.dim(" then ") + skillCmd + chalk27.dim(" at any time.")
23017
23792
  );
23018
23793
  }
23019
23794
  async function reuseInboxFolderIfPresent(session, beat, folderPath = defaultAiInboxDir()) {
23020
23795
  if (getAiInboxDir()) return false;
23021
23796
  if (!existsSync22(folderPath)) return false;
23022
- console.log(" " + chalk25.dim("Pickup folder still on disk: ") + folderPath);
23797
+ console.log(" " + chalk27.dim("Pickup folder still on disk: ") + folderPath);
23023
23798
  const reuse = await session.confirm("Reuse this pickup folder?", true);
23024
23799
  if (!reuse) return false;
23025
23800
  const resolved = setAiInboxDir(folderPath);
23026
23801
  markDemoOffered();
23027
23802
  if (beat === "production") markProductionOffered();
23028
23803
  console.log();
23029
- console.log(" " + paint("accent", "Inbox ready") + chalk25.dim(" ") + resolved);
23804
+ console.log(" " + paint("accent", "Inbox ready") + chalk27.dim(" ") + resolved);
23030
23805
  console.log(
23031
- " " + chalk25.dim("Folder reused. Type ") + paint("accent", "/inbox skill") + chalk25.dim(" to print the finder again.")
23806
+ " " + chalk27.dim("Folder reused. Type ") + paint("accent", "/inbox skill") + chalk27.dim(" to print the finder again.")
23032
23807
  );
23033
23808
  console.log();
23034
23809
  return true;
@@ -23039,12 +23814,12 @@ async function offerInboxSkillSetup(session, opts = {}) {
23039
23814
  console.log();
23040
23815
  console.log(" " + bold("Teach Claude where handoffs live"));
23041
23816
  console.log(
23042
- " " + chalk25.dim(
23817
+ " " + chalk27.dim(
23043
23818
  "Optional. NTRP copies every handoff into one folder. You paste instructions once; later /handoff just writes the file."
23044
23819
  )
23045
23820
  );
23046
23821
  if (beat === "production" && getConfigValue("ai-inbox-nudge-seen") === "true") {
23047
- console.log(" " + chalk25.dim("You skipped this during demo."));
23822
+ console.log(" " + chalk27.dim("You skipped this during demo."));
23048
23823
  }
23049
23824
  console.log();
23050
23825
  if (await reuseInboxFolderIfPresent(session, beat)) return;
@@ -23075,16 +23850,16 @@ async function offerInboxSkillSetup(session, opts = {}) {
23075
23850
  markDemoOffered();
23076
23851
  if (beat === "production") markProductionOffered();
23077
23852
  console.log();
23078
- console.log(" " + paint("accent", "Inbox ready") + chalk25.dim(" ") + resolved);
23853
+ console.log(" " + paint("accent", "Inbox ready") + chalk27.dim(" ") + resolved);
23079
23854
  if (n > 0) {
23080
- console.log(" " + chalk25.dim(`Synced ${n} recent export${n === 1 ? "" : "s"}.`));
23855
+ console.log(" " + chalk27.dim(`Synced ${n} recent export${n === 1 ? "" : "s"}.`));
23081
23856
  }
23082
23857
  console.log(
23083
- " " + chalk25.dim("Paste this skill into Claude once. Later, tell Claude to open the latest NTRP handoff.")
23858
+ " " + chalk27.dim("Paste this skill into Claude once. Later, tell Claude to open the latest NTRP handoff.")
23084
23859
  );
23085
23860
  printStandingSkill();
23086
23861
  await session.askPressEnter("Paste the skill into Claude. Then continue");
23087
- console.log(" " + chalk25.dim("Done. Later handoffs write to that folder. The skill is not printed again."));
23862
+ console.log(" " + chalk27.dim("Done. Later handoffs write to that folder. The skill is not printed again."));
23088
23863
  console.log();
23089
23864
  }
23090
23865
  async function maybeOfferInboxOnProduction(ctx) {
@@ -23117,7 +23892,7 @@ var ingest_exports = {};
23117
23892
  __export(ingest_exports, {
23118
23893
  handler: () => handler3
23119
23894
  });
23120
- import chalk26 from "chalk";
23895
+ import chalk28 from "chalk";
23121
23896
  import { readFileSync as readFileSync20, existsSync as existsSync23 } from "fs";
23122
23897
  import { basename as basename6 } from "path";
23123
23898
  async function handler3(args, ctx) {
@@ -23138,21 +23913,21 @@ async function handler3(args, ctx) {
23138
23913
  const source = getString(flags, "source", "s") ?? "salesforce";
23139
23914
  const skipResolve = getBool(flags, "skip-resolve");
23140
23915
  if (!file) {
23141
- console.error(chalk26.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
23142
- console.error(chalk26.dim(" /ingest --demo [--scenario <name>]"));
23916
+ console.error(chalk28.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
23917
+ console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
23143
23918
  process.exit(1);
23144
23919
  }
23145
23920
  if (!existsSync23(file)) {
23146
- console.error(chalk26.red(` File not found: ${file}`));
23921
+ console.error(chalk28.red(` File not found: ${file}`));
23147
23922
  process.exit(1);
23148
23923
  }
23149
23924
  const profile = loadProfile();
23150
23925
  const skipProfile = getFalse(flags, "profile");
23151
23926
  if (!profile && !skipProfile) {
23152
23927
  console.error();
23153
- console.error(" " + chalk26.red("No company profile found."));
23154
- console.error(" " + chalk26.dim("Run ") + paint("accent", "/onboard") + chalk26.dim(" first for better column mapping,"));
23155
- console.error(" " + chalk26.dim("or pass ") + paint("accent", "--no-profile") + chalk26.dim(" to skip."));
23928
+ console.error(" " + chalk28.red("No company profile found."));
23929
+ console.error(" " + chalk28.dim("Run ") + paint("accent", "/onboard") + chalk28.dim(" first for better column mapping,"));
23930
+ console.error(" " + chalk28.dim("or pass ") + paint("accent", "--no-profile") + chalk28.dim(" to skip."));
23156
23931
  console.error();
23157
23932
  process.exit(1);
23158
23933
  }
@@ -23184,15 +23959,15 @@ async function handler3(args, ctx) {
23184
23959
  row_count: result2.imported
23185
23960
  });
23186
23961
  spinner.succeed(
23187
- `Imported ${chalk26.bold(result2.imported.toString())} revenue events from ${chalk26.dim(basename6(file))}`
23962
+ `Imported ${chalk28.bold(result2.imported.toString())} revenue events from ${chalk28.dim(basename6(file))}`
23188
23963
  );
23189
23964
  if (result2.errors.length > 0) {
23190
- console.log(chalk26.yellow(` ${result2.errors.length} rows skipped`));
23965
+ console.log(chalk28.yellow(` ${result2.errors.length} rows skipped`));
23191
23966
  }
23192
23967
  if (ctx.analysis) {
23193
23968
  ctx.analysis.data_source_type = "revenue_ledger";
23194
23969
  }
23195
- console.log(chalk26.dim(" Run ") + chalk26.cyan("/metrics") + chalk26.dim(" for SaaS metrics with ledger-backed retention."));
23970
+ console.log(chalk28.dim(" Run ") + chalk28.cyan("/metrics") + chalk28.dim(" for SaaS metrics with ledger-backed retention."));
23196
23971
  const { maybeOfferInboxOnProduction: maybeOfferInboxOnProduction3 } = await Promise.resolve().then(() => (init_inbox_setup(), inbox_setup_exports));
23197
23972
  await maybeOfferInboxOnProduction3(ctx);
23198
23973
  return `${result2.imported} revenue events from ${basename6(file)}`;
@@ -23201,7 +23976,7 @@ async function handler3(args, ctx) {
23201
23976
  const detection = detectEntityType(headers, source);
23202
23977
  if (!detection) {
23203
23978
  spinner.fail(`Could not auto-detect entity type for source: ${source}`);
23204
- console.log(chalk26.dim(" Headers found: " + headers.join(", ")));
23979
+ console.log(chalk28.dim(" Headers found: " + headers.join(", ")));
23205
23980
  process.exit(1);
23206
23981
  }
23207
23982
  spinner.text = `Importing ${rows.length} ${detection.entityType} rows...`;
@@ -23224,15 +23999,15 @@ async function handler3(args, ctx) {
23224
23999
  row_count: result.imported
23225
24000
  });
23226
24001
  spinner.succeed(
23227
- `Imported ${chalk26.bold(result.imported.toString())} ${detection.entityType} from ${chalk26.dim(basename6(file))} (${source})`
24002
+ `Imported ${chalk28.bold(result.imported.toString())} ${detection.entityType} from ${chalk28.dim(basename6(file))} (${source})`
23228
24003
  );
23229
24004
  if (result.errors.length > 0) {
23230
- console.log(chalk26.yellow(` ${result.errors.length} rows skipped`));
24005
+ console.log(chalk28.yellow(` ${result.errors.length} rows skipped`));
23231
24006
  for (const err of result.errors.slice(0, 3)) {
23232
- console.log(chalk26.dim(` - ${err}`));
24007
+ console.log(chalk28.dim(` - ${err}`));
23233
24008
  }
23234
24009
  if (result.errors.length > 3) {
23235
- console.log(chalk26.dim(` ... and ${result.errors.length - 3} more`));
24010
+ console.log(chalk28.dim(` ... and ${result.errors.length - 3} more`));
23236
24011
  }
23237
24012
  }
23238
24013
  if (!skipResolve) {
@@ -23251,7 +24026,7 @@ async function handler3(args, ctx) {
23251
24026
  return `${result.imported} ${detection.entityType} from ${basename6(file)}`;
23252
24027
  } catch (err) {
23253
24028
  spinner.fail("Import failed");
23254
- console.error(chalk26.red(String(err)));
24029
+ console.error(chalk28.red(String(err)));
23255
24030
  process.exit(1);
23256
24031
  }
23257
24032
  }
@@ -23363,13 +24138,13 @@ __export(demo_fit_exports, {
23363
24138
  resolveDemoScenarioForLoad: () => resolveDemoScenarioForLoad,
23364
24139
  runDemoFitQuiz: () => runDemoFitQuiz
23365
24140
  });
23366
- import chalk27 from "chalk";
24141
+ import chalk29 from "chalk";
23367
24142
  async function runDemoFitQuiz(session, opts = {}) {
23368
24143
  if (opts.intro !== false) {
23369
24144
  console.log();
23370
24145
  console.log(" " + bold("Fit a sample book of business"));
23371
24146
  console.log(
23372
- " " + chalk27.dim(
24147
+ " " + chalk29.dim(
23373
24148
  "No API key needed. Two questions about how you sell, then you pick which of seven sample pipelines feels closest."
23374
24149
  )
23375
24150
  );
@@ -23387,8 +24162,8 @@ async function runDemoFitQuiz(session, opts = {}) {
23387
24162
  );
23388
24163
  const recommended = inferDemoScenario({ salesMotion: motion, dealBand });
23389
24164
  console.log();
23390
- console.log(" " + chalk27.dim("Recommended: ") + paint("accent", getScenario(recommended.scenario).label));
23391
- console.log(" " + chalk27.dim(recommended.reason));
24165
+ console.log(" " + chalk29.dim("Recommended: ") + paint("accent", getScenario(recommended.scenario).label));
24166
+ console.log(" " + chalk29.dim(recommended.reason));
23392
24167
  const scenario = await session.choose(
23393
24168
  "Which of these sample books feels closest to the one you manage?",
23394
24169
  scenarioMenuChoices(),
@@ -23408,8 +24183,8 @@ async function offerFittedDemoAfterOnboard(session, ctx, profile) {
23408
24183
  const s = getScenario(fit.scenario);
23409
24184
  console.log();
23410
24185
  console.log(" " + bold("A sample pipeline that looks like you"));
23411
- console.log(" " + paint("accent", s.label) + chalk27.dim(" \u2014 " + s.hook));
23412
- console.log(" " + chalk27.dim(fit.reason));
24186
+ console.log(" " + paint("accent", s.label) + chalk29.dim(" \u2014 " + s.hook));
24187
+ console.log(" " + chalk29.dim(fit.reason));
23413
24188
  console.log();
23414
24189
  const action = await session.choose(
23415
24190
  "Try NTRP on that book of business?",
@@ -23466,8 +24241,8 @@ async function resolveDemoScenarioForLoad(ctx, opts = {}) {
23466
24241
  if (!opts.forceQuiz && isProfileConfigured(profile) && profile) {
23467
24242
  const fit = await resolveProfileFit(profile, ctx);
23468
24243
  console.log();
23469
- console.log(" " + chalk27.dim("Recommended: ") + paint("accent", getScenario(fit.scenario).label));
23470
- console.log(" " + chalk27.dim(fit.reason));
24244
+ console.log(" " + chalk29.dim("Recommended: ") + paint("accent", getScenario(fit.scenario).label));
24245
+ console.log(" " + chalk29.dim(fit.reason));
23471
24246
  const scenario = await session.choose(
23472
24247
  "Which sample book of business?",
23473
24248
  scenarioMenuChoices(),
@@ -23531,7 +24306,7 @@ __export(ingest_chat_exports, {
23531
24306
  import { existsSync as existsSync24 } from "fs";
23532
24307
  import { basename as basename7, resolve as resolve9 } from "path";
23533
24308
  import { homedir as homedir8 } from "os";
23534
- import chalk28 from "chalk";
24309
+ import chalk30 from "chalk";
23535
24310
  function extractFilePath(input) {
23536
24311
  const trimmed = input.trim();
23537
24312
  const patterns = [
@@ -23566,7 +24341,7 @@ function looksLikeFilePath(input) {
23566
24341
  }
23567
24342
  async function ingestFromChat(ctx, filePath) {
23568
24343
  if (!ctx.rl) {
23569
- console.log(" " + chalk28.red("Ingest confirm requires interactive mode."));
24344
+ console.log(" " + chalk30.red("Ingest confirm requires interactive mode."));
23570
24345
  return false;
23571
24346
  }
23572
24347
  const name = basename7(filePath);
@@ -23574,7 +24349,7 @@ async function ingestFromChat(ctx, filePath) {
23574
24349
  try {
23575
24350
  const ok = await prompts.confirm(`Ingest ${name} as CRM export?`, true);
23576
24351
  if (!ok) {
23577
- console.log(" " + chalk28.dim("Ingest cancelled."));
24352
+ console.log(" " + chalk30.dim("Ingest cancelled."));
23578
24353
  return false;
23579
24354
  }
23580
24355
  } finally {
@@ -23602,7 +24377,7 @@ async function ingestFromChat(ctx, filePath) {
23602
24377
  true
23603
24378
  );
23604
24379
  if (useAi) {
23605
- console.log(" " + chalk28.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
24380
+ console.log(" " + chalk30.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
23606
24381
  }
23607
24382
  } finally {
23608
24383
  prompts2.close();
@@ -23629,7 +24404,7 @@ async function ingestFromChat(ctx, filePath) {
23629
24404
  invalidateGapAudit(ctx);
23630
24405
  saveSessionState(ctx);
23631
24406
  console.log();
23632
- console.log(" " + paint("accent", "\u2713 Data loaded") + chalk28.dim(` \u2014 ${name}`));
24407
+ console.log(" " + paint("accent", "\u2713 Data loaded") + chalk30.dim(` \u2014 ${name}`));
23633
24408
  recordMessage(ctx, "user", `[ingested ${name}]`);
23634
24409
  recordMessage(ctx, "agent", `Loaded ${name}. Checking what we can analyze\u2026`);
23635
24410
  const audit = await refreshGapAudit(ctx);
@@ -23637,7 +24412,7 @@ async function ingestFromChat(ctx, filePath) {
23637
24412
  if (audit.can_compute && ctx.scope?.confirmed_at) {
23638
24413
  if (ctx.pendingAsk) {
23639
24414
  console.log();
23640
- console.log(" " + chalk28.dim("Computing so I can answer\u2026"));
24415
+ console.log(" " + chalk30.dim("Computing so I can answer\u2026"));
23641
24416
  await runConversationCompute(ctx);
23642
24417
  return true;
23643
24418
  }
@@ -23685,7 +24460,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
23685
24460
  const { getScenario: getScenario2 } = await Promise.resolve().then(() => (init_scenarios(), scenarios_exports));
23686
24461
  const s = getScenario2(chosen);
23687
24462
  console.log();
23688
- console.log(" " + paint("accent", "Fitting ") + s.label + chalk28.dim(" \u2014 " + s.hook));
24463
+ console.log(" " + paint("accent", "Fitting ") + s.label + chalk30.dim(" \u2014 " + s.hook));
23689
24464
  }
23690
24465
  const { handler: demo } = await Promise.resolve().then(() => (init_generate(), generate_exports));
23691
24466
  const args = ["--no-profile", "--brief"];
@@ -23721,7 +24496,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
23721
24496
  const shouldAuto = opts.autoCompute || Boolean(ctx.pendingAsk && audit.can_compute && ctx.scope?.confirmed_at);
23722
24497
  if (shouldAuto && audit.can_compute) {
23723
24498
  console.log();
23724
- console.log(" " + chalk28.dim("Computing so I can answer\u2026"));
24499
+ console.log(" " + chalk30.dim("Computing so I can answer\u2026"));
23725
24500
  await runConversationCompute(ctx);
23726
24501
  return true;
23727
24502
  }
@@ -23754,7 +24529,7 @@ __export(pending_ask_exports, {
23754
24529
  queuePendingAsk: () => queuePendingAsk,
23755
24530
  resumePendingAsk: () => resumePendingAsk
23756
24531
  });
23757
- import chalk29 from "chalk";
24532
+ import chalk31 from "chalk";
23758
24533
  function looksLikeQuestion(input) {
23759
24534
  const text = input.trim();
23760
24535
  if (!text) return false;
@@ -23790,7 +24565,7 @@ function printFocusChip(ctx) {
23790
24565
  const period = ctx.scope.time_horizon ? ` \xB7 ${ctx.scope.time_horizon}` : "";
23791
24566
  console.log();
23792
24567
  console.log(
23793
- " " + chalk29.dim("Focus: ") + paint("accent", lens) + chalk29.dim(period) + chalk29.dim(" \u2014 type ") + chalk29.cyan("adjust") + chalk29.dim(" to change")
24568
+ " " + chalk31.dim("Focus: ") + paint("accent", lens) + chalk31.dim(period) + chalk31.dim(" \u2014 type ") + chalk31.cyan("adjust") + chalk31.dim(" to change")
23794
24569
  );
23795
24570
  console.log();
23796
24571
  }
@@ -23801,7 +24576,7 @@ async function resumePendingAsk(ctx) {
23801
24576
  if (canUseReplAi(ctx)) {
23802
24577
  console.log();
23803
24578
  console.log(
23804
- " " + chalk29.dim(
24579
+ " " + chalk31.dim(
23805
24580
  pending.keylessAnswered ? "Picking up your question with the connected engine\u2026" : "Picking up your question\u2026"
23806
24581
  )
23807
24582
  );
@@ -23834,7 +24609,7 @@ async function offerDemoToAnswer(ctx) {
23834
24609
  const go = await prompts.confirm("Use demo data to answer this?", true);
23835
24610
  if (!go) {
23836
24611
  console.log(
23837
- " " + chalk29.dim("Paste a CSV path when ready, or say ") + chalk29.cyan("use demo data") + chalk29.dim(".")
24612
+ " " + chalk31.dim("Paste a CSV path when ready, or say ") + chalk31.cyan("use demo data") + chalk31.dim(".")
23838
24613
  );
23839
24614
  console.log();
23840
24615
  return false;
@@ -23866,11 +24641,11 @@ __export(compute_exports2, {
23866
24641
  isComputeIntent: () => isComputeIntent,
23867
24642
  runConversationCompute: () => runConversationCompute
23868
24643
  });
23869
- import chalk30 from "chalk";
24644
+ import chalk32 from "chalk";
23870
24645
  async function runConversationCompute(ctx) {
23871
24646
  const lens = ctx.scope?.primary_lens ?? ctx.analysis.primary;
23872
24647
  ctx.computeInProgress = true;
23873
- const willAnswer = Boolean(ctx.pendingAsk?.text) && !ctx.strategistState;
24648
+ const willAnswer = Boolean(ctx.pendingAsk?.text) && !ctx.strategistState && ctx.thinkState?.step !== "awaiting_analysis";
23874
24649
  try {
23875
24650
  if (lens === "revenue_metrics") {
23876
24651
  const { runMetricsAnalysis: runMetricsAnalysis2, extractHeadlineMetrics: extractHeadlineMetrics2 } = await Promise.resolve().then(() => (init_metrics_analysis(), metrics_analysis_exports));
@@ -23898,6 +24673,7 @@ async function runConversationCompute(ctx) {
23898
24673
  interactive: !willAnswer
23899
24674
  });
23900
24675
  await resumeQueuedStrategist(ctx);
24676
+ await resumeQueuedThink(ctx);
23901
24677
  await closeComputeTurn(ctx, willAnswer, "revenue_metrics");
23902
24678
  creditGapCompute(ctx);
23903
24679
  creditMetricsComplete(ctx, false);
@@ -23917,11 +24693,12 @@ async function runConversationCompute(ctx) {
23917
24693
  invalidateGapAudit(ctx);
23918
24694
  saveSessionState(ctx);
23919
24695
  await resumeQueuedStrategist(ctx);
24696
+ await resumeQueuedThink(ctx);
23920
24697
  await closeComputeTurn(ctx, willAnswer, "gtm_health");
23921
24698
  creditGapCompute(ctx);
23922
24699
  return typeof summary === "string" ? summary : "Health analysis ready";
23923
24700
  } catch (err) {
23924
- console.error(" " + chalk30.red(String(err.message ?? err)));
24701
+ console.error(" " + chalk32.red(String(err.message ?? err)));
23925
24702
  return;
23926
24703
  } finally {
23927
24704
  ctx.computeInProgress = false;
@@ -23934,9 +24711,16 @@ async function resumeQueuedStrategist(ctx) {
23934
24711
  const { resumeStrategistAfterCompute: resumeStrategistAfterCompute2 } = await Promise.resolve().then(() => (init_strategist_flow(), strategist_flow_exports));
23935
24712
  await resumeStrategistAfterCompute2(ctx);
23936
24713
  }
24714
+ async function resumeQueuedThink(ctx) {
24715
+ if (ctx.thinkState?.step !== "awaiting_analysis") return;
24716
+ ctx.computeInProgress = false;
24717
+ const { resumeThinkAfterCompute: resumeThinkAfterCompute2 } = await Promise.resolve().then(() => (init_think_flow(), think_flow_exports));
24718
+ await resumeThinkAfterCompute2(ctx);
24719
+ }
23937
24720
  async function resumePendingAskAfterCompute(ctx) {
23938
24721
  if (!ctx.pendingAsk?.text) return false;
23939
24722
  if (ctx.strategistState) return false;
24723
+ if (ctx.thinkState?.step === "active" || ctx.thinkState?.step === "awaiting_analysis") return false;
23940
24724
  const { resumePendingAsk: resumePendingAsk2 } = await Promise.resolve().then(() => (init_pending_ask(), pending_ask_exports));
23941
24725
  return resumePendingAsk2(ctx);
23942
24726
  }
@@ -24405,6 +25189,7 @@ async function handleDraftStrategy(input) {
24405
25189
  if (!objective) return { error: "objective is required." };
24406
25190
  if (!isAnalysisReady2(ctx)) {
24407
25191
  ctx.strategistState = { step: "awaiting_analysis", objective, origin: "ai" };
25192
+ if (ctx.thinkState) ctx.thinkState = void 0;
24408
25193
  saveSessionState2(ctx);
24409
25194
  return {
24410
25195
  queued: true,
@@ -24413,6 +25198,7 @@ async function handleDraftStrategy(input) {
24413
25198
  };
24414
25199
  }
24415
25200
  ctx.strategistState = { step: "objective_confirm", objective, origin: "ai" };
25201
+ if (ctx.thinkState) ctx.thinkState = void 0;
24416
25202
  saveSessionState2(ctx);
24417
25203
  return {
24418
25204
  launched: true,
@@ -24420,6 +25206,32 @@ async function handleDraftStrategy(input) {
24420
25206
  note: "Strategist handoff armed. After your reply the user sees an objective confirmation card and the engine runs a full grounding/backcast/stress-test session. Keep your reply to one or two sentences introducing the handoff \u2014 do NOT write the plan yourself."
24421
25207
  };
24422
25208
  }
25209
+ async function handleUpdateThinkScratch(input) {
25210
+ const { getAgentContext: getAgentContext2 } = await Promise.resolve().then(() => (init_agent_context(), agent_context_exports));
25211
+ const { saveSessionState: saveSessionState2 } = await Promise.resolve().then(() => (init_context2(), context_exports));
25212
+ const ctx = getAgentContext2();
25213
+ if (!ctx) return { error: "No active session context." };
25214
+ if (!ctx.thinkState || ctx.thinkState.step !== "active") {
25215
+ return { error: "Think channel is not active." };
25216
+ }
25217
+ const asStringList = (value) => {
25218
+ if (!Array.isArray(value)) return void 0;
25219
+ return value.filter((v) => typeof v === "string").map((s) => s.trim()).filter(Boolean).slice(0, 12);
25220
+ };
25221
+ const open = asStringList(input.open_questions);
25222
+ const challenged = asStringList(input.challenged_assumptions);
25223
+ const hypotheses = asStringList(input.working_hypotheses);
25224
+ if (open) ctx.thinkState.open_questions = open;
25225
+ if (challenged) ctx.thinkState.challenged_assumptions = challenged;
25226
+ if (hypotheses) ctx.thinkState.working_hypotheses = hypotheses;
25227
+ saveSessionState2(ctx);
25228
+ return {
25229
+ updated: true,
25230
+ open_questions: ctx.thinkState.open_questions ?? [],
25231
+ challenged_assumptions: ctx.thinkState.challenged_assumptions ?? [],
25232
+ working_hypotheses: ctx.thinkState.working_hypotheses ?? []
25233
+ };
25234
+ }
24423
25235
  async function handleGetSessionBrief(input) {
24424
25236
  const raw = typeof input.session_id === "string" ? input.session_id.trim() : "";
24425
25237
  if (!raw) return { error: "session_id is required." };
@@ -24545,13 +25357,182 @@ var init_tool_handlers = __esm({
24545
25357
  audit_data_gaps: (_, __) => handleAuditDataGaps(),
24546
25358
  run_compute: (_, __) => handleRunCompute(),
24547
25359
  draft_handoff: (input, _) => handleDraftHandoff(input),
24548
- draft_strategy: (input, _) => handleDraftStrategy(input)
25360
+ draft_strategy: (input, _) => handleDraftStrategy(input),
25361
+ update_think_scratch: (input, _) => handleUpdateThinkScratch(input)
24549
25362
  };
24550
25363
  }
24551
25364
  });
24552
25365
 
24553
- // src/ai/agentic-loop.ts
25366
+ // src/ai/think-prompt.ts
24554
25367
  function companyContextSection3() {
25368
+ const block = buildCompanyProfileBlock();
25369
+ return block ? `COMPANY CONTEXT:
25370
+ ${block}
25371
+
25372
+ ` : "";
25373
+ }
25374
+ function operatorSection3() {
25375
+ const block = buildOperatorBlock();
25376
+ return block ? `${block}
25377
+
25378
+ ` : "";
25379
+ }
25380
+ function buildScratchBlock(state2) {
25381
+ if (!state2) return "";
25382
+ const lines = [];
25383
+ if (state2.seed) lines.push(`Seed topic: ${state2.seed}`);
25384
+ if (state2.open_questions?.length) {
25385
+ lines.push("Open questions:");
25386
+ for (const q of state2.open_questions) lines.push(`- ${q}`);
25387
+ }
25388
+ if (state2.challenged_assumptions?.length) {
25389
+ lines.push("Challenged assumptions:");
25390
+ for (const a of state2.challenged_assumptions) lines.push(`- ${a}`);
25391
+ }
25392
+ if (state2.working_hypotheses?.length) {
25393
+ lines.push("Working hypotheses:");
25394
+ for (const h of state2.working_hypotheses) lines.push(`- ${h}`);
25395
+ }
25396
+ if (lines.length === 0) return "";
25397
+ return `
25398
+ THINK SCRATCH (session working memory \u2014 update via update_think_scratch):
25399
+ ${lines.join("\n")}
25400
+ `;
25401
+ }
25402
+ function buildThinkWithMeSystemPrompt(opts = {}) {
25403
+ const sessionBlock = opts.sessionContext ? `
25404
+ PREVIOUS SESSION CONTEXT:
25405
+ The user resumed an earlier session. Here is what they were investigating before:
25406
+ ${opts.sessionContext}
25407
+ Treat this as already-established background. Pick up where it left off \u2014 do not re-introduce it as if it were new.
25408
+
25409
+ ` : "";
25410
+ const memorySection = opts.memoryBlock ? `
25411
+ WHAT YOU ALREADY KNOW ABOUT THIS BUSINESS (durable memory):
25412
+ ${opts.memoryBlock}
25413
+ Reference this naturally. Do not re-derive things you already know; build on them.
25414
+
25415
+ ` : "";
25416
+ const analysisSection = opts.analysisBlock ? `
25417
+ SESSION ANALYSIS CONTEXT:
25418
+ ${opts.analysisBlock}
25419
+ Use get_revenue_metrics and get_revenue_metrics_timeseries when the user asks about SaaS metrics, retention, or period trends.
25420
+
25421
+ ` : "";
25422
+ const artifactSection = opts.sessionArtifact ? `
25423
+ COMPLETED SESSION ANALYSIS:
25424
+ ${opts.sessionArtifact}
25425
+ Cite numbers from here; call tools when you need a new cut or to pressure-test a claim.
25426
+
25427
+ ` : "";
25428
+ const conversationSection = opts.conversationBlock ? `
25429
+ CONVERSATION STATE:
25430
+ ${opts.conversationBlock}
25431
+
25432
+ ` : "";
25433
+ const scratchSection = buildScratchBlock(opts.thinkState);
25434
+ const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
25435
+ - You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
25436
+ - Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
25437
+ - Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
25438
+ - Ground claims about THIS pipeline in tool results. Label speculation explicitly ("hypothesis:", "speculation:").
25439
+ - Each turn should ADVANCE the thread: a new question, a challenge, a synthesis, or a fork \u2014 never rehash.
25440
+ - If the question is ambiguous, ask at most ONE clarifying question. Otherwise choose a fork and state it.
25441
+ - Never invent a multi-week roadmap inline. When they want commitment, call draft_strategy with a crisp objective.
25442
+ - Keep open_questions, challenged_assumptions, and working_hypotheses current via update_think_scratch.
25443
+ - You have continuity via prior think-channel messages. Never repeat an angle already covered unless asked.`;
25444
+ const jobSection = `YOUR JOB (THINK CHANNEL \u2014 always deep):
25445
+ - Decide whether you need tools, a direct answer, or both. Do not re-call a tool whose result you already have.
25446
+ - Lead with the answer or the question that matters most, then structure.
25447
+ - When you use numbers, include dollar values where available and lead with financial impact.
25448
+ - Descriptive exploration stays in this channel; plan-of-attack questions hand off via draft_strategy.
25449
+ - After a successful draft_strategy tool result: reply with at most 1\u20132 sentences introducing the handoff. Do not write the plan.`;
25450
+ const formattingSection = `
25451
+ FORMATTING (your answer is rendered in a terminal via a small markdown renderer):
25452
+ - Use **bold** for every dollar figure, score, play name, and person name.
25453
+ - Use *italics* for conversational asides and your closing follow-up question.
25454
+ - Use ### for section headings \u2014 never # or ##. The renderer flattens depth.
25455
+ - For multi-point answers, prefer a one-line lead + bullet list over long paragraph blocks.
25456
+ - Keep paragraphs to 3-4 sentences.
25457
+ - Prefer short tables (\u22643 columns, \u22645 rows, cells \u226430 chars).
25458
+ - End with a dim horizontal rule (---) followed by one italicized follow-up question or fork.
25459
+
25460
+ ${buildUserVisibleProseBlock()}
25461
+ `;
25462
+ const commandSection = `PRESET COMMANDS (slash commands the user can type \u2014 you may SUGGEST these; you cannot run them):
25463
+ ${buildCommandCatalogBlock()}
25464
+
25465
+ COMMAND SUGGESTION RULES:
25466
+ - Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
25467
+ - Never claim a command was run. Never invent flags.
25468
+ - Type \`done\` or \`cancel\` leaves the think channel back to ask \u203A.`;
25469
+ const stable = `You are a world-class GTM operating partner in a dedicated THINK WITH ME channel. The user wants collaborative exploration \u2014 imagination, pressure-testing, and evidence \u2014 not a slide deck and not a finished strategy plan.
25470
+
25471
+ ${companyContextSection3()}${operatorSection3()}${socraticCraft}
25472
+
25473
+ ANALYST INSTINCT (judgment a CEO pays for \u2014 apply by default):
25474
+ ${ANALYST_INSTINCT_BLOCK}
25475
+
25476
+ ${jobSection}
25477
+
25478
+ EXECUTION BIAS (how you work):
25479
+ ${EXECUTION_BIAS_BLOCK}
25480
+
25481
+ GTM ENGINEERING (how recommendations become systems):
25482
+ ${GTM_ENGINEERING_BLOCK}
25483
+
25484
+ OUTPUT DOCTRINE (how answers are structured for recall):
25485
+ ${PYRAMID_OUTPUT_BLOCK}
25486
+
25487
+ ${buildUserVisibleProseBlock()}
25488
+
25489
+ CONTEXT:
25490
+ The user's health scores and top divergences were included as JSON at the start of this conversation. Use them, the think-channel history, and SESSION STATE as background and as hints for which tools to call.
25491
+
25492
+ VITAL SIGNS EXPLAINED (with dollar translations):
25493
+ ${VITAL_SIGNS_BLOCK}
25494
+
25495
+ PLAYBOOK \u2014 name a play when it helps the user act (not a full program):
25496
+ ${buildPlaybookBlock()}
25497
+
25498
+ ${commandSection}
25499
+ ${formattingSection}
25500
+ OUTPUT RULES:
25501
+ - Respond in plain text markdown (not JSON). You do NOT need to emit the findings schema.
25502
+ - Include specific numbers from tool results, never guess.
25503
+ - When you have enough information, answer or ask \u2014 don't call tools you don't need.
25504
+
25505
+ SAFETY & EVIDENCE (non-negotiable):
25506
+ ${SAFETY_BLOCK}`;
25507
+ const dynamicSections = [
25508
+ sessionBlock,
25509
+ memorySection,
25510
+ analysisSection,
25511
+ artifactSection,
25512
+ conversationSection,
25513
+ scratchSection
25514
+ ].map((s) => s.trim()).filter(Boolean);
25515
+ const dynamic = [
25516
+ "SESSION STATE (current \u2014 changes as the session progresses):",
25517
+ ...dynamicSections,
25518
+ buildRuntimeBlock()
25519
+ ].join("\n\n");
25520
+ return { stable, dynamic };
25521
+ }
25522
+ var init_think_prompt = __esm({
25523
+ "src/ai/think-prompt.ts"() {
25524
+ "use strict";
25525
+ init_prompt_parts();
25526
+ init_prompt();
25527
+ }
25528
+ });
25529
+
25530
+ // src/ai/agentic-loop.ts
25531
+ function findingsJsonSchemaNudge() {
25532
+ return `Respond with ONLY a JSON array of finding objects \u2014 no prose, no markdown fences. Each object needs: severity, segment, finding, vital_signs, entity_count, recommended_focus, dollar_value, recommended_plays. Shape:
25533
+ ${buildFindingsSchemaBlock()}`;
25534
+ }
25535
+ function companyContextSection4() {
24555
25536
  const block = buildCompanyProfileBlock();
24556
25537
  if (!block) return "";
24557
25538
  return `COMPANY CONTEXT (use this to make every answer specific to the business \u2014 use industry-appropriate language, anchor dollar figures to their deal size):
@@ -24559,7 +25540,7 @@ ${block}
24559
25540
 
24560
25541
  `;
24561
25542
  }
24562
- function operatorSection3() {
25543
+ function operatorSection4() {
24563
25544
  const block = buildOperatorBlock();
24564
25545
  if (!block) return "";
24565
25546
  return `${block}
@@ -24569,7 +25550,7 @@ function operatorSection3() {
24569
25550
  function buildSystemPrompt2() {
24570
25551
  const stable = `You are an expert GTM health investigator. You have tools to query a local database of CRM and pipeline data. Your job is to investigate the health scores you've been given and discover the specific root causes behind any problems.
24571
25552
 
24572
- ${companyContextSection3()}${operatorSection3()}INVESTIGATION APPROACH:
25553
+ ${companyContextSection4()}${operatorSection4()}INVESTIGATION APPROACH:
24573
25554
  1. Start by examining the health summary to understand the overall picture
24574
25555
  2. Drill into the lowest-scoring vital signs using get_vital_sign_detail
24575
25556
  3. Check divergences to find segments that are significantly worse than average
@@ -24595,7 +25576,7 @@ ${buildPlaybookBlock()}
24595
25576
  OUTPUT DOCTRINE (how findings are structured for recall):
24596
25577
  ${PYRAMID_OUTPUT_BLOCK}
24597
25578
 
24598
- ${USER_VISIBLE_STE_BLOCK}
25579
+ ${buildUserVisibleProseBlock()}
24599
25580
 
24600
25581
  RULES:
24601
25582
  - Build evidence BEFORE claiming a finding. Call at least 2 tools before emitting a finding.
@@ -24617,7 +25598,7 @@ FORMATTING (the \`finding\` field on each finding is rendered in a terminal via
24617
25598
  - Never place emoji inside a table cell or heading \u2014 they break column alignment.
24618
25599
 
24619
25600
  OUTPUT FORMAT \u2014 when you're ready to present findings, respond with ONLY a JSON array:
24620
- ${FINDINGS_SCHEMA_BLOCK}
25601
+ ${buildFindingsSchemaBlock()}
24621
25602
 
24622
25603
  Keep investigating until you have a clear picture. Don't guess \u2014 use the tools.`;
24623
25604
  return { stable, dynamic: buildRuntimeBlock() };
@@ -24700,7 +25681,7 @@ FORMATTING (your answer is rendered in a terminal via a small markdown renderer)
24700
25681
  - Use \`inline code\` for CRM field names, SQL snippets, or literal values.
24701
25682
  - End with a dim horizontal rule (---) followed by one italicized follow-up question.
24702
25683
 
24703
- ${USER_VISIBLE_STE_BLOCK}
25684
+ ${buildUserVisibleProseBlock()}
24704
25685
  ` : `
24705
25686
  FORMATTING (brief mode \u2014 scannable, not chunky):
24706
25687
  - Open with ONE direct sentence that answers the question. Then a blank line before any list.
@@ -24715,7 +25696,7 @@ FORMATTING (brief mode \u2014 scannable, not chunky):
24715
25696
  - No ### headings, no tables, no --- footer, no closing "want me to dig deeper?" question.
24716
25697
  - Single-point answers can stay one sentence \u2014 don't force bullets when one line is enough.
24717
25698
 
24718
- ${USER_VISIBLE_STE_BLOCK}
25699
+ ${buildUserVisibleProseBlock()}
24719
25700
  `;
24720
25701
  const commandStyleRule = responseMode === "brief" ? '- Style in brief mode: a suggestion is at most ONE short trailing line after your answer (e.g. "`/segment compare enterprise smb` gives the full side-by-side.") \u2014 no heading, no footer.' : "- Style: weave the suggestion naturally into your close \u2014 one sentence with the exact invocation in `inline code`.";
24721
25702
  const commandSection = `PRESET COMMANDS (slash commands the user can type at the prompt \u2014 you may SUGGEST these; you have no ability to run them):
@@ -24743,7 +25724,7 @@ ${GTM_ENGINEERING_BLOCK}
24743
25724
  OUTPUT DOCTRINE (how answers are structured for recall):
24744
25725
  ${PYRAMID_OUTPUT_BLOCK}
24745
25726
 
24746
- ${USER_VISIBLE_STE_BLOCK}
25727
+ ${buildUserVisibleProseBlock()}
24747
25728
 
24748
25729
  `;
24749
25730
  const referenceSection = responseMode === "brief" ? "" : `CONTEXT:
@@ -24758,7 +25739,7 @@ ${buildPlaybookBlock()}
24758
25739
  ${commandSection}`;
24759
25740
  const stable = `You are a world-class GTM operating partner \u2014 the kind of analyst a CEO keeps on speed dial. You are exceptionally well-read, rigorous, and commercially sharp, and you have tools to query a local database of this company's CRM and pipeline data. The user is having an ongoing, free-form conversation with you about their go-to-market health and SaaS metrics.
24760
25741
 
24761
- ${companyContextSection3()}${operatorSection3()}${conversationRules}
25742
+ ${companyContextSection4()}${operatorSection4()}${conversationRules}
24762
25743
 
24763
25744
  ${analystSection}
24764
25745
 
@@ -24804,20 +25785,27 @@ async function* agenticFindings(computeResult, divergences, options) {
24804
25785
  assertReplAi(options.ctx);
24805
25786
  const mode = options.mode ?? "investigation";
24806
25787
  const experiment = options.experiment ?? "production";
24807
- const responseMode = experiment === "baseline" || experiment === "a" || experiment === "b" ? "deep" : options.responseMode ?? (options.sessionArtifact ? "brief" : "deep");
24808
- const useTools = mode === "investigation" || responseMode === "deep";
25788
+ const responseMode = mode === "think" ? "deep" : experiment === "baseline" || experiment === "a" || experiment === "b" ? "deep" : options.responseMode ?? (options.sessionArtifact ? "brief" : "deep");
25789
+ const useTools = mode === "investigation" || mode === "think" || responseMode === "deep";
24809
25790
  const maxTokens = mode === "fresh" && responseMode === "brief" ? BRIEF_MAX_TOKENS : DEEP_MAX_TOKENS;
24810
25791
  const surface = mode === "fresh" ? responseMode === "brief" ? "agentic_fresh_brief" : "agentic_investigation" : "agentic_investigation";
24811
25792
  const llmCfg = loadLlmConfig();
24812
25793
  const tier = tierForSurface(surface, llmCfg.tier);
24813
- const systemPrompt = mode === "fresh" ? buildFreshNlSystemPrompt(
25794
+ const systemPrompt = mode === "think" ? buildThinkWithMeSystemPrompt({
25795
+ sessionContext: options.sessionContext,
25796
+ memoryBlock: options.memoryBlock,
25797
+ analysisBlock: options.analysisBlock,
25798
+ conversationBlock: options.conversationBlock,
25799
+ sessionArtifact: options.sessionArtifact,
25800
+ thinkState: options.ctx.thinkState
25801
+ }) : mode === "fresh" ? buildFreshNlSystemPrompt(
24814
25802
  options.sessionContext,
24815
25803
  options.memoryBlock,
24816
25804
  options.analysisBlock,
24817
25805
  options.conversationBlock,
24818
25806
  { responseMode, sessionArtifact: options.sessionArtifact, experiment }
24819
25807
  ) : buildSystemPrompt2();
24820
- const tools2 = useTools ? mode === "fresh" ? buildFreshNlTools() : buildInvestigationTools() : [];
25808
+ const tools2 = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools() : buildInvestigationTools() : [];
24821
25809
  const toolCtx = { computeResult, divergences };
24822
25810
  if (options.includeMetrics) {
24823
25811
  try {
@@ -24829,7 +25817,8 @@ async function* agenticFindings(computeResult, divergences, options) {
24829
25817
  const initialContext = buildInitialContext(computeResult, divergences, options.userQuestion);
24830
25818
  const priorRaw = options.priorMessages ?? [];
24831
25819
  const priorMessages = priorRaw.length > 0 && typeof priorRaw[0] === "object" && priorRaw[0] !== null && "content" in priorRaw[0] ? normalizeThread(priorRaw) : priorRaw;
24832
- const messages = mode === "fresh" && priorMessages.length > 0 && options.userQuestion ? [...priorMessages, { role: "user", content: options.userQuestion }] : [{ role: "user", content: initialContext }];
25820
+ const conversational = mode === "fresh" || mode === "think";
25821
+ const messages = conversational && priorMessages.length > 0 && options.userQuestion ? [...priorMessages, { role: "user", content: options.userQuestion }] : [{ role: "user", content: initialContext }];
24833
25822
  let lastMeta = { provider_used: "anthropic", model_used: "unknown" };
24834
25823
  const loopGuard = new ToolLoopGuard();
24835
25824
  const allowedTools = new Set(tools2.map((t) => t.name));
@@ -24859,7 +25848,7 @@ async function* agenticFindings(computeResult, divergences, options) {
24859
25848
  const response = await callLlm(messages, maxTokens, true);
24860
25849
  if (response.tool_calls.length === 0) {
24861
25850
  const fullText = response.text;
24862
- if (mode === "fresh") {
25851
+ if (conversational) {
24863
25852
  const findings3 = parseFindings(fullText);
24864
25853
  if (findings3.length > 0) {
24865
25854
  for (const finding of findings3) yield { type: "finding", finding };
@@ -24879,7 +25868,7 @@ async function* agenticFindings(computeResult, divergences, options) {
24879
25868
  messages.push(retry.assistant_message);
24880
25869
  }
24881
25870
  if (findings2.length === 0) {
24882
- messages.push({ role: "user", content: FINDINGS_JSON_SCHEMA_NUDGE });
25871
+ messages.push({ role: "user", content: findingsJsonSchemaNudge() });
24883
25872
  const retry2 = await callLlm(messages, maxTokens, false);
24884
25873
  findings2 = parseFindings(retry2.text);
24885
25874
  messages.push(retry2.assistant_message);
@@ -24907,7 +25896,7 @@ async function* agenticFindings(computeResult, divergences, options) {
24907
25896
  });
24908
25897
  }
24909
25898
  }
24910
- if (mode === "fresh") {
25899
+ if (conversational) {
24911
25900
  messages.push({
24912
25901
  role: "user",
24913
25902
  content: "You've reached the tool budget. Please answer the user's question now with what you've learned."
@@ -24926,7 +25915,7 @@ async function* agenticFindings(computeResult, divergences, options) {
24926
25915
  let findings = parseFindings(final.text);
24927
25916
  messages.push(final.assistant_message);
24928
25917
  if (findings.length === 0) {
24929
- messages.push({ role: "user", content: FINDINGS_JSON_SCHEMA_NUDGE });
25918
+ messages.push({ role: "user", content: findingsJsonSchemaNudge() });
24930
25919
  final = await callLlm(messages, 4096, false);
24931
25920
  findings = parseFindings(final.text);
24932
25921
  messages.push(final.assistant_message);
@@ -24965,7 +25954,7 @@ Here is the current GTM health snapshot as background. Use it plus any tools you
24965
25954
  function parseFindings(text) {
24966
25955
  return (parseJsonArrayFromText(text) ?? []).map(withKnownRecommendedPlays);
24967
25956
  }
24968
- var MAX_ITERATIONS, INVESTIGATION_EVIDENCE_NUDGE_AFTER, BRIEF_MAX_TOKENS, DEEP_MAX_TOKENS, FINDINGS_JSON_NUDGE, FINDINGS_JSON_SCHEMA_NUDGE;
25957
+ var MAX_ITERATIONS, INVESTIGATION_EVIDENCE_NUDGE_AFTER, BRIEF_MAX_TOKENS, DEEP_MAX_TOKENS, FINDINGS_JSON_NUDGE;
24969
25958
  var init_agentic_loop = __esm({
24970
25959
  "src/ai/agentic-loop.ts"() {
24971
25960
  "use strict";
@@ -24983,13 +25972,13 @@ var init_agentic_loop = __esm({
24983
25972
  init_thread();
24984
25973
  init_untrusted();
24985
25974
  init_prompt_parts();
25975
+ init_think_prompt();
25976
+ init_prompt();
24986
25977
  MAX_ITERATIONS = 10;
24987
25978
  INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
24988
25979
  BRIEF_MAX_TOKENS = 768;
24989
25980
  DEEP_MAX_TOKENS = 4096;
24990
25981
  FINDINGS_JSON_NUDGE = "Please format your findings as the JSON array specified in your instructions. Respond with ONLY the JSON array, no other text.";
24991
- FINDINGS_JSON_SCHEMA_NUDGE = `Respond with ONLY a JSON array of finding objects \u2014 no prose, no markdown fences. Each object needs: severity, segment, finding, vital_signs, entity_count, recommended_focus, dollar_value, recommended_plays. Shape:
24992
- ${FINDINGS_SCHEMA_BLOCK}`;
24993
25982
  }
24994
25983
  });
24995
25984