@crouton-kit/humanloop 0.3.22 → 0.3.23

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.
@@ -1,47 +1,21 @@
1
1
  import { query } from '@r-cli/sdk';
2
2
  import { renderMarkdown } from '../render/termrender.js';
3
- const VISUAL_SYSTEM_PROMPT = `You're briefing a CTO-level engineer in the 30 seconds before they decide. They've been off this problem for days; they need a fast re-ground in what *already exists* — the files, data flow, or constraint they're deciding inside of not a lecture on tradeoffs.
3
+ const VISUAL_SYSTEM_PROMPT = `You are re-grounding a decision-maker in the moment before they answer the question below. They were deep in this problem but got pulled away; in the next 20 seconds they need to remember *what is actually in play* — the current state, the files, the constraint this decision lives inside — so the question stops feeling cold and they can answer with confidence.
4
4
 
5
- # Length
5
+ Write from the conversation history you're given. Lead with what *is*: the real files, functions, data, and constraints that ground this decision, named concretely (as \`path/to/file.ts:123\` so they can jump straight there). Reconstruct just enough of how they arrived here to make the question legible — no more.
6
6
 
7
- Target 15–25 lines. Hard cap 30. A tight paragraph with two file refs is often perfect don't pad.
7
+ Keep it tight: usually a short paragraph or a few bullets, 30 lines hard cap. Say less when less is true. Choose whatever shape carries the meaning fastest — prose by default, a list when enumerating, a table only to compare several things across the same dimensions, a small diagram when the structure itself is the point. You're trusted to pick; don't force a format.
8
8
 
9
- # What to write
9
+ The one rule that matters: **only reference files, identifiers, and facts that actually appear in the conversation.** Never invent a plausible-looking path or name. If the conversation is thin, write a short honest briefing about what little is grounded — that beats confident fabrication every time.
10
10
 
11
- Lead with *what is*, not *what could be*. Name the actual files, functions, tables, or data structures in play. Reference them as \`path/to/file.ts:123\` so they can jump to it. Skip preamble. Skip "here are the tradeoffs." Skip explaining the alternative they're deciding, they know the alternative exists.
11
+ And stay in your lane: don't restate the question, don't recommend an option or tell them how to decide, don't lay out tradeoffs or sketch alternatives. They own the decision; you only reconstruct the ground it stands on.
12
12
 
13
- If one sentence captures the current state, write one sentence. If they need to see a flow, draw it. If they need to compare 3+ options across same dimensions, use a table. Don't reach for a directive unless it genuinely clarifies plain prose + bullet lists is the default.
14
-
15
- # Directives (termrender-flavored markdown)
16
-
17
- :::panel{title="T" color="c"} Bordered box (colors: red|green|yellow|blue|magenta|cyan|white|gray)
18
- :::tree{color="c"} Indented hierarchy (2-space indent = nesting)
19
- :::callout{type="info|warning|error|success"} Status callout with icon
20
- ::::columns / :::col{width="50%"} Side-by-side layout (use 4 colons on the outer columns)
21
-
22
- Each opens with ::: and closes with :::. GFM tables (\`| col | col |\` with a \`| --- |\` separator) render directly — no directive needed. Standard markdown also works: **bold**, *italic*, \`code\`, bullets.
23
-
24
- # Critical: ASCII art must live inside a :::panel
25
-
26
- Plain text outside directives gets reflowed — box-drawing will be destroyed. If you draw a flow diagram or ASCII box, wrap it in \`:::panel\` to preserve it verbatim.
27
-
28
- # Grounding — the single most important rule
29
-
30
- **Only name files, functions, variables, or patterns that actually appear in the conversation history provided.** Do not invent plausible-sounding file paths, class names, or dependencies. If the conversation doesn't ground a fact, don't assert it. When in doubt, speak at a higher level of abstraction ("the state file," "the render loop") rather than making up a specific identifier.
31
-
32
- If the conversation doesn't contain enough context to write a grounded briefing, write a very short briefing that honestly reflects what little is known — a one-paragraph summary is better than a confident fabrication.
33
-
34
- # Hard rules
35
-
36
- - When nesting directives, the outer fence needs strictly more colons than the inner — e.g. \`::::columns\` wrapping \`:::col\`. Don't nest a panel inside a panel.
37
- - Never wrap output in backtick fences
38
- - Never repeat the question/statement text
39
- - Never write "tradeoffs to consider" or "here are some options"
40
- - Never describe an alternative architecture — just describe the current one
41
- - Never recommend an option or tell the user how to decide. They are the decider. You describe.
42
- - Never ask the user a question back. You are producing a briefing, not a conversation.
43
- - Do NOT use these section headings: **Recommendation:**, **Decide by:**, **Trade-off:**, **Why it matters:**, **What you're locking in:**. These invite editorializing. Use neutral labels like **Current state:**, **Constraint:**, or none at all.
44
- - 30 lines maximum`;
13
+ Formatting: plain markdown renders (**bold**, *italic*, \`code\`, bullets, and GFM tables). These termrender directives are available if one genuinely helps anything you draw with box-drawing/ASCII must sit inside a :::panel or it will be reflowed and destroyed:
14
+ :::panel{title="T" color="cyan"} bordered box (red|green|yellow|blue|magenta|cyan|white|gray)
15
+ :::tree{color="c"} indented hierarchy (2-space indent = nesting)
16
+ :::callout{type="info|warning|error|success"} status callout
17
+ ::::columns / :::col{width="50%"} side-by-side (outer fence needs strictly more colons)
18
+ Never wrap the whole output in a code fence.`;
45
19
  async function callHaiku(prompt, systemPrompt) {
46
20
  try {
47
21
  const session = await query({
@@ -72,16 +46,26 @@ async function callHaiku(prompt, systemPrompt) {
72
46
  // mountPanel. Width is read from process.stdout.columns so callers that
73
47
  // embed humanloop in a sub-region should supply their own closure that bakes
74
48
  // in the correct width.
49
+ // Cap on how much conversation we hand the generator. Recency is what grounds
50
+ // the decision in front of the human, so when history is long we keep the tail
51
+ // (most recent messages) rather than the head.
52
+ const MAX_CONTEXT_CHARS = 24000;
75
53
  export async function defaultGenerateVisual(interaction, conversationContext) {
76
54
  const width = Math.max(40, Math.min((process.stdout.columns || 80) - 4, 76));
77
55
  const optionsSummary = interaction.options.length > 0
78
56
  ? `\nOptions: ${interaction.options.map((o) => o.label).join(' | ')}`
79
57
  : '';
80
58
  const subtitleLine = interaction.subtitle ? `\nContext: ${interaction.subtitle}` : '';
81
- const questionText = `Title: "${interaction.title}"${subtitleLine}${optionsSummary}`;
82
- const prompt = conversationContext
83
- ? `Here is the conversation so far:\n\n${conversationContext}\n\n---\n\nGenerate a visual context block for this decision point:\n\n${questionText}`
84
- : `Generate a visual context block for this decision point:\n\n${questionText}`;
59
+ // The body is the richest statement of what's being asked — hand it to the
60
+ // generator so the briefing grounds in the actual question, not just its title.
61
+ const bodyLine = interaction.body ? `\nDetail:\n${interaction.body}` : '';
62
+ const questionText = `Title: "${interaction.title}"${subtitleLine}${bodyLine}${optionsSummary}`;
63
+ const trimmedContext = conversationContext.length > MAX_CONTEXT_CHARS
64
+ ? `…(earlier conversation trimmed)…\n\n${conversationContext.slice(-MAX_CONTEXT_CHARS)}`
65
+ : conversationContext;
66
+ const prompt = trimmedContext
67
+ ? `Here is the conversation so far:\n\n${trimmedContext}\n\n---\n\nThe human is about to answer this decision. Re-ground them in what's in play:\n\n${questionText}`
68
+ : `The human is about to answer this decision. Re-ground them in what's in play:\n\n${questionText}`;
85
69
  const result = await callHaiku(prompt, VISUAL_SYSTEM_PROMPT);
86
70
  if (result) {
87
71
  const markdown = result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/humanloop",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "description": "Human-in-the-loop decision TUI — agents write questions, humans answer them",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",