joonecli 0.1.1 → 0.2.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 (235) hide show
  1. package/dist/__tests__/config.test.js +1 -0
  2. package/dist/__tests__/config.test.js.map +1 -1
  3. package/dist/__tests__/installHostDeps.test.js +45 -0
  4. package/dist/__tests__/installHostDeps.test.js.map +1 -0
  5. package/dist/__tests__/whitelistedBackend.test.js +18 -0
  6. package/dist/__tests__/whitelistedBackend.test.js.map +1 -0
  7. package/dist/cli/config.d.ts +2 -0
  8. package/dist/cli/config.js +1 -0
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +84 -97
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/commands/builtinCommands.js +6 -6
  13. package/dist/commands/builtinCommands.js.map +1 -1
  14. package/dist/commands/commandRegistry.d.ts +3 -1
  15. package/dist/commands/commandRegistry.js.map +1 -1
  16. package/dist/core/agentLoop.d.ts +11 -28
  17. package/dist/core/agentLoop.js +68 -229
  18. package/dist/core/agentLoop.js.map +1 -1
  19. package/dist/core/compactor.js +2 -2
  20. package/dist/core/compactor.js.map +1 -1
  21. package/dist/core/contextGuard.d.ts +5 -0
  22. package/dist/core/contextGuard.js +30 -3
  23. package/dist/core/contextGuard.js.map +1 -1
  24. package/dist/core/events.d.ts +45 -0
  25. package/dist/core/events.js +8 -0
  26. package/dist/core/events.js.map +1 -0
  27. package/dist/core/promptBuilder.js.map +1 -1
  28. package/dist/core/sessionStore.js +3 -2
  29. package/dist/core/sessionStore.js.map +1 -1
  30. package/dist/core/tokenCounter.d.ts +8 -1
  31. package/dist/core/tokenCounter.js +28 -0
  32. package/dist/core/tokenCounter.js.map +1 -1
  33. package/dist/hitl/bridge.js +1 -27
  34. package/dist/hitl/bridge.js.map +1 -1
  35. package/dist/middleware/loopDetection.d.ts +7 -23
  36. package/dist/middleware/loopDetection.js +38 -42
  37. package/dist/middleware/loopDetection.js.map +1 -1
  38. package/dist/sandbox/whitelistedBackend.d.ts +5 -0
  39. package/dist/sandbox/whitelistedBackend.js +27 -0
  40. package/dist/sandbox/whitelistedBackend.js.map +1 -0
  41. package/dist/tools/askUser.d.ts +12 -3
  42. package/dist/tools/askUser.js +16 -28
  43. package/dist/tools/askUser.js.map +1 -1
  44. package/dist/tools/bashTool.d.ts +11 -0
  45. package/dist/tools/bashTool.js +51 -0
  46. package/dist/tools/bashTool.js.map +1 -0
  47. package/dist/tools/index.d.ts +15 -27
  48. package/dist/tools/index.js +9 -181
  49. package/dist/tools/index.js.map +1 -1
  50. package/dist/tools/installHostDeps.d.ts +8 -0
  51. package/dist/tools/installHostDeps.js +44 -0
  52. package/dist/tools/installHostDeps.js.map +1 -0
  53. package/dist/tracing/sessionTracer.d.ts +1 -0
  54. package/dist/tracing/sessionTracer.js +4 -1
  55. package/dist/tracing/sessionTracer.js.map +1 -1
  56. package/dist/ui/App.js +116 -55
  57. package/dist/ui/App.js.map +1 -1
  58. package/dist/ui/components/ActionLog.d.ts +7 -0
  59. package/dist/ui/components/ActionLog.js +63 -0
  60. package/dist/ui/components/ActionLog.js.map +1 -0
  61. package/dist/ui/components/FileBrowser.d.ts +2 -0
  62. package/dist/ui/components/FileBrowser.js +41 -0
  63. package/dist/ui/components/FileBrowser.js.map +1 -0
  64. package/dist/ui/components/MessageBubble.js +1 -1
  65. package/dist/ui/components/MessageBubble.js.map +1 -1
  66. package/package.json +8 -5
  67. package/AGENTS.md +0 -56
  68. package/Handover.md +0 -115
  69. package/PROGRESS.md +0 -160
  70. package/dist/__tests__/m55.test.js +0 -160
  71. package/dist/__tests__/m55.test.js.map +0 -1
  72. package/dist/__tests__/middleware.test.js +0 -169
  73. package/dist/__tests__/middleware.test.js.map +0 -1
  74. package/dist/__tests__/optimizations.test.d.ts +0 -1
  75. package/dist/__tests__/optimizations.test.js +0 -136
  76. package/dist/__tests__/optimizations.test.js.map +0 -1
  77. package/dist/__tests__/security.test.d.ts +0 -1
  78. package/dist/__tests__/security.test.js +0 -86
  79. package/dist/__tests__/security.test.js.map +0 -1
  80. package/dist/__tests__/streaming.test.d.ts +0 -1
  81. package/dist/__tests__/streaming.test.js +0 -71
  82. package/dist/__tests__/streaming.test.js.map +0 -1
  83. package/dist/__tests__/toolRouter.test.d.ts +0 -1
  84. package/dist/__tests__/toolRouter.test.js +0 -37
  85. package/dist/__tests__/toolRouter.test.js.map +0 -1
  86. package/dist/__tests__/tools.test.d.ts +0 -1
  87. package/dist/__tests__/tools.test.js +0 -112
  88. package/dist/__tests__/tools.test.js.map +0 -1
  89. package/dist/core/subAgent.d.ts +0 -56
  90. package/dist/core/subAgent.js +0 -240
  91. package/dist/core/subAgent.js.map +0 -1
  92. package/dist/debug_google.d.ts +0 -1
  93. package/dist/debug_google.js +0 -23
  94. package/dist/debug_google.js.map +0 -1
  95. package/dist/middleware/commandSanitizer.d.ts +0 -18
  96. package/dist/middleware/commandSanitizer.js +0 -50
  97. package/dist/middleware/commandSanitizer.js.map +0 -1
  98. package/dist/middleware/permission.d.ts +0 -17
  99. package/dist/middleware/permission.js +0 -59
  100. package/dist/middleware/permission.js.map +0 -1
  101. package/dist/middleware/pipeline.d.ts +0 -31
  102. package/dist/middleware/pipeline.js +0 -62
  103. package/dist/middleware/pipeline.js.map +0 -1
  104. package/dist/middleware/preCompletion.d.ts +0 -29
  105. package/dist/middleware/preCompletion.js +0 -82
  106. package/dist/middleware/preCompletion.js.map +0 -1
  107. package/dist/middleware/types.d.ts +0 -40
  108. package/dist/middleware/types.js +0 -8
  109. package/dist/middleware/types.js.map +0 -1
  110. package/dist/skills/loader.d.ts +0 -55
  111. package/dist/skills/loader.js +0 -132
  112. package/dist/skills/loader.js.map +0 -1
  113. package/dist/skills/tools.d.ts +0 -5
  114. package/dist/skills/tools.js +0 -78
  115. package/dist/skills/tools.js.map +0 -1
  116. package/dist/test_cache.d.ts +0 -1
  117. package/dist/test_cache.js +0 -55
  118. package/dist/test_cache.js.map +0 -1
  119. package/dist/test_google.d.ts +0 -1
  120. package/dist/test_google.js +0 -36
  121. package/dist/test_google.js.map +0 -1
  122. package/dist/tools/browser.d.ts +0 -19
  123. package/dist/tools/browser.js +0 -111
  124. package/dist/tools/browser.js.map +0 -1
  125. package/dist/tools/registry.d.ts +0 -31
  126. package/dist/tools/registry.js +0 -168
  127. package/dist/tools/registry.js.map +0 -1
  128. package/dist/tools/router.d.ts +0 -34
  129. package/dist/tools/router.js +0 -75
  130. package/dist/tools/router.js.map +0 -1
  131. package/dist/tools/security.d.ts +0 -28
  132. package/dist/tools/security.js +0 -183
  133. package/dist/tools/security.js.map +0 -1
  134. package/dist/tools/spawnAgent.d.ts +0 -19
  135. package/dist/tools/spawnAgent.js +0 -130
  136. package/dist/tools/spawnAgent.js.map +0 -1
  137. package/dist/tools/webSearch.d.ts +0 -6
  138. package/dist/tools/webSearch.js +0 -120
  139. package/dist/tools/webSearch.js.map +0 -1
  140. package/docs/01_insights_and_patterns.md +0 -27
  141. package/docs/02_edge_cases_and_mitigations.md +0 -143
  142. package/docs/03_initial_implementation_plan.md +0 -66
  143. package/docs/04_tech_stack_proposal.md +0 -20
  144. package/docs/05_prd.md +0 -87
  145. package/docs/06_user_stories.md +0 -72
  146. package/docs/07_system_architecture.md +0 -138
  147. package/docs/08_roadmap.md +0 -200
  148. package/e2b/Dockerfile +0 -26
  149. package/src/__tests__/bootstrap.test.ts +0 -111
  150. package/src/__tests__/config.test.ts +0 -97
  151. package/src/__tests__/m55.test.ts +0 -238
  152. package/src/__tests__/middleware.test.ts +0 -219
  153. package/src/__tests__/modelFactory.test.ts +0 -63
  154. package/src/__tests__/optimizations.test.ts +0 -201
  155. package/src/__tests__/promptBuilder.test.ts +0 -141
  156. package/src/__tests__/sandbox.test.ts +0 -102
  157. package/src/__tests__/security.test.ts +0 -122
  158. package/src/__tests__/streaming.test.ts +0 -82
  159. package/src/__tests__/toolRouter.test.ts +0 -52
  160. package/src/__tests__/tools.test.ts +0 -146
  161. package/src/__tests__/tracing.test.ts +0 -196
  162. package/src/agents/agentRegistry.ts +0 -69
  163. package/src/agents/agentSpec.ts +0 -67
  164. package/src/agents/builtinAgents.ts +0 -142
  165. package/src/cli/config.ts +0 -124
  166. package/src/cli/index.ts +0 -742
  167. package/src/cli/modelFactory.ts +0 -174
  168. package/src/cli/postinstall.ts +0 -28
  169. package/src/cli/providers.ts +0 -107
  170. package/src/commands/builtinCommands.ts +0 -293
  171. package/src/commands/commandRegistry.ts +0 -194
  172. package/src/core/agentLoop.d.ts.map +0 -1
  173. package/src/core/agentLoop.ts +0 -312
  174. package/src/core/autoSave.ts +0 -95
  175. package/src/core/compactor.ts +0 -252
  176. package/src/core/contextGuard.ts +0 -129
  177. package/src/core/errors.ts +0 -202
  178. package/src/core/promptBuilder.d.ts.map +0 -1
  179. package/src/core/promptBuilder.ts +0 -139
  180. package/src/core/reasoningRouter.ts +0 -121
  181. package/src/core/retry.ts +0 -75
  182. package/src/core/sessionResumer.ts +0 -90
  183. package/src/core/sessionStore.ts +0 -216
  184. package/src/core/subAgent.ts +0 -339
  185. package/src/core/tokenCounter.ts +0 -64
  186. package/src/evals/dataset.ts +0 -67
  187. package/src/evals/evaluator.ts +0 -81
  188. package/src/hitl/bridge.ts +0 -160
  189. package/src/middleware/commandSanitizer.ts +0 -60
  190. package/src/middleware/loopDetection.ts +0 -63
  191. package/src/middleware/permission.ts +0 -72
  192. package/src/middleware/pipeline.ts +0 -75
  193. package/src/middleware/preCompletion.ts +0 -94
  194. package/src/middleware/types.ts +0 -45
  195. package/src/sandbox/bootstrap.ts +0 -121
  196. package/src/sandbox/manager.ts +0 -239
  197. package/src/sandbox/sync.ts +0 -157
  198. package/src/skills/loader.ts +0 -143
  199. package/src/skills/tools.ts +0 -99
  200. package/src/skills/types.ts +0 -13
  201. package/src/test_cache.ts +0 -72
  202. package/src/tools/askUser.ts +0 -47
  203. package/src/tools/browser.ts +0 -137
  204. package/src/tools/index.d.ts.map +0 -1
  205. package/src/tools/index.ts +0 -237
  206. package/src/tools/registry.ts +0 -198
  207. package/src/tools/router.ts +0 -78
  208. package/src/tools/security.ts +0 -220
  209. package/src/tools/spawnAgent.ts +0 -158
  210. package/src/tools/webSearch.ts +0 -142
  211. package/src/tracing/analyzer.ts +0 -265
  212. package/src/tracing/langsmith.ts +0 -63
  213. package/src/tracing/sessionTracer.ts +0 -202
  214. package/src/tracing/types.ts +0 -49
  215. package/src/types/valyu.d.ts +0 -37
  216. package/src/ui/App.tsx +0 -404
  217. package/src/ui/components/HITLPrompt.tsx +0 -119
  218. package/src/ui/components/Header.tsx +0 -51
  219. package/src/ui/components/MessageBubble.tsx +0 -46
  220. package/src/ui/components/StatusBar.tsx +0 -138
  221. package/src/ui/components/StreamingText.tsx +0 -48
  222. package/src/ui/components/ToolCallPanel.tsx +0 -80
  223. package/tests/commands/commands.test.ts +0 -356
  224. package/tests/core/compactor.test.ts +0 -217
  225. package/tests/core/retryAndErrors.test.ts +0 -164
  226. package/tests/core/sessionResumer.test.ts +0 -95
  227. package/tests/core/sessionStore.test.ts +0 -84
  228. package/tests/core/stability.test.ts +0 -165
  229. package/tests/core/subAgent.test.ts +0 -238
  230. package/tests/hitl/hitlBridge.test.ts +0 -115
  231. package/tsconfig.json +0 -16
  232. package/vitest.config.ts +0 -10
  233. package/vitest.out +0 -48
  234. /package/dist/__tests__/{m55.test.d.ts → installHostDeps.test.d.ts} +0 -0
  235. /package/dist/__tests__/{middleware.test.d.ts → whitelistedBackend.test.d.ts} +0 -0
@@ -1,142 +0,0 @@
1
- import { DynamicToolInterface, ToolResult } from "./index.js";
2
-
3
- /**
4
- * Web Search Tool — wraps the Valyu AI Search SDK.
5
- *
6
- * Provides AI-optimized web search and domain-specific search
7
- * (papers, finance, patents, SEC filings, etc.). Runs on the Host
8
- * (API call, not a shell command).
9
- *
10
- * Requires a Valyu API key in config (`valyuApiKey`).
11
- */
12
-
13
- let _valyuApiKey: string | undefined;
14
-
15
- /**
16
- * Bind the Valyu API key at session start.
17
- */
18
- export function bindValyuApiKey(key: string | undefined): void {
19
- _valyuApiKey = key;
20
- }
21
-
22
- export const WebSearchTool: DynamicToolInterface = {
23
- name: "web_search",
24
- description:
25
- "Search the web for information. Supports general web search and specialized sources: " +
26
- "papers (arXiv/PubMed), finance, patents, SEC filings, companies. " +
27
- "Returns AI-optimized structured results.",
28
- schema: {
29
- type: "object",
30
- properties: {
31
- query: {
32
- type: "string",
33
- description: "The search query",
34
- },
35
- source: {
36
- type: "string",
37
- enum: ["web", "papers", "finance", "patents", "sec", "companies"],
38
- description:
39
- 'Search source (default: "web"). Use "papers" for academic, "finance" for financial data, etc.',
40
- },
41
- maxResults: {
42
- type: "number",
43
- description: "Maximum number of results to return (default: 5)",
44
- },
45
- },
46
- required: ["query"],
47
- },
48
- execute: async (args: {
49
- query: string;
50
- source?: string;
51
- maxResults?: number;
52
- }): Promise<ToolResult> => {
53
- if (!_valyuApiKey) {
54
- return {
55
- content:
56
- "Error: Valyu API key not configured.\n" +
57
- 'Run `joone config` and set your Valyu API key, or add "valyuApiKey" to ~/.joone/config.json.',
58
- isError: true
59
- };
60
- }
61
-
62
- const source = args.source ?? "web";
63
- const maxResults = args.maxResults ?? 5;
64
-
65
- try {
66
- // Dynamic import to avoid requiring the dependency at startup
67
- const { Valyu } = await import("@valyu/ai-sdk");
68
-
69
- const valyu = new Valyu({ apiKey: _valyuApiKey });
70
-
71
- let results: any;
72
-
73
- switch (source) {
74
- case "web":
75
- results = await valyu.search({
76
- query: args.query,
77
- maxResults,
78
- });
79
- break;
80
- case "papers":
81
- results = await valyu.paperSearch({
82
- query: args.query,
83
- maxResults,
84
- });
85
- break;
86
- case "finance":
87
- results = await valyu.financeSearch({
88
- query: args.query,
89
- maxResults,
90
- });
91
- break;
92
- case "patents":
93
- results = await valyu.patentSearch({
94
- query: args.query,
95
- maxResults,
96
- });
97
- break;
98
- case "sec":
99
- results = await valyu.secSearch({
100
- query: args.query,
101
- maxResults,
102
- });
103
- break;
104
- case "companies":
105
- results = await valyu.companyResearch({
106
- query: args.query,
107
- maxResults,
108
- });
109
- break;
110
- default:
111
- return {
112
- content: `Error: Unknown source "${source}". Use: web, papers, finance, patents, sec, companies.`,
113
- isError: true
114
- };
115
- }
116
-
117
- // Format results for the LLM
118
- if (!results || !results.results || results.results.length === 0) {
119
- return { content: `No results found for "${args.query}" in ${source} source.` };
120
- }
121
-
122
- const formatted = results.results
123
- .map(
124
- (r: any, i: number) =>
125
- `${i + 1}. **${r.title || "Untitled"}**\n ${r.url || ""}\n ${r.snippet || r.content || ""}`
126
- )
127
- .join("\n\n");
128
-
129
- return { content: `Search results for "${args.query}" (${source}):\n\n${formatted}` };
130
- } catch (error: any) {
131
- if (error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND") {
132
- return {
133
- content:
134
- "Error: @valyu/ai-sdk is not installed.\n" +
135
- "Run: npm install @valyu/ai-sdk",
136
- isError: true
137
- };
138
- }
139
- return { content: `Search error: ${error.message}`, isError: true };
140
- }
141
- },
142
- };
@@ -1,265 +0,0 @@
1
- import { SessionTrace } from "./types.js";
2
-
3
- /**
4
- * TraceAnalyzer — reads a SessionTrace and produces actionable insight reports.
5
- *
6
- * Analysis capabilities:
7
- * - Loop detection: Identifies repeated tool call patterns
8
- * - Cost hotspots: Flags turns that consumed >20% of total tokens
9
- * - Error clustering: Groups errors by tool
10
- * - Cache efficiency: Warns if cache hit rate < 70%
11
- * - Recommendations: Human-readable suggestions for improvement
12
- */
13
-
14
- export interface AnalysisReport {
15
- /** Human-readable title. */
16
- title: string;
17
- /** Key metrics. */
18
- metrics: Record<string, string | number>;
19
- /** Detected issues. */
20
- issues: AnalysisIssue[];
21
- /** Actionable recommendations. */
22
- recommendations: string[];
23
- }
24
-
25
- export interface AnalysisIssue {
26
- severity: "info" | "warning" | "critical";
27
- category: "loop" | "cost" | "error" | "cache" | "performance";
28
- message: string;
29
- }
30
-
31
- export class TraceAnalyzer {
32
- private trace: SessionTrace;
33
-
34
- constructor(trace: SessionTrace) {
35
- this.trace = trace;
36
- }
37
-
38
- /**
39
- * Run all analysis checks and produce a full report.
40
- */
41
- analyze(): AnalysisReport {
42
- const issues: AnalysisIssue[] = [
43
- ...this.detectLoops(),
44
- ...this.detectCostHotspots(),
45
- ...this.detectCacheIssues(),
46
- ...this.clusterErrors(),
47
- ];
48
-
49
- const recommendations = this.generateRecommendations(issues);
50
- const { summary } = this.trace;
51
-
52
- return {
53
- title: `Session Analysis: ${this.trace.sessionId}`,
54
- metrics: {
55
- "Total Tokens": summary.totalTokens,
56
- "Prompt Tokens": summary.promptTokens,
57
- "Completion Tokens": summary.completionTokens,
58
- "Estimated Cost": `$${summary.totalCost.toFixed(4)}`,
59
- "Cache Hit Rate": `${(summary.cacheHitRate * 100).toFixed(1)}%`,
60
- "Tool Calls": summary.toolCallCount,
61
- "Errors": summary.errorCount,
62
- "Turns": summary.turnCount,
63
- "Duration": `${(summary.totalDuration / 1000).toFixed(1)}s`,
64
- },
65
- issues,
66
- recommendations,
67
- };
68
- }
69
-
70
- /**
71
- * Detect repeated identical tool call patterns (potential doom-loops).
72
- */
73
- detectLoops(): AnalysisIssue[] {
74
- const issues: AnalysisIssue[] = [];
75
- const toolCalls = this.trace.events.filter((e) => e.type === "tool_call");
76
-
77
- let consecutiveCount = 1;
78
- let loopToolName: string | null = null;
79
-
80
- for (let i = 1; i < toolCalls.length; i++) {
81
- const prev = toolCalls[i - 1];
82
- const curr = toolCalls[i];
83
-
84
- if (
85
- prev.data.name === curr.data.name &&
86
- JSON.stringify(prev.data.args) === JSON.stringify(curr.data.args)
87
- ) {
88
- consecutiveCount++;
89
- loopToolName = curr.data.name;
90
- } else {
91
- if (consecutiveCount >= 3 && loopToolName) {
92
- issues.push({
93
- severity: "critical",
94
- category: "loop",
95
- message: `Doom-loop detected: "${loopToolName}" called ${consecutiveCount} times consecutively with identical args.`,
96
- });
97
- }
98
- consecutiveCount = 1;
99
- loopToolName = null;
100
- }
101
- }
102
-
103
- // Handle loop at end of array
104
- if (consecutiveCount >= 3 && loopToolName) {
105
- issues.push({
106
- severity: "critical",
107
- category: "loop",
108
- message: `Doom-loop detected: "${loopToolName}" called ${consecutiveCount} times consecutively with identical args.`,
109
- });
110
- }
111
-
112
- return issues;
113
- }
114
-
115
- /**
116
- * Find LLM calls that consumed >20% of total tokens (cost hotspots).
117
- */
118
- detectCostHotspots(): AnalysisIssue[] {
119
- const issues: AnalysisIssue[] = [];
120
- const { totalTokens } = this.trace.summary;
121
-
122
- if (totalTokens === 0) return issues;
123
-
124
- const llmCalls = this.trace.events.filter((e) => e.type === "llm_call");
125
-
126
- llmCalls.forEach((event, index) => {
127
- const callTokens =
128
- (event.data.promptTokens || 0) + (event.data.completionTokens || 0);
129
- const ratio = callTokens / totalTokens;
130
-
131
- if (ratio > 0.2) {
132
- issues.push({
133
- severity: "warning",
134
- category: "cost",
135
- message: `Turn ${index + 1} consumed ${(ratio * 100).toFixed(0)}% of total tokens (${callTokens} tokens).`,
136
- });
137
- }
138
- });
139
-
140
- return issues;
141
- }
142
-
143
- /**
144
- * Warn if cache hit rate is below 70%.
145
- */
146
- detectCacheIssues(): AnalysisIssue[] {
147
- const issues: AnalysisIssue[] = [];
148
- const { cacheHitRate, turnCount } = this.trace.summary;
149
-
150
- // Only relevant if we have enough turns
151
- if (turnCount >= 3 && cacheHitRate < 0.7) {
152
- issues.push({
153
- severity: "warning",
154
- category: "cache",
155
- message: `Cache hit rate is ${(cacheHitRate * 100).toFixed(1)}% (below 70% target). This increases cost significantly.`,
156
- });
157
- }
158
-
159
- return issues;
160
- }
161
-
162
- /**
163
- * Group errors by tool and report clusters.
164
- */
165
- clusterErrors(): AnalysisIssue[] {
166
- const issues: AnalysisIssue[] = [];
167
- const errors = this.trace.events.filter((e) => e.type === "error");
168
-
169
- if (errors.length === 0) return issues;
170
-
171
- const byTool = new Map<string, number>();
172
- for (const error of errors) {
173
- const tool = error.data.tool || "unknown";
174
- byTool.set(tool, (byTool.get(tool) || 0) + 1);
175
- }
176
-
177
- for (const [tool, count] of byTool) {
178
- if (count >= 2) {
179
- issues.push({
180
- severity: "warning",
181
- category: "error",
182
- message: `${count} errors from tool "${tool}" — may indicate a systemic issue.`,
183
- });
184
- }
185
- }
186
-
187
- return issues;
188
- }
189
-
190
- /**
191
- * Generate actionable recommendations from detected issues.
192
- */
193
- generateRecommendations(issues: AnalysisIssue[]): string[] {
194
- const recs: string[] = [];
195
-
196
- const hasLoops = issues.some((i) => i.category === "loop");
197
- const hasCostHotspots = issues.some((i) => i.category === "cost");
198
- const hasCacheIssues = issues.some((i) => i.category === "cache");
199
- const hasErrors = issues.some((i) => i.category === "error");
200
-
201
- if (hasLoops) {
202
- recs.push(
203
- "Consider lowering the LoopDetectionMiddleware threshold or adding argument variation hints to the system prompt."
204
- );
205
- }
206
-
207
- if (hasCostHotspots) {
208
- recs.push(
209
- "Consider compacting context earlier — some turns consumed a large share of the token budget."
210
- );
211
- }
212
-
213
- if (hasCacheIssues) {
214
- recs.push(
215
- "Review the static system prompt prefix — cache misses often indicate the prompt prefix is being mutated between turns."
216
- );
217
- }
218
-
219
- if (hasErrors) {
220
- recs.push(
221
- "Investigate recurring tool errors — consider adding fallback strategies or better error messages in tool implementations."
222
- );
223
- }
224
-
225
- if (issues.length === 0) {
226
- recs.push("Session looks healthy! No issues detected.");
227
- }
228
-
229
- return recs;
230
- }
231
-
232
- /**
233
- * Format the report as a human-readable string (for CLI output).
234
- */
235
- static formatReport(report: AnalysisReport): string {
236
- const lines: string[] = [];
237
-
238
- lines.push(`\n ◆ ${report.title}`);
239
- lines.push(" ─────────────────────────────────────");
240
-
241
- // Metrics
242
- lines.push("\n 📊 Metrics:");
243
- for (const [key, value] of Object.entries(report.metrics)) {
244
- lines.push(` ${key.padEnd(20)} ${value}`);
245
- }
246
-
247
- // Issues
248
- if (report.issues.length > 0) {
249
- lines.push("\n ⚠ Issues:");
250
- for (const issue of report.issues) {
251
- const icon = issue.severity === "critical" ? "🔴" : issue.severity === "warning" ? "🟡" : "🔵";
252
- lines.push(` ${icon} [${issue.category}] ${issue.message}`);
253
- }
254
- }
255
-
256
- // Recommendations
257
- lines.push("\n 💡 Recommendations:");
258
- for (const rec of report.recommendations) {
259
- lines.push(` • ${rec}`);
260
- }
261
-
262
- lines.push("");
263
- return lines.join("\n");
264
- }
265
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * LangSmith Integration — thin configuration layer.
3
- *
4
- * LangChain has built-in LangSmith tracing via environment variables.
5
- * This module reads from JooneConfig and sets the required env vars
6
- * so LangChain auto-traces to LangSmith when enabled.
7
- *
8
- * Required env vars for LangSmith:
9
- * LANGCHAIN_TRACING_V2=true
10
- * LANGCHAIN_API_KEY=<key>
11
- * LANGCHAIN_PROJECT=<project> (optional, defaults to "joone")
12
- */
13
-
14
- interface LangSmithConfig {
15
- apiKey: string;
16
- project?: string;
17
- }
18
-
19
- /**
20
- * Enables LangSmith tracing by setting the required environment variables.
21
- * LangChain will automatically detect these and send traces.
22
- */
23
- export function enableLangSmith(config: LangSmithConfig): void {
24
- process.env.LANGCHAIN_TRACING_V2 = "true";
25
- process.env.LANGCHAIN_API_KEY = config.apiKey;
26
- process.env.LANGCHAIN_PROJECT = config.project ?? "joone";
27
- }
28
-
29
- /**
30
- * Disables LangSmith tracing.
31
- */
32
- export function disableLangSmith(): void {
33
- delete process.env.LANGCHAIN_TRACING_V2;
34
- delete process.env.LANGCHAIN_API_KEY;
35
- delete process.env.LANGCHAIN_PROJECT;
36
- }
37
-
38
- /**
39
- * Checks if LangSmith tracing is currently enabled.
40
- */
41
- export function isLangSmithEnabled(): boolean {
42
- return (
43
- process.env.LANGCHAIN_TRACING_V2 === "true" &&
44
- !!process.env.LANGCHAIN_API_KEY
45
- );
46
- }
47
-
48
- /**
49
- * Attempts to enable LangSmith from JooneConfig values.
50
- * Returns true if successfully enabled.
51
- */
52
- export function tryEnableLangSmithFromConfig(config: {
53
- langsmithApiKey?: string;
54
- langsmithProject?: string;
55
- }): boolean {
56
- if (!config.langsmithApiKey) return false;
57
-
58
- enableLangSmith({
59
- apiKey: config.langsmithApiKey,
60
- project: config.langsmithProject,
61
- });
62
- return true;
63
- }
@@ -1,202 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as os from "node:os";
4
- import { TraceEvent, TraceSummary, SessionTrace } from "./types.js";
5
-
6
- /**
7
- * SessionTracer — records events during an agent session and computes metrics.
8
- *
9
- * Usage:
10
- * const tracer = new SessionTracer();
11
- * tracer.recordLLMCall({ promptTokens: 500, completionTokens: 100, cached: true, duration: 800 });
12
- * tracer.recordToolCall({ name: "bash", args: { command: "ls" }, result: "...", duration: 50 });
13
- * tracer.recordError({ message: "Timeout", tool: "bash" });
14
- * const summary = tracer.getSummary();
15
- * tracer.save(); // writes to ~/.joone/traces/{sessionId}.json
16
- */
17
- export class SessionTracer {
18
- private sessionId: string;
19
- private startedAt: number;
20
- private events: TraceEvent[] = [];
21
-
22
- constructor(sessionId?: string) {
23
- this.sessionId = sessionId ?? crypto.randomUUID();
24
- this.startedAt = Date.now();
25
- }
26
-
27
- // ─── Recording Methods ──────────────────────────────────────────────────────
28
-
29
- /**
30
- * Record an LLM call with token usage and cache information.
31
- */
32
- recordLLMCall(data: {
33
- promptTokens: number;
34
- completionTokens: number;
35
- cached: boolean;
36
- duration: number;
37
- model?: string;
38
- }): void {
39
- this.events.push({
40
- type: "llm_call",
41
- timestamp: Date.now(),
42
- duration: data.duration,
43
- data,
44
- });
45
- }
46
-
47
- /**
48
- * Record a tool execution.
49
- */
50
- recordToolCall(data: {
51
- name: string;
52
- args: Record<string, any>;
53
- result?: string;
54
- duration: number;
55
- success: boolean;
56
- }): void {
57
- this.events.push({
58
- type: "tool_call",
59
- timestamp: Date.now(),
60
- duration: data.duration,
61
- data,
62
- });
63
- }
64
-
65
- /**
66
- * Record an error.
67
- */
68
- recordError(data: {
69
- message: string;
70
- tool?: string;
71
- stack?: string;
72
- }): void {
73
- this.events.push({
74
- type: "error",
75
- timestamp: Date.now(),
76
- data,
77
- });
78
- }
79
-
80
- /**
81
- * Record a context compaction event.
82
- */
83
- recordCompaction(data: {
84
- tokensBefore: number;
85
- tokensAfter: number;
86
- messagesSummarized: number;
87
- }): void {
88
- this.events.push({
89
- type: "compaction",
90
- timestamp: Date.now(),
91
- data,
92
- });
93
- }
94
-
95
- // ─── Summary Computation ────────────────────────────────────────────────────
96
-
97
- /**
98
- * Compute aggregated metrics from all recorded events.
99
- */
100
- getSummary(): TraceSummary {
101
- let promptTokens = 0;
102
- let completionTokens = 0;
103
- let cachedPromptTokens = 0;
104
- let toolCallCount = 0;
105
- let errorCount = 0;
106
- let turnCount = 0;
107
-
108
- for (const event of this.events) {
109
- switch (event.type) {
110
- case "llm_call":
111
- promptTokens += event.data.promptTokens || 0;
112
- completionTokens += event.data.completionTokens || 0;
113
- if (event.data.cached) {
114
- cachedPromptTokens += event.data.promptTokens || 0;
115
- }
116
- turnCount++;
117
- break;
118
- case "tool_call":
119
- toolCallCount++;
120
- break;
121
- case "error":
122
- errorCount++;
123
- break;
124
- }
125
- }
126
-
127
- const totalTokens = promptTokens + completionTokens;
128
- const cacheHitRate = promptTokens > 0 ? cachedPromptTokens / promptTokens : 0;
129
-
130
- // Rough cost estimate: ~$3/1M input tokens, ~$15/1M output tokens (Claude Sonnet pricing)
131
- const totalCost =
132
- (promptTokens / 1_000_000) * 3 + (completionTokens / 1_000_000) * 15;
133
-
134
- const totalDuration = Date.now() - this.startedAt;
135
-
136
- return {
137
- totalTokens,
138
- promptTokens,
139
- completionTokens,
140
- totalCost,
141
- cacheHitRate,
142
- toolCallCount,
143
- errorCount,
144
- totalDuration,
145
- turnCount,
146
- };
147
- }
148
-
149
- // ─── Export & Persistence ───────────────────────────────────────────────────
150
-
151
- /**
152
- * Returns the full session trace as a serializable object.
153
- */
154
- export(): SessionTrace {
155
- return {
156
- sessionId: this.sessionId,
157
- startedAt: this.startedAt,
158
- endedAt: Date.now(),
159
- events: this.events,
160
- summary: this.getSummary(),
161
- };
162
- }
163
-
164
- /**
165
- * Saves the session trace to ~/.joone/traces/{sessionId}.json.
166
- */
167
- save(dir?: string): string {
168
- const tracesDir = dir ?? path.join(os.homedir(), ".joone", "traces");
169
-
170
- if (!fs.existsSync(tracesDir)) {
171
- fs.mkdirSync(tracesDir, { recursive: true });
172
- }
173
-
174
- // Sanitize sessionId to prevent path traversal
175
- const safeSessionId = path.basename(this.sessionId);
176
- const filePath = path.join(tracesDir, `${safeSessionId}.json`);
177
- fs.writeFileSync(filePath, JSON.stringify(this.export(), null, 2));
178
-
179
- return filePath;
180
- }
181
- /**
182
- * Loads a session trace from a JSON file.
183
- */
184
- static load(filePath: string): SessionTrace {
185
- const raw = fs.readFileSync(filePath, "utf-8");
186
- return JSON.parse(raw) as SessionTrace;
187
- }
188
-
189
- // ─── Accessors ─────────────────────────────────────────────────────────────
190
-
191
- getSessionId(): string {
192
- return this.sessionId;
193
- }
194
-
195
- getEvents(): readonly TraceEvent[] {
196
- return this.events;
197
- }
198
-
199
- getEventCount(): number {
200
- return this.events.length;
201
- }
202
- }