@phuetz/code-buddy 1.2.0 → 1.3.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/README.md +56 -3
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/tool-handler.js +3 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +6 -1
- package/dist/codebuddy/tools.d.ts +7 -0
- package/dist/codebuddy/tools.js +40 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- package/dist/commands/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- package/dist/companion/percepts.js +11 -1
- package/dist/context/bootstrap-loader.js +6 -23
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +80 -2
- package/dist/harness/contract.d.ts +28 -28
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +17 -1
- package/dist/mcp/mcp-resources.js +2 -3
- package/dist/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +89 -0
- package/dist/services/prompt-builder.d.ts +10 -0
- package/dist/services/prompt-builder.js +75 -9
- package/dist/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- package/dist/tools/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -0
- package/dist/utils/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -0
- package/dist/utils/settings-manager.d.ts +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<p align="center">
|
|
18
18
|
<a href="https://github.com/phuetz/code-buddy/stargazers"><img src="https://img.shields.io/github/stars/phuetz/code-buddy?style=flat-square&logo=github&color=feca57&label=Star" alt="GitHub stars"/></a>
|
|
19
19
|
<img src="https://img.shields.io/badge/Tests-27K%2B-00d26a?style=flat-square&logo=jest" alt="Tests"/>
|
|
20
|
-
<img src="https://img.shields.io/badge/v1.
|
|
20
|
+
<img src="https://img.shields.io/badge/v1.3.0-GA-blueviolet?style=flat-square" alt="Version 1.3.0 GA"/>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<br/>
|
|
@@ -62,6 +62,12 @@ An open-source, multi-provider AI coding agent with a terminal UI, an HTTP/WebSo
|
|
|
62
62
|
|
|
63
63
|
## In action
|
|
64
64
|
|
|
65
|
+
**It writes the code *and* the test, then runs it — `$0`.** Hand Code Buddy a task in the terminal; here Grok (a flat-fee subscription, no API key) writes FizzBuzz + a test and runs it green — then a human re-runs the test to confirm. Unedited:
|
|
66
|
+
|
|
67
|
+
<p align="center">
|
|
68
|
+
<img src="docs/assets/coding-demo.gif" alt="Code Buddy writes fizzbuzz.mjs and a test on Grok, runs it, and the test passes — $0, no API key" width="760"/>
|
|
69
|
+
</p>
|
|
70
|
+
|
|
65
71
|
**Free local AI, with the reasoning on screen.** A local Ollama model (`qwen3.6:35b-a3b`) thinks through a task, then *uses tools* to do it — no cloud, ~`$0.0001`. Unedited captures from the Cowork desktop app:
|
|
66
72
|
|
|
67
73
|
<table>
|
|
@@ -83,6 +89,12 @@ An open-source, multi-provider AI coding agent with a terminal UI, an HTTP/WebSo
|
|
|
83
89
|
<img src="docs/screenshots/chatgpt-oauth-login.png" alt="ChatGPT OAuth login flow" width="820"/>
|
|
84
90
|
</p>
|
|
85
91
|
|
|
92
|
+
**xAI / SuperGrok login** — `buddy login xai`, sign in once, then Grok answers for `$0` (flat-fee subscription, no API key):
|
|
93
|
+
|
|
94
|
+
<p align="center">
|
|
95
|
+
<img src="docs/assets/login-demo.gif" alt="After buddy login xai, Grok writes a haiku with no API key, $0 marginal" width="760"/>
|
|
96
|
+
</p>
|
|
97
|
+
|
|
86
98
|
**Self-audit.** Asked to find a bug in its own integration code, `gpt-5.5` reads `provider-chatgpt-responses.ts`, spots a stale-variable issue (mutated `body.model` not propagated), and proposes the exact fix:
|
|
87
99
|
|
|
88
100
|
<p align="center">
|
|
@@ -108,6 +120,23 @@ More desktop demos (Fleet, Autonomy, Companion, …) and captures: [`cowork/read
|
|
|
108
120
|
|
|
109
121
|
---
|
|
110
122
|
|
|
123
|
+
## Research — a sensory "nervous system" *(experimental)*
|
|
124
|
+
|
|
125
|
+
Toward the long-term companion/robot vision, [`buddy-sense/`](buddy-sense/) is a **Rust, event-driven perception layer**. Parallel **sense modules** (audio VAD — energy or Silero neural; an autonomic **heartbeat**; screen via `xcap`; UI focus via AT-SPI) feed a **thalamus** that gates + coalesces the stream and broadcasts it over a loopback WebSocket into Code Buddy's event bus — where the heartbeat **paces background memory consolidation** ("dreaming", inspired by OpenClaw). Local, `$0`, permissive deps only (clean-room — no proprietary code copied).
|
|
126
|
+
|
|
127
|
+
<p align="center"><img src="buddy-sense/docs/architecture.svg" alt="buddy-sense nervous-system architecture: senses → thalamus → bridge → Code Buddy event bus" width="840"/></p>
|
|
128
|
+
|
|
129
|
+
**Honestly experimental** — distinct from the GA core above: the default daemon emits the heartbeat (+ audio from a WAV file); the live camera/mic aren't wired into the daemon yet. `speech → STT → 'hearing' percept` **is** wired (faster-whisper), with a hook left for driving a full agent turn. What's real today: the pure detector cores + thalamus + bridge are unit-tested (`cargo test`, 20 tests, no hardware), and the loopback bridge → event bus → reaction path (incl. the speech transcription) is covered on the Code Buddy side.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cd buddy-sense && cargo test # 20 tests, no hardware
|
|
133
|
+
./buddy-sense/demo.sh # headless end-to-end: heartbeat + audio VAD → Code Buddy
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Design, the five sense modules, the opt-in features, and the diagrams: [`buddy-sense/README.md`](buddy-sense/README.md).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
111
140
|
## Quick Start
|
|
112
141
|
|
|
113
142
|
```bash
|
|
@@ -154,6 +183,18 @@ buddy llm ensemble "is this approach sound?" # ask ChatGPT + Grok + Ollama toget
|
|
|
154
183
|
CODEBUDDY_LLM_FAILOVER=1 buddy -p "…" # if the primary errors, auto-continue on the next active LLM
|
|
155
184
|
```
|
|
156
185
|
|
|
186
|
+
<p align="center">
|
|
187
|
+
<img src="docs/assets/llm-demo.gif" alt="buddy llm lists your active LLMs, then auto-fails over from Grok to ChatGPT when the primary errors" width="760"/>
|
|
188
|
+
<br/>
|
|
189
|
+
<sub>Your logins at a glance — and automatic failover from one to the next when one has a problem, at <code>$0</code>. Real run, unedited.</sub>
|
|
190
|
+
</p>
|
|
191
|
+
|
|
192
|
+
<p align="center">
|
|
193
|
+
<img src="docs/assets/ensemble-demo.gif" alt="buddy llm ensemble asks ChatGPT, Ollama and Grok the same question, then synthesizes one answer" width="760"/>
|
|
194
|
+
<br/>
|
|
195
|
+
<sub><code>buddy llm ensemble</code> — every brain you're logged into answers, then it's synthesized into one. Real run, unedited.</sub>
|
|
196
|
+
</p>
|
|
197
|
+
|
|
157
198
|
See [Getting Started](docs/getting-started.md) for install options, headless mode, sessions, and typical workflows.
|
|
158
199
|
|
|
159
200
|
---
|
|
@@ -168,10 +209,22 @@ Cowork is the desktop cockpit for Code Buddy: chat, tools, traces, workflows, se
|
|
|
168
209
|
<sub>Real <code>gpt-5.5</code> in the Cowork desktop app — the answer streams in, cost <code>$0.0000</code>. <a href="docs/qa/code-buddy-studio/showcase-2026-06-16/cowork-chat-stream.mp4">MP4 →</a></sub>
|
|
169
210
|
</p>
|
|
170
211
|
|
|
212
|
+
<p align="center">
|
|
213
|
+
<a href="docs/assets/cowork-chat-demo.mp4"><img src="docs/assets/cowork-chat-demo.gif" alt="A local reasoning model thinks through a haiku on screen in the Cowork desktop app, $0" width="760"/></a>
|
|
214
|
+
<br/>
|
|
215
|
+
<sub>…and fully local: a reasoning model (<code>qwen3.6:35b-a3b</code>) <b>thinks on screen</b>, then answers — no cloud, <code>$0</code>. <a href="docs/assets/cowork-chat-demo.mp4">MP4 →</a></sub>
|
|
216
|
+
</p>
|
|
217
|
+
|
|
218
|
+
<p align="center">
|
|
219
|
+
<a href="docs/assets/cowork-panels-demo.mp4"><img src="docs/assets/cowork-panels-demo.gif" alt="The Cowork left rail opens the Autonomy dashboard, Memory and other panels as dock tabs" width="760"/></a>
|
|
220
|
+
<br/>
|
|
221
|
+
<sub>The left rail opens every panel as a dock tab — here the <b>Autonomy dashboard</b> (24/7 daemon, free-first model ladder, live subagents) and <b>Project Memory</b>. <a href="docs/assets/cowork-panels-demo.mp4">MP4 →</a></sub>
|
|
222
|
+
</p>
|
|
223
|
+
|
|
171
224
|
<table>
|
|
172
225
|
<tr>
|
|
173
|
-
<td width="50%" align="center"><img src="docs/
|
|
174
|
-
<td width="50%" align="center"><img src="docs/
|
|
226
|
+
<td width="50%" align="center"><img src="docs/assets/cowork-welcome.png" alt="Cowork desktop home with the expanded left menu and quick-action cards" width="430"/><br/><sub>Home — expanded menu, quick-action cards, gradient hero</sub></td>
|
|
227
|
+
<td width="50%" align="center"><img src="docs/assets/cowork-panel.png" alt="A launcher opens its panel as a tab — here the Autonomy dashboard" width="430"/><br/><sub>A launcher opens its panel as a tab — here the Autonomy dashboard (daemon, model ladder, subagents)</sub></td>
|
|
175
228
|
</tr>
|
|
176
229
|
<tr>
|
|
177
230
|
<td width="50%" align="center"><img src="docs/qa/code-buddy-studio/showcase-2026-06-16/03-fleet-autonomy.png" alt="Fleet and autonomy dashboard" width="430"/><br/><sub>Fleet dispatch · tool-permission posture · Hermes toolsets</sub></td>
|
|
@@ -58,15 +58,15 @@ export declare const agenticCodingApprovalDecisionSchema: z.ZodObject<{
|
|
|
58
58
|
}, "strict", z.ZodTypeAny, {
|
|
59
59
|
reason: string;
|
|
60
60
|
decision: "rejected" | "approved";
|
|
61
|
-
kind: "agentic-coding-approval-decision";
|
|
62
61
|
schemaVersion: 1;
|
|
62
|
+
kind: "agentic-coding-approval-decision";
|
|
63
63
|
reviewer: string;
|
|
64
64
|
decidedAt?: string | undefined;
|
|
65
65
|
}, {
|
|
66
66
|
reason: string;
|
|
67
67
|
decision: "rejected" | "approved";
|
|
68
|
-
kind: "agentic-coding-approval-decision";
|
|
69
68
|
schemaVersion: 1;
|
|
69
|
+
kind: "agentic-coding-approval-decision";
|
|
70
70
|
reviewer: string;
|
|
71
71
|
decidedAt?: string | undefined;
|
|
72
72
|
}>;
|
|
@@ -108,6 +108,7 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
108
108
|
risks: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
109
109
|
}, "strict", z.ZodTypeAny, {
|
|
110
110
|
summary: string;
|
|
111
|
+
schemaVersion: 1;
|
|
111
112
|
kind: "agentic-coding-workflow-builder-proposal";
|
|
112
113
|
nodes: {
|
|
113
114
|
type: "action" | "trigger" | "logic";
|
|
@@ -116,7 +117,6 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
116
117
|
description: string;
|
|
117
118
|
agenticType: "edit" | "analysis" | "handoff" | "approval" | "verification" | "gate";
|
|
118
119
|
}[];
|
|
119
|
-
schemaVersion: 1;
|
|
120
120
|
edges: {
|
|
121
121
|
source: string;
|
|
122
122
|
target: string;
|
|
@@ -126,6 +126,7 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
126
126
|
coworkVisualizationNotes: string[];
|
|
127
127
|
}, {
|
|
128
128
|
summary: string;
|
|
129
|
+
schemaVersion: 1;
|
|
129
130
|
kind: "agentic-coding-workflow-builder-proposal";
|
|
130
131
|
nodes: {
|
|
131
132
|
type: "action" | "trigger" | "logic";
|
|
@@ -134,7 +135,6 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
134
135
|
description: string;
|
|
135
136
|
agenticType: "edit" | "analysis" | "handoff" | "approval" | "verification" | "gate";
|
|
136
137
|
}[];
|
|
137
|
-
schemaVersion: 1;
|
|
138
138
|
edges: {
|
|
139
139
|
source: string;
|
|
140
140
|
target: string;
|
|
@@ -144,6 +144,7 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
144
144
|
coworkVisualizationNotes?: string[] | undefined;
|
|
145
145
|
}>, {
|
|
146
146
|
summary: string;
|
|
147
|
+
schemaVersion: 1;
|
|
147
148
|
kind: "agentic-coding-workflow-builder-proposal";
|
|
148
149
|
nodes: {
|
|
149
150
|
type: "action" | "trigger" | "logic";
|
|
@@ -152,7 +153,6 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
152
153
|
description: string;
|
|
153
154
|
agenticType: "edit" | "analysis" | "handoff" | "approval" | "verification" | "gate";
|
|
154
155
|
}[];
|
|
155
|
-
schemaVersion: 1;
|
|
156
156
|
edges: {
|
|
157
157
|
source: string;
|
|
158
158
|
target: string;
|
|
@@ -162,6 +162,7 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
162
162
|
coworkVisualizationNotes: string[];
|
|
163
163
|
}, {
|
|
164
164
|
summary: string;
|
|
165
|
+
schemaVersion: 1;
|
|
165
166
|
kind: "agentic-coding-workflow-builder-proposal";
|
|
166
167
|
nodes: {
|
|
167
168
|
type: "action" | "trigger" | "logic";
|
|
@@ -170,7 +171,6 @@ export declare const agenticCodingWorkflowBuilderProposalSchema: z.ZodEffects<z.
|
|
|
170
171
|
description: string;
|
|
171
172
|
agenticType: "edit" | "analysis" | "handoff" | "approval" | "verification" | "gate";
|
|
172
173
|
}[];
|
|
173
|
-
schemaVersion: 1;
|
|
174
174
|
edges: {
|
|
175
175
|
source: string;
|
|
176
176
|
target: string;
|
|
@@ -191,6 +191,10 @@ export declare abstract class BaseAgent extends EventEmitter implements Agent {
|
|
|
191
191
|
getMCPTools(): Promise<Map<string, unknown[]>>;
|
|
192
192
|
getMCPClient(): MCPClient;
|
|
193
193
|
protected initializeMCP(): void;
|
|
194
|
+
/** Resolves once MCP server initialization has settled (or no-op if MCP was
|
|
195
|
+
* never started). Headless/one-shot callers await this so the gitnexus-style
|
|
196
|
+
* MCP tools are registered before the first turn instead of racing init. */
|
|
197
|
+
getMCPReady(): Promise<void>;
|
|
194
198
|
getContextStats(): import("./facades/agent-context-facade.js").ContextStats;
|
|
195
199
|
formatContextStats(): string;
|
|
196
200
|
/**
|
package/dist/agent/base-agent.js
CHANGED
|
@@ -316,6 +316,12 @@ export class BaseAgent extends EventEmitter {
|
|
|
316
316
|
initializeMCP() {
|
|
317
317
|
this.infrastructureFacade.initializeMCP();
|
|
318
318
|
}
|
|
319
|
+
/** Resolves once MCP server initialization has settled (or no-op if MCP was
|
|
320
|
+
* never started). Headless/one-shot callers await this so the gitnexus-style
|
|
321
|
+
* MCP tools are registered before the first turn instead of racing init. */
|
|
322
|
+
getMCPReady() {
|
|
323
|
+
return this.infrastructureFacade.getMCPReady();
|
|
324
|
+
}
|
|
319
325
|
// ============================================================================
|
|
320
326
|
// Context Management (delegates to contextFacade)
|
|
321
327
|
// ============================================================================
|
|
@@ -77,11 +77,18 @@ export declare class InfrastructureFacade {
|
|
|
77
77
|
* Get the MCP client instance (for advanced operations)
|
|
78
78
|
*/
|
|
79
79
|
getMCPClient(): MCPClient;
|
|
80
|
+
/** Resolves once initializeMCP() has finished connecting servers (or failed
|
|
81
|
+
* softly). Callers that need MCP tools available — notably headless one-shot
|
|
82
|
+
* runs — can await getMCPReady() instead of racing the fire-and-forget init. */
|
|
83
|
+
private mcpReady;
|
|
80
84
|
/**
|
|
81
|
-
* Initialize MCP servers asynchronously
|
|
82
|
-
*
|
|
85
|
+
* Initialize MCP servers asynchronously. Kicks off in the background (the
|
|
86
|
+
* constructor path stays non-blocking), but the readiness promise is captured
|
|
87
|
+
* so a one-shot/headless caller can await getMCPReady() before its first turn.
|
|
83
88
|
*/
|
|
84
89
|
initializeMCP(): void;
|
|
90
|
+
/** Promise that resolves when MCP server initialization has settled. */
|
|
91
|
+
getMCPReady(): Promise<void>;
|
|
85
92
|
/**
|
|
86
93
|
* Get the ICM bridge instance for memory operations
|
|
87
94
|
*/
|
|
@@ -67,12 +67,17 @@ export class InfrastructureFacade {
|
|
|
67
67
|
getMCPClient() {
|
|
68
68
|
return this.mcpClient;
|
|
69
69
|
}
|
|
70
|
+
/** Resolves once initializeMCP() has finished connecting servers (or failed
|
|
71
|
+
* softly). Callers that need MCP tools available — notably headless one-shot
|
|
72
|
+
* runs — can await getMCPReady() instead of racing the fire-and-forget init. */
|
|
73
|
+
mcpReady = Promise.resolve();
|
|
70
74
|
/**
|
|
71
|
-
* Initialize MCP servers asynchronously
|
|
72
|
-
*
|
|
75
|
+
* Initialize MCP servers asynchronously. Kicks off in the background (the
|
|
76
|
+
* constructor path stays non-blocking), but the readiness promise is captured
|
|
77
|
+
* so a one-shot/headless caller can await getMCPReady() before its first turn.
|
|
73
78
|
*/
|
|
74
79
|
initializeMCP() {
|
|
75
|
-
(async () => {
|
|
80
|
+
this.mcpReady = (async () => {
|
|
76
81
|
try {
|
|
77
82
|
const config = loadMCPConfig();
|
|
78
83
|
if (config.servers.length > 0) {
|
|
@@ -87,9 +92,13 @@ export class InfrastructureFacade {
|
|
|
87
92
|
catch (error) {
|
|
88
93
|
logger.warn('MCP initialization failed', { error: getErrorMessage(error) });
|
|
89
94
|
}
|
|
90
|
-
})()
|
|
91
|
-
|
|
92
|
-
});
|
|
95
|
+
})();
|
|
96
|
+
// Never let this become an unhandled rejection for callers that don't await.
|
|
97
|
+
void this.mcpReady.catch(() => { });
|
|
98
|
+
}
|
|
99
|
+
/** Promise that resolves when MCP server initialization has settled. */
|
|
100
|
+
getMCPReady() {
|
|
101
|
+
return this.mcpReady;
|
|
93
102
|
}
|
|
94
103
|
/**
|
|
95
104
|
* Get the ICM bridge instance for memory operations
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-artifact gate — the static (no-execution) safety scan applied to any
|
|
3
|
+
* code the agent authors for itself (tools or skill scripts) BEFORE it is run or
|
|
4
|
+
* registered. Blocking and ordered; the behavioural held-out scoring (which DOES
|
|
5
|
+
* run the code, sandboxed) lives in sandbox-scorer.ts and runs only after this.
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/authored-artifact-gate
|
|
8
|
+
*/
|
|
9
|
+
export interface ArtifactGateResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
reasons: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Statically inspect authored code. Returns ok=false with one or more reasons on
|
|
15
|
+
* any finding. `subsystem` selects the dangerous-pattern set ('code' for tools,
|
|
16
|
+
* 'skill' for skill scripts).
|
|
17
|
+
*/
|
|
18
|
+
export declare function inspectAuthoredCode(code: string, subsystem?: 'code' | 'skill'): ArtifactGateResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-artifact gate — the static (no-execution) safety scan applied to any
|
|
3
|
+
* code the agent authors for itself (tools or skill scripts) BEFORE it is run or
|
|
4
|
+
* registered. Blocking and ordered; the behavioural held-out scoring (which DOES
|
|
5
|
+
* run the code, sandboxed) lives in sandbox-scorer.ts and runs only after this.
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/authored-artifact-gate
|
|
8
|
+
*/
|
|
9
|
+
import { matchAllDangerousPatterns } from '../../security/dangerous-patterns.js';
|
|
10
|
+
/** Omission placeholders that signal truncated / non-self-contained code. */
|
|
11
|
+
const OMISSION_RE = /\/\/\s*\.\.\.\s*(rest|remaining|other|more|implementation|code)\b|#\s*\.\.\.\s*(rest|remaining)\b/i;
|
|
12
|
+
/** Obvious secret shapes — never let an authored artifact embed these. */
|
|
13
|
+
const SECRET_RE = /(sk-[a-z0-9]{16,}|api[_-]?key\s*[:=]\s*['"]?\S{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|ghp_[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16})/i;
|
|
14
|
+
/** Writing under src/ is the hard self-modification invariant — refuse it. */
|
|
15
|
+
const SRC_WRITE_RE = /(writeFile|writeFileSync|appendFile\w*|fs\.\w*write|open\s*\([^)]*['"]w|>>?\s*['"]?\.{0,2}\/?src\/)/i;
|
|
16
|
+
const MAX_CODE_BYTES = 64 * 1024;
|
|
17
|
+
/**
|
|
18
|
+
* Statically inspect authored code. Returns ok=false with one or more reasons on
|
|
19
|
+
* any finding. `subsystem` selects the dangerous-pattern set ('code' for tools,
|
|
20
|
+
* 'skill' for skill scripts).
|
|
21
|
+
*/
|
|
22
|
+
export function inspectAuthoredCode(code, subsystem = 'code') {
|
|
23
|
+
const reasons = [];
|
|
24
|
+
const text = String(code ?? '');
|
|
25
|
+
if (!text.trim())
|
|
26
|
+
reasons.push('code is empty');
|
|
27
|
+
if (text.length > MAX_CODE_BYTES)
|
|
28
|
+
reasons.push(`code too large (${text.length} > ${MAX_CODE_BYTES} bytes)`);
|
|
29
|
+
const dangerous = matchAllDangerousPatterns(text, subsystem);
|
|
30
|
+
if (dangerous.length > 0) {
|
|
31
|
+
reasons.push(`matched ${dangerous.length} dangerous pattern(s): ${dangerous.map((d) => d.description).slice(0, 4).join('; ')}`);
|
|
32
|
+
}
|
|
33
|
+
if (SECRET_RE.test(text))
|
|
34
|
+
reasons.push('looks like it embeds a secret');
|
|
35
|
+
if (OMISSION_RE.test(text))
|
|
36
|
+
reasons.push('contains an omission placeholder (not self-contained)');
|
|
37
|
+
if (/src\//.test(text) && SRC_WRITE_RE.test(text)) {
|
|
38
|
+
reasons.push('writes under src/ (forbidden self-modification invariant)');
|
|
39
|
+
}
|
|
40
|
+
return { ok: reasons.length === 0, reasons };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=authored-artifact-gate.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-tool runtime — shared by `register_tool` (the live capability) and the
|
|
3
|
+
* self-improvement engine's sandbox scorer (the gate). One definition of "how an
|
|
4
|
+
* authored tool runs" so the thing we GATE is exactly the thing we REGISTER.
|
|
5
|
+
*
|
|
6
|
+
* An authored tool runs its `code` SANDBOXED: a throwaway cwd, RPC off (no
|
|
7
|
+
* callback into the tool system), and its call arguments arrive as JSON in the
|
|
8
|
+
* CODEBUDDY_TOOL_INPUT env var; the script prints its result to stdout.
|
|
9
|
+
*
|
|
10
|
+
* @module agent/self-improvement/authored-tool-runtime
|
|
11
|
+
*/
|
|
12
|
+
import type { ITool } from '../../tools/registry/types.js';
|
|
13
|
+
import { type ExecuteCodeLanguage } from '../../tools/execute-code-runner.js';
|
|
14
|
+
export declare const AUTHORED_PREFIX = "authored__";
|
|
15
|
+
export declare const AUTHORED_LANGUAGES: ExecuteCodeLanguage[];
|
|
16
|
+
export interface AuthoredToolSpec {
|
|
17
|
+
/** Namespaced tool name (see toAuthoredName). */
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
parameters: Record<string, unknown>;
|
|
21
|
+
language: ExecuteCodeLanguage;
|
|
22
|
+
code: string;
|
|
23
|
+
}
|
|
24
|
+
/** Namespace + sanitize a raw tool name to `authored__<slug>` (never shadows a built-in). */
|
|
25
|
+
export declare function toAuthoredName(raw: string): string;
|
|
26
|
+
/** Build an ITool that runs the authored `code` sandboxed when invoked. */
|
|
27
|
+
export declare function buildAuthoredTool(spec: AuthoredToolSpec): ITool;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-tool runtime — shared by `register_tool` (the live capability) and the
|
|
3
|
+
* self-improvement engine's sandbox scorer (the gate). One definition of "how an
|
|
4
|
+
* authored tool runs" so the thing we GATE is exactly the thing we REGISTER.
|
|
5
|
+
*
|
|
6
|
+
* An authored tool runs its `code` SANDBOXED: a throwaway cwd, RPC off (no
|
|
7
|
+
* callback into the tool system), and its call arguments arrive as JSON in the
|
|
8
|
+
* CODEBUDDY_TOOL_INPUT env var; the script prints its result to stdout.
|
|
9
|
+
*
|
|
10
|
+
* @module agent/self-improvement/authored-tool-runtime
|
|
11
|
+
*/
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import { executeCode } from '../../tools/execute-code-runner.js';
|
|
16
|
+
export const AUTHORED_PREFIX = 'authored__';
|
|
17
|
+
export const AUTHORED_LANGUAGES = ['javascript', 'typescript', 'python'];
|
|
18
|
+
/** Namespace + sanitize a raw tool name to `authored__<slug>` (never shadows a built-in). */
|
|
19
|
+
export function toAuthoredName(raw) {
|
|
20
|
+
const base = String(raw)
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9_]+/g, '_')
|
|
24
|
+
.replace(/^_+|_+$/g, '');
|
|
25
|
+
return base.startsWith(AUTHORED_PREFIX) ? base : `${AUTHORED_PREFIX}${base || 'tool'}`;
|
|
26
|
+
}
|
|
27
|
+
/** Build an ITool that runs the authored `code` sandboxed when invoked. */
|
|
28
|
+
export function buildAuthoredTool(spec) {
|
|
29
|
+
const { name, description, parameters, language, code } = spec;
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
description,
|
|
33
|
+
async execute(input) {
|
|
34
|
+
const rootDir = path.join(os.tmpdir(), `cb-authored-${randomUUID()}`);
|
|
35
|
+
try {
|
|
36
|
+
const res = await executeCode({ code, language, env: { CODEBUDDY_TOOL_INPUT: JSON.stringify(input ?? {}) } }, { rootDir, rpcEnabled: false });
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: `authored tool "${name}" failed (exit ${res.exitCode}): ${res.stderr.slice(0, 2000) || res.error || 'no output'}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { success: true, output: res.stdout.slice(0, 100_000) || '(no stdout)' };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: `authored tool "${name}" error: ${err instanceof Error ? err.message : String(err)}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
getSchema() {
|
|
53
|
+
return { name, description, parameters: parameters };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=authored-tool-runtime.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-tool store — durable persistence for tools the agent has authored and
|
|
3
|
+
* kept (auto-apply). Without this, authored tools live only for the session; with
|
|
4
|
+
* it, they are reloaded into both registries at startup so a self-improvement
|
|
5
|
+
* survives a restart. A flat JSON file alongside the evolutionary archive.
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/authored-tool-store
|
|
8
|
+
*/
|
|
9
|
+
import type { AuthoredToolSpec } from './authored-tool-runtime.js';
|
|
10
|
+
export declare const AUTHORED_TOOL_STORE_SCHEMA_VERSION = 1;
|
|
11
|
+
export interface AuthoredToolStoreOptions {
|
|
12
|
+
workDir?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class AuthoredToolStore {
|
|
15
|
+
private readonly filePath;
|
|
16
|
+
constructor(options?: AuthoredToolStoreOptions);
|
|
17
|
+
get path(): string;
|
|
18
|
+
private read;
|
|
19
|
+
private write;
|
|
20
|
+
list(): AuthoredToolSpec[];
|
|
21
|
+
/** Upsert a spec by name (a re-authored tool replaces the prior version). */
|
|
22
|
+
add(spec: AuthoredToolSpec): void;
|
|
23
|
+
remove(name: string): boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored-tool store — durable persistence for tools the agent has authored and
|
|
3
|
+
* kept (auto-apply). Without this, authored tools live only for the session; with
|
|
4
|
+
* it, they are reloaded into both registries at startup so a self-improvement
|
|
5
|
+
* survives a restart. A flat JSON file alongside the evolutionary archive.
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/authored-tool-store
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
export const AUTHORED_TOOL_STORE_SCHEMA_VERSION = 1;
|
|
12
|
+
export class AuthoredToolStore {
|
|
13
|
+
filePath;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
const root = options.workDir ?? process.cwd();
|
|
16
|
+
this.filePath = path.join(root, '.codebuddy', 'self-improvement', 'authored-tools.json');
|
|
17
|
+
}
|
|
18
|
+
get path() {
|
|
19
|
+
return this.filePath;
|
|
20
|
+
}
|
|
21
|
+
read() {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
|
|
24
|
+
if (Array.isArray(parsed.tools)) {
|
|
25
|
+
return { schemaVersion: AUTHORED_TOOL_STORE_SCHEMA_VERSION, tools: parsed.tools };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* no store yet */
|
|
30
|
+
}
|
|
31
|
+
return { schemaVersion: AUTHORED_TOOL_STORE_SCHEMA_VERSION, tools: [] };
|
|
32
|
+
}
|
|
33
|
+
write(file) {
|
|
34
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
35
|
+
fs.writeFileSync(this.filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
list() {
|
|
38
|
+
return this.read().tools;
|
|
39
|
+
}
|
|
40
|
+
/** Upsert a spec by name (a re-authored tool replaces the prior version). */
|
|
41
|
+
add(spec) {
|
|
42
|
+
const file = this.read();
|
|
43
|
+
file.tools = file.tools.filter((t) => t.name !== spec.name);
|
|
44
|
+
file.tools.push(spec);
|
|
45
|
+
this.write(file);
|
|
46
|
+
}
|
|
47
|
+
remove(name) {
|
|
48
|
+
const file = this.read();
|
|
49
|
+
const before = file.tools.length;
|
|
50
|
+
file.tools = file.tools.filter((t) => t.name !== name);
|
|
51
|
+
if (file.tools.length === before)
|
|
52
|
+
return false;
|
|
53
|
+
this.write(file);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=authored-tool-store.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM tool proposer — authors a candidate tool with the agent's own model from a
|
|
3
|
+
* REDACTED scenario view (capability + visible cases only; never the held-out
|
|
4
|
+
* cases). The behavioural held-out gate validates every draft, so even a gamed or
|
|
5
|
+
* broken draft is caught — the LLM is a generator, not a trusted oracle.
|
|
6
|
+
*
|
|
7
|
+
* Lazy + graceful: if no provider is configured the proposer declines (null) and
|
|
8
|
+
* the engine simply finds no proposal.
|
|
9
|
+
*
|
|
10
|
+
* @module agent/self-improvement/llm-tool-proposer
|
|
11
|
+
*/
|
|
12
|
+
import { type AuthoredToolSpec } from './authored-tool-runtime.js';
|
|
13
|
+
import type { ProposerScenarioView, ToolProposer } from './tool-proposer.js';
|
|
14
|
+
import type { ToolProposal } from './tool-types.js';
|
|
15
|
+
interface MinimalClient {
|
|
16
|
+
chat(messages: Array<{
|
|
17
|
+
role: string;
|
|
18
|
+
content: string;
|
|
19
|
+
}>, tools?: unknown[]): Promise<{
|
|
20
|
+
choices?: Array<{
|
|
21
|
+
message?: {
|
|
22
|
+
content?: string | null;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildToolDraftPrompt(view: ProposerScenarioView): string;
|
|
28
|
+
/** Parse + validate a model draft into an AuthoredToolSpec, or null. */
|
|
29
|
+
export declare function parseToolDraft(text: string): AuthoredToolSpec | null;
|
|
30
|
+
export interface LlmToolProposerOptions {
|
|
31
|
+
/** Override the chat client (tests). Defaults to provider-detected CodeBuddyClient. */
|
|
32
|
+
client?: MinimalClient | null;
|
|
33
|
+
}
|
|
34
|
+
export declare class LlmToolProposer implements ToolProposer {
|
|
35
|
+
private readonly options;
|
|
36
|
+
private clientPromise;
|
|
37
|
+
constructor(options?: LlmToolProposerOptions);
|
|
38
|
+
private getClient;
|
|
39
|
+
propose(view: ProposerScenarioView): Promise<ToolProposal | null>;
|
|
40
|
+
}
|
|
41
|
+
export {};
|