@kinqs/brainrouter-cli 0.3.4 → 0.3.6

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 (50) hide show
  1. package/.env.example +55 -48
  2. package/README.md +102 -142
  3. package/bin/cli.cjs +71 -0
  4. package/dist/agent/agent.d.ts +212 -2
  5. package/dist/agent/agent.js +430 -40
  6. package/dist/cli/banner.d.ts +60 -0
  7. package/dist/cli/banner.js +199 -0
  8. package/dist/cli/cliPrompt.d.ts +69 -0
  9. package/dist/cli/cliPrompt.js +287 -0
  10. package/dist/cli/commands/_helpers.js +6 -6
  11. package/dist/cli/commands/guard.js +75 -10
  12. package/dist/cli/commands/mcp.d.ts +17 -0
  13. package/dist/cli/commands/mcp.js +121 -0
  14. package/dist/cli/commands/memory.js +2 -2
  15. package/dist/cli/commands/obs.js +22 -22
  16. package/dist/cli/commands/session.js +13 -5
  17. package/dist/cli/commands/ui.js +97 -45
  18. package/dist/cli/commands/workflow.d.ts +18 -0
  19. package/dist/cli/commands/workflow.js +314 -43
  20. package/dist/cli/repl.js +219 -132
  21. package/dist/cli/spinner.d.ts +34 -0
  22. package/dist/cli/spinner.js +36 -0
  23. package/dist/cli/statusline.d.ts +67 -0
  24. package/dist/cli/statusline.js +204 -0
  25. package/dist/cli/theme.d.ts +79 -0
  26. package/dist/cli/theme.js +106 -0
  27. package/dist/cli/whereView.d.ts +81 -0
  28. package/dist/cli/whereView.js +245 -0
  29. package/dist/config/config.d.ts +40 -0
  30. package/dist/config/config.js +45 -73
  31. package/dist/index.js +81 -14
  32. package/dist/memory/briefing.d.ts +10 -0
  33. package/dist/memory/briefing.js +69 -1
  34. package/dist/prompt/breadthHint.d.ts +5 -0
  35. package/dist/prompt/breadthHint.js +44 -0
  36. package/dist/prompt/systemPrompt.d.ts +34 -0
  37. package/dist/prompt/systemPrompt.js +124 -108
  38. package/dist/runtime/dangerousCommand.d.ts +53 -0
  39. package/dist/runtime/dangerousCommand.js +105 -0
  40. package/dist/runtime/mcpClient.d.ts +38 -1
  41. package/dist/runtime/mcpClient.js +91 -3
  42. package/dist/state/goalStore.d.ts +98 -17
  43. package/dist/state/goalStore.js +132 -42
  44. package/dist/state/preferencesStore.d.ts +67 -3
  45. package/dist/state/preferencesStore.js +84 -1
  46. package/dist/state/workflowArtifacts.d.ts +63 -2
  47. package/dist/state/workflowArtifacts.js +120 -8
  48. package/dist/tests/_helpers.d.ts +31 -0
  49. package/dist/tests/_helpers.js +91 -0
  50. package/package.json +5 -4
@@ -1,6 +1,8 @@
1
1
  import type { McpClientWrapper } from '../runtime/mcpClient.js';
2
2
  import type { LLMConfig } from '../config/config.js';
3
3
  import type { AccessMode } from '../orchestration/roles.js';
4
+ import { type RecalledRecord } from '../memory/briefing.js';
5
+ import { type EffortLevel } from '../state/preferencesStore.js';
4
6
  export interface RunTurnCallbacks {
5
7
  onStatusUpdate: (status: string) => void;
6
8
  onToolStart: (name: string, args: Record<string, any>) => void;
@@ -77,6 +79,12 @@ export interface ChatCompletionPayload {
77
79
  };
78
80
  }>;
79
81
  tool_choice?: 'auto';
82
+ /**
83
+ * OpenAI Chat Completions reasoning slot — accepted by gpt-5 / o-series.
84
+ * Only set when the user has chosen a non-default `/effort` AND the
85
+ * endpoint+model combo accepts the field (see `supportsReasoningEffortField`).
86
+ */
87
+ reasoning_effort?: EffortLevel;
80
88
  }
81
89
  export interface AgentOptions {
82
90
  workspaceRoot: string;
@@ -131,6 +139,10 @@ export declare const LOCAL_TOOLS: ({
131
139
  url?: undefined;
132
140
  maxResults?: undefined;
133
141
  patch?: undefined;
142
+ question?: undefined;
143
+ header?: undefined;
144
+ options?: undefined;
145
+ multiSelect?: undefined;
134
146
  explanation?: undefined;
135
147
  plan?: undefined;
136
148
  proof?: undefined;
@@ -163,6 +175,10 @@ export declare const LOCAL_TOOLS: ({
163
175
  url?: undefined;
164
176
  maxResults?: undefined;
165
177
  patch?: undefined;
178
+ question?: undefined;
179
+ header?: undefined;
180
+ options?: undefined;
181
+ multiSelect?: undefined;
166
182
  explanation?: undefined;
167
183
  plan?: undefined;
168
184
  proof?: undefined;
@@ -198,6 +214,10 @@ export declare const LOCAL_TOOLS: ({
198
214
  url?: undefined;
199
215
  maxResults?: undefined;
200
216
  patch?: undefined;
217
+ question?: undefined;
218
+ header?: undefined;
219
+ options?: undefined;
220
+ multiSelect?: undefined;
201
221
  explanation?: undefined;
202
222
  plan?: undefined;
203
223
  proof?: undefined;
@@ -227,6 +247,10 @@ export declare const LOCAL_TOOLS: ({
227
247
  url?: undefined;
228
248
  maxResults?: undefined;
229
249
  patch?: undefined;
250
+ question?: undefined;
251
+ header?: undefined;
252
+ options?: undefined;
253
+ multiSelect?: undefined;
230
254
  explanation?: undefined;
231
255
  plan?: undefined;
232
256
  proof?: undefined;
@@ -259,6 +283,10 @@ export declare const LOCAL_TOOLS: ({
259
283
  url?: undefined;
260
284
  maxResults?: undefined;
261
285
  patch?: undefined;
286
+ question?: undefined;
287
+ header?: undefined;
288
+ options?: undefined;
289
+ multiSelect?: undefined;
262
290
  explanation?: undefined;
263
291
  plan?: undefined;
264
292
  proof?: undefined;
@@ -288,6 +316,10 @@ export declare const LOCAL_TOOLS: ({
288
316
  url?: undefined;
289
317
  maxResults?: undefined;
290
318
  patch?: undefined;
319
+ question?: undefined;
320
+ header?: undefined;
321
+ options?: undefined;
322
+ multiSelect?: undefined;
291
323
  explanation?: undefined;
292
324
  plan?: undefined;
293
325
  proof?: undefined;
@@ -317,6 +349,10 @@ export declare const LOCAL_TOOLS: ({
317
349
  url?: undefined;
318
350
  maxResults?: undefined;
319
351
  patch?: undefined;
352
+ question?: undefined;
353
+ header?: undefined;
354
+ options?: undefined;
355
+ multiSelect?: undefined;
320
356
  explanation?: undefined;
321
357
  plan?: undefined;
322
358
  proof?: undefined;
@@ -346,6 +382,10 @@ export declare const LOCAL_TOOLS: ({
346
382
  command?: undefined;
347
383
  maxResults?: undefined;
348
384
  patch?: undefined;
385
+ question?: undefined;
386
+ header?: undefined;
387
+ options?: undefined;
388
+ multiSelect?: undefined;
349
389
  explanation?: undefined;
350
390
  plan?: undefined;
351
391
  proof?: undefined;
@@ -378,6 +418,10 @@ export declare const LOCAL_TOOLS: ({
378
418
  command?: undefined;
379
419
  url?: undefined;
380
420
  patch?: undefined;
421
+ question?: undefined;
422
+ header?: undefined;
423
+ options?: undefined;
424
+ multiSelect?: undefined;
381
425
  explanation?: undefined;
382
426
  plan?: undefined;
383
427
  proof?: undefined;
@@ -407,6 +451,68 @@ export declare const LOCAL_TOOLS: ({
407
451
  command?: undefined;
408
452
  url?: undefined;
409
453
  maxResults?: undefined;
454
+ question?: undefined;
455
+ header?: undefined;
456
+ options?: undefined;
457
+ multiSelect?: undefined;
458
+ explanation?: undefined;
459
+ plan?: undefined;
460
+ proof?: undefined;
461
+ reason?: undefined;
462
+ needed?: undefined;
463
+ };
464
+ required: string[];
465
+ };
466
+ } | {
467
+ name: string;
468
+ description: string;
469
+ inputSchema: {
470
+ type: string;
471
+ properties: {
472
+ question: {
473
+ type: string;
474
+ description: string;
475
+ };
476
+ header: {
477
+ type: string;
478
+ description: string;
479
+ };
480
+ options: {
481
+ type: string;
482
+ description: string;
483
+ minItems: number;
484
+ maxItems: number;
485
+ items: {
486
+ type: string;
487
+ properties: {
488
+ label: {
489
+ type: string;
490
+ description: string;
491
+ };
492
+ description: {
493
+ type: string;
494
+ description: string;
495
+ };
496
+ };
497
+ required: string[];
498
+ };
499
+ };
500
+ multiSelect: {
501
+ type: string;
502
+ description: string;
503
+ };
504
+ path?: undefined;
505
+ startLine?: undefined;
506
+ endLine?: undefined;
507
+ content?: undefined;
508
+ targetContent?: undefined;
509
+ replacementContent?: undefined;
510
+ query?: undefined;
511
+ pattern?: undefined;
512
+ command?: undefined;
513
+ url?: undefined;
514
+ maxResults?: undefined;
515
+ patch?: undefined;
410
516
  explanation?: undefined;
411
517
  plan?: undefined;
412
518
  proof?: undefined;
@@ -454,6 +560,10 @@ export declare const LOCAL_TOOLS: ({
454
560
  url?: undefined;
455
561
  maxResults?: undefined;
456
562
  patch?: undefined;
563
+ question?: undefined;
564
+ header?: undefined;
565
+ options?: undefined;
566
+ multiSelect?: undefined;
457
567
  proof?: undefined;
458
568
  reason?: undefined;
459
569
  needed?: undefined;
@@ -482,6 +592,10 @@ export declare const LOCAL_TOOLS: ({
482
592
  url?: undefined;
483
593
  maxResults?: undefined;
484
594
  patch?: undefined;
595
+ question?: undefined;
596
+ header?: undefined;
597
+ options?: undefined;
598
+ multiSelect?: undefined;
485
599
  explanation?: undefined;
486
600
  plan?: undefined;
487
601
  reason?: undefined;
@@ -515,6 +629,10 @@ export declare const LOCAL_TOOLS: ({
515
629
  url?: undefined;
516
630
  maxResults?: undefined;
517
631
  patch?: undefined;
632
+ question?: undefined;
633
+ header?: undefined;
634
+ options?: undefined;
635
+ multiSelect?: undefined;
518
636
  explanation?: undefined;
519
637
  plan?: undefined;
520
638
  proof?: undefined;
@@ -575,6 +693,28 @@ export declare class Agent {
575
693
  private recalledRecordIds;
576
694
  private recalledRecords;
577
695
  private lastBriefingSources;
696
+ /**
697
+ * 10b: latest MCP tool inventory captured by `listTools()` calls. Used by
698
+ * `createSystemMessage` to decide whether the BrainRouter memory section
699
+ * should render — when `memory_recall` is missing from this list (the
700
+ * cloud brain is offline), the prompt swaps to a brain-offline notice so
701
+ * the model doesn't try to call tools that aren't there. Undefined until
702
+ * the first successful list; treated as "assume online" by the prompt
703
+ * builder until then (back-compat for callers that don't list pre-turn).
704
+ */
705
+ private lastKnownMcpTools?;
706
+ /**
707
+ * 9b: gated recall state. `recallHasFiredThisSession` flips to true on the
708
+ * first successful briefing injection so subsequent turns can skip the
709
+ * fresh recall pull unless a gated trigger fires. `recallNextTurnIsPost-
710
+ * Compaction` is set by `compactHistory()` to force the next turn through
711
+ * the full briefing path (compaction just dropped the prior briefing as
712
+ * collateral; replay it once so the model isn't blind). Both are
713
+ * cleared on `loadHistory` / `fork` / `bootstrapSession` so a fresh
714
+ * session re-pulls.
715
+ */
716
+ private recallHasFiredThisSession;
717
+ private recallNextTurnIsPostCompaction;
578
718
  private roleOverlay?;
579
719
  private accessMode;
580
720
  private silent;
@@ -708,9 +848,30 @@ export declare class Agent {
708
848
  * Idempotent: calling this with a tag that isn't present is a no-op.
709
849
  */
710
850
  removeTaggedSystemMessage(tag: string): void;
851
+ /**
852
+ * Zero the in-process counters that back `/tokens`. Call this on any
853
+ * conceptual session boundary (`/resume`, `fork`) — otherwise the parent
854
+ * row keeps accumulating across the switch and "this session" no longer
855
+ * matches the displayed sessionKey.
856
+ */
857
+ resetSessionCounters(): void;
711
858
  /** Fork the current chat history into a fresh sessionKey. Returns the new key. */
712
859
  fork(newSessionKey: string): string;
713
860
  private bootstrapSession;
861
+ /**
862
+ * Public, callback-free wrapper around bootstrapSession for slash commands
863
+ * that mutate per-session state (notably `/goal`) BEFORE any runTurn has
864
+ * fired. Without this, the FIRST `/goal` of a session writes goal.json
865
+ * under the deterministic fallback sessionKey ("brainrouter-cli:<path>")
866
+ * because bootstrap hasn't happened yet, but every subsequent runTurn
867
+ * reads from the MCP-resolved UUID sessionKey — split-brain that left
868
+ * the agent reading a stale goal from a different directory.
869
+ *
870
+ * Idempotent: returns immediately if already initialized. Tolerates
871
+ * missing MCP — falls back to the deterministic key the same way
872
+ * bootstrapSession does.
873
+ */
874
+ ensureInitialized(): Promise<void>;
714
875
  private createSystemMessage;
715
876
  private injectRecallContext;
716
877
  /** Inspectable summary of the most recent memory briefing. Used by the `/briefing` slash command. */
@@ -718,6 +879,13 @@ export declare class Agent {
718
879
  sources: string[];
719
880
  recordIds: string[];
720
881
  };
882
+ /**
883
+ * Snapshot of the records produced by the most recent pre-turn briefing.
884
+ * `/where` surfaces a few of these to give the user a sense of what the
885
+ * agent is leaning on right now. Returns a shallow copy so callers can't
886
+ * mutate the agent's internal state.
887
+ */
888
+ getRecalledRecords(): RecalledRecord[];
721
889
  /** One-line summary of any new contradiction surfaced after the last capture, or undefined if none. */
722
890
  private lastContradictionWarning?;
723
891
  takeContradictionWarning(): string | undefined;
@@ -757,8 +925,50 @@ export declare function getToolSummary(name: string, args: Record<string, any>,
757
925
  * the terminal. Returns undefined when no useful preview is available.
758
926
  */
759
927
  export declare function getToolPreview(name: string, args: Record<string, any>, result: string): string | undefined;
760
- export declare function buildChatCompletionPayload(config: LLMConfig, messages: any[], tools: any[]): ChatCompletionPayload;
761
- export declare function callOpenAI(config: LLMConfig, messages: any[], tools: any[]): Promise<{
928
+ /**
929
+ * Heuristic for "does this model accept the OpenAI Chat Completions
930
+ * `reasoning_effort` field?". The signal that actually matters is the
931
+ * **model name**, not the endpoint hostname — modern OpenAI-compatible
932
+ * servers (LM Studio 0.3.29+, Ollama, vLLM, OpenRouter, OpenAI itself)
933
+ * all accept the field on /v1/chat/completions for the reasoning-capable
934
+ * model classes below, and silently ignore it for everything else. So a
935
+ * `gpt-oss-20b` served from localhost via LM Studio gets the same
936
+ * treatment as `gpt-5` on `api.openai.com`.
937
+ *
938
+ * Borrowed shape from openai-node's `ReasoningEffort` enum
939
+ * (openSrc/openai-node/src/resources/shared.ts) — `low|medium|high` map
940
+ * straight through to the provider field across OpenAI, DeepSeek,
941
+ * LM Studio, Ollama, and OpenRouter's pass-through. Anthropic models
942
+ * (`claude-*`) use a different field shape (`thinking: { budget_tokens }`)
943
+ * and a different endpoint (`/v1/messages`), so they're intentionally
944
+ * skipped here — brainrouter would need a separate provider adapter to
945
+ * forward into Anthropic's native API.
946
+ */
947
+ /**
948
+ * 9b: resolve the recall-gating mode for this process. `BRAINROUTER_RECALL_MODE`
949
+ * env var beats everything; unset defaults to `gated`. Anything outside the
950
+ * three valid values falls back to `gated` (defensive — better to be helpful
951
+ * than crash on a typo). Re-resolved each turn so users can flip with
952
+ * `export BRAINROUTER_RECALL_MODE=always` mid-session via a /run command.
953
+ */
954
+ export declare function resolveRecallMode(): 'always' | 'gated' | 'off';
955
+ /**
956
+ * 9b: cheap local heuristic for "the user message names something specific
957
+ * memory might have history on." Counts entity-shaped tokens: proper nouns
958
+ * (capitalized words that aren't sentence-starting), file paths (anything
959
+ * with `/` or `\\` or a `.<ext>` suffix), and identifier-shaped tokens (`camelCase`
960
+ * / `snake_case` / `PascalCase` longer than 4 chars). Crude but the bar is
961
+ * "is recall plausibly worth it?" — false positives waste a recall call,
962
+ * false negatives waste an ask. Tunable threshold via the caller.
963
+ */
964
+ export declare function countEntityTokens(text: string): number;
965
+ export declare function supportsReasoningEffortField(config: LLMConfig): boolean;
966
+ export interface BuildPayloadOptions {
967
+ /** Reasoning-depth preference, when provider supports it. `medium` is a no-op. */
968
+ effort?: EffortLevel;
969
+ }
970
+ export declare function buildChatCompletionPayload(config: LLMConfig, messages: any[], tools: any[], options?: BuildPayloadOptions): ChatCompletionPayload;
971
+ export declare function callOpenAI(config: LLMConfig, messages: any[], tools: any[], options?: BuildPayloadOptions): Promise<{
762
972
  content: any;
763
973
  toolCalls: any;
764
974
  usage: any;