@kinqs/brainrouter-cli 0.3.4

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 (87) hide show
  1. package/.env.example +109 -0
  2. package/README.md +185 -0
  3. package/dist/agent/agent.d.ts +765 -0
  4. package/dist/agent/agent.js +1977 -0
  5. package/dist/cli/cliPrompt.d.ts +15 -0
  6. package/dist/cli/cliPrompt.js +62 -0
  7. package/dist/cli/commands/_context.d.ts +53 -0
  8. package/dist/cli/commands/_context.js +14 -0
  9. package/dist/cli/commands/_helpers.d.ts +45 -0
  10. package/dist/cli/commands/_helpers.js +140 -0
  11. package/dist/cli/commands/guard.d.ts +6 -0
  12. package/dist/cli/commands/guard.js +292 -0
  13. package/dist/cli/commands/memory.d.ts +12 -0
  14. package/dist/cli/commands/memory.js +263 -0
  15. package/dist/cli/commands/obs.d.ts +6 -0
  16. package/dist/cli/commands/obs.js +208 -0
  17. package/dist/cli/commands/orchestration.d.ts +6 -0
  18. package/dist/cli/commands/orchestration.js +218 -0
  19. package/dist/cli/commands/session.d.ts +6 -0
  20. package/dist/cli/commands/session.js +191 -0
  21. package/dist/cli/commands/ui.d.ts +6 -0
  22. package/dist/cli/commands/ui.js +477 -0
  23. package/dist/cli/commands/workflow.d.ts +6 -0
  24. package/dist/cli/commands/workflow.js +691 -0
  25. package/dist/cli/repl.d.ts +12 -0
  26. package/dist/cli/repl.js +894 -0
  27. package/dist/config/config.d.ts +22 -0
  28. package/dist/config/config.js +105 -0
  29. package/dist/config/workspace.d.ts +7 -0
  30. package/dist/config/workspace.js +62 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +610 -0
  33. package/dist/memory/briefing.d.ts +46 -0
  34. package/dist/memory/briefing.js +152 -0
  35. package/dist/memory/consolidation.d.ts +60 -0
  36. package/dist/memory/consolidation.js +208 -0
  37. package/dist/memory/formatters.d.ts +38 -0
  38. package/dist/memory/formatters.js +102 -0
  39. package/dist/memory/mentions.d.ts +10 -0
  40. package/dist/memory/mentions.js +72 -0
  41. package/dist/orchestration/orchestrator.d.ts +36 -0
  42. package/dist/orchestration/orchestrator.js +71 -0
  43. package/dist/orchestration/roles.d.ts +11 -0
  44. package/dist/orchestration/roles.js +117 -0
  45. package/dist/orchestration/tools.d.ts +244 -0
  46. package/dist/orchestration/tools.js +528 -0
  47. package/dist/prompt/breadthHint.d.ts +48 -0
  48. package/dist/prompt/breadthHint.js +93 -0
  49. package/dist/prompt/compactor.d.ts +31 -0
  50. package/dist/prompt/compactor.js +112 -0
  51. package/dist/prompt/initAgentMd.d.ts +13 -0
  52. package/dist/prompt/initAgentMd.js +194 -0
  53. package/dist/prompt/skillRunner.d.ts +34 -0
  54. package/dist/prompt/skillRunner.js +146 -0
  55. package/dist/prompt/systemPrompt.d.ts +10 -0
  56. package/dist/prompt/systemPrompt.js +171 -0
  57. package/dist/runtime/clipboard.d.ts +17 -0
  58. package/dist/runtime/clipboard.js +52 -0
  59. package/dist/runtime/llmSemaphore.d.ts +30 -0
  60. package/dist/runtime/llmSemaphore.js +67 -0
  61. package/dist/runtime/loopRunner.d.ts +25 -0
  62. package/dist/runtime/loopRunner.js +79 -0
  63. package/dist/runtime/mcpClient.d.ts +156 -0
  64. package/dist/runtime/mcpClient.js +234 -0
  65. package/dist/runtime/mcpUtils.d.ts +36 -0
  66. package/dist/runtime/mcpUtils.js +64 -0
  67. package/dist/runtime/sandbox.d.ts +48 -0
  68. package/dist/runtime/sandbox.js +156 -0
  69. package/dist/runtime/tracing.d.ts +25 -0
  70. package/dist/runtime/tracing.js +91 -0
  71. package/dist/state/cliState.d.ts +59 -0
  72. package/dist/state/cliState.js +311 -0
  73. package/dist/state/goalStore.d.ts +174 -0
  74. package/dist/state/goalStore.js +410 -0
  75. package/dist/state/hookifyStore.d.ts +80 -0
  76. package/dist/state/hookifyStore.js +237 -0
  77. package/dist/state/hooksStore.d.ts +42 -0
  78. package/dist/state/hooksStore.js +71 -0
  79. package/dist/state/preferencesStore.d.ts +41 -0
  80. package/dist/state/preferencesStore.js +25 -0
  81. package/dist/state/sessionStore.d.ts +42 -0
  82. package/dist/state/sessionStore.js +193 -0
  83. package/dist/state/taskStore.d.ts +23 -0
  84. package/dist/state/taskStore.js +80 -0
  85. package/dist/state/workflowArtifacts.d.ts +33 -0
  86. package/dist/state/workflowArtifacts.js +139 -0
  87. package/package.json +71 -0
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { getCliStateFile, readJsonFile, writeJsonFile } from '../state/cliState.js';
3
+ import { resolveRole } from './roles.js';
4
+ const EMPTY = { sessions: [] };
5
+ function readFile(workspaceRoot) {
6
+ return readJsonFile(getCliStateFile(workspaceRoot, 'sessions.json'), EMPTY);
7
+ }
8
+ function writeFile(workspaceRoot, data) {
9
+ writeJsonFile(getCliStateFile(workspaceRoot, 'sessions.json'), data);
10
+ }
11
+ export function listSessions(workspaceRoot) {
12
+ return readFile(workspaceRoot).sessions;
13
+ }
14
+ export function getSession(workspaceRoot, id) {
15
+ return readFile(workspaceRoot).sessions.find((s) => s.id === id);
16
+ }
17
+ export function createSession(workspaceRoot, input) {
18
+ const role = resolveRole(input.role);
19
+ const now = new Date().toISOString();
20
+ const record = {
21
+ id: `agent-${randomUUID().slice(0, 8)}`,
22
+ role: role.name,
23
+ label: input.label,
24
+ access: input.access ?? role.defaultAccess,
25
+ parentSessionKey: input.parentSessionKey,
26
+ prompt: input.prompt,
27
+ status: 'pending',
28
+ startedAt: now,
29
+ updatedAt: now,
30
+ pid: process.pid,
31
+ };
32
+ const data = readFile(workspaceRoot);
33
+ data.sessions.push(record);
34
+ writeFile(workspaceRoot, data);
35
+ return record;
36
+ }
37
+ export function updateSession(workspaceRoot, id, patch) {
38
+ const data = readFile(workspaceRoot);
39
+ const idx = data.sessions.findIndex((s) => s.id === id);
40
+ if (idx === -1)
41
+ throw new Error(`No child session with id ${id}.`);
42
+ const merged = {
43
+ ...data.sessions[idx],
44
+ ...patch,
45
+ updatedAt: new Date().toISOString(),
46
+ };
47
+ data.sessions[idx] = merged;
48
+ writeFile(workspaceRoot, data);
49
+ return merged;
50
+ }
51
+ export function reconcileStale(workspaceRoot) {
52
+ const data = readFile(workspaceRoot);
53
+ let changed = 0;
54
+ for (const s of data.sessions) {
55
+ if ((s.status === 'pending' || s.status === 'running') && s.pid !== process.pid) {
56
+ s.status = 'stale';
57
+ s.updatedAt = new Date().toISOString();
58
+ changed++;
59
+ }
60
+ }
61
+ if (changed > 0)
62
+ writeFile(workspaceRoot, data);
63
+ return changed;
64
+ }
65
+ export function formatSessionSummary(s) {
66
+ const started = new Date(s.startedAt).getTime();
67
+ const ended = s.completedAt ? new Date(s.completedAt).getTime() : Date.now();
68
+ const elapsedSec = Math.max(0, Math.round((ended - started) / 1000));
69
+ const label = s.label ? ` "${s.label}"` : '';
70
+ return `${s.id} [${s.status}] ${s.role}${label} (${elapsedSec}s)`;
71
+ }
@@ -0,0 +1,11 @@
1
+ export type AccessMode = 'read' | 'write' | 'shell';
2
+ export interface AgentRole {
3
+ name: string;
4
+ description: string;
5
+ defaultAccess: AccessMode;
6
+ promptOverlay: string;
7
+ }
8
+ export declare const BUILT_IN_ROLES: Record<string, AgentRole>;
9
+ export declare function resolveRole(name: string): AgentRole;
10
+ export declare function listRoles(): AgentRole[];
11
+ export declare function buildRolePrompt(role: AgentRole, basePrompt: string, taskPrompt: string): string;
@@ -0,0 +1,117 @@
1
+ export const BUILT_IN_ROLES = {
2
+ explorer: {
3
+ name: 'explorer',
4
+ description: 'Read-only codebase investigator. Returns findings and key files.',
5
+ defaultAccess: 'read',
6
+ promptOverlay: [
7
+ '## Role: Explorer',
8
+ 'You are a read-only investigator. Do not edit files or run shell commands.',
9
+ 'Goal: map the relevant code, return concrete file paths with line ranges, and surface the few facts the parent needs to decide.',
10
+ '',
11
+ '### Memory-first opening (mandatory)',
12
+ '- Step 1: `memory_search` for the topic of investigation. Past explorers may have mapped this already — do not re-discover what BrainRouter already knows.',
13
+ '- Step 2: `memory_graph_query` with the dominant feature/entity name to surface related memories across 2 hops.',
14
+ '- Step 3: `memory_file_history` for any file the parent specifically mentions.',
15
+ '- Cite every recordId you build on. Your output begins with a `### Memory consulted` block listing the record IDs and what they told you.',
16
+ '',
17
+ 'Output structure: 1) Memory consulted, 2) Summary (3-5 bullets), 3) Key files with line ranges, 4) Open questions, 5) Suggested next probe.',
18
+ 'Never claim work is complete without naming actual files you read AND showing the memory you consulted.',
19
+ ].join('\n'),
20
+ },
21
+ architect: {
22
+ name: 'architect',
23
+ description: 'Design alternatives and tradeoffs. No file writes.',
24
+ defaultAccess: 'read',
25
+ promptOverlay: [
26
+ '## Role: Architect',
27
+ 'You design solutions; you do not write production code.',
28
+ '',
29
+ '### Memory-first opening (mandatory)',
30
+ '- `memory_search` and `memory_graph_query` for the feature/domain — past architecture decisions often constrain new ones.',
31
+ '- `memory_contradictions` (action: list) — if prior designs contradict the proposed change, flag it.',
32
+ '- Cite any architecture_decision records you find with their recordId.',
33
+ '',
34
+ 'Always present at least two design alternatives with explicit tradeoffs (complexity, blast radius, reversibility, test cost).',
35
+ 'End with a clear recommendation and the smallest first vertical slice.',
36
+ ].join('\n'),
37
+ },
38
+ reviewer: {
39
+ name: 'reviewer',
40
+ description: 'Code review stance; findings first. Read-only.',
41
+ defaultAccess: 'read',
42
+ promptOverlay: [
43
+ '## Role: Reviewer',
44
+ 'You review changes critically. Findings first; severity-ordered (blocker, major, minor, nit).',
45
+ '',
46
+ '### Memory-first opening (mandatory)',
47
+ '- `memory_search` for prior reviews on the same files or feature — never re-flag an issue another reviewer already decided is acceptable.',
48
+ '- `memory_file_history` for each file in the diff — known regressions and prior bug fixes inform your verdict.',
49
+ '- Cite related recordIds inline in each finding so the parent can see the precedent.',
50
+ '',
51
+ 'For each finding: file:line, what is wrong, why it matters, suggested fix.',
52
+ 'Do not make edits. The parent will decide what to apply.',
53
+ ].join('\n'),
54
+ },
55
+ worker: {
56
+ name: 'worker',
57
+ description: 'Implementation-focused. May edit files when granted write access.',
58
+ defaultAccess: 'write',
59
+ promptOverlay: [
60
+ '## Role: Worker',
61
+ 'You implement a single bounded task. Keep edits minimal and scoped.',
62
+ '',
63
+ '### Memory-first opening (mandatory)',
64
+ '- `memory_recall` for the task topic — past instructions, conventions, and tool_preference records often dictate HOW to implement.',
65
+ '- `memory_file_history` for the files you intend to touch — known fragility lives there.',
66
+ '- If the parent gave you `seedRecordIds`, treat those as authoritative context.',
67
+ '- `memory_task_state` if this looks like a continuation — pick up where prior work left off.',
68
+ '',
69
+ 'Read before editing. Prefer edit_file over write_file when possible. Prefer apply_patch for multi-file edits.',
70
+ 'On completion call `memory_task_update` with the outcome, then report exactly which files you changed and any follow-ups the verifier should run.',
71
+ ].join('\n'),
72
+ },
73
+ verifier: {
74
+ name: 'verifier',
75
+ description: 'Runs tests and checks; reports pass/fail with evidence.',
76
+ defaultAccess: 'shell',
77
+ promptOverlay: [
78
+ '## Role: Verifier',
79
+ 'You verify that recent changes work. Run the smallest useful set of tests/typechecks.',
80
+ '',
81
+ '### Memory-first opening (mandatory)',
82
+ '- `memory_search` for prior failure modes on these tests — flaky tests, environment caveats, and known-bad commands live in memory.',
83
+ '- `memory_file_history` for any test file involved — past fixes for the same suite are highly relevant.',
84
+ '',
85
+ 'Report: which command(s) you ran, exit codes, failing output (trimmed), and a clear PASS/FAIL verdict.',
86
+ 'Never claim PASS without actually executing a check. On failure, call `memory_task_update` with the blocker so the next worker can pick it up.',
87
+ ].join('\n'),
88
+ },
89
+ };
90
+ export function resolveRole(name) {
91
+ const role = BUILT_IN_ROLES[name];
92
+ if (!role) {
93
+ const known = Object.keys(BUILT_IN_ROLES).join(', ');
94
+ throw new Error(`Unknown agent role "${name}". Known roles: ${known}.`);
95
+ }
96
+ return role;
97
+ }
98
+ export function listRoles() {
99
+ return Object.values(BUILT_IN_ROLES);
100
+ }
101
+ export function buildRolePrompt(role, basePrompt, taskPrompt) {
102
+ return [
103
+ basePrompt,
104
+ '',
105
+ role.promptOverlay,
106
+ '',
107
+ // Universal headline rule. The parent only sees a clamped preview of
108
+ // your output (~800 chars); the rest goes to working memory. Open with
109
+ // a short headline so the parent sees the conclusion, not the framing.
110
+ // extractChildPreview() looks for these exact heading variants.
111
+ '## Headline-first output (universal)',
112
+ 'Open your final response with a `## Headline` block (≤ 6 lines, the verdict + the 1-3 most important facts the parent needs). Detail follows. If you do not produce this block, the parent will only see your intro paragraph and the conclusion will be lost behind a "fetch full output" ref.',
113
+ '',
114
+ '## Task',
115
+ taskPrompt,
116
+ ].join('\n');
117
+ }
@@ -0,0 +1,244 @@
1
+ import type { McpClientWrapper } from '../runtime/mcpClient.js';
2
+ import type { LLMConfig } from '../config/config.js';
3
+ import { type AccessMode } from './roles.js';
4
+ export interface OrchestrationContext {
5
+ workspaceRoot: string;
6
+ parentSessionKey: string;
7
+ /**
8
+ * Parent agent's access mode. Child agents may not exceed this — a `read`
9
+ * parent cannot spawn a `shell` child, even if the LLM passes `access:'shell'`
10
+ * to spawn_agent. Without this clamp, `spawn_agent` was a privilege-escalation
11
+ * primitive: a read-mode parent could request a shell-mode child and the
12
+ * child would silently run with elevated permissions.
13
+ */
14
+ parentAccessMode?: AccessMode;
15
+ /**
16
+ * Parent OTEL trace context. When set, child agents nest their per-turn
17
+ * spans under the dispatching `spawn_agent` tool span instead of starting
18
+ * a fresh trace. Lets observability viewers reconstruct fan-out trees.
19
+ */
20
+ parentTraceId?: string;
21
+ parentSpanId?: string;
22
+ /** Parent agent_id so children can be grouped via attribute even without trace links. */
23
+ parentAgentId?: string;
24
+ mcpClient: McpClientWrapper;
25
+ llmConfig: LLMConfig;
26
+ launchCwd: string;
27
+ /** Called when a child output got offloaded — chars beyond preview that didn't land in parent context. */
28
+ recordOffload?: (charsAvoided: number) => void;
29
+ /** Called when the child agent emits a tool call, for live observability. */
30
+ onChildToolEvent?: (event: {
31
+ childId: string;
32
+ role: string;
33
+ tool: string;
34
+ ok: boolean;
35
+ summary: string;
36
+ }) => void;
37
+ /**
38
+ * Called when a child agent's runTurn ends — success, fail, or empty answer.
39
+ * Lets the REPL surface "✓ agent X completed" so the user knows when to act,
40
+ * instead of seeing tool events and then silence.
41
+ */
42
+ onChildComplete?: (event: {
43
+ childId: string;
44
+ role: string;
45
+ status: 'completed' | 'failed';
46
+ preview?: string;
47
+ error?: string;
48
+ }) => void;
49
+ }
50
+ export declare function clampAccess(parent: AccessMode, requested: AccessMode): AccessMode;
51
+ /**
52
+ * Build the parent-visible preview of an offloaded child output. The naive
53
+ * `slice(0, N)` form hid the conclusion when children wrote long reports;
54
+ * here we prefer an explicit summary section (the role overlays nudge each
55
+ * child to start with one) and fall back to head+tail so both the framing
56
+ * and the punchline survive the clamp.
57
+ *
58
+ * Exported for testability.
59
+ */
60
+ export declare function extractChildPreview(output: string, maxChars: number): string;
61
+ /**
62
+ * Heuristic auto-router. Maps a free-text task to the best role based on
63
+ * leading verbs and intent keywords. Pure text-classification — callers can
64
+ * opt in via `route_agent` without first spending an LLM turn.
65
+ */
66
+ export declare function inferRoleFromTask(task: string): 'explorer' | 'architect' | 'reviewer' | 'worker' | 'verifier';
67
+ export declare function isOrchestrationToolName(name: string): boolean;
68
+ export declare function trackedPromiseFor(id: string): Promise<void> | undefined;
69
+ export declare function createSpawnAgentTool(): {
70
+ name: string;
71
+ description: string;
72
+ inputSchema: {
73
+ type: string;
74
+ properties: {
75
+ role: {
76
+ type: string;
77
+ description: string;
78
+ };
79
+ prompt: {
80
+ type: string;
81
+ description: string;
82
+ };
83
+ label: {
84
+ type: string;
85
+ description: string;
86
+ };
87
+ access: {
88
+ type: string;
89
+ enum: string[];
90
+ description: string;
91
+ };
92
+ wait: {
93
+ type: string;
94
+ description: string;
95
+ };
96
+ timeoutMs: {
97
+ type: string;
98
+ description: string;
99
+ };
100
+ seedRecordIds: {
101
+ type: string;
102
+ items: {
103
+ type: string;
104
+ };
105
+ description: string;
106
+ };
107
+ };
108
+ required: string[];
109
+ };
110
+ };
111
+ export declare function createListAgentsTool(): {
112
+ name: string;
113
+ description: string;
114
+ inputSchema: {
115
+ type: string;
116
+ properties: {};
117
+ };
118
+ };
119
+ export declare function createWaitAgentTool(): {
120
+ name: string;
121
+ description: string;
122
+ inputSchema: {
123
+ type: string;
124
+ properties: {
125
+ id: {
126
+ type: string;
127
+ description: string;
128
+ };
129
+ timeoutMs: {
130
+ type: string;
131
+ description: string;
132
+ };
133
+ };
134
+ required: string[];
135
+ };
136
+ };
137
+ export declare function createReadAgentTranscriptTool(): {
138
+ name: string;
139
+ description: string;
140
+ inputSchema: {
141
+ type: string;
142
+ properties: {
143
+ id: {
144
+ type: string;
145
+ description: string;
146
+ };
147
+ limit: {
148
+ type: string;
149
+ description: string;
150
+ };
151
+ };
152
+ required: string[];
153
+ };
154
+ };
155
+ export declare function createCloseAgentTool(): {
156
+ name: string;
157
+ description: string;
158
+ inputSchema: {
159
+ type: string;
160
+ properties: {
161
+ id: {
162
+ type: string;
163
+ description: string;
164
+ };
165
+ };
166
+ required: string[];
167
+ };
168
+ };
169
+ export declare function createSpawnAgentsTool(): {
170
+ name: string;
171
+ description: string;
172
+ inputSchema: {
173
+ type: string;
174
+ properties: {
175
+ agents: {
176
+ type: string;
177
+ minItems: number;
178
+ items: {
179
+ type: string;
180
+ properties: {
181
+ role: {
182
+ type: string;
183
+ description: string;
184
+ };
185
+ prompt: {
186
+ type: string;
187
+ description: string;
188
+ };
189
+ label: {
190
+ type: string;
191
+ };
192
+ access: {
193
+ type: string;
194
+ enum: string[];
195
+ };
196
+ seedRecordIds: {
197
+ type: string;
198
+ items: {
199
+ type: string;
200
+ };
201
+ };
202
+ };
203
+ required: string[];
204
+ };
205
+ };
206
+ };
207
+ required: string[];
208
+ };
209
+ };
210
+ export declare function createWaitAgentsTool(): {
211
+ name: string;
212
+ description: string;
213
+ inputSchema: {
214
+ type: string;
215
+ properties: {
216
+ ids: {
217
+ type: string;
218
+ items: {
219
+ type: string;
220
+ };
221
+ minItems: number;
222
+ };
223
+ timeoutMs: {
224
+ type: string;
225
+ description: string;
226
+ };
227
+ };
228
+ required: string[];
229
+ };
230
+ };
231
+ export declare function createRouteAgentTool(): {
232
+ name: string;
233
+ description: string;
234
+ inputSchema: {
235
+ type: string;
236
+ properties: {
237
+ task: {
238
+ type: string;
239
+ };
240
+ };
241
+ required: string[];
242
+ };
243
+ };
244
+ export declare function executeOrchestrationTool(name: string, args: any, ctx: OrchestrationContext): Promise<string>;