@bubblebrain-ai/bubble 0.0.46 → 0.0.48
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/dist/agent/child-runner.js +11 -0
- package/dist/agent/internal-reminder-sanitizer.d.ts +7 -0
- package/dist/agent/internal-reminder-sanitizer.js +13 -0
- package/dist/agent/subagent/router.d.ts +65 -0
- package/dist/agent/subagent/router.js +198 -0
- package/dist/agent/subagent/runtime.d.ts +203 -0
- package/dist/agent/subagent/runtime.js +789 -0
- package/dist/agent/subagent-control.d.ts +5 -0
- package/dist/agent/subagent-store.d.ts +18 -2
- package/dist/agent/subagent-store.js +32 -1
- package/dist/agent/tool-intent.d.ts +10 -0
- package/dist/agent/tool-intent.js +18 -0
- package/dist/agent/tool-visibility.d.ts +10 -0
- package/dist/agent/tool-visibility.js +6 -0
- package/dist/agent/workflow/runs.d.ts +72 -0
- package/dist/agent/workflow/runs.js +157 -0
- package/dist/agent.d.ts +78 -96
- package/dist/agent.js +266 -1072
- package/dist/context/compact-llm.js +14 -5
- package/dist/context/compact.d.ts +9 -0
- package/dist/context/compact.js +36 -10
- package/dist/context/compaction-files.d.ts +44 -0
- package/dist/context/compaction-files.js +148 -0
- package/dist/context/llm-compactor.js +24 -5
- package/dist/feishu/agent-host/run-driver.js +3 -0
- package/dist/feishu/serve.js +13 -9
- package/dist/goal/tools.js +12 -3
- package/dist/main.js +24 -19
- package/dist/model-catalog.js +36 -2
- package/dist/model-config.d.ts +2 -0
- package/dist/model-data.generated.d.ts +16 -0
- package/dist/model-data.generated.js +275 -0
- package/dist/model-pricing.d.ts +11 -7
- package/dist/model-pricing.js +0 -0
- package/dist/oauth/grok.d.ts +1 -1
- package/dist/oauth/grok.js +5 -2
- package/dist/oauth/storage.d.ts +9 -2
- package/dist/oauth/storage.js +16 -9
- package/dist/orchestrator/default-hooks.d.ts +20 -0
- package/dist/orchestrator/default-hooks.js +47 -295
- package/dist/orchestrator/hooks.d.ts +6 -39
- package/dist/permission/mode.js +1 -1
- package/dist/prompt/reminders.d.ts +9 -35
- package/dist/prompt/reminders.js +30 -136
- package/dist/prompt/task-reminders.d.ts +0 -31
- package/dist/prompt/task-reminders.js +0 -87
- package/dist/provider-ai-sdk.d.ts +2 -0
- package/dist/provider-ai-sdk.js +1 -0
- package/dist/provider-anthropic.d.ts +2 -0
- package/dist/provider-anthropic.js +3 -1
- package/dist/provider-ark-responses.d.ts +2 -0
- package/dist/provider-ark-responses.js +1 -0
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +39 -0
- package/dist/provider-registry.d.ts +14 -1
- package/dist/provider-registry.js +131 -3
- package/dist/provider-transform.js +37 -1
- package/dist/provider.d.ts +7 -0
- package/dist/provider.js +13 -1
- package/dist/sdk/index.d.ts +152 -0
- package/dist/sdk/index.js +400 -0
- package/dist/session.js +9 -1
- package/dist/slash-commands/commands.js +33 -19
- package/dist/tools/exit-plan-mode.js +8 -0
- package/dist/tui/detect-theme.d.ts +1 -0
- package/dist/tui/detect-theme.js +11 -5
- package/dist/tui-ink/app.d.ts +2 -6
- package/dist/tui-ink/app.js +11 -18
- package/dist/tui-ink/display-history.d.ts +0 -2
- package/dist/tui-ink/display-history.js +0 -52
- package/dist/tui-ink/footer.js +2 -2
- package/dist/tui-ink/input-box.js +18 -6
- package/dist/tui-ink/message-list.js +62 -23
- package/dist/tui-ink/run.js +10 -5
- package/dist/tui-ink/sync-output.d.ts +17 -0
- package/dist/tui-ink/sync-output.js +35 -0
- package/dist/tui-ink/theme.d.ts +0 -1
- package/dist/tui-ink/theme.js +0 -2
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/types.d.ts +21 -1
- package/package.json +16 -2
- package/dist/agent/discovery-barrier.d.ts +0 -21
- package/dist/agent/discovery-barrier.js +0 -173
- package/dist/agent/evidence-tracker.d.ts +0 -15
- package/dist/agent/evidence-tracker.js +0 -93
- package/dist/agent/execution-governor.d.ts +0 -36
- package/dist/agent/execution-governor.js +0 -218
- package/dist/agent/task-classifier.d.ts +0 -3
- package/dist/agent/task-classifier.js +0 -114
- package/dist/agent/task-size.d.ts +0 -9
- package/dist/agent/task-size.js +0 -33
- package/dist/agent/tool-arbiter.d.ts +0 -7
- package/dist/agent/tool-arbiter.js +0 -33
- package/dist/orchestrator/workflow.d.ts +0 -10
- package/dist/orchestrator/workflow.js +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebrain-ai/bubble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"description": "A terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"bubble": "dist/bin.js"
|
|
11
11
|
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/sdk/index.d.ts",
|
|
15
|
+
"default": "./dist/sdk/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./sdk": {
|
|
18
|
+
"types": "./dist/sdk/index.d.ts",
|
|
19
|
+
"default": "./dist/sdk/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./dist/*": "./dist/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
12
24
|
"files": [
|
|
13
25
|
"dist",
|
|
14
26
|
"!dist/**/__tests__/**",
|
|
@@ -18,6 +30,8 @@
|
|
|
18
30
|
"scripts": {
|
|
19
31
|
"build": "rm -rf dist && tsc && chmod +x dist/bin.js dist/main.js",
|
|
20
32
|
"dev": "tsc && bun dist/main.js",
|
|
33
|
+
"refresh-models": "NODE_USE_ENV_PROXY=1 node scripts/generate-model-data.mjs",
|
|
34
|
+
"eval": "bun evals/run.ts",
|
|
21
35
|
"prepack": "npm run build",
|
|
22
36
|
"start": "bun dist/main.js",
|
|
23
37
|
"test": "vitest run",
|
|
@@ -31,7 +45,7 @@
|
|
|
31
45
|
"@types/better-sqlite3": "^7.6.13",
|
|
32
46
|
"@types/react": "^19.2.14",
|
|
33
47
|
"@vue/language-server": "^3.2.7",
|
|
34
|
-
"better-sqlite3": "^
|
|
48
|
+
"better-sqlite3": "^13.0.1",
|
|
35
49
|
"chalk": "^5.3.0",
|
|
36
50
|
"diff": "^9.0.0",
|
|
37
51
|
"ink": "^7.0.3",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { ContentPart, ParsedToolCall, ToolResult, ToolResultMetadata } from "../types.js";
|
|
2
|
-
export interface DiscoveryBarrierOptions {
|
|
3
|
-
cwd: string;
|
|
4
|
-
input: string | ContentPart[];
|
|
5
|
-
enabled: boolean;
|
|
6
|
-
}
|
|
7
|
-
export declare class DiscoveryBarrier {
|
|
8
|
-
private readonly cwd;
|
|
9
|
-
private readonly enabled;
|
|
10
|
-
private readonly knownPaths;
|
|
11
|
-
private readonly userMentionedPaths;
|
|
12
|
-
constructor(options: DiscoveryBarrierOptions);
|
|
13
|
-
isEnabled(): boolean;
|
|
14
|
-
shouldBufferStreamingToolCall(name: string): boolean;
|
|
15
|
-
orderToolCalls<T extends ParsedToolCall>(toolCalls: T[]): T[];
|
|
16
|
-
beforeToolCall(toolCall: ParsedToolCall): ToolResult | undefined;
|
|
17
|
-
afterToolCall(toolCall: ParsedToolCall, result: ToolResult): void;
|
|
18
|
-
observeMetadata(metadata: ToolResultMetadata | undefined): void;
|
|
19
|
-
}
|
|
20
|
-
export declare function isHiddenToolResult(result: ToolResult | undefined): result is ToolResult;
|
|
21
|
-
export declare function isHiddenToolMetadata(metadata: ToolResultMetadata | undefined): boolean;
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
-
import { basename, resolve } from "node:path";
|
|
3
|
-
import { analyzeToolIntent } from "./tool-intent.js";
|
|
4
|
-
import { resolveToolPath } from "../tools/path-utils.js";
|
|
5
|
-
export class DiscoveryBarrier {
|
|
6
|
-
cwd;
|
|
7
|
-
enabled;
|
|
8
|
-
knownPaths = new Set();
|
|
9
|
-
userMentionedPaths = new Set();
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.cwd = options.cwd;
|
|
12
|
-
this.enabled = options.enabled;
|
|
13
|
-
for (const path of extractUserMentionedPaths(options.input)) {
|
|
14
|
-
this.userMentionedPaths.add(canonicalPath(this.cwd, path));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
isEnabled() {
|
|
18
|
-
return this.enabled;
|
|
19
|
-
}
|
|
20
|
-
shouldBufferStreamingToolCall(name) {
|
|
21
|
-
return this.enabled && (name === "read" || name === "bash" || name === "lsp");
|
|
22
|
-
}
|
|
23
|
-
orderToolCalls(toolCalls) {
|
|
24
|
-
if (!this.enabled || toolCalls.length < 2)
|
|
25
|
-
return toolCalls;
|
|
26
|
-
const discovery = [];
|
|
27
|
-
const rest = [];
|
|
28
|
-
for (const toolCall of toolCalls) {
|
|
29
|
-
if (isDiscoveryToolCall(toolCall))
|
|
30
|
-
discovery.push(toolCall);
|
|
31
|
-
else
|
|
32
|
-
rest.push(toolCall);
|
|
33
|
-
}
|
|
34
|
-
if (discovery.length === 0 || rest.length === 0)
|
|
35
|
-
return toolCalls;
|
|
36
|
-
return [...discovery, ...rest];
|
|
37
|
-
}
|
|
38
|
-
beforeToolCall(toolCall) {
|
|
39
|
-
if (!this.enabled)
|
|
40
|
-
return undefined;
|
|
41
|
-
const target = pathSensitiveReadTarget(toolCall);
|
|
42
|
-
if (!target)
|
|
43
|
-
return undefined;
|
|
44
|
-
const canonical = canonicalPath(this.cwd, target.path);
|
|
45
|
-
if (this.knownPaths.has(canonical) || this.userMentionedPaths.has(canonical))
|
|
46
|
-
return undefined;
|
|
47
|
-
return {
|
|
48
|
-
content: `Blocked speculative ${target.kind}. The path "${target.path}" has not been discovered in this repository context ` +
|
|
49
|
-
"and was not explicitly requested by the user. Run glob/grep discovery first, then read only returned paths. " +
|
|
50
|
-
"Do not infer from this blocked call whether the path exists.",
|
|
51
|
-
isError: true,
|
|
52
|
-
status: "blocked",
|
|
53
|
-
metadata: {
|
|
54
|
-
kind: "internal",
|
|
55
|
-
reason: "speculative_read_blocked",
|
|
56
|
-
hiddenFromTranscript: true,
|
|
57
|
-
path: canonical,
|
|
58
|
-
requestedPath: target.path,
|
|
59
|
-
toolName: toolCall.name,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
afterToolCall(toolCall, result) {
|
|
64
|
-
if (!this.enabled || result.metadata?.hiddenFromTranscript === true)
|
|
65
|
-
return;
|
|
66
|
-
this.observeMetadata(result.metadata);
|
|
67
|
-
if (isSuccessfulAccessResult(result)) {
|
|
68
|
-
const target = pathSensitiveReadTarget(toolCall);
|
|
69
|
-
if (target)
|
|
70
|
-
this.knownPaths.add(canonicalPath(this.cwd, target.path));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
observeMetadata(metadata) {
|
|
74
|
-
if (!metadata)
|
|
75
|
-
return;
|
|
76
|
-
for (const path of metadataPaths(metadata)) {
|
|
77
|
-
this.knownPaths.add(canonicalPath(this.cwd, path));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
export function isHiddenToolResult(result) {
|
|
82
|
-
return result?.metadata?.hiddenFromTranscript === true;
|
|
83
|
-
}
|
|
84
|
-
export function isHiddenToolMetadata(metadata) {
|
|
85
|
-
return metadata?.hiddenFromTranscript === true;
|
|
86
|
-
}
|
|
87
|
-
function isDiscoveryToolCall(toolCall) {
|
|
88
|
-
if (toolCall.name === "glob" || toolCall.name === "grep")
|
|
89
|
-
return true;
|
|
90
|
-
return analyzeToolIntent(toolCall).family === "search";
|
|
91
|
-
}
|
|
92
|
-
function pathSensitiveReadTarget(toolCall) {
|
|
93
|
-
if (toolCall.name === "read") {
|
|
94
|
-
const path = stringArg(toolCall.parsedArgs.path ?? toolCall.parsedArgs.file);
|
|
95
|
-
return path ? { kind: "read", path } : undefined;
|
|
96
|
-
}
|
|
97
|
-
if (toolCall.name === "bash") {
|
|
98
|
-
const intent = analyzeToolIntent(toolCall);
|
|
99
|
-
const path = intent.read?.path;
|
|
100
|
-
return path ? { kind: "bash read", path } : undefined;
|
|
101
|
-
}
|
|
102
|
-
if (toolCall.name === "lsp") {
|
|
103
|
-
const path = stringArg(toolCall.parsedArgs.filePath);
|
|
104
|
-
return path ? { kind: "lsp", path } : undefined;
|
|
105
|
-
}
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
function metadataPaths(metadata) {
|
|
109
|
-
const paths = new Set();
|
|
110
|
-
addPathValue(paths, metadata.path);
|
|
111
|
-
addPathValue(paths, metadata.paths);
|
|
112
|
-
return [...paths];
|
|
113
|
-
}
|
|
114
|
-
function addPathValue(out, value) {
|
|
115
|
-
if (typeof value === "string" && value.trim()) {
|
|
116
|
-
out.add(value);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (!Array.isArray(value))
|
|
120
|
-
return;
|
|
121
|
-
for (const item of value) {
|
|
122
|
-
if (typeof item === "string" && item.trim())
|
|
123
|
-
out.add(item);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function isSuccessfulAccessResult(result) {
|
|
127
|
-
return !result.isError
|
|
128
|
-
&& result.status !== "blocked"
|
|
129
|
-
&& result.status !== "cancelled"
|
|
130
|
-
&& result.status !== "command_error"
|
|
131
|
-
&& result.status !== "timeout";
|
|
132
|
-
}
|
|
133
|
-
function canonicalPath(cwd, value) {
|
|
134
|
-
const absolute = resolveToolPath(cwd, value);
|
|
135
|
-
try {
|
|
136
|
-
if (existsSync(absolute))
|
|
137
|
-
return realpathSync.native(absolute);
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// Fall back to lexical resolution for unreadable or racing paths.
|
|
141
|
-
}
|
|
142
|
-
return resolve(absolute);
|
|
143
|
-
}
|
|
144
|
-
function extractUserMentionedPaths(input) {
|
|
145
|
-
const text = typeof input === "string"
|
|
146
|
-
? input
|
|
147
|
-
: input
|
|
148
|
-
.filter((part) => part.type === "text")
|
|
149
|
-
.map((part) => part.text)
|
|
150
|
-
.join("\n");
|
|
151
|
-
const paths = new Set();
|
|
152
|
-
const regex = /(?:^|[\s`'"])(~?\.{0,2}\/[^\s`'"]+|[\w@.-]+\.[A-Za-z0-9][\w.-]*)(?=$|[\s`'",.;:!?,。;:!?))])/g;
|
|
153
|
-
let match;
|
|
154
|
-
while ((match = regex.exec(text)) !== null) {
|
|
155
|
-
const value = match[1]?.trim();
|
|
156
|
-
if (value && isLikelyFilePath(value))
|
|
157
|
-
paths.add(stripTrailingPunctuation(value));
|
|
158
|
-
}
|
|
159
|
-
return [...paths];
|
|
160
|
-
}
|
|
161
|
-
function isLikelyFilePath(value) {
|
|
162
|
-
const stripped = stripTrailingPunctuation(value);
|
|
163
|
-
if (stripped.startsWith("./") || stripped.startsWith("../") || stripped.startsWith("~/") || stripped.startsWith("/")) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return /\.[A-Za-z0-9][\w.-]*$/.test(basename(stripped));
|
|
167
|
-
}
|
|
168
|
-
function stripTrailingPunctuation(value) {
|
|
169
|
-
return value.replace(/[.,;:!?,。;:!?))]+$/u, "");
|
|
170
|
-
}
|
|
171
|
-
function stringArg(value) {
|
|
172
|
-
return typeof value === "string" ? value.trim() : "";
|
|
173
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ParsedToolCall, ToolResult } from "../types.js";
|
|
2
|
-
export interface InvestigationCoverage {
|
|
3
|
-
configLoadPaths: boolean;
|
|
4
|
-
envReads: boolean;
|
|
5
|
-
persistencePaths: boolean;
|
|
6
|
-
exposurePaths: boolean;
|
|
7
|
-
maskingSignals: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare class EvidenceTracker {
|
|
10
|
-
private coverage;
|
|
11
|
-
observe(toolCall: Pick<ParsedToolCall, "name" | "parsedArgs">, result: ToolResult): void;
|
|
12
|
-
snapshot(): InvestigationCoverage;
|
|
13
|
-
key(): string;
|
|
14
|
-
isCoreCoverageComplete(): boolean;
|
|
15
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const DEFAULT_COVERAGE = {
|
|
2
|
-
configLoadPaths: false,
|
|
3
|
-
envReads: false,
|
|
4
|
-
persistencePaths: false,
|
|
5
|
-
exposurePaths: false,
|
|
6
|
-
maskingSignals: false,
|
|
7
|
-
};
|
|
8
|
-
const CONFIG_PATTERNS = [
|
|
9
|
-
/\bconfig\.(json|ya?ml|toml|ini)\b/i,
|
|
10
|
-
/\bsettings\.(json|ya?ml|toml)\b/i,
|
|
11
|
-
/\bauth\.json\b/i,
|
|
12
|
-
/\buserconfig\b/i,
|
|
13
|
-
/\breadfilesync\b/i,
|
|
14
|
-
];
|
|
15
|
-
const ENV_PATTERNS = [
|
|
16
|
-
/\bprocess\.env\b/i,
|
|
17
|
-
/\bimport\.meta\.env\b/i,
|
|
18
|
-
/\bgetenv\(/i,
|
|
19
|
-
/\bloadenv\b/i,
|
|
20
|
-
/\bdotenv\b/i,
|
|
21
|
-
/\b\.env\b/i,
|
|
22
|
-
];
|
|
23
|
-
const PERSISTENCE_PATTERNS = [
|
|
24
|
-
/\bwritefilesync\b/i,
|
|
25
|
-
/\bwritefile\b/i,
|
|
26
|
-
/\bauth\.json\b/i,
|
|
27
|
-
/\bconfig\.json\b/i,
|
|
28
|
-
/\blocalstorage\b/i,
|
|
29
|
-
/\bsqlite\b/i,
|
|
30
|
-
/\bkeychain\b/i,
|
|
31
|
-
/\bcredential\b/i,
|
|
32
|
-
/\bstore\b/i,
|
|
33
|
-
];
|
|
34
|
-
const EXPOSURE_PATTERNS = [
|
|
35
|
-
/\bconsole\.log\b/i,
|
|
36
|
-
/\blogger\./i,
|
|
37
|
-
/\bres\.json\b/i,
|
|
38
|
-
/\breturn\s+\{/i,
|
|
39
|
-
/\bwindow\./i,
|
|
40
|
-
/\bdocument\./i,
|
|
41
|
-
/\bserialize\b/i,
|
|
42
|
-
/\bresponse\b/i,
|
|
43
|
-
];
|
|
44
|
-
const MASKING_PATTERNS = [
|
|
45
|
-
/\bmask(key|ed)?\b/i,
|
|
46
|
-
/\bredact(ed|ion)?\b/i,
|
|
47
|
-
/\bhidden\b/i,
|
|
48
|
-
/\bobfuscated\b/i,
|
|
49
|
-
/\*\*\*\*/i,
|
|
50
|
-
/\.\.\./i,
|
|
51
|
-
];
|
|
52
|
-
export class EvidenceTracker {
|
|
53
|
-
coverage = { ...DEFAULT_COVERAGE };
|
|
54
|
-
observe(toolCall, result) {
|
|
55
|
-
const haystack = [
|
|
56
|
-
typeof toolCall.parsedArgs.path === "string" ? toolCall.parsedArgs.path : "",
|
|
57
|
-
typeof toolCall.parsedArgs.command === "string" ? toolCall.parsedArgs.command : "",
|
|
58
|
-
typeof toolCall.parsedArgs.pattern === "string" ? toolCall.parsedArgs.pattern : "",
|
|
59
|
-
result.content,
|
|
60
|
-
].join("\n");
|
|
61
|
-
if (!this.coverage.configLoadPaths && matchesAny(CONFIG_PATTERNS, haystack)) {
|
|
62
|
-
this.coverage.configLoadPaths = true;
|
|
63
|
-
}
|
|
64
|
-
if (!this.coverage.envReads && matchesAny(ENV_PATTERNS, haystack)) {
|
|
65
|
-
this.coverage.envReads = true;
|
|
66
|
-
}
|
|
67
|
-
if (!this.coverage.persistencePaths && matchesAny(PERSISTENCE_PATTERNS, haystack)) {
|
|
68
|
-
this.coverage.persistencePaths = true;
|
|
69
|
-
}
|
|
70
|
-
if (!this.coverage.exposurePaths && matchesAny(EXPOSURE_PATTERNS, haystack)) {
|
|
71
|
-
this.coverage.exposurePaths = true;
|
|
72
|
-
}
|
|
73
|
-
if (!this.coverage.maskingSignals && matchesAny(MASKING_PATTERNS, haystack)) {
|
|
74
|
-
this.coverage.maskingSignals = true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
snapshot() {
|
|
78
|
-
return { ...this.coverage };
|
|
79
|
-
}
|
|
80
|
-
key() {
|
|
81
|
-
const coverage = this.snapshot();
|
|
82
|
-
return Object.values(coverage).map((value) => (value ? "1" : "0")).join("");
|
|
83
|
-
}
|
|
84
|
-
isCoreCoverageComplete() {
|
|
85
|
-
return this.coverage.configLoadPaths
|
|
86
|
-
&& this.coverage.envReads
|
|
87
|
-
&& this.coverage.persistencePaths
|
|
88
|
-
&& this.coverage.exposurePaths;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function matchesAny(patterns, haystack) {
|
|
92
|
-
return patterns.some((pattern) => pattern.test(haystack));
|
|
93
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ParsedToolCall, ToolRegistryEntry, ToolResult } from "../types.js";
|
|
2
|
-
import type { TaskType } from "./task-classifier.js";
|
|
3
|
-
export interface GovernorDecision {
|
|
4
|
-
blockedResult?: ToolResult;
|
|
5
|
-
}
|
|
6
|
-
export declare class ExecutionGovernor {
|
|
7
|
-
private budget;
|
|
8
|
-
private history;
|
|
9
|
-
private totalSteps;
|
|
10
|
-
private searchSteps;
|
|
11
|
-
private readSteps;
|
|
12
|
-
private mutationVersion;
|
|
13
|
-
private reminderQueue;
|
|
14
|
-
private warnedFamilies;
|
|
15
|
-
private warnedSignatures;
|
|
16
|
-
private softTotalWarned;
|
|
17
|
-
private softSearchWarned;
|
|
18
|
-
private softReadWarned;
|
|
19
|
-
constructor(taskType: TaskType);
|
|
20
|
-
consumePendingReminders(): string[];
|
|
21
|
-
snapshot(): {
|
|
22
|
-
totalSteps: number;
|
|
23
|
-
searchSteps: number;
|
|
24
|
-
readSteps: number;
|
|
25
|
-
searchFrozen: boolean;
|
|
26
|
-
explorationFrozen: boolean;
|
|
27
|
-
phase: "observe";
|
|
28
|
-
};
|
|
29
|
-
filterToolDefinitions(toolDefinitions: ToolRegistryEntry[]): ToolRegistryEntry[];
|
|
30
|
-
beforeToolCall(toolCall: ParsedToolCall): GovernorDecision;
|
|
31
|
-
afterToolResult(toolCall: ParsedToolCall, result: ToolResult): void;
|
|
32
|
-
private trailingNoProgressCount;
|
|
33
|
-
private hasCurrentMutationObservation;
|
|
34
|
-
private warnOnce;
|
|
35
|
-
private maybeWarnOnSoftBudgets;
|
|
36
|
-
}
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { analyzeToolIntent } from "./tool-intent.js";
|
|
2
|
-
import { buildInvestigationReminder, buildLoopWarningReminder } from "../prompt/reminders.js";
|
|
3
|
-
const BUDGETS = {
|
|
4
|
-
security_investigation: {
|
|
5
|
-
softTotalSteps: 14,
|
|
6
|
-
softSearchSteps: 6,
|
|
7
|
-
softReadSteps: 8,
|
|
8
|
-
warningExactRepeats: 2,
|
|
9
|
-
warningFamilyRepeats: 2,
|
|
10
|
-
},
|
|
11
|
-
code_search: {
|
|
12
|
-
softTotalSteps: 16,
|
|
13
|
-
softSearchSteps: 8,
|
|
14
|
-
softReadSteps: 10,
|
|
15
|
-
warningExactRepeats: 2,
|
|
16
|
-
warningFamilyRepeats: 3,
|
|
17
|
-
},
|
|
18
|
-
debugging: {
|
|
19
|
-
softTotalSteps: 18,
|
|
20
|
-
softSearchSteps: 8,
|
|
21
|
-
softReadSteps: 7,
|
|
22
|
-
warningExactRepeats: 1,
|
|
23
|
-
warningFamilyRepeats: 3,
|
|
24
|
-
},
|
|
25
|
-
implementation: {
|
|
26
|
-
softTotalSteps: 18,
|
|
27
|
-
softSearchSteps: 8,
|
|
28
|
-
softReadSteps: 6,
|
|
29
|
-
warningExactRepeats: 1,
|
|
30
|
-
warningFamilyRepeats: 3,
|
|
31
|
-
},
|
|
32
|
-
code_review: {
|
|
33
|
-
softTotalSteps: 14,
|
|
34
|
-
softSearchSteps: 6,
|
|
35
|
-
softReadSteps: 8,
|
|
36
|
-
warningExactRepeats: 2,
|
|
37
|
-
warningFamilyRepeats: 3,
|
|
38
|
-
},
|
|
39
|
-
code_explanation: {
|
|
40
|
-
softTotalSteps: 12,
|
|
41
|
-
softSearchSteps: 6,
|
|
42
|
-
softReadSteps: 8,
|
|
43
|
-
warningExactRepeats: 2,
|
|
44
|
-
warningFamilyRepeats: 3,
|
|
45
|
-
},
|
|
46
|
-
repo_orientation: {
|
|
47
|
-
softTotalSteps: 12,
|
|
48
|
-
softSearchSteps: 6,
|
|
49
|
-
softReadSteps: 8,
|
|
50
|
-
warningExactRepeats: 2,
|
|
51
|
-
warningFamilyRepeats: 3,
|
|
52
|
-
},
|
|
53
|
-
product_discussion: {
|
|
54
|
-
softTotalSteps: 10,
|
|
55
|
-
softSearchSteps: 4,
|
|
56
|
-
softReadSteps: 4,
|
|
57
|
-
warningExactRepeats: 2,
|
|
58
|
-
warningFamilyRepeats: 2,
|
|
59
|
-
},
|
|
60
|
-
general: {
|
|
61
|
-
softTotalSteps: 18,
|
|
62
|
-
softSearchSteps: 8,
|
|
63
|
-
softReadSteps: 10,
|
|
64
|
-
warningExactRepeats: 2,
|
|
65
|
-
warningFamilyRepeats: 3,
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
export class ExecutionGovernor {
|
|
69
|
-
budget;
|
|
70
|
-
history = [];
|
|
71
|
-
totalSteps = 0;
|
|
72
|
-
searchSteps = 0;
|
|
73
|
-
readSteps = 0;
|
|
74
|
-
mutationVersion = 0;
|
|
75
|
-
reminderQueue = [];
|
|
76
|
-
warnedFamilies = new Set();
|
|
77
|
-
warnedSignatures = new Set();
|
|
78
|
-
softTotalWarned = false;
|
|
79
|
-
softSearchWarned = false;
|
|
80
|
-
softReadWarned = false;
|
|
81
|
-
constructor(taskType) {
|
|
82
|
-
this.budget = BUDGETS[taskType];
|
|
83
|
-
if (taskType === "security_investigation") {
|
|
84
|
-
this.reminderQueue.push(buildInvestigationReminder());
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
consumePendingReminders() {
|
|
88
|
-
const reminders = [...this.reminderQueue];
|
|
89
|
-
this.reminderQueue.length = 0;
|
|
90
|
-
return reminders;
|
|
91
|
-
}
|
|
92
|
-
snapshot() {
|
|
93
|
-
return {
|
|
94
|
-
totalSteps: this.totalSteps,
|
|
95
|
-
searchSteps: this.searchSteps,
|
|
96
|
-
readSteps: this.readSteps,
|
|
97
|
-
searchFrozen: false,
|
|
98
|
-
explorationFrozen: false,
|
|
99
|
-
phase: "observe",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
filterToolDefinitions(toolDefinitions) {
|
|
103
|
-
return toolDefinitions;
|
|
104
|
-
}
|
|
105
|
-
beforeToolCall(toolCall) {
|
|
106
|
-
const intent = analyzeToolIntent(toolCall);
|
|
107
|
-
if (intent.family === "read") {
|
|
108
|
-
const signature = intent.read?.signature;
|
|
109
|
-
if (signature && this.hasCurrentMutationObservation((entry) => entry.signature === signature)) {
|
|
110
|
-
this.warnOnce(`read:${signature}`, "This exact file range was already read since the last successful edit/write. If the content is still available and nothing changed, use the prior result; otherwise it is okay to re-read to recover context or verify a change.");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (intent.family === "search") {
|
|
114
|
-
const signature = intent.search?.signature;
|
|
115
|
-
const familyKey = intent.search?.familyKey;
|
|
116
|
-
if (signature && this.trailingNoProgressCount((entry) => entry.signature === signature) >= this.budget.warningExactRepeats) {
|
|
117
|
-
this.warnOnce(`search:${signature}`, "This search is very similar to one you already ran and it did not produce new evidence. Change the query/path, follow a concrete lead, or summarize the strongest findings.");
|
|
118
|
-
}
|
|
119
|
-
if (familyKey) {
|
|
120
|
-
const familyNoProgress = this.trailingNoProgressCount((entry) => entry.familyKey === familyKey);
|
|
121
|
-
if (familyNoProgress >= this.budget.warningFamilyRepeats && !this.warnedFamilies.has(familyKey)) {
|
|
122
|
-
this.warnedFamilies.add(familyKey);
|
|
123
|
-
this.reminderQueue.push(buildLoopWarningReminder("Repeated searches in the same family are yielding little new evidence. Change your hypothesis, narrow the path, follow a specific file lead, or summarize current findings instead of repeating variants."));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
this.totalSteps += 1;
|
|
128
|
-
if (intent.family === "search") {
|
|
129
|
-
this.searchSteps += 1;
|
|
130
|
-
}
|
|
131
|
-
if (intent.family === "read") {
|
|
132
|
-
this.readSteps += 1;
|
|
133
|
-
}
|
|
134
|
-
this.maybeWarnOnSoftBudgets(intent.family === "search", intent.family === "read");
|
|
135
|
-
return {};
|
|
136
|
-
}
|
|
137
|
-
afterToolResult(toolCall, result) {
|
|
138
|
-
const intent = analyzeToolIntent(toolCall);
|
|
139
|
-
const repeatedRead = intent.family === "read"
|
|
140
|
-
&& !!intent.read?.signature
|
|
141
|
-
&& this.history.some((entry) => entry.signature === intent.read?.signature);
|
|
142
|
-
const progress = inferProgress(intent, result) && !repeatedRead;
|
|
143
|
-
this.history.push({
|
|
144
|
-
family: intent.family,
|
|
145
|
-
signature: intent.search?.signature ?? intent.read?.signature,
|
|
146
|
-
familyKey: intent.search?.familyKey ?? intent.read?.familyKey,
|
|
147
|
-
progress,
|
|
148
|
-
mutationVersion: this.mutationVersion,
|
|
149
|
-
});
|
|
150
|
-
if (isSuccessfulWriteIntent(intent, result)) {
|
|
151
|
-
this.mutationVersion += 1;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
trailingNoProgressCount(predicate) {
|
|
155
|
-
let count = 0;
|
|
156
|
-
for (let index = this.history.length - 1; index >= 0; index--) {
|
|
157
|
-
const entry = this.history[index];
|
|
158
|
-
if (entry.mutationVersion !== this.mutationVersion) {
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
if (!predicate(entry)) {
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
if (entry.progress) {
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
count += 1;
|
|
168
|
-
}
|
|
169
|
-
return count;
|
|
170
|
-
}
|
|
171
|
-
hasCurrentMutationObservation(predicate) {
|
|
172
|
-
return this.history.some((entry) => entry.mutationVersion === this.mutationVersion && predicate(entry));
|
|
173
|
-
}
|
|
174
|
-
warnOnce(key, reason) {
|
|
175
|
-
if (this.warnedSignatures.has(key)) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
this.warnedSignatures.add(key);
|
|
179
|
-
this.reminderQueue.push(buildLoopWarningReminder(reason));
|
|
180
|
-
}
|
|
181
|
-
maybeWarnOnSoftBudgets(isSearchStep, isReadStep) {
|
|
182
|
-
if (!this.softTotalWarned && this.totalSteps >= this.budget.softTotalSteps) {
|
|
183
|
-
this.softTotalWarned = true;
|
|
184
|
-
this.reminderQueue.push(buildLoopWarningReminder("This task has already used many tool steps. Do not keep exploring by default; synthesize what you know unless a concrete missing gap remains."));
|
|
185
|
-
}
|
|
186
|
-
if (isSearchStep && !this.softSearchWarned && this.searchSteps >= this.budget.softSearchSteps) {
|
|
187
|
-
this.softSearchWarned = true;
|
|
188
|
-
this.reminderQueue.push(buildLoopWarningReminder("This task has already used many search steps. Stop broad searching unless you can point to a specific remaining evidence gap."));
|
|
189
|
-
}
|
|
190
|
-
if (isReadStep && !this.softReadWarned && this.readSteps >= this.budget.softReadSteps) {
|
|
191
|
-
this.softReadWarned = true;
|
|
192
|
-
this.reminderQueue.push(buildLoopWarningReminder("This task has already used many file reads. Stop re-reading context unless a concrete edit requires one exact missing snippet."));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
function isSuccessfulWriteIntent(intent, result) {
|
|
197
|
-
if (result.isError || result.status === "blocked" || result.status === "command_error") {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
return intent.family === "write" || intent.family === "edit" || result.metadata?.kind === "write" || result.metadata?.kind === "edit" || result.metadata?.kind === "patch";
|
|
201
|
-
}
|
|
202
|
-
function inferProgress(intent, result) {
|
|
203
|
-
if (result.status === "blocked" || result.status === "timeout" || result.status === "command_error") {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
if (intent.family === "search") {
|
|
207
|
-
const matches = result.metadata?.matches;
|
|
208
|
-
if (typeof matches === "number") {
|
|
209
|
-
return matches > 0;
|
|
210
|
-
}
|
|
211
|
-
const normalized = result.content.toLowerCase();
|
|
212
|
-
if (normalized.includes("no matches found") || normalized.includes("(no matches)")) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
return !result.isError;
|
|
216
|
-
}
|
|
217
|
-
return !result.isError;
|
|
218
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { ContentPart } from "../types.js";
|
|
2
|
-
export type TaskType = "security_investigation" | "debugging" | "implementation" | "code_review" | "code_explanation" | "repo_orientation" | "product_discussion" | "code_search" | "general";
|
|
3
|
-
export declare function classifyTask(input: string | ContentPart[]): TaskType;
|