@phuetz/code-buddy 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -24
- 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 +2 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolImprovementEngine — the tool sibling of SelfImprovementEngine. One cycle:
|
|
3
|
+
* pick an uncovered tool scenario → propose (redacted view) → tool gate
|
|
4
|
+
* (static scan → visible → held-out) → keep+archive (auto-apply) or report.
|
|
5
|
+
*
|
|
6
|
+
* Autonomy is the same fail-safe toggle: 'propose-only' (default) validates and
|
|
7
|
+
* reports; 'auto-apply' (CODEBUDDY_SELF_IMPROVE=true) keeps only tools that pass
|
|
8
|
+
* the behavioural held-out gate, and archives them (reversible — un-registerable).
|
|
9
|
+
*
|
|
10
|
+
* @module agent/self-improvement/tool-engine
|
|
11
|
+
*/
|
|
12
|
+
import { EvolutionaryArchive } from './evolutionary-archive.js';
|
|
13
|
+
import { resolveAutonomy } from './engine.js';
|
|
14
|
+
import { validateToolProposal } from './tool-gate.js';
|
|
15
|
+
import { LiveToolMutator } from './tool-skill-mutator.js';
|
|
16
|
+
import { toProposerView } from './tool-proposer.js';
|
|
17
|
+
export class ToolImprovementEngine {
|
|
18
|
+
scenarios;
|
|
19
|
+
proposer;
|
|
20
|
+
mutator;
|
|
21
|
+
archive;
|
|
22
|
+
autonomy;
|
|
23
|
+
now;
|
|
24
|
+
/** Scenario ids already satisfied this run (coverage is per-scenario, not per tool name). */
|
|
25
|
+
covered = new Set();
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.scenarios = options.scenarios;
|
|
28
|
+
this.proposer = options.proposer;
|
|
29
|
+
this.mutator = options.mutator ?? new LiveToolMutator();
|
|
30
|
+
this.archive = options.archive ?? new EvolutionaryArchive();
|
|
31
|
+
this.autonomy = options.autonomy ?? resolveAutonomy();
|
|
32
|
+
this.now = options.now ?? (() => new Date());
|
|
33
|
+
}
|
|
34
|
+
/** Run exactly one tool-improvement cycle. */
|
|
35
|
+
async runCycle() {
|
|
36
|
+
const startedAt = this.now().toISOString();
|
|
37
|
+
const base = { kind: 'tool_improvement_cycle', startedAt, autonomy: this.autonomy };
|
|
38
|
+
for (const scenario of this.scenarios) {
|
|
39
|
+
// Coverage is per-scenario: once a tool has satisfied this scenario's gate,
|
|
40
|
+
// don't re-author it (even if the model would pick a different name).
|
|
41
|
+
if (this.covered.has(scenario.id))
|
|
42
|
+
continue;
|
|
43
|
+
const proposal = await this.proposer.propose(toProposerView(scenario));
|
|
44
|
+
if (!proposal)
|
|
45
|
+
continue;
|
|
46
|
+
// A tool with this exact name already exists — skip (avoid dup-register).
|
|
47
|
+
if (this.mutator.has(proposal.spec.name)) {
|
|
48
|
+
this.covered.add(scenario.id);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const gate = await validateToolProposal(proposal, scenario, this.mutator, {
|
|
52
|
+
keepOnAccept: this.autonomy === 'auto-apply',
|
|
53
|
+
});
|
|
54
|
+
const applied = gate.accepted && !!gate.appliedRef;
|
|
55
|
+
if (applied) {
|
|
56
|
+
this.covered.add(scenario.id);
|
|
57
|
+
this.archive.append({
|
|
58
|
+
proposalId: proposal.id,
|
|
59
|
+
kind: 'tool',
|
|
60
|
+
targetScenarioId: scenario.id,
|
|
61
|
+
experienceId: proposal.experienceId,
|
|
62
|
+
delta: 1,
|
|
63
|
+
scoreAfter: gate.visiblePassed + gate.heldOutPassed,
|
|
64
|
+
appliedRef: gate.appliedRef,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
selectedScenarioId: scenario.id,
|
|
70
|
+
proposalId: proposal.id,
|
|
71
|
+
gate,
|
|
72
|
+
applied,
|
|
73
|
+
notes: gate.reasons,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
...base,
|
|
78
|
+
selectedScenarioId: null,
|
|
79
|
+
proposalId: null,
|
|
80
|
+
gate: null,
|
|
81
|
+
applied: false,
|
|
82
|
+
notes: ['no uncovered tool scenario with an available proposal'],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Run cycles until nothing new is applied (or maxCycles). */
|
|
86
|
+
async runLoop(maxCycles) {
|
|
87
|
+
const cap = Math.max(1, maxCycles ?? this.scenarios.length + 1);
|
|
88
|
+
const results = [];
|
|
89
|
+
for (let i = 0; i < cap; i++) {
|
|
90
|
+
const r = await this.runCycle();
|
|
91
|
+
results.push(r);
|
|
92
|
+
if (!r.applied)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
status() {
|
|
98
|
+
return { autonomy: this.autonomy, scenarios: this.scenarios.length, archive: this.archive.summary() };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=tool-engine.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool gate — the empirical, anti-gaming validator for an authored-tool proposal.
|
|
3
|
+
* Ordered, blocking, fail-closed:
|
|
4
|
+
* G1 static scan (authored-artifact-gate, no execution)
|
|
5
|
+
* G3 VISIBLE behavioural cases must all pass (the tool actually works)
|
|
6
|
+
* G4 HELD-OUT behavioural cases must all pass (fresh inputs the proposer never
|
|
7
|
+
* saw — a tool that hardcodes the visible outputs fails here → rejected).
|
|
8
|
+
* A scenario with no held-out cases is rejected (anti-gaming is then undefined).
|
|
9
|
+
*
|
|
10
|
+
* Scoring never registers the tool; registration happens only on accept+keep.
|
|
11
|
+
*
|
|
12
|
+
* @module agent/self-improvement/tool-gate
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolMutatorPort } from './tool-skill-mutator.js';
|
|
15
|
+
import type { ToolBenchmarkScenario, ToolGateOutcome, ToolProposal } from './tool-types.js';
|
|
16
|
+
export interface ValidateToolOptions {
|
|
17
|
+
/** auto-apply: keep (register) an accepted tool. propose-only: report only. */
|
|
18
|
+
keepOnAccept: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function validateToolProposal(proposal: ToolProposal, scenario: ToolBenchmarkScenario, mutator: ToolMutatorPort, options: ValidateToolOptions): Promise<ToolGateOutcome>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool gate — the empirical, anti-gaming validator for an authored-tool proposal.
|
|
3
|
+
* Ordered, blocking, fail-closed:
|
|
4
|
+
* G1 static scan (authored-artifact-gate, no execution)
|
|
5
|
+
* G3 VISIBLE behavioural cases must all pass (the tool actually works)
|
|
6
|
+
* G4 HELD-OUT behavioural cases must all pass (fresh inputs the proposer never
|
|
7
|
+
* saw — a tool that hardcodes the visible outputs fails here → rejected).
|
|
8
|
+
* A scenario with no held-out cases is rejected (anti-gaming is then undefined).
|
|
9
|
+
*
|
|
10
|
+
* Scoring never registers the tool; registration happens only on accept+keep.
|
|
11
|
+
*
|
|
12
|
+
* @module agent/self-improvement/tool-gate
|
|
13
|
+
*/
|
|
14
|
+
import { inspectAuthoredCode } from './authored-artifact-gate.js';
|
|
15
|
+
import { scoreToolCases } from './sandbox-scorer.js';
|
|
16
|
+
export async function validateToolProposal(proposal, scenario, mutator, options) {
|
|
17
|
+
const zero = {
|
|
18
|
+
proposalId: proposal.id,
|
|
19
|
+
scenarioId: scenario.id,
|
|
20
|
+
visiblePassed: 0,
|
|
21
|
+
visibleTotal: scenario.visibleCases.length,
|
|
22
|
+
heldOutPassed: 0,
|
|
23
|
+
heldOutTotal: scenario.heldOutCases.length,
|
|
24
|
+
};
|
|
25
|
+
// G1 — static scan (no execution).
|
|
26
|
+
const scan = inspectAuthoredCode(proposal.spec.code, 'code');
|
|
27
|
+
if (!scan.ok) {
|
|
28
|
+
return { ...zero, accepted: false, rejectionReason: 'static-scan', reasons: scan.reasons };
|
|
29
|
+
}
|
|
30
|
+
// Fail-closed: a scenario with no held-out cases can't defend against gaming.
|
|
31
|
+
if (scenario.heldOutCases.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
...zero,
|
|
34
|
+
accepted: false,
|
|
35
|
+
rejectionReason: 'heldout-fail',
|
|
36
|
+
reasons: ['scenario has no held-out cases — anti-gaming is undefined, refusing'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// G3 — visible behavioural cases (the tool must actually work).
|
|
40
|
+
const visible = await scoreToolCases(proposal.spec, scenario.visibleCases);
|
|
41
|
+
if (visible.passed < visible.total) {
|
|
42
|
+
return {
|
|
43
|
+
...zero,
|
|
44
|
+
accepted: false,
|
|
45
|
+
visiblePassed: visible.passed,
|
|
46
|
+
rejectionReason: 'visible-fail',
|
|
47
|
+
reasons: visible.failures,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// G4 — held-out cases on FRESH inputs (catches hardcoded-output gaming).
|
|
51
|
+
const heldOut = await scoreToolCases(proposal.spec, scenario.heldOutCases);
|
|
52
|
+
if (heldOut.passed < heldOut.total) {
|
|
53
|
+
return {
|
|
54
|
+
...zero,
|
|
55
|
+
accepted: false,
|
|
56
|
+
visiblePassed: visible.passed,
|
|
57
|
+
heldOutPassed: heldOut.passed,
|
|
58
|
+
rejectionReason: 'heldout-fail',
|
|
59
|
+
reasons: ['held-out cases failed — likely gamed/overfit visible outputs', ...heldOut.failures],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Accepted. Keep (auto-apply) or just report (propose-only).
|
|
63
|
+
let appliedRef;
|
|
64
|
+
if (options.keepOnAccept) {
|
|
65
|
+
appliedRef = mutator.register(proposal.spec).name;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...zero,
|
|
69
|
+
accepted: true,
|
|
70
|
+
visiblePassed: visible.passed,
|
|
71
|
+
heldOutPassed: heldOut.passed,
|
|
72
|
+
reasons: options.keepOnAccept
|
|
73
|
+
? ['accepted and kept (auto-apply): passed visible + held-out, statically clean']
|
|
74
|
+
: ['accepted (propose-only): passed visible + held-out, not persisted'],
|
|
75
|
+
...(appliedRef ? { appliedRef } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=tool-gate.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool proposer — authors a candidate tool for a scenario. It receives a REDACTED
|
|
3
|
+
* view of the scenario (capability + visible cases only); the held-out cases are
|
|
4
|
+
* never exposed, so a proposer (static or LLM) cannot overfit to them. This is the
|
|
5
|
+
* structural half of the anti-gaming defence (the gate is the empirical half).
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/tool-proposer
|
|
8
|
+
*/
|
|
9
|
+
import type { AuthoredToolSpec } from './authored-tool-runtime.js';
|
|
10
|
+
import type { ToolBenchmarkScenario, ToolCase, ToolProposal } from './tool-types.js';
|
|
11
|
+
/** What a proposer is allowed to see — NO held-out cases. */
|
|
12
|
+
export interface ProposerScenarioView {
|
|
13
|
+
id: string;
|
|
14
|
+
capability: string;
|
|
15
|
+
description: string;
|
|
16
|
+
visibleCases: ToolCase[];
|
|
17
|
+
}
|
|
18
|
+
/** Redact a scenario down to what a proposer may see. */
|
|
19
|
+
export declare function toProposerView(scenario: ToolBenchmarkScenario): ProposerScenarioView;
|
|
20
|
+
export interface ToolProposer {
|
|
21
|
+
propose(view: ProposerScenarioView): Promise<ToolProposal | null>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Deterministic proposer backed by a fixture map (scenarioId → spec). Ships first
|
|
25
|
+
* (testable, no LLM). The LLM proposer is Phase 3 and uses the same redacted view.
|
|
26
|
+
*/
|
|
27
|
+
export declare class StaticToolProposer implements ToolProposer {
|
|
28
|
+
private readonly specs;
|
|
29
|
+
constructor(specs: Map<string, AuthoredToolSpec>);
|
|
30
|
+
propose(view: ProposerScenarioView): Promise<ToolProposal | null>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool proposer — authors a candidate tool for a scenario. It receives a REDACTED
|
|
3
|
+
* view of the scenario (capability + visible cases only); the held-out cases are
|
|
4
|
+
* never exposed, so a proposer (static or LLM) cannot overfit to them. This is the
|
|
5
|
+
* structural half of the anti-gaming defence (the gate is the empirical half).
|
|
6
|
+
*
|
|
7
|
+
* @module agent/self-improvement/tool-proposer
|
|
8
|
+
*/
|
|
9
|
+
/** Redact a scenario down to what a proposer may see. */
|
|
10
|
+
export function toProposerView(scenario) {
|
|
11
|
+
return {
|
|
12
|
+
id: scenario.id,
|
|
13
|
+
capability: scenario.capability,
|
|
14
|
+
description: scenario.description,
|
|
15
|
+
visibleCases: scenario.visibleCases,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Deterministic proposer backed by a fixture map (scenarioId → spec). Ships first
|
|
20
|
+
* (testable, no LLM). The LLM proposer is Phase 3 and uses the same redacted view.
|
|
21
|
+
*/
|
|
22
|
+
export class StaticToolProposer {
|
|
23
|
+
specs;
|
|
24
|
+
constructor(specs) {
|
|
25
|
+
this.specs = specs;
|
|
26
|
+
}
|
|
27
|
+
async propose(view) {
|
|
28
|
+
const spec = this.specs.get(view.id);
|
|
29
|
+
if (!spec)
|
|
30
|
+
return null;
|
|
31
|
+
return { id: `tool-proposal:${view.id}`, targetScenarioId: view.id, spec };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=tool-proposer.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool mutator port — applies/reverts an authored tool across BOTH registries
|
|
3
|
+
* with a proven inverse, so the engine can keep it (auto-apply) or cleanly
|
|
4
|
+
* un-register it (propose-only / rejection):
|
|
5
|
+
* - FormalToolRegistry → makes it CALLABLE (dispatch reads this),
|
|
6
|
+
* - legacy ToolRegistry → makes its schema VISIBLE to the model next turn.
|
|
7
|
+
*
|
|
8
|
+
* @module agent/self-improvement/tool-skill-mutator
|
|
9
|
+
*/
|
|
10
|
+
import { type AuthoredToolSpec } from './authored-tool-runtime.js';
|
|
11
|
+
import { AuthoredToolStore } from './authored-tool-store.js';
|
|
12
|
+
export interface ToolMutatorPort {
|
|
13
|
+
register(spec: AuthoredToolSpec): {
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
unregister(name: string): boolean;
|
|
17
|
+
has(name: string): boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface LiveToolMutatorOptions {
|
|
20
|
+
/** Persist kept tools to disk so they survive a restart (default true). */
|
|
21
|
+
persist?: boolean;
|
|
22
|
+
store?: AuthoredToolStore;
|
|
23
|
+
}
|
|
24
|
+
/** Dual-registry mutator over the live singletons, with optional disk persistence. */
|
|
25
|
+
export declare class LiveToolMutator implements ToolMutatorPort {
|
|
26
|
+
private readonly persist;
|
|
27
|
+
private readonly store;
|
|
28
|
+
constructor(options?: LiveToolMutatorOptions);
|
|
29
|
+
register(spec: AuthoredToolSpec): {
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
unregister(name: string): boolean;
|
|
33
|
+
has(name: string): boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Re-register persisted authored tools into both registries at startup. Does NOT
|
|
37
|
+
* re-persist (it's loading what's already on disk). Returns the names loaded.
|
|
38
|
+
* Gated by callers on CODEBUDDY_SELF_IMPROVE.
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadAuthoredTools(workDir?: string): string[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool mutator port — applies/reverts an authored tool across BOTH registries
|
|
3
|
+
* with a proven inverse, so the engine can keep it (auto-apply) or cleanly
|
|
4
|
+
* un-register it (propose-only / rejection):
|
|
5
|
+
* - FormalToolRegistry → makes it CALLABLE (dispatch reads this),
|
|
6
|
+
* - legacy ToolRegistry → makes its schema VISIBLE to the model next turn.
|
|
7
|
+
*
|
|
8
|
+
* @module agent/self-improvement/tool-skill-mutator
|
|
9
|
+
*/
|
|
10
|
+
import { FormalToolRegistry } from '../../tools/registry/tool-registry.js';
|
|
11
|
+
import { getToolRegistry } from '../../tools/registry.js';
|
|
12
|
+
import { buildAuthoredTool } from './authored-tool-runtime.js';
|
|
13
|
+
import { AuthoredToolStore } from './authored-tool-store.js';
|
|
14
|
+
/** Dual-registry mutator over the live singletons, with optional disk persistence. */
|
|
15
|
+
export class LiveToolMutator {
|
|
16
|
+
persist;
|
|
17
|
+
store;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.persist = options.persist ?? true;
|
|
20
|
+
this.store = options.store ?? new AuthoredToolStore();
|
|
21
|
+
}
|
|
22
|
+
register(spec) {
|
|
23
|
+
const tool = buildAuthoredTool(spec);
|
|
24
|
+
FormalToolRegistry.getInstance().register(tool, { override: true });
|
|
25
|
+
const definition = {
|
|
26
|
+
type: 'function',
|
|
27
|
+
function: {
|
|
28
|
+
name: spec.name,
|
|
29
|
+
description: spec.description,
|
|
30
|
+
parameters: spec.parameters,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const metadata = {
|
|
34
|
+
name: spec.name,
|
|
35
|
+
category: 'system',
|
|
36
|
+
keywords: ['authored', 'self-extension', 'tool'],
|
|
37
|
+
priority: 5,
|
|
38
|
+
description: spec.description,
|
|
39
|
+
};
|
|
40
|
+
getToolRegistry().registerTool(definition, metadata);
|
|
41
|
+
if (this.persist)
|
|
42
|
+
this.store.add(spec);
|
|
43
|
+
return { name: spec.name };
|
|
44
|
+
}
|
|
45
|
+
unregister(name) {
|
|
46
|
+
const a = FormalToolRegistry.getInstance().unregister(name);
|
|
47
|
+
const b = getToolRegistry().removeTool(name);
|
|
48
|
+
if (this.persist)
|
|
49
|
+
this.store.remove(name);
|
|
50
|
+
return a || b;
|
|
51
|
+
}
|
|
52
|
+
has(name) {
|
|
53
|
+
return FormalToolRegistry.getInstance().has(name) || getToolRegistry().getTool(name) !== undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Re-register persisted authored tools into both registries at startup. Does NOT
|
|
58
|
+
* re-persist (it's loading what's already on disk). Returns the names loaded.
|
|
59
|
+
* Gated by callers on CODEBUDDY_SELF_IMPROVE.
|
|
60
|
+
*/
|
|
61
|
+
export function loadAuthoredTools(workDir) {
|
|
62
|
+
const store = new AuthoredToolStore(workDir ? { workDir } : {});
|
|
63
|
+
const specs = store.list();
|
|
64
|
+
if (specs.length === 0)
|
|
65
|
+
return [];
|
|
66
|
+
const loader = new LiveToolMutator({ persist: false, store });
|
|
67
|
+
const loaded = [];
|
|
68
|
+
for (const spec of specs) {
|
|
69
|
+
try {
|
|
70
|
+
loader.register(spec);
|
|
71
|
+
loaded.push(spec.name);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* skip a malformed persisted spec */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return loaded;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=tool-skill-mutator.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the TOOL self-improvement path (sibling to the lesson path). A tool
|
|
3
|
+
* "covers" a scenario only BEHAVIORALLY: it must run on fixed inputs and produce
|
|
4
|
+
* asserted outputs (a presence predicate would be trivially gamed).
|
|
5
|
+
*
|
|
6
|
+
* @module agent/self-improvement/tool-types
|
|
7
|
+
*/
|
|
8
|
+
import type { AuthoredToolSpec } from './authored-tool-runtime.js';
|
|
9
|
+
/** One behavioural assertion: run the tool on `input`, output must contain ALL of `expectIncludes`. */
|
|
10
|
+
export interface ToolCase {
|
|
11
|
+
input: Record<string, unknown>;
|
|
12
|
+
expectIncludes: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A behavioural benchmark for an authored tool. `visibleCases` describe the
|
|
16
|
+
* capability (and may be shown to the proposer); `heldOutCases` use fresh inputs
|
|
17
|
+
* and are NEVER shown to the proposer — a tool that hardcodes the visible
|
|
18
|
+
* outputs passes visible but fails held-out (the anti-reward-hacking defence).
|
|
19
|
+
*/
|
|
20
|
+
export interface ToolBenchmarkScenario {
|
|
21
|
+
id: string;
|
|
22
|
+
/** Human capability description (safe to show the proposer). */
|
|
23
|
+
capability: string;
|
|
24
|
+
description: string;
|
|
25
|
+
visibleCases: ToolCase[];
|
|
26
|
+
heldOutCases: ToolCase[];
|
|
27
|
+
}
|
|
28
|
+
/** A candidate authored tool, motivated by a scenario. The spec must be produced WITHOUT seeing heldOutCases. */
|
|
29
|
+
export interface ToolProposal {
|
|
30
|
+
id: string;
|
|
31
|
+
targetScenarioId: string;
|
|
32
|
+
experienceId?: string;
|
|
33
|
+
spec: AuthoredToolSpec;
|
|
34
|
+
}
|
|
35
|
+
export type ToolGateRejection = 'static-scan' | 'visible-fail' | 'heldout-fail';
|
|
36
|
+
export interface ToolGateOutcome {
|
|
37
|
+
accepted: boolean;
|
|
38
|
+
proposalId: string;
|
|
39
|
+
scenarioId: string;
|
|
40
|
+
visiblePassed: number;
|
|
41
|
+
visibleTotal: number;
|
|
42
|
+
heldOutPassed: number;
|
|
43
|
+
heldOutTotal: number;
|
|
44
|
+
rejectionReason?: ToolGateRejection;
|
|
45
|
+
reasons: string[];
|
|
46
|
+
/** Name of the tool left registered (only when accepted AND kept). */
|
|
47
|
+
appliedRef?: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the TOOL self-improvement path (sibling to the lesson path). A tool
|
|
3
|
+
* "covers" a scenario only BEHAVIORALLY: it must run on fixed inputs and produce
|
|
4
|
+
* asserted outputs (a presence predicate would be trivially gamed).
|
|
5
|
+
*
|
|
6
|
+
* @module agent/self-improvement/tool-types
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=tool-types.js.map
|
|
@@ -97,13 +97,15 @@ export interface GateOutcome {
|
|
|
97
97
|
/** One accepted improvement, kept as an evolutionary stepping stone (DGM). */
|
|
98
98
|
export interface ArchiveEntry {
|
|
99
99
|
proposalId: string;
|
|
100
|
-
kind: ImprovementProposal['kind'];
|
|
100
|
+
kind: ImprovementProposal['kind'] | 'tool' | 'skill';
|
|
101
101
|
targetScenarioId: string;
|
|
102
102
|
experienceId?: string;
|
|
103
103
|
delta: number;
|
|
104
104
|
scoreAfter: number;
|
|
105
105
|
/** Id of the applied lesson (so it can be traced/rolled back later). */
|
|
106
106
|
appliedRef?: string;
|
|
107
|
+
/** For consolidation: the umbrella skill an archived sibling was absorbed into. */
|
|
108
|
+
absorbedInto?: string;
|
|
107
109
|
createdAt: string;
|
|
108
110
|
/** Sentinel for auditability, mirrors the learning-loop convention. */
|
|
109
111
|
reviewedBy: string;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { TextEditorTool, MorphEditorTool, ImageTool, BashTool, } from "../tools/index.js";
|
|
19
19
|
import { getFormalToolRegistry, createTextEditorTools, createBashTools, createLsTools, createSearchTools, createWebTools, createTodoTools, createCronjobTools, createDockerTools, createKubernetesTools, createGitTools, createMiscTools, createBrowserTools, createProcessTools, createVisionTools, createScriptTools, createPlanTools, createKnowledgeTools, createRelationshipIntelligenceTools, createInternetScoutTools, createLeadScoutTools, createBrowserOperatorTools, createWindowsTools, createMemoryTools, createParallelTools, createAttentionTools, createSkillsInspectionTools, createLessonsTools, createUserModelTools, createAliasTools, createMultimodalTools, createAdvancedTools, createCanvasTools, createLspTools, createMergeConflictTools, createVulnScannerTools, createCodebaseReplaceTools, createAdvisorTools, createFleetTools, createAskUserQuestionTools, createExitPlanModeTools, createGuiTools, createSessionTools, createGitNexusTools, createScreenpipeTools, } from "../tools/registry/index.js";
|
|
20
|
+
import { createRegisterToolTool } from "../tools/register-tool-handler.js";
|
|
20
21
|
import { getMCPManager } from "../codebuddy/tools.js";
|
|
21
22
|
import { getErrorMessage } from "../errors/index.js";
|
|
22
23
|
import { logger } from "../utils/logger.js";
|
|
@@ -239,6 +240,8 @@ export class ToolHandler {
|
|
|
239
240
|
...createSessionTools(),
|
|
240
241
|
...createGitNexusTools(),
|
|
241
242
|
...createScreenpipeTools(),
|
|
243
|
+
// Self-improvement: the agent can author its own tools (opt-in only).
|
|
244
|
+
...(process.env.CODEBUDDY_SELF_IMPROVE === 'true' ? [createRegisterToolTool()] : []),
|
|
242
245
|
];
|
|
243
246
|
// Register canonical-prefix alias tools (shell_exec→bash, file_read→view_file, etc.)
|
|
244
247
|
const aliasTools = createAliasTools(allTools);
|
|
@@ -459,6 +459,11 @@ export async function* parseSseStream(body, model, onReasoningItem, idleTimeoutM
|
|
|
459
459
|
const decoder = new TextDecoder();
|
|
460
460
|
let buffer = '';
|
|
461
461
|
let chunkIndex = 0;
|
|
462
|
+
// Monotonic index per function_call so the downstream message-reducer keeps
|
|
463
|
+
// parallel tool calls in separate slots. Hardcoding 0 made every parallel
|
|
464
|
+
// call merge into one — names/call_ids/arguments concatenated — producing a
|
|
465
|
+
// 100+ char call_id the Responses backend rejects (max 64) on the next turn.
|
|
466
|
+
let functionCallIndex = 0;
|
|
462
467
|
const makeChunk = (delta, finishReason) => ({
|
|
463
468
|
id: `chatcmpl-codex-${chunkIndex++}`,
|
|
464
469
|
object: 'chat.completion.chunk',
|
|
@@ -547,7 +552,7 @@ export async function* parseSseStream(body, model, onReasoningItem, idleTimeoutM
|
|
|
547
552
|
if (item.name && item.call_id) {
|
|
548
553
|
yield makeChunk({
|
|
549
554
|
tool_calls: [{
|
|
550
|
-
index:
|
|
555
|
+
index: functionCallIndex++,
|
|
551
556
|
id: item.call_id,
|
|
552
557
|
type: 'function',
|
|
553
558
|
function: {
|
|
@@ -26,6 +26,13 @@ export declare function getBuiltinToolNames(): string[];
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function initializeToolRegistry(): void;
|
|
28
28
|
export declare function getMCPManager(): MCPManager;
|
|
29
|
+
/**
|
|
30
|
+
* True when the Code Explorer (gitnexus) MCP tools are connected — i.e. at least
|
|
31
|
+
* one `mcp__gitnexus__*` tool is registered. Used to conditionally steer the
|
|
32
|
+
* agent toward Code Explorer for relationship/impact questions. Returns false
|
|
33
|
+
* (no behavior change) whenever Code Explorer is not installed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isCodeExplorerAvailable(): boolean;
|
|
29
36
|
export declare function initializeMCPServers(): Promise<void>;
|
|
30
37
|
export declare function convertMCPToolToCodeBuddyTool(mcpTool: MCPTool): CodeBuddyTool;
|
|
31
38
|
/**
|
package/dist/codebuddy/tools.js
CHANGED
|
@@ -9,6 +9,8 @@ import { loadMCPConfig } from "../mcp/config.js";
|
|
|
9
9
|
import { getToolSelector, selectRelevantTools } from "../tools/tool-selector.js";
|
|
10
10
|
import { logger } from "../utils/logger.js";
|
|
11
11
|
import { getToolRegistry } from "../tools/registry.js";
|
|
12
|
+
import { createRegisterToolTool } from "../tools/register-tool-handler.js";
|
|
13
|
+
import { loadAuthoredTools } from "../agent/self-improvement/tool-skill-mutator.js";
|
|
12
14
|
import { applyToolFilter } from "../utils/tool-filter.js";
|
|
13
15
|
import { TOOL_METADATA } from "../tools/metadata.js";
|
|
14
16
|
import { getPluginMarketplace } from "../plugins/marketplace.js";
|
|
@@ -89,6 +91,18 @@ export function initializeToolRegistry() {
|
|
|
89
91
|
description: 'High-speed file editing with Morph'
|
|
90
92
|
};
|
|
91
93
|
registry.registerTool(MORPH_EDIT_TOOL, morphMetadata, isMorphEnabled);
|
|
94
|
+
// Self-improvement: expose `register_tool` + reload persisted authored tools,
|
|
95
|
+
// only when opted in.
|
|
96
|
+
if (process.env.CODEBUDDY_SELF_IMPROVE === 'true') {
|
|
97
|
+
const schema = createRegisterToolTool().getSchema();
|
|
98
|
+
registry.registerTool({ type: 'function', function: { name: schema.name, description: schema.description, parameters: schema.parameters } }, { name: schema.name, category: 'system', keywords: ['authored', 'self-extension', 'register', 'tool'], priority: 6, description: schema.description });
|
|
99
|
+
try {
|
|
100
|
+
const loaded = loadAuthoredTools();
|
|
101
|
+
if (loaded.length > 0)
|
|
102
|
+
logger.info(`[self-improve] reloaded ${loaded.length} authored tool(s): ${loaded.join(', ')}`);
|
|
103
|
+
}
|
|
104
|
+
catch { /* persisted store optional */ }
|
|
105
|
+
}
|
|
92
106
|
registerGroup(SEARCH_TOOLS);
|
|
93
107
|
registerGroup(TODO_TOOLS);
|
|
94
108
|
registerGroup(KANBAN_TOOLS);
|
|
@@ -147,6 +161,20 @@ export function getMCPManager() {
|
|
|
147
161
|
}
|
|
148
162
|
return mcpManager;
|
|
149
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* True when the Code Explorer (gitnexus) MCP tools are connected — i.e. at least
|
|
166
|
+
* one `mcp__gitnexus__*` tool is registered. Used to conditionally steer the
|
|
167
|
+
* agent toward Code Explorer for relationship/impact questions. Returns false
|
|
168
|
+
* (no behavior change) whenever Code Explorer is not installed.
|
|
169
|
+
*/
|
|
170
|
+
export function isCodeExplorerAvailable() {
|
|
171
|
+
try {
|
|
172
|
+
return getMCPManager().getTools().some((t) => t.name.startsWith('mcp__gitnexus__'));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
150
178
|
export async function initializeMCPServers() {
|
|
151
179
|
const manager = getMCPManager();
|
|
152
180
|
const config = loadMCPConfig();
|
|
@@ -359,6 +387,18 @@ export async function getAllCodeBuddyTools() {
|
|
|
359
387
|
}
|
|
360
388
|
// Apply CLI tool filter (--enabled-tools, --disabled-tools, --allowed-tools)
|
|
361
389
|
allTools = applyToolFilter(allTools);
|
|
390
|
+
// When Code Explorer (gitnexus) is connected, make the built-in graph tools
|
|
391
|
+
// defer to it at the decision point — its graph is broader / more complete.
|
|
392
|
+
// Conditional & non-mutating: returns fresh objects only for code_graph /
|
|
393
|
+
// codebase_map, and only when a gitnexus tool is present (no change otherwise).
|
|
394
|
+
if (allTools.some((t) => t.function.name.startsWith('mcp__gitnexus__'))) {
|
|
395
|
+
const DEFER = ' NOTE: Code Explorer (gitnexus) is available — for code-relationship, blast-radius/impact, ' +
|
|
396
|
+
'dead-code and cycle questions PREFER its MCP tools (`mcp__gitnexus__impact` / `context` / ' +
|
|
397
|
+
'`query` / `find_cycles`); use this built-in only as a fallback if a gitnexus tool errors.';
|
|
398
|
+
allTools = allTools.map((t) => t.function.name === 'code_graph' || t.function.name === 'codebase_map'
|
|
399
|
+
? { ...t, function: { ...t.function, description: (t.function.description ?? '') + DEFER } }
|
|
400
|
+
: t);
|
|
401
|
+
}
|
|
362
402
|
return allTools;
|
|
363
403
|
}
|
|
364
404
|
// ============================================================================
|