joonecli 0.1.0
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/AGENTS.md +56 -0
- package/Handover.md +115 -0
- package/LICENSE +201 -0
- package/PROGRESS.md +160 -0
- package/README.md +114 -0
- package/dist/__tests__/bootstrap.test.d.ts +1 -0
- package/dist/__tests__/bootstrap.test.js +76 -0
- package/dist/__tests__/bootstrap.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +84 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/m55.test.d.ts +1 -0
- package/dist/__tests__/m55.test.js +160 -0
- package/dist/__tests__/m55.test.js.map +1 -0
- package/dist/__tests__/middleware.test.d.ts +1 -0
- package/dist/__tests__/middleware.test.js +169 -0
- package/dist/__tests__/middleware.test.js.map +1 -0
- package/dist/__tests__/modelFactory.test.d.ts +1 -0
- package/dist/__tests__/modelFactory.test.js +50 -0
- package/dist/__tests__/modelFactory.test.js.map +1 -0
- package/dist/__tests__/optimizations.test.d.ts +1 -0
- package/dist/__tests__/optimizations.test.js +136 -0
- package/dist/__tests__/optimizations.test.js.map +1 -0
- package/dist/__tests__/promptBuilder.test.d.ts +1 -0
- package/dist/__tests__/promptBuilder.test.js +108 -0
- package/dist/__tests__/promptBuilder.test.js.map +1 -0
- package/dist/__tests__/sandbox.test.d.ts +1 -0
- package/dist/__tests__/sandbox.test.js +78 -0
- package/dist/__tests__/sandbox.test.js.map +1 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.js +86 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/streaming.test.d.ts +1 -0
- package/dist/__tests__/streaming.test.js +71 -0
- package/dist/__tests__/streaming.test.js.map +1 -0
- package/dist/__tests__/toolRouter.test.d.ts +1 -0
- package/dist/__tests__/toolRouter.test.js +37 -0
- package/dist/__tests__/toolRouter.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +1 -0
- package/dist/__tests__/tools.test.js +112 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/__tests__/tracing.test.d.ts +1 -0
- package/dist/__tests__/tracing.test.js +147 -0
- package/dist/__tests__/tracing.test.js.map +1 -0
- package/dist/cli/config.d.ts +49 -0
- package/dist/cli/config.js +86 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +625 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/modelFactory.d.ts +9 -0
- package/dist/cli/modelFactory.js +154 -0
- package/dist/cli/modelFactory.js.map +1 -0
- package/dist/cli/providers.d.ts +18 -0
- package/dist/cli/providers.js +94 -0
- package/dist/cli/providers.js.map +1 -0
- package/dist/core/agentLoop.d.ts +43 -0
- package/dist/core/agentLoop.js +245 -0
- package/dist/core/agentLoop.js.map +1 -0
- package/dist/core/errors.d.ts +62 -0
- package/dist/core/errors.js +139 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/promptBuilder.d.ts +49 -0
- package/dist/core/promptBuilder.js +84 -0
- package/dist/core/promptBuilder.js.map +1 -0
- package/dist/core/reasoningRouter.d.ts +62 -0
- package/dist/core/reasoningRouter.js +102 -0
- package/dist/core/reasoningRouter.js.map +1 -0
- package/dist/core/retry.d.ts +25 -0
- package/dist/core/retry.js +49 -0
- package/dist/core/retry.js.map +1 -0
- package/dist/core/sessionResumer.d.ts +17 -0
- package/dist/core/sessionResumer.js +78 -0
- package/dist/core/sessionResumer.js.map +1 -0
- package/dist/core/sessionStore.d.ts +45 -0
- package/dist/core/sessionStore.js +167 -0
- package/dist/core/sessionStore.js.map +1 -0
- package/dist/core/tokenCounter.d.ts +17 -0
- package/dist/core/tokenCounter.js +54 -0
- package/dist/core/tokenCounter.js.map +1 -0
- package/dist/evals/dataset.d.ts +4 -0
- package/dist/evals/dataset.js +61 -0
- package/dist/evals/dataset.js.map +1 -0
- package/dist/evals/evaluator.d.ts +21 -0
- package/dist/evals/evaluator.js +68 -0
- package/dist/evals/evaluator.js.map +1 -0
- package/dist/hitl/bridge.d.ts +65 -0
- package/dist/hitl/bridge.js +120 -0
- package/dist/hitl/bridge.js.map +1 -0
- package/dist/middleware/commandSanitizer.d.ts +18 -0
- package/dist/middleware/commandSanitizer.js +50 -0
- package/dist/middleware/commandSanitizer.js.map +1 -0
- package/dist/middleware/loopDetection.d.ts +28 -0
- package/dist/middleware/loopDetection.js +49 -0
- package/dist/middleware/loopDetection.js.map +1 -0
- package/dist/middleware/permission.d.ts +17 -0
- package/dist/middleware/permission.js +59 -0
- package/dist/middleware/permission.js.map +1 -0
- package/dist/middleware/pipeline.d.ts +31 -0
- package/dist/middleware/pipeline.js +62 -0
- package/dist/middleware/pipeline.js.map +1 -0
- package/dist/middleware/preCompletion.d.ts +29 -0
- package/dist/middleware/preCompletion.js +82 -0
- package/dist/middleware/preCompletion.js.map +1 -0
- package/dist/middleware/types.d.ts +40 -0
- package/dist/middleware/types.js +8 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/sandbox/bootstrap.d.ts +38 -0
- package/dist/sandbox/bootstrap.js +107 -0
- package/dist/sandbox/bootstrap.js.map +1 -0
- package/dist/sandbox/manager.d.ts +72 -0
- package/dist/sandbox/manager.js +180 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/sync.d.ts +55 -0
- package/dist/sandbox/sync.js +135 -0
- package/dist/sandbox/sync.js.map +1 -0
- package/dist/skills/loader.d.ts +55 -0
- package/dist/skills/loader.js +132 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/tools.d.ts +5 -0
- package/dist/skills/tools.js +78 -0
- package/dist/skills/tools.js.map +1 -0
- package/dist/skills/types.d.ts +13 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/test_cache.d.ts +1 -0
- package/dist/test_cache.js +55 -0
- package/dist/test_cache.js.map +1 -0
- package/dist/test_google.js +93 -0
- package/dist/tools/askUser.d.ts +10 -0
- package/dist/tools/askUser.js +42 -0
- package/dist/tools/askUser.js.map +1 -0
- package/dist/tools/browser.d.ts +19 -0
- package/dist/tools/browser.js +111 -0
- package/dist/tools/browser.js.map +1 -0
- package/dist/tools/index.d.ts +27 -0
- package/dist/tools/index.js +184 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +31 -0
- package/dist/tools/registry.js +168 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/router.d.ts +34 -0
- package/dist/tools/router.js +73 -0
- package/dist/tools/router.js.map +1 -0
- package/dist/tools/security.d.ts +28 -0
- package/dist/tools/security.js +183 -0
- package/dist/tools/security.js.map +1 -0
- package/dist/tools/webSearch.d.ts +6 -0
- package/dist/tools/webSearch.js +120 -0
- package/dist/tools/webSearch.js.map +1 -0
- package/dist/tracing/analyzer.d.ts +58 -0
- package/dist/tracing/analyzer.js +190 -0
- package/dist/tracing/analyzer.js.map +1 -0
- package/dist/tracing/langsmith.d.ts +38 -0
- package/dist/tracing/langsmith.js +50 -0
- package/dist/tracing/langsmith.js.map +1 -0
- package/dist/tracing/sessionTracer.d.ts +73 -0
- package/dist/tracing/sessionTracer.js +157 -0
- package/dist/tracing/sessionTracer.js.map +1 -0
- package/dist/tracing/types.d.ts +46 -0
- package/dist/tracing/types.js +5 -0
- package/dist/tracing/types.js.map +1 -0
- package/dist/ui/App.d.ts +24 -0
- package/dist/ui/App.js +172 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/components/HITLPrompt.d.ts +15 -0
- package/dist/ui/components/HITLPrompt.js +35 -0
- package/dist/ui/components/HITLPrompt.js.map +1 -0
- package/dist/ui/components/Header.d.ts +8 -0
- package/dist/ui/components/Header.js +6 -0
- package/dist/ui/components/Header.js.map +1 -0
- package/dist/ui/components/MessageBubble.d.ts +13 -0
- package/dist/ui/components/MessageBubble.js +17 -0
- package/dist/ui/components/MessageBubble.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +21 -0
- package/dist/ui/components/StatusBar.js +34 -0
- package/dist/ui/components/StatusBar.js.map +1 -0
- package/dist/ui/components/StreamingText.d.ts +13 -0
- package/dist/ui/components/StreamingText.js +24 -0
- package/dist/ui/components/StreamingText.js.map +1 -0
- package/dist/ui/components/ToolCallPanel.d.ts +15 -0
- package/dist/ui/components/ToolCallPanel.js +18 -0
- package/dist/ui/components/ToolCallPanel.js.map +1 -0
- package/docs/01_insights_and_patterns.md +27 -0
- package/docs/02_edge_cases_and_mitigations.md +143 -0
- package/docs/03_initial_implementation_plan.md +66 -0
- package/docs/04_tech_stack_proposal.md +20 -0
- package/docs/05_prd.md +87 -0
- package/docs/06_user_stories.md +72 -0
- package/docs/07_system_architecture.md +138 -0
- package/docs/08_roadmap.md +200 -0
- package/e2b/Dockerfile +26 -0
- package/package.json +57 -0
- package/src/__tests__/bootstrap.test.ts +111 -0
- package/src/__tests__/config.test.ts +97 -0
- package/src/__tests__/m55.test.ts +238 -0
- package/src/__tests__/middleware.test.ts +219 -0
- package/src/__tests__/modelFactory.test.ts +63 -0
- package/src/__tests__/optimizations.test.ts +201 -0
- package/src/__tests__/promptBuilder.test.ts +141 -0
- package/src/__tests__/sandbox.test.ts +102 -0
- package/src/__tests__/security.test.ts +122 -0
- package/src/__tests__/streaming.test.ts +82 -0
- package/src/__tests__/toolRouter.test.ts +52 -0
- package/src/__tests__/tools.test.ts +146 -0
- package/src/__tests__/tracing.test.ts +196 -0
- package/src/agents/agentRegistry.ts +69 -0
- package/src/agents/agentSpec.ts +67 -0
- package/src/agents/builtinAgents.ts +142 -0
- package/src/cli/config.ts +124 -0
- package/src/cli/index.ts +730 -0
- package/src/cli/modelFactory.ts +174 -0
- package/src/cli/providers.ts +107 -0
- package/src/commands/builtinCommands.ts +293 -0
- package/src/commands/commandRegistry.ts +194 -0
- package/src/core/agentLoop.d.ts.map +1 -0
- package/src/core/agentLoop.ts +312 -0
- package/src/core/autoSave.ts +95 -0
- package/src/core/compactor.ts +252 -0
- package/src/core/contextGuard.ts +129 -0
- package/src/core/errors.ts +202 -0
- package/src/core/promptBuilder.d.ts.map +1 -0
- package/src/core/promptBuilder.ts +139 -0
- package/src/core/reasoningRouter.ts +121 -0
- package/src/core/retry.ts +75 -0
- package/src/core/sessionResumer.ts +90 -0
- package/src/core/sessionStore.ts +215 -0
- package/src/core/subAgent.ts +339 -0
- package/src/core/tokenCounter.ts +64 -0
- package/src/evals/dataset.ts +67 -0
- package/src/evals/evaluator.ts +81 -0
- package/src/hitl/bridge.ts +160 -0
- package/src/middleware/commandSanitizer.ts +60 -0
- package/src/middleware/loopDetection.ts +63 -0
- package/src/middleware/permission.ts +72 -0
- package/src/middleware/pipeline.ts +75 -0
- package/src/middleware/preCompletion.ts +94 -0
- package/src/middleware/types.ts +45 -0
- package/src/sandbox/bootstrap.ts +121 -0
- package/src/sandbox/manager.ts +239 -0
- package/src/sandbox/sync.ts +157 -0
- package/src/skills/loader.ts +143 -0
- package/src/skills/tools.ts +99 -0
- package/src/skills/types.ts +13 -0
- package/src/test_cache.ts +72 -0
- package/src/test_google.js +40 -0
- package/src/test_google.ts +40 -0
- package/src/tools/askUser.ts +47 -0
- package/src/tools/browser.ts +137 -0
- package/src/tools/index.d.ts.map +1 -0
- package/src/tools/index.ts +237 -0
- package/src/tools/registry.ts +198 -0
- package/src/tools/router.ts +78 -0
- package/src/tools/security.ts +220 -0
- package/src/tools/spawnAgent.ts +158 -0
- package/src/tools/webSearch.ts +142 -0
- package/src/tracing/analyzer.ts +265 -0
- package/src/tracing/langsmith.ts +63 -0
- package/src/tracing/sessionTracer.ts +202 -0
- package/src/tracing/types.ts +49 -0
- package/src/types/valyu.d.ts +37 -0
- package/src/ui/App.tsx +404 -0
- package/src/ui/components/HITLPrompt.tsx +119 -0
- package/src/ui/components/Header.tsx +51 -0
- package/src/ui/components/MessageBubble.tsx +46 -0
- package/src/ui/components/StatusBar.tsx +138 -0
- package/src/ui/components/StreamingText.tsx +48 -0
- package/src/ui/components/ToolCallPanel.tsx +80 -0
- package/tests/commands/commands.test.ts +356 -0
- package/tests/core/compactor.test.ts +217 -0
- package/tests/core/retryAndErrors.test.ts +164 -0
- package/tests/core/sessionResumer.test.ts +95 -0
- package/tests/core/sessionStore.test.ts +84 -0
- package/tests/core/stability.test.ts +165 -0
- package/tests/core/subAgent.test.ts +238 -0
- package/tests/hitl/hitlBridge.test.ts +115 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +10 -0
- package/vitest.out +48 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { AIMessage, SystemMessage } from "@langchain/core/messages";
|
|
4
|
+
export class SessionResumer {
|
|
5
|
+
workspaceDir;
|
|
6
|
+
constructor(workspaceDir) {
|
|
7
|
+
this.workspaceDir = workspaceDir;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Prepares a loaded session state for execution by detecting external drift
|
|
11
|
+
* and injecting the Wakeup prompt so the LLM knows it is inside a fresh sandbox.
|
|
12
|
+
*/
|
|
13
|
+
prepareForResume(payload) {
|
|
14
|
+
const state = { ...payload.state };
|
|
15
|
+
// 1. Detect File Drift
|
|
16
|
+
const driftedFiles = this.detectFileDrift(state, payload.header.lastSavedAt);
|
|
17
|
+
// 2. Formulate the Sandbox Amnesia & Drift Wakeup prompt
|
|
18
|
+
let wakeupPrompt = `[SYSTEM NOTIFICATION: SESSION RESUMED]\n`;
|
|
19
|
+
wakeupPrompt += `You were paused and have just been loaded into a **NEW** execution session. \n`;
|
|
20
|
+
wakeupPrompt += `IMPORTANT CONTEXT:\n`;
|
|
21
|
+
wakeupPrompt += `- The execution Sandbox is completely fresh. Any background processes, dev servers, or in-memory databases you were running previously are GONE. You must restart them if needed.\n`;
|
|
22
|
+
if (driftedFiles.length > 0) {
|
|
23
|
+
wakeupPrompt += `- The following files were mutated externally on the host machine while you were offline:\n`;
|
|
24
|
+
for (const file of driftedFiles) {
|
|
25
|
+
wakeupPrompt += ` - \`${file}\`\n`;
|
|
26
|
+
}
|
|
27
|
+
wakeupPrompt += `Before modifying these files using replace_file_content, you MUST re-read them using view_code_item or view_file to understand the external changes.\n`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
wakeupPrompt += `- No files in your active context appear to have been edited on the host while you were paused.\n`;
|
|
31
|
+
}
|
|
32
|
+
// Inject as a System Message at the very end of the history
|
|
33
|
+
// so it acts as an immediate reminder before the next LLM generation.
|
|
34
|
+
state.conversationHistory.push(new SystemMessage(wakeupPrompt));
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Analyzes the conversation history for any files the agent has interacted with.
|
|
39
|
+
* Checks their `mtime` against the session `lastSavedAt`.
|
|
40
|
+
* If the file on disk is newer, it has drifted.
|
|
41
|
+
*/
|
|
42
|
+
detectFileDrift(state, lastSavedAt) {
|
|
43
|
+
const interactedFiles = new Set();
|
|
44
|
+
// We deduce file interaction by looking at what tools were called
|
|
45
|
+
for (const msg of state.conversationHistory) {
|
|
46
|
+
if (msg instanceof AIMessage && msg.tool_calls) {
|
|
47
|
+
for (const call of msg.tool_calls) {
|
|
48
|
+
if (call.name === "read_file" || call.name === "write_file" || call.name === "replace_file_content" || call.name === "multi_replace_file_content" || call.name === "view_file") {
|
|
49
|
+
let targetPath = "";
|
|
50
|
+
if (call.args.path)
|
|
51
|
+
targetPath = call.args.path;
|
|
52
|
+
if (call.args.AbsolutePath)
|
|
53
|
+
targetPath = call.args.AbsolutePath;
|
|
54
|
+
if (call.args.TargetFile)
|
|
55
|
+
targetPath = call.args.TargetFile;
|
|
56
|
+
if (targetPath) {
|
|
57
|
+
interactedFiles.add(targetPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const drifted = [];
|
|
64
|
+
for (const file of interactedFiles) {
|
|
65
|
+
// Because paths might be absolute or relative, attempt resolution
|
|
66
|
+
const absolutePath = path.isAbsolute(file) ? file : path.resolve(this.workspaceDir, file);
|
|
67
|
+
if (fs.existsSync(absolutePath)) {
|
|
68
|
+
const stats = fs.statSync(absolutePath);
|
|
69
|
+
// If the file's modification time is STRICTLY greater than the save time, it drifted
|
|
70
|
+
if (stats.mtimeMs > lastSavedAt) {
|
|
71
|
+
drifted.push(file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return drifted;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=sessionResumer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionResumer.js","sourceRoot":"","sources":["../../src/core/sessionResumer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ContextState } from "./promptBuilder.js";
|
|
2
|
+
export interface SessionHeader {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
startedAt: number;
|
|
5
|
+
lastSavedAt: number;
|
|
6
|
+
provider: string;
|
|
7
|
+
model: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionStatePayload {
|
|
11
|
+
header: SessionHeader;
|
|
12
|
+
state: ContextState;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Serializes and deserializes agent conversation history to streaming JSONL files.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SessionStore {
|
|
18
|
+
constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Serializes a LangChain BaseMessage instance into a raw JSON object.
|
|
21
|
+
*/
|
|
22
|
+
private serializeMessage;
|
|
23
|
+
/**
|
|
24
|
+
* Rehydrates a raw JSON object back into a LangChain BaseMessage class instance.
|
|
25
|
+
*/
|
|
26
|
+
private deserializeMessage;
|
|
27
|
+
/**
|
|
28
|
+
* Saves the entire session state cleanly to a .jsonl file, overwriting the previous save.
|
|
29
|
+
* We don't append to avoid partial mid-turn corruption. Since we compact the context,
|
|
30
|
+
* the file size remains extremely small.
|
|
31
|
+
*/
|
|
32
|
+
saveSession(sessionId: string, state: ContextState, provider: string, model: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Loads the session state from disk.
|
|
35
|
+
*/
|
|
36
|
+
loadSession(sessionId: string): Promise<SessionStatePayload>;
|
|
37
|
+
/**
|
|
38
|
+
* Only reads the first line of the given session file to quickly extract the header.
|
|
39
|
+
*/
|
|
40
|
+
private loadHeader;
|
|
41
|
+
/**
|
|
42
|
+
* Lists all saved sessions, sorted by most recently saved.
|
|
43
|
+
*/
|
|
44
|
+
listSessions(): Promise<SessionHeader[]>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
|
|
6
|
+
// Ensure the sessions directory exists
|
|
7
|
+
const SESSIONS_DIR = path.join(os.homedir(), ".joone", "sessions");
|
|
8
|
+
/**
|
|
9
|
+
* Serializes and deserializes agent conversation history to streaming JSONL files.
|
|
10
|
+
*/
|
|
11
|
+
export class SessionStore {
|
|
12
|
+
constructor() {
|
|
13
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
14
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Serializes a LangChain BaseMessage instance into a raw JSON object.
|
|
19
|
+
*/
|
|
20
|
+
serializeMessage(msg) {
|
|
21
|
+
const base = {
|
|
22
|
+
type: msg._getType(),
|
|
23
|
+
content: msg.content,
|
|
24
|
+
};
|
|
25
|
+
if (msg instanceof AIMessage && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
26
|
+
base.tool_calls = msg.tool_calls;
|
|
27
|
+
}
|
|
28
|
+
if (msg instanceof ToolMessage) {
|
|
29
|
+
base.tool_call_id = msg.tool_call_id;
|
|
30
|
+
}
|
|
31
|
+
return base;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rehydrates a raw JSON object back into a LangChain BaseMessage class instance.
|
|
35
|
+
*/
|
|
36
|
+
deserializeMessage(raw) {
|
|
37
|
+
switch (raw.type) {
|
|
38
|
+
case "human":
|
|
39
|
+
return new HumanMessage(raw.content);
|
|
40
|
+
case "ai":
|
|
41
|
+
return new AIMessage({
|
|
42
|
+
content: raw.content,
|
|
43
|
+
tool_calls: raw.tool_calls || undefined,
|
|
44
|
+
});
|
|
45
|
+
case "system":
|
|
46
|
+
return new SystemMessage(raw.content);
|
|
47
|
+
case "tool":
|
|
48
|
+
return new ToolMessage({
|
|
49
|
+
content: raw.content,
|
|
50
|
+
tool_call_id: raw.tool_call_id,
|
|
51
|
+
});
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Unknown message type in session history: ${raw.type}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Saves the entire session state cleanly to a .jsonl file, overwriting the previous save.
|
|
58
|
+
* We don't append to avoid partial mid-turn corruption. Since we compact the context,
|
|
59
|
+
* the file size remains extremely small.
|
|
60
|
+
*/
|
|
61
|
+
async saveSession(sessionId, state, provider, model) {
|
|
62
|
+
const filePath = path.join(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
63
|
+
let description = "Empty session";
|
|
64
|
+
if (state.conversationHistory.length > 0) {
|
|
65
|
+
const firstMsg = state.conversationHistory.find(m => m instanceof HumanMessage);
|
|
66
|
+
if (firstMsg && typeof firstMsg.content === "string") {
|
|
67
|
+
description = firstMsg.content.substring(0, 100).replace(/\n/g, " ") + "...";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const header = {
|
|
71
|
+
sessionId,
|
|
72
|
+
startedAt: fs.existsSync(filePath) ? (await this.loadHeader(sessionId))?.startedAt || Date.now() : Date.now(),
|
|
73
|
+
lastSavedAt: Date.now(),
|
|
74
|
+
provider,
|
|
75
|
+
model,
|
|
76
|
+
description,
|
|
77
|
+
};
|
|
78
|
+
const writeStream = fs.createWriteStream(filePath, { encoding: "utf8" });
|
|
79
|
+
// Line 1: Header + System State
|
|
80
|
+
const payload = {
|
|
81
|
+
header,
|
|
82
|
+
globalSystemInstructions: state.globalSystemInstructions,
|
|
83
|
+
projectMemory: state.projectMemory,
|
|
84
|
+
sessionContext: state.sessionContext,
|
|
85
|
+
};
|
|
86
|
+
writeStream.write(JSON.stringify({ __type: "header", ...payload }) + "\n");
|
|
87
|
+
// Line 2+: conversationHistory messages
|
|
88
|
+
for (const msg of state.conversationHistory) {
|
|
89
|
+
writeStream.write(JSON.stringify(this.serializeMessage(msg)) + "\n");
|
|
90
|
+
}
|
|
91
|
+
await new Promise((resolve) => writeStream.end(resolve));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Loads the session state from disk.
|
|
95
|
+
*/
|
|
96
|
+
async loadSession(sessionId) {
|
|
97
|
+
const filePath = path.join(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
98
|
+
if (!fs.existsSync(filePath)) {
|
|
99
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
100
|
+
}
|
|
101
|
+
const readStream = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
102
|
+
const rl = readline.createInterface({ input: readStream, crlfDelay: Infinity });
|
|
103
|
+
let headerData = null;
|
|
104
|
+
const conversationHistory = [];
|
|
105
|
+
for await (const line of rl) {
|
|
106
|
+
if (!line.trim())
|
|
107
|
+
continue;
|
|
108
|
+
const parsed = JSON.parse(line);
|
|
109
|
+
if (parsed.__type === "header") {
|
|
110
|
+
headerData = parsed;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
conversationHistory.push(this.deserializeMessage(parsed));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!headerData) {
|
|
117
|
+
throw new Error(`Malformed session file: Missing header block in ${sessionId}.jsonl`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
header: headerData.header,
|
|
121
|
+
state: {
|
|
122
|
+
globalSystemInstructions: headerData.globalSystemInstructions,
|
|
123
|
+
projectMemory: headerData.projectMemory,
|
|
124
|
+
sessionContext: headerData.sessionContext,
|
|
125
|
+
conversationHistory,
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Only reads the first line of the given session file to quickly extract the header.
|
|
131
|
+
*/
|
|
132
|
+
async loadHeader(sessionId) {
|
|
133
|
+
const filePath = path.join(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
134
|
+
if (!fs.existsSync(filePath))
|
|
135
|
+
return null;
|
|
136
|
+
const readStream = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
137
|
+
const rl = readline.createInterface({ input: readStream, crlfDelay: Infinity });
|
|
138
|
+
for await (const line of rl) {
|
|
139
|
+
if (!line.trim())
|
|
140
|
+
continue;
|
|
141
|
+
const parsed = JSON.parse(line);
|
|
142
|
+
if (parsed.__type === "header") {
|
|
143
|
+
readStream.close();
|
|
144
|
+
return parsed.header;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Lists all saved sessions, sorted by most recently saved.
|
|
151
|
+
*/
|
|
152
|
+
async listSessions() {
|
|
153
|
+
if (!fs.existsSync(SESSIONS_DIR))
|
|
154
|
+
return [];
|
|
155
|
+
const files = fs.readdirSync(SESSIONS_DIR).filter(f => f.endsWith(".jsonl"));
|
|
156
|
+
const sessions = [];
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
const sessionId = file.replace(".jsonl", "");
|
|
159
|
+
const header = await this.loadHeader(sessionId);
|
|
160
|
+
if (header) {
|
|
161
|
+
sessions.push(header);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return sessions.sort((a, b) => b.lastSavedAt - a.lastSavedAt);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=sessionStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStore.js","sourceRoot":"","sources":["../../src/core/sessionStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
/**
|
|
3
|
+
* Estimates the token count for a string.
|
|
4
|
+
*/
|
|
5
|
+
export declare function estimateTokens(text: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* Estimates the total token count across a list of messages.
|
|
8
|
+
*/
|
|
9
|
+
export declare function countMessageTokens(messages: BaseMessage[]): number;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if the message history is approaching the context window limit.
|
|
12
|
+
*
|
|
13
|
+
* @param messages - The current conversation messages.
|
|
14
|
+
* @param maxTokens - The model's context window size.
|
|
15
|
+
* @param threshold - Fraction of capacity to trigger compaction (default: 0.8 = 80%).
|
|
16
|
+
*/
|
|
17
|
+
export declare function isNearCapacity(messages: BaseMessage[], maxTokens: number, threshold?: number): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight token counter using character-based heuristic.
|
|
3
|
+
*
|
|
4
|
+
* Approximation: ~4 characters per token for English text.
|
|
5
|
+
* This avoids a dependency on tiktoken while being accurate enough
|
|
6
|
+
* for capacity threshold decisions (~90% accuracy for English).
|
|
7
|
+
*
|
|
8
|
+
* For production accuracy, swap to tiktoken with the appropriate
|
|
9
|
+
* model-specific encoding.
|
|
10
|
+
*/
|
|
11
|
+
const CHARS_PER_TOKEN = 4;
|
|
12
|
+
/**
|
|
13
|
+
* Estimates the token count for a string.
|
|
14
|
+
*/
|
|
15
|
+
export function estimateTokens(text) {
|
|
16
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Estimates the total token count across a list of messages.
|
|
20
|
+
*/
|
|
21
|
+
export function countMessageTokens(messages) {
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (const msg of messages) {
|
|
24
|
+
if (typeof msg.content === "string") {
|
|
25
|
+
total += estimateTokens(msg.content);
|
|
26
|
+
}
|
|
27
|
+
else if (Array.isArray(msg.content)) {
|
|
28
|
+
// Handle multi-part messages (text + tool calls)
|
|
29
|
+
for (const part of msg.content) {
|
|
30
|
+
if (typeof part === "string") {
|
|
31
|
+
total += estimateTokens(part);
|
|
32
|
+
}
|
|
33
|
+
else if ("text" in part && typeof part.text === "string") {
|
|
34
|
+
total += estimateTokens(part.text);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Account for role/name overhead (~4 tokens per message)
|
|
39
|
+
total += 4;
|
|
40
|
+
}
|
|
41
|
+
return total;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the message history is approaching the context window limit.
|
|
45
|
+
*
|
|
46
|
+
* @param messages - The current conversation messages.
|
|
47
|
+
* @param maxTokens - The model's context window size.
|
|
48
|
+
* @param threshold - Fraction of capacity to trigger compaction (default: 0.8 = 80%).
|
|
49
|
+
*/
|
|
50
|
+
export function isNearCapacity(messages, maxTokens, threshold = 0.8) {
|
|
51
|
+
const used = countMessageTokens(messages);
|
|
52
|
+
return used >= maxTokens * threshold;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=tokenCounter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenCounter.js","sourceRoot":"","sources":["../../src/core/tokenCounter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAuB;IACxD,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACpC,KAAK,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,iDAAiD;YACjD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC7B,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;gBAChC,CAAC;qBAAM,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC3D,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAED,yDAAyD;QACzD,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAuB,EACvB,SAAiB,EACjB,SAAS,GAAG,GAAG;IAEf,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,IAAI,IAAI,SAAS,GAAG,SAAS,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Client } from "langsmith";
|
|
2
|
+
const client = new Client();
|
|
3
|
+
const DATASET_NAME = "joone-baseline-v1";
|
|
4
|
+
/**
|
|
5
|
+
* Definition of our baseline evaluation dataset.
|
|
6
|
+
*/
|
|
7
|
+
const BASELINE_EXAMPLES = [
|
|
8
|
+
{
|
|
9
|
+
inputs: {
|
|
10
|
+
instruction: "Write a python script that calculates the 10th fibonacci number and saves the result to /workspace/fib_result.txt",
|
|
11
|
+
},
|
|
12
|
+
outputs: {
|
|
13
|
+
expected_file: "/workspace/fib_result.txt",
|
|
14
|
+
expected_content: "55\n", // 0,1,1,2,3,5,8,13,21,34,55
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
inputs: {
|
|
19
|
+
instruction: `Create a TypeScript file at /workspace/math.ts with a function 'add(a: number, b: number)' that returns their sum.
|
|
20
|
+
Then write a test file at /workspace/math.test.ts using the 'node:assert' module.
|
|
21
|
+
Finally, use the bash tool to run 'npx tsx math.test.ts' to verify it passes.`,
|
|
22
|
+
},
|
|
23
|
+
outputs: {
|
|
24
|
+
expected_file: "/workspace/math.ts",
|
|
25
|
+
expected_test_execution: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
inputs: {
|
|
30
|
+
instruction: "List all files in the current project root directory and save the output to /workspace/ls.txt",
|
|
31
|
+
},
|
|
32
|
+
outputs: {
|
|
33
|
+
expected_file: "/workspace/ls.txt",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Programmatically creates the baseline dataset in LangSmith if it doesn't already exist.
|
|
39
|
+
*/
|
|
40
|
+
export async function ensureBaselineDataset() {
|
|
41
|
+
try {
|
|
42
|
+
const dataset = await client.readDataset({ datasetName: DATASET_NAME });
|
|
43
|
+
console.log(`[Eval] Dataset '${DATASET_NAME}' already exists (ID: ${dataset.id}).`);
|
|
44
|
+
return DATASET_NAME;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error?.message?.includes("not found") || error?.status === 404) {
|
|
48
|
+
console.log(`[Eval] Creating dataset '${DATASET_NAME}' from scratch...`);
|
|
49
|
+
const dataset = await client.createDataset(DATASET_NAME, {
|
|
50
|
+
description: "Baseline tasks to evaluate Joone's core sandbox, tool routing, and reasoning precision.",
|
|
51
|
+
});
|
|
52
|
+
for (const example of BASELINE_EXAMPLES) {
|
|
53
|
+
await client.createExample(example.inputs, example.outputs, { datasetId: dataset.id });
|
|
54
|
+
}
|
|
55
|
+
console.log(`[Eval] Successfully seeded dataset '${DATASET_NAME}' with ${BASELINE_EXAMPLES.length} examples.`);
|
|
56
|
+
return DATASET_NAME;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset.js","sourceRoot":"","sources":["../../src/evals/dataset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;AAC5B,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAEzC;;GAEG;AACH,MAAM,iBAAiB,GAAG;IACxB;QACE,MAAM,EAAE;YACN,WAAW,EAAE,mHAAmH;SACjI;QACD,OAAO,EAAE;YACP,aAAa,EAAE,2BAA2B;YAC1C,gBAAgB,EAAE,MAAM,EAAE,4BAA4B;SACvD;KACF;IACD;QACE,MAAM,EAAE;YACN,WAAW,EAAE;;8EAE2D;SACzE;QACD,OAAO,EAAE;YACP,aAAa,EAAE,oBAAoB;YACnC,uBAAuB,EAAE,IAAI;SAC9B;KACF;IACD;QACE,MAAM,EAAE;YACN,WAAW,EAAE,+FAA+F;SAC7G;QACD,OAAO,EAAE;YACP,aAAa,EAAE,mBAAmB;SACnC;KACF;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,mBAAmB,YAAY,yBAAyB,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;QACpF,OAAO,YAAY,CAAC;IACtB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAI,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,IAAI,KAAK,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,4BAA4B,YAAY,mBAAmB,CAAC,CAAC;YACzE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE;gBACvD,WAAW,EAAE,yFAAyF;aACvG,CAAC,CAAC;YAEH,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;gBACxC,MAAM,MAAM,CAAC,aAAa,CACxB,OAAO,CAAC,MAAM,EACd,OAAO,CAAC,OAAO,EACf,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CAC1B,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,uCAAuC,YAAY,UAAU,iBAAiB,CAAC,MAAM,YAAY,CAAC,CAAC;YAC/G,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Run, Example } from "langsmith";
|
|
2
|
+
import { EvaluationResult } from "langsmith/evaluation";
|
|
3
|
+
/**
|
|
4
|
+
* Custom evaluator: Success Validator
|
|
5
|
+
* Checks if the agent crashed or returned a fatal error trace.
|
|
6
|
+
*/
|
|
7
|
+
export declare function successEvaluator(run: Run, example?: Example): Promise<EvaluationResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Custom evaluator: Cache Efficiency
|
|
10
|
+
* Checks if the run utilized Anthropic Prompt Caching efficiently (> 70%).
|
|
11
|
+
*
|
|
12
|
+
* Note: Requires the LLM to emit `cache_creation_input_tokens` and `cache_read_input_tokens`
|
|
13
|
+
* in its usage metadata payload, which is currently extracted by the SessionTracer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cacheEfficiencyEvaluator(run: Run, example?: Example): Promise<EvaluationResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Custom evaluator: Output Artifact Check
|
|
18
|
+
* Verifies if the file the agent was instructed to create actually exists
|
|
19
|
+
* in the Sandbox after execution.
|
|
20
|
+
*/
|
|
21
|
+
export declare function filePresenceEvaluator(run: Run, example?: Example): Promise<EvaluationResult>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom evaluator: Success Validator
|
|
3
|
+
* Checks if the agent crashed or returned a fatal error trace.
|
|
4
|
+
*/
|
|
5
|
+
export async function successEvaluator(run, example) {
|
|
6
|
+
// If the trace has an error field, the harness threw an unhandled exception.
|
|
7
|
+
const isError = !!run.error;
|
|
8
|
+
return {
|
|
9
|
+
key: "execution_success",
|
|
10
|
+
score: isError ? 0 : 1,
|
|
11
|
+
comment: isError ? run.error : "Agent completed execution loop cleanly.",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Custom evaluator: Cache Efficiency
|
|
16
|
+
* Checks if the run utilized Anthropic Prompt Caching efficiently (> 70%).
|
|
17
|
+
*
|
|
18
|
+
* Note: Requires the LLM to emit `cache_creation_input_tokens` and `cache_read_input_tokens`
|
|
19
|
+
* in its usage metadata payload, which is currently extracted by the SessionTracer.
|
|
20
|
+
*/
|
|
21
|
+
export async function cacheEfficiencyEvaluator(run, example) {
|
|
22
|
+
const outputs = run.outputs || {};
|
|
23
|
+
const metrics = outputs.metrics; // We will attach metrics to the harness output
|
|
24
|
+
if (!metrics || !metrics.totalTokens) {
|
|
25
|
+
return {
|
|
26
|
+
key: "cache_hit_rate",
|
|
27
|
+
score: null, // N/A (e.g., OpenAI or missing data)
|
|
28
|
+
comment: "No token metrics found in run output.",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const creationTokens = metrics.cacheCreationTokens || 0;
|
|
32
|
+
const readTokens = metrics.cacheReadTokens || 0;
|
|
33
|
+
if (creationTokens === 0 && readTokens === 0) {
|
|
34
|
+
return {
|
|
35
|
+
key: "cache_hit_rate",
|
|
36
|
+
score: 0,
|
|
37
|
+
comment: "Prompt caching is not active or not supported by this provider.",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const totalInputTokens = metrics.promptTokens;
|
|
41
|
+
const hitRate = readTokens / totalInputTokens;
|
|
42
|
+
return {
|
|
43
|
+
key: "cache_hit_rate",
|
|
44
|
+
score: hitRate,
|
|
45
|
+
comment: `Cache Hit Rate: ${(hitRate * 100).toFixed(1)}% (${readTokens} / ${totalInputTokens} input tokens)`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Custom evaluator: Output Artifact Check
|
|
50
|
+
* Verifies if the file the agent was instructed to create actually exists
|
|
51
|
+
* in the Sandbox after execution.
|
|
52
|
+
*/
|
|
53
|
+
export async function filePresenceEvaluator(run, example) {
|
|
54
|
+
if (!example?.outputs?.expected_file) {
|
|
55
|
+
return { key: "expected_file_created", score: null };
|
|
56
|
+
}
|
|
57
|
+
// The harnessed output should return a manifest or state snapshot we can verify
|
|
58
|
+
const outputs = run.outputs || {};
|
|
59
|
+
const fileManifest = outputs.fileManifest || [];
|
|
60
|
+
const expectedFile = example.outputs.expected_file;
|
|
61
|
+
const didCreate = fileManifest.includes(expectedFile);
|
|
62
|
+
return {
|
|
63
|
+
key: "expected_file_created",
|
|
64
|
+
score: didCreate ? 1 : 0,
|
|
65
|
+
comment: didCreate ? `File ${expectedFile} created successfully.` : `Failed to create expected file: ${expectedFile}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../src/evals/evaluator.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAQ,EAAE,OAAiB;IAChE,6EAA6E;IAC7E,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;IAE5B,OAAO;QACL,GAAG,EAAE,mBAAmB;QACxB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,yCAAyC;KACzE,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAQ,EAAE,OAAiB;IACxE,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,+CAA+C;IAEhF,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO;YACL,GAAG,EAAE,gBAAgB;YACrB,KAAK,EAAE,IAAI,EAAE,qCAAqC;YAClD,OAAO,EAAE,uCAAuC;SACjD,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,OAAO,CAAC,mBAAmB,IAAI,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC;IAEhD,IAAI,cAAc,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO;YACL,GAAG,EAAE,gBAAgB;YACrB,KAAK,EAAE,CAAC;YACR,OAAO,EAAE,iEAAiE;SAC3E,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,GAAG,gBAAgB,CAAC;IAE9C,OAAO;QACL,GAAG,EAAE,gBAAgB;QACrB,KAAK,EAAE,OAAO;QACd,OAAO,EAAE,mBAAmB,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,UAAU,MAAM,gBAAgB,gBAAgB;KAC7G,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAQ,EAAE,OAAiB;IACrE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;QACrC,OAAO,EAAE,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACvD,CAAC;IAED,gFAAgF;IAChF,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAClC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;IAEhD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;IACnD,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEtD,OAAO;QACL,GAAG,EAAE,uBAAuB;QAC5B,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,YAAY,wBAAwB,CAAC,CAAC,CAAC,mCAAmC,YAAY,EAAE;KACtH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
export interface HITLQuestion {
|
|
3
|
+
/** Unique ID for this question. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** The question text to display to the user. */
|
|
6
|
+
question: string;
|
|
7
|
+
/** Optional predefined answer choices. */
|
|
8
|
+
options?: string[];
|
|
9
|
+
/** Timestamp when the question was posed. */
|
|
10
|
+
createdAt: number;
|
|
11
|
+
}
|
|
12
|
+
export interface HITLPermissionRequest {
|
|
13
|
+
/** Unique ID for this request. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** The tool requesting permission. */
|
|
16
|
+
toolName: string;
|
|
17
|
+
/** The arguments the tool was called with. */
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
/** Timestamp when the request was created. */
|
|
20
|
+
createdAt: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* HITLBridge — Human-in-the-Loop communication bridge.
|
|
24
|
+
*
|
|
25
|
+
* Provides a typed event-based interface between the tool execution layer
|
|
26
|
+
* and the TUI rendering layer. When a tool needs user input, it emits
|
|
27
|
+
* a question event and awaits the response. The TUI listens, renders
|
|
28
|
+
* the prompt, and resolves the answer.
|
|
29
|
+
*
|
|
30
|
+
* Singleton pattern: one bridge per session.
|
|
31
|
+
*/
|
|
32
|
+
export declare class HITLBridge extends EventEmitter {
|
|
33
|
+
private static instance;
|
|
34
|
+
private pendingResolvers;
|
|
35
|
+
private timeoutMs;
|
|
36
|
+
private questionCounter;
|
|
37
|
+
constructor(timeoutMs?: number);
|
|
38
|
+
static getInstance(timeoutMs?: number): HITLBridge;
|
|
39
|
+
static resetInstance(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Called by a tool to ask the user a free-form question.
|
|
42
|
+
* Blocks until the user responds (or times out).
|
|
43
|
+
*
|
|
44
|
+
* @returns The user's answer as a string.
|
|
45
|
+
*/
|
|
46
|
+
askUser(question: string, options?: string[]): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Called by the PermissionMiddleware to request tool execution approval.
|
|
49
|
+
* Blocks until the user responds [y/n] (or times out with denial).
|
|
50
|
+
*
|
|
51
|
+
* @returns true if approved, false if denied or timed out.
|
|
52
|
+
*/
|
|
53
|
+
requestPermission(toolName: string, args: Record<string, unknown>): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Called by the TUI when the user submits an answer.
|
|
56
|
+
*
|
|
57
|
+
* @param id - The question/permission request ID.
|
|
58
|
+
* @param answer - The user's text response.
|
|
59
|
+
*/
|
|
60
|
+
resolveAnswer(id: string, answer: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Returns true if there is an outstanding question awaiting an answer.
|
|
63
|
+
*/
|
|
64
|
+
hasPendingQuestion(): boolean;
|
|
65
|
+
}
|