@bahulam/code 0.1.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.
- package/README.md +93 -0
- package/package.json +56 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +223 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +312 -0
- package/src/commands/agent.mjs +221 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +202 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/model-catalog.mjs +57 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/prose-chunker.mjs +255 -0
- package/src/context/retriever.mjs +425 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +145 -0
- package/src/core/approval.mjs +700 -0
- package/src/core/attachments.mjs +666 -0
- package/src/core/backend-url.mjs +68 -0
- package/src/core/bundled-runtime.mjs +418 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +331 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +460 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +538 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +388 -0
- package/src/core/safety.mjs +260 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +957 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +2231 -0
- package/src/core/trust.mjs +160 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +374 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +700 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +122 -0
- package/src/terminal/agents.mjs +353 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +310 -0
- package/src/terminal/repl-ask-form.mjs +120 -0
- package/src/terminal/repl-explore.mjs +44 -0
- package/src/terminal/repl-format.mjs +317 -0
- package/src/terminal/repl-model-form.mjs +132 -0
- package/src/terminal/repl-render.mjs +833 -0
- package/src/terminal/repl-resume.mjs +640 -0
- package/src/terminal/repl-state.mjs +120 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +5032 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +392 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +703 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +510 -0
- package/src/ui/banner.mjs +232 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +170 -0
- package/src/ui/input-dock.mjs +772 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/render-queue.mjs +500 -0
- package/src/ui/slash-commands.mjs +257 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +167 -0
- package/src/ui/term.mjs +174 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +740 -0
- package/src/ui/tool-details.mjs +504 -0
- package/src/ui/transcript-block.mjs +20 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry — Funnel event emitter (PRD-076 W3).
|
|
3
|
+
*
|
|
4
|
+
* Buffers funnel events and flushes to {backend}/api/telemetry/funnel
|
|
5
|
+
* on a timer and on process exit. Device ID is generated once per install
|
|
6
|
+
* for anon-to-logged-in correlation.
|
|
7
|
+
*
|
|
8
|
+
* Opt-out: BAHULAM_DISABLE_TELEMETRY=1 or CLAUDE_CODE_DISABLE_TELEMETRY=1
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as crypto from 'node:crypto';
|
|
14
|
+
import { bahulamHome } from '../core/paths.mjs';
|
|
15
|
+
|
|
16
|
+
const BUF_LIMIT = 500;
|
|
17
|
+
const FLUSH_INTERVAL_MS = 30_000;
|
|
18
|
+
|
|
19
|
+
let events = [];
|
|
20
|
+
let enabled = true;
|
|
21
|
+
let flushTimer = null;
|
|
22
|
+
let _backendUrl = null;
|
|
23
|
+
let _token = null;
|
|
24
|
+
let _deviceId = null;
|
|
25
|
+
|
|
26
|
+
/* ── Configuration ── */
|
|
27
|
+
|
|
28
|
+
export function disable() {
|
|
29
|
+
enabled = false;
|
|
30
|
+
if (flushTimer) { clearInterval(flushTimer); flushTimer = null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function configure(backendUrl, token) {
|
|
34
|
+
_backendUrl = backendUrl;
|
|
35
|
+
_token = token;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── Device ID ── */
|
|
39
|
+
|
|
40
|
+
function readOrCreateDeviceId() {
|
|
41
|
+
const dir = bahulamHome();
|
|
42
|
+
const idPath = path.join(dir, 'device_id');
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(idPath)) {
|
|
45
|
+
return fs.readFileSync(idPath, 'utf-8').trim();
|
|
46
|
+
}
|
|
47
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
48
|
+
const id = crypto.randomUUID();
|
|
49
|
+
fs.writeFileSync(idPath, id, { mode: 0o600 });
|
|
50
|
+
return id;
|
|
51
|
+
} catch { return 'unknown'; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getDeviceId() {
|
|
55
|
+
if (!_deviceId) _deviceId = readOrCreateDeviceId();
|
|
56
|
+
return _deviceId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── Tracking ── */
|
|
60
|
+
|
|
61
|
+
export function track(event, properties = {}) {
|
|
62
|
+
if (!enabled) return;
|
|
63
|
+
if (process.env.BAHULAM_DISABLE_TELEMETRY === '1') return;
|
|
64
|
+
if (process.env.CLAUDE_CODE_DISABLE_TELEMETRY === '1') return;
|
|
65
|
+
|
|
66
|
+
const entry = { event, properties: { ...properties }, device_id: getDeviceId(), timestamp: Date.now() };
|
|
67
|
+
events.push(entry);
|
|
68
|
+
if (events.length > BUF_LIMIT) events.splice(0, events.length - BUF_LIMIT);
|
|
69
|
+
|
|
70
|
+
if (process.env.BAHULAM_DEBUG_TELEMETRY) {
|
|
71
|
+
process.stderr.write(`[telemetry] ${event} ${JSON.stringify(properties).slice(0, 200)}\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!flushTimer) {
|
|
75
|
+
flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
|
|
76
|
+
flushTimer.unref();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function trackTiming(event, durationMs, properties = {}) {
|
|
81
|
+
track(event, { ...properties, durationMs });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function trackError(event, error) {
|
|
85
|
+
track(`error.${event}`, { message: error.message, stack: error.stack?.split('\n').slice(0, 3).join('\n') });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── Transport ── */
|
|
89
|
+
|
|
90
|
+
export async function flush() {
|
|
91
|
+
if (!enabled || events.length === 0 || !_backendUrl) return;
|
|
92
|
+
const batch = events.splice(0);
|
|
93
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
94
|
+
if (_token) headers['Authorization'] = `Bearer ${_token}`;
|
|
95
|
+
try {
|
|
96
|
+
const resp = await fetch(`${_backendUrl}/api/telemetry/funnel`, {
|
|
97
|
+
method: 'POST', headers,
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
events: batch,
|
|
100
|
+
user_id: _token ? undefined : undefined, // backend derives from token if present
|
|
101
|
+
source: 'cli',
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
if (!resp.ok && resp.status >= 500) events.unshift(...batch);
|
|
105
|
+
} catch { events.unshift(...batch); if (events.length > BUF_LIMIT * 2) events.splice(0, events.length - BUF_LIMIT); }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function shutdown() {
|
|
109
|
+
if (flushTimer) { clearInterval(flushTimer); flushTimer = null; }
|
|
110
|
+
await flush();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── Debug ── */
|
|
114
|
+
|
|
115
|
+
export function getEvents() { return [...events]; }
|
|
116
|
+
export function clear() { events.length = 0; }
|
|
117
|
+
export function setEnabled(value) { enabled = value; }
|
|
118
|
+
export function getStats() {
|
|
119
|
+
const counts = {};
|
|
120
|
+
for (const e of events) counts[e.event] = (counts[e.event] || 0) + 1;
|
|
121
|
+
return { totalEvents: events.length, enabled, eventCounts: counts };
|
|
122
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Agents — specialized agent modes invoked via slash commands.
|
|
3
|
+
*
|
|
4
|
+
* Each agent wraps the same backend SSE flow but with a specialized
|
|
5
|
+
* system prompt prefix that focuses the AI on a specific task type.
|
|
6
|
+
*
|
|
7
|
+
* Agents:
|
|
8
|
+
* - explore: Code explorer — traces execution paths, maps architecture
|
|
9
|
+
* - review: Code reviewer — finds bugs, security issues, quality problems
|
|
10
|
+
* - architect: Feature architect — designs implementations, file plans
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { c } from './ansi.mjs';
|
|
14
|
+
import { TarangStreamClient } from '../core/stream-client.mjs';
|
|
15
|
+
|
|
16
|
+
// ── Agent Definitions ────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export const BUILTIN_AGENTS = [
|
|
19
|
+
{
|
|
20
|
+
command: 'explore',
|
|
21
|
+
name: 'Code Explorer',
|
|
22
|
+
description: 'Deeply analyze codebase features and architecture',
|
|
23
|
+
detail: 'Traces execution paths, maps layers, documents dependencies',
|
|
24
|
+
icon: '🔭',
|
|
25
|
+
systemPrompt: `You are a Code Explorer agent. Your job is to deeply analyze the codebase to answer the user's question.
|
|
26
|
+
|
|
27
|
+
Your approach:
|
|
28
|
+
1. Start by understanding what the user wants to know
|
|
29
|
+
2. Search for relevant files using search_code and list_files
|
|
30
|
+
3. Read the key files to trace the execution path
|
|
31
|
+
4. Map the architecture layers (entry points → business logic → data layer)
|
|
32
|
+
5. Document dependencies between components
|
|
33
|
+
6. Provide a clear, structured answer with file references
|
|
34
|
+
|
|
35
|
+
Rules:
|
|
36
|
+
- ONLY use read-only tools: read_file, search_code, list_files, search_files, get_file_info
|
|
37
|
+
- NEVER modify any files
|
|
38
|
+
- NEVER run shell commands that modify state
|
|
39
|
+
- Include file paths and line numbers in your references
|
|
40
|
+
- Structure your response with clear sections: Overview, Key Files, Execution Flow, Dependencies
|
|
41
|
+
- Be thorough but concise — focus on what matters for the user's question`,
|
|
42
|
+
readOnly: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: 'review',
|
|
46
|
+
name: 'Code Reviewer',
|
|
47
|
+
description: 'Review code for bugs, security issues, and quality',
|
|
48
|
+
detail: 'Scans for OWASP top 10, logic errors, code smells',
|
|
49
|
+
icon: '🔍',
|
|
50
|
+
systemPrompt: `You are a Code Review agent. Your job is to review code for issues.
|
|
51
|
+
|
|
52
|
+
Your approach:
|
|
53
|
+
1. Read the files or directories the user specifies
|
|
54
|
+
2. Check for:
|
|
55
|
+
- Security vulnerabilities (OWASP top 10: injection, XSS, auth bypass, etc.)
|
|
56
|
+
- Logic errors and edge cases
|
|
57
|
+
- Error handling gaps
|
|
58
|
+
- Performance issues (N+1 queries, memory leaks, blocking operations)
|
|
59
|
+
- Code quality (naming, complexity, duplication)
|
|
60
|
+
3. Rate each finding by severity: CRITICAL / HIGH / MEDIUM / LOW
|
|
61
|
+
4. Provide specific fix suggestions with code
|
|
62
|
+
|
|
63
|
+
Rules:
|
|
64
|
+
- ONLY use read-only tools: read_file, search_code, list_files, search_files
|
|
65
|
+
- NEVER modify any files
|
|
66
|
+
- Focus on HIGH and CRITICAL issues first
|
|
67
|
+
- Include file:line references
|
|
68
|
+
- Structure: Summary → Critical Issues → Other Findings → Recommendations
|
|
69
|
+
- Be specific — "line 42 has SQL injection via string interpolation" not "check for SQL injection"`,
|
|
70
|
+
readOnly: true,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
command: 'architect',
|
|
74
|
+
name: 'Feature Architect',
|
|
75
|
+
description: 'Design feature implementations with file plans',
|
|
76
|
+
detail: 'Analyzes patterns, designs components, maps data flows',
|
|
77
|
+
icon: '📐',
|
|
78
|
+
systemPrompt: `You are a Feature Architect agent. Your job is to design how a feature should be implemented.
|
|
79
|
+
|
|
80
|
+
Your approach:
|
|
81
|
+
1. Understand the feature requirements from the user
|
|
82
|
+
2. Analyze existing codebase patterns and conventions:
|
|
83
|
+
- File organization and naming
|
|
84
|
+
- Import patterns and module structure
|
|
85
|
+
- Error handling patterns
|
|
86
|
+
- Testing patterns
|
|
87
|
+
3. Design the implementation:
|
|
88
|
+
- List all files to create/modify
|
|
89
|
+
- Component/module design with interfaces
|
|
90
|
+
- Data flow (request → processing → response)
|
|
91
|
+
- Database schema changes if needed
|
|
92
|
+
4. Provide implementation order (what to build first)
|
|
93
|
+
|
|
94
|
+
Rules:
|
|
95
|
+
- ONLY use read-only tools: read_file, search_code, list_files, search_files
|
|
96
|
+
- NEVER modify any files — you DESIGN, you don't implement
|
|
97
|
+
- Follow existing project conventions
|
|
98
|
+
- Structure: Requirements → Architecture → File Plan → Implementation Order → Risks
|
|
99
|
+
- Include code sketches for key interfaces
|
|
100
|
+
- Call out edge cases and potential pitfalls`,
|
|
101
|
+
readOnly: true,
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// ── Agent Runner ─────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
const TOOL_ALIASES = new Map([
|
|
108
|
+
['bash', 'shell'],
|
|
109
|
+
['shell_command', 'shell'],
|
|
110
|
+
['read', 'read_file'],
|
|
111
|
+
['write', 'write_file'],
|
|
112
|
+
['edit', 'edit_file'],
|
|
113
|
+
['grep', 'search_code'],
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
function canonicalToolName(value) {
|
|
117
|
+
const key = String(value || '').trim().toLowerCase();
|
|
118
|
+
return TOOL_ALIASES.get(key) || key;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const RUNTIME_PLACEHOLDER_CWDS = new Set([
|
|
122
|
+
'/workspace',
|
|
123
|
+
'/workspace/kepler-code',
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
function normalizeScopedArgs(toolName, args = {}, { projectRoot = null } = {}) {
|
|
127
|
+
if (canonicalToolName(toolName) !== 'shell' || !projectRoot) return args;
|
|
128
|
+
const next = { ...(args || {}) };
|
|
129
|
+
const cwd = String(next.cwd || '').trim();
|
|
130
|
+
if (!cwd || RUNTIME_PLACEHOLDER_CWDS.has(cwd)) {
|
|
131
|
+
next.cwd = projectRoot;
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function createScopedToolExecutor(baseExecutor, agent, { projectRoot = null } = {}) {
|
|
137
|
+
const tools = Array.isArray(agent.tools) ? agent.tools : [];
|
|
138
|
+
const allowed = new Set(tools.map(canonicalToolName).filter(Boolean));
|
|
139
|
+
if (!allowed.size) return baseExecutor;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...baseExecutor,
|
|
143
|
+
execute: async (toolName, args = {}, options = {}) => {
|
|
144
|
+
const canonical = canonicalToolName(toolName);
|
|
145
|
+
if (!allowed.has(canonical)) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
output: `Tool '${toolName}' is not allowed for agent '${agent.name}'. Allowed tools: ${tools.join(', ')}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return baseExecutor.execute.call(
|
|
152
|
+
baseExecutor,
|
|
153
|
+
toolName,
|
|
154
|
+
normalizeScopedArgs(toolName, args, { projectRoot }),
|
|
155
|
+
options,
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function findBuiltinAgent(agentName) {
|
|
162
|
+
const target = String(agentName || '').trim().toLowerCase();
|
|
163
|
+
return BUILTIN_AGENTS.find(agent => agent.command === target || agent.name.toLowerCase() === target) || null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function localAgentMatches(agent, target) {
|
|
167
|
+
const needle = String(target || '').trim().toLowerCase();
|
|
168
|
+
if (!needle) return false;
|
|
169
|
+
return [
|
|
170
|
+
agent.slug,
|
|
171
|
+
agent.id,
|
|
172
|
+
agent.name,
|
|
173
|
+
].some(value => String(value || '').trim().toLowerCase() === needle);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeRunnableAgent(agent) {
|
|
177
|
+
const spec = agent?.spec || {};
|
|
178
|
+
const config = agent?.config || spec.config || agent?.raw_config || {};
|
|
179
|
+
const configAgent = config.agent || {};
|
|
180
|
+
const tools = Array.isArray(agent?.tools)
|
|
181
|
+
? agent.tools
|
|
182
|
+
: Array.isArray(spec.tools)
|
|
183
|
+
? spec.tools
|
|
184
|
+
: Array.isArray(config.tools)
|
|
185
|
+
? config.tools
|
|
186
|
+
: [];
|
|
187
|
+
|
|
188
|
+
const name = agent?.name || spec.name || agent?.slug || agent?.command || 'agent';
|
|
189
|
+
return {
|
|
190
|
+
command: agent?.command || agent?.slug || spec.slug || name,
|
|
191
|
+
slug: agent?.slug || spec.slug || agent?.command || name,
|
|
192
|
+
name,
|
|
193
|
+
description: agent?.description || spec.description || '',
|
|
194
|
+
role: agent?.role || spec.role || 'specialist',
|
|
195
|
+
icon: agent?.icon || '◇',
|
|
196
|
+
systemPrompt: agent?.systemPrompt || agent?.system_prompt || agent?.prompt || spec.system_prompt || configAgent.system_prompt || '',
|
|
197
|
+
readOnly: Boolean(agent?.readOnly),
|
|
198
|
+
model: agent?.model || spec.model || configAgent.model || null,
|
|
199
|
+
models: agent?.models || spec.models || configAgent.models || null,
|
|
200
|
+
tools,
|
|
201
|
+
source: agent?.source || spec.source || '',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function agentInstructionPrefix(agent, execContext = {}) {
|
|
206
|
+
const lines = [];
|
|
207
|
+
if (agent.systemPrompt) {
|
|
208
|
+
lines.push(agent.systemPrompt);
|
|
209
|
+
} else {
|
|
210
|
+
lines.push(`You are ${agent.name}, a Bahulam Code sub-agent.`);
|
|
211
|
+
}
|
|
212
|
+
if (agent.description) lines.push(`\nAgent description: ${agent.description}`);
|
|
213
|
+
if (agent.role) lines.push(`Agent role: ${agent.role}`);
|
|
214
|
+
if (agent.tools.length) {
|
|
215
|
+
lines.push(
|
|
216
|
+
`Allowed tools for this sub-agent: ${agent.tools.join(', ')}. ` +
|
|
217
|
+
'Do not request tools outside this list.',
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (execContext.project_root) {
|
|
221
|
+
lines.push(`Runtime project root: ${execContext.project_root}. Use this as the cwd for shell commands unless the task explicitly requires a different registered project root.`);
|
|
222
|
+
}
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function displayEventForDirectAgent(event, agent) {
|
|
227
|
+
if (!event || !event.type) return event;
|
|
228
|
+
if (!['tool_call', 'tool_request', 'tool_result', 'tool_done', 'sub_agent_tool'].includes(event.type)) {
|
|
229
|
+
return event;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...event,
|
|
233
|
+
data: {
|
|
234
|
+
...(event.data || {}),
|
|
235
|
+
internal: true,
|
|
236
|
+
sub_agent: event.data?.sub_agent || agent.slug || agent.command || agent.name || 'agent',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Run a normalized agent definition with the given instruction.
|
|
243
|
+
* @param {Object} agentDefinition - Built-in or .bahulam/agents definition
|
|
244
|
+
* @param {string} instruction - User's instruction
|
|
245
|
+
* @param {Object} ctx - { auth, toolExecutor, approval }
|
|
246
|
+
* @param {Object} session - Session state
|
|
247
|
+
* @param {Function} renderEvent - Event renderer function
|
|
248
|
+
* @param {Object} [options]
|
|
249
|
+
*/
|
|
250
|
+
export async function runAgentDefinition(agentDefinition, instruction, ctx, session, renderEvent, options = {}) {
|
|
251
|
+
const agent = normalizeRunnableAgent(agentDefinition);
|
|
252
|
+
const userInstruction = String(instruction || '').trim() || 'Run your assigned task now.';
|
|
253
|
+
const suppliedContext = options.execContext || options.context || {};
|
|
254
|
+
const baseCwd = suppliedContext.cwd || options.cwd || process.cwd();
|
|
255
|
+
const suppliedSubAgent = suppliedContext.sub_agent && typeof suppliedContext.sub_agent === 'object'
|
|
256
|
+
? suppliedContext.sub_agent
|
|
257
|
+
: {};
|
|
258
|
+
const projectRoot = suppliedContext.project_root || null;
|
|
259
|
+
const execContext = {
|
|
260
|
+
...suppliedContext,
|
|
261
|
+
cwd: suppliedContext.cwd || baseCwd,
|
|
262
|
+
...(projectRoot ? { project_root: projectRoot } : {}),
|
|
263
|
+
sub_agent: {
|
|
264
|
+
...suppliedSubAgent,
|
|
265
|
+
name: agent.name,
|
|
266
|
+
slug: agent.slug,
|
|
267
|
+
role: agent.role,
|
|
268
|
+
description: agent.description,
|
|
269
|
+
tools: agent.tools,
|
|
270
|
+
source: agent.source,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const creds = ctx.auth.loadCredentials();
|
|
275
|
+
if (!creds.token) {
|
|
276
|
+
process.stderr.write(` ${c.red('Not logged in. Run /login first.')}\n`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Header
|
|
281
|
+
process.stderr.write(`\n ${agent.icon} ${c.bold(c.brand(agent.name))}\n`);
|
|
282
|
+
process.stderr.write(` ${c.gray('─'.repeat(40))}\n`);
|
|
283
|
+
if (agent.description) process.stderr.write(` ${c.gray(agent.description)}\n`);
|
|
284
|
+
if (agent.source) process.stderr.write(` ${c.dim(agent.source)}\n`);
|
|
285
|
+
process.stderr.write(` ${c.gray(userInstruction)}\n\n`);
|
|
286
|
+
|
|
287
|
+
// Prepend agent system prompt to instruction
|
|
288
|
+
const fullInstruction = `${agentInstructionPrefix(agent, execContext)}\n\n---\n\nUser request: ${userInstruction}`;
|
|
289
|
+
|
|
290
|
+
// For read-only agents, use a restricted approval manager
|
|
291
|
+
const { ApprovalManager } = await import('../core/approval.mjs');
|
|
292
|
+
const agentApproval = agent.readOnly
|
|
293
|
+
? new ApprovalManager({ planMode: true }) // planMode blocks all writes
|
|
294
|
+
: ctx.approval;
|
|
295
|
+
const toolExecutor = createScopedToolExecutor(ctx.toolExecutor, agent, {
|
|
296
|
+
projectRoot: execContext.project_root || null,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const client = new TarangStreamClient({
|
|
300
|
+
baseUrl: creds.backendUrl,
|
|
301
|
+
token: creds.token,
|
|
302
|
+
toolExecutor,
|
|
303
|
+
approvalManager: agentApproval,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
session.turns++;
|
|
307
|
+
session.toolCalls = 0;
|
|
308
|
+
let assistantContent = '';
|
|
309
|
+
if (agent.model) execContext.model_override = agent.model;
|
|
310
|
+
if (agent.models && typeof agent.models === 'object' && Object.keys(agent.models).length) {
|
|
311
|
+
execContext.model_overrides = agent.models;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
for await (const event of client.execute(fullInstruction, execContext)) {
|
|
316
|
+
renderEvent(displayEventForDirectAgent(event, agent));
|
|
317
|
+
|
|
318
|
+
if (event.type === 'content' || event.type === 'content_partial') {
|
|
319
|
+
const text = event.data?.text || '';
|
|
320
|
+
if (text) assistantContent += text;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} catch (err) {
|
|
324
|
+
process.stderr.write(` ${c.red('Agent error: ' + err.message)}\n`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Save to conversation history
|
|
328
|
+
if (assistantContent) {
|
|
329
|
+
session.history.push(
|
|
330
|
+
{ role: 'user', content: `[${agent.name}] ${userInstruction}` },
|
|
331
|
+
{ role: 'assistant', content: assistantContent }
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
process.stderr.write('\n');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Run a built-in agent with the given instruction.
|
|
340
|
+
* @param {string} agentName - e.g. 'explore', 'review', 'architect'
|
|
341
|
+
* @param {string} instruction - User's instruction
|
|
342
|
+
* @param {Object} ctx - { auth, toolExecutor, approval }
|
|
343
|
+
* @param {Object} session - Session state
|
|
344
|
+
* @param {Function} renderEvent - Event renderer function
|
|
345
|
+
*/
|
|
346
|
+
export async function runAgent(agentName, instruction, ctx, session, renderEvent) {
|
|
347
|
+
const agent = findBuiltinAgent(agentName);
|
|
348
|
+
if (!agent) {
|
|
349
|
+
process.stderr.write(` ${c.red('Unknown agent: ' + agentName)}\n`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
return runAgentDefinition(agent, instruction, ctx, session, renderEvent);
|
|
353
|
+
}
|