@yuaone/core 0.9.8 → 0.9.10
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/__tests__/context-manager.test.js +5 -9
- package/dist/__tests__/context-manager.test.js.map +1 -1
- package/dist/agent-coordinator.d.ts +172 -0
- package/dist/agent-coordinator.d.ts.map +1 -0
- package/dist/agent-coordinator.js +390 -0
- package/dist/agent-coordinator.js.map +1 -0
- package/dist/agent-loop.d.ts +83 -39
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +694 -471
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent-reputation.d.ts +72 -0
- package/dist/agent-reputation.d.ts.map +1 -0
- package/dist/agent-reputation.js +222 -0
- package/dist/agent-reputation.js.map +1 -0
- package/dist/arch-summarizer.d.ts +48 -0
- package/dist/arch-summarizer.d.ts.map +1 -0
- package/dist/arch-summarizer.js +239 -0
- package/dist/arch-summarizer.js.map +1 -0
- package/dist/autonomous/explicit-planner.d.ts +45 -0
- package/dist/autonomous/explicit-planner.d.ts.map +1 -0
- package/dist/autonomous/explicit-planner.js +99 -0
- package/dist/autonomous/explicit-planner.js.map +1 -0
- package/dist/autonomous/incident-debugger.d.ts +78 -0
- package/dist/autonomous/incident-debugger.d.ts.map +1 -0
- package/dist/autonomous/incident-debugger.js +324 -0
- package/dist/autonomous/incident-debugger.js.map +1 -0
- package/dist/autonomous/index.d.ts +15 -0
- package/dist/autonomous/index.d.ts.map +1 -0
- package/dist/autonomous/index.js +10 -0
- package/dist/autonomous/index.js.map +1 -0
- package/dist/autonomous/patch-tournament.d.ts +82 -0
- package/dist/autonomous/patch-tournament.d.ts.map +1 -0
- package/dist/autonomous/patch-tournament.js +150 -0
- package/dist/autonomous/patch-tournament.js.map +1 -0
- package/dist/autonomous/research-agent.d.ts +66 -0
- package/dist/autonomous/research-agent.d.ts.map +1 -0
- package/dist/autonomous/research-agent.js +210 -0
- package/dist/autonomous/research-agent.js.map +1 -0
- package/dist/autonomous/task-memory.d.ts +63 -0
- package/dist/autonomous/task-memory.d.ts.map +1 -0
- package/dist/autonomous/task-memory.js +143 -0
- package/dist/autonomous/task-memory.js.map +1 -0
- package/dist/budget-governor-v2.d.ts +93 -0
- package/dist/budget-governor-v2.d.ts.map +1 -0
- package/dist/budget-governor-v2.js +345 -0
- package/dist/budget-governor-v2.js.map +1 -0
- package/dist/capability-graph.d.ts +102 -0
- package/dist/capability-graph.d.ts.map +1 -0
- package/dist/capability-graph.js +397 -0
- package/dist/capability-graph.js.map +1 -0
- package/dist/capability-self-model.d.ts +144 -0
- package/dist/capability-self-model.d.ts.map +1 -0
- package/dist/capability-self-model.js +312 -0
- package/dist/capability-self-model.js.map +1 -0
- package/dist/checkpoint-manager.d.ts +94 -0
- package/dist/checkpoint-manager.d.ts.map +1 -0
- package/dist/checkpoint-manager.js +225 -0
- package/dist/checkpoint-manager.js.map +1 -0
- package/dist/continuation-engine.js +1 -1
- package/dist/continuation-engine.js.map +1 -1
- package/dist/dag-orchestrator.d.ts +0 -3
- package/dist/dag-orchestrator.d.ts.map +1 -1
- package/dist/dag-orchestrator.js +0 -1
- package/dist/dag-orchestrator.js.map +1 -1
- package/dist/evidence-chain.d.ts +99 -0
- package/dist/evidence-chain.d.ts.map +1 -0
- package/dist/evidence-chain.js +200 -0
- package/dist/evidence-chain.js.map +1 -0
- package/dist/execution-engine.d.ts.map +1 -1
- package/dist/execution-engine.js +0 -1
- package/dist/execution-engine.js.map +1 -1
- package/dist/failure-signature-memory.d.ts +61 -0
- package/dist/failure-signature-memory.d.ts.map +1 -0
- package/dist/failure-signature-memory.js +278 -0
- package/dist/failure-signature-memory.js.map +1 -0
- package/dist/index.d.ts +52 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -7
- package/dist/index.js.map +1 -1
- package/dist/language-detector.d.ts.map +1 -1
- package/dist/language-detector.js +122 -43
- package/dist/language-detector.js.map +1 -1
- package/dist/llm-client.d.ts +0 -7
- package/dist/llm-client.d.ts.map +1 -1
- package/dist/llm-client.js +29 -113
- package/dist/llm-client.js.map +1 -1
- package/dist/mcp-client.js +1 -1
- package/dist/mcp-client.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +0 -15
- package/dist/memory.js.map +1 -1
- package/dist/meta-learning-collector.d.ts +64 -0
- package/dist/meta-learning-collector.d.ts.map +1 -0
- package/dist/meta-learning-collector.js +169 -0
- package/dist/meta-learning-collector.js.map +1 -0
- package/dist/meta-learning-engine.d.ts +61 -0
- package/dist/meta-learning-engine.d.ts.map +1 -0
- package/dist/meta-learning-engine.js +250 -0
- package/dist/meta-learning-engine.js.map +1 -0
- package/dist/overhead-governor.d.ts +105 -0
- package/dist/overhead-governor.d.ts.map +1 -0
- package/dist/overhead-governor.js +239 -0
- package/dist/overhead-governor.js.map +1 -0
- package/dist/playbook-library.d.ts +75 -0
- package/dist/playbook-library.d.ts.map +1 -0
- package/dist/playbook-library.js +241 -0
- package/dist/playbook-library.js.map +1 -0
- package/dist/project-executive.d.ts +97 -0
- package/dist/project-executive.d.ts.map +1 -0
- package/dist/project-executive.js +223 -0
- package/dist/project-executive.js.map +1 -0
- package/dist/reasoning-adapter.d.ts.map +1 -1
- package/dist/reasoning-adapter.js +11 -36
- package/dist/reasoning-adapter.js.map +1 -1
- package/dist/research-loop.d.ts +79 -0
- package/dist/research-loop.d.ts.map +1 -0
- package/dist/research-loop.js +363 -0
- package/dist/research-loop.js.map +1 -0
- package/dist/resolve-memory-path.d.ts +32 -0
- package/dist/resolve-memory-path.d.ts.map +1 -0
- package/dist/resolve-memory-path.js +97 -0
- package/dist/resolve-memory-path.js.map +1 -0
- package/dist/safe-bounds.d.ts +101 -0
- package/dist/safe-bounds.d.ts.map +1 -0
- package/dist/safe-bounds.js +140 -0
- package/dist/safe-bounds.js.map +1 -0
- package/dist/sandbox-tiers.d.ts +5 -0
- package/dist/sandbox-tiers.d.ts.map +1 -1
- package/dist/sandbox-tiers.js +14 -6
- package/dist/sandbox-tiers.js.map +1 -1
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +3 -0
- package/dist/security.js.map +1 -1
- package/dist/self-improvement-loop.d.ts +64 -0
- package/dist/self-improvement-loop.d.ts.map +1 -0
- package/dist/self-improvement-loop.js +156 -0
- package/dist/self-improvement-loop.js.map +1 -0
- package/dist/session-persistence.d.ts +5 -0
- package/dist/session-persistence.d.ts.map +1 -1
- package/dist/session-persistence.js +19 -3
- package/dist/session-persistence.js.map +1 -1
- package/dist/skill-loader.d.ts +16 -9
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +52 -116
- package/dist/skill-loader.js.map +1 -1
- package/dist/skill-registry.d.ts +60 -0
- package/dist/skill-registry.d.ts.map +1 -0
- package/dist/skill-registry.js +162 -0
- package/dist/skill-registry.js.map +1 -0
- package/dist/stall-detector.d.ts +56 -0
- package/dist/stall-detector.d.ts.map +1 -0
- package/dist/stall-detector.js +142 -0
- package/dist/stall-detector.js.map +1 -0
- package/dist/strategy-learner.d.ts +57 -0
- package/dist/strategy-learner.d.ts.map +1 -0
- package/dist/strategy-learner.js +160 -0
- package/dist/strategy-learner.js.map +1 -0
- package/dist/strategy-market.d.ts +73 -0
- package/dist/strategy-market.d.ts.map +1 -0
- package/dist/strategy-market.js +200 -0
- package/dist/strategy-market.js.map +1 -0
- package/dist/sub-agent.d.ts +0 -3
- package/dist/sub-agent.d.ts.map +1 -1
- package/dist/sub-agent.js +0 -10
- package/dist/sub-agent.js.map +1 -1
- package/dist/system-prompt.d.ts +0 -2
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +97 -490
- package/dist/system-prompt.js.map +1 -1
- package/dist/task-classifier.d.ts.map +1 -1
- package/dist/task-classifier.js +2 -54
- package/dist/task-classifier.js.map +1 -1
- package/dist/tool-synthesizer.d.ts +149 -0
- package/dist/tool-synthesizer.d.ts.map +1 -0
- package/dist/tool-synthesizer.js +455 -0
- package/dist/tool-synthesizer.js.map +1 -0
- package/dist/trace-pattern-extractor.d.ts +76 -0
- package/dist/trace-pattern-extractor.d.ts.map +1 -0
- package/dist/trace-pattern-extractor.js +321 -0
- package/dist/trace-pattern-extractor.js.map +1 -0
- package/dist/trace-recorder.d.ts +38 -0
- package/dist/trace-recorder.d.ts.map +1 -0
- package/dist/trace-recorder.js +94 -0
- package/dist/trace-recorder.js.map +1 -0
- package/dist/trust-economics.d.ts +50 -0
- package/dist/trust-economics.d.ts.map +1 -0
- package/dist/trust-economics.js +148 -0
- package/dist/trust-economics.js.map +1 -0
- package/dist/types.d.ts +273 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/yuan-md-loader.d.ts +22 -0
- package/dist/yuan-md-loader.d.ts.map +1 -0
- package/dist/yuan-md-loader.js +75 -0
- package/dist/yuan-md-loader.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module autonomous/patch-tournament
|
|
3
|
+
* @description Patch Tournament Executor — generates N candidate patches for a goal,
|
|
4
|
+
* scores each with the QA pipeline, and selects the best.
|
|
5
|
+
*
|
|
6
|
+
* Design:
|
|
7
|
+
* - Takes a "run agent" callback (injected from CLI — keeps this module pure)
|
|
8
|
+
* - Runs candidates sequentially (not parallel) to avoid file conflicts
|
|
9
|
+
* - Scores via QAPipeline + diff stats from evidence history
|
|
10
|
+
* - Emits agent:tournament_result with winner index
|
|
11
|
+
*
|
|
12
|
+
* Constraints:
|
|
13
|
+
* - Does NOT touch the main agent loop (deterministic)
|
|
14
|
+
* - All scoring goes through QAPipeline (no bypass)
|
|
15
|
+
* - Goes through OverheadGovernor (caller must check shouldRunTournament())
|
|
16
|
+
* - Emits events at every candidate + final result
|
|
17
|
+
*/
|
|
18
|
+
import { EventEmitter } from "node:events";
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
import { QAPipeline } from "../qa-pipeline.js";
|
|
21
|
+
// ─── Scoring ─────────────────────────────────────────────────────────────────
|
|
22
|
+
function scoreQAResult(qa) {
|
|
23
|
+
if (!qa)
|
|
24
|
+
return 0;
|
|
25
|
+
const base = qa.overall === "pass" ? 1.0 : qa.overall === "warn" ? 0.6 : 0.2;
|
|
26
|
+
const passRatio = qa.totalChecks > 0 ? qa.passed / qa.totalChecks : 0;
|
|
27
|
+
const failPenalty = qa.failures * 0.1;
|
|
28
|
+
return Math.max(0, Math.min(1, base * passRatio - failPenalty));
|
|
29
|
+
}
|
|
30
|
+
// ─── PatchTournamentExecutor ─────────────────────────────────────────────────
|
|
31
|
+
export class PatchTournamentExecutor extends EventEmitter {
|
|
32
|
+
config;
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
super();
|
|
35
|
+
const n = config.candidates ?? 3;
|
|
36
|
+
this.config = {
|
|
37
|
+
candidates: n,
|
|
38
|
+
strategies: config.strategies ?? this.defaultStrategies(n),
|
|
39
|
+
qaConfig: config.qaConfig ?? {},
|
|
40
|
+
projectPath: config.projectPath ?? process.cwd(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run the tournament. For each candidate:
|
|
45
|
+
* 1. Call runAgent() to generate a patch
|
|
46
|
+
* 2. Score with QAPipeline
|
|
47
|
+
* 3. Track best
|
|
48
|
+
*
|
|
49
|
+
* Returns TournamentResult with winner index.
|
|
50
|
+
* Emits agent:tournament_result at completion.
|
|
51
|
+
*
|
|
52
|
+
* NOTE: The CLI is responsible for:
|
|
53
|
+
* - Stashing current git state before the first candidate
|
|
54
|
+
* - Restoring baseline between candidates (git stash pop + git stash)
|
|
55
|
+
* - Applying the winner's patch after selection
|
|
56
|
+
*/
|
|
57
|
+
async run(goal, runAgent, taskId) {
|
|
58
|
+
const resolvedTaskId = taskId ?? randomUUID();
|
|
59
|
+
const timestamp = Date.now();
|
|
60
|
+
const candidates = [];
|
|
61
|
+
for (let i = 0; i < this.config.candidates; i++) {
|
|
62
|
+
const strategy = this.config.strategies[i] ?? `strategy-${i}`;
|
|
63
|
+
this.emitProgress(resolvedTaskId, i, "running", strategy);
|
|
64
|
+
let filesChanged = [];
|
|
65
|
+
let qaResult = null;
|
|
66
|
+
try {
|
|
67
|
+
filesChanged = await runAgent(goal, strategy, i);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
71
|
+
this.emitProgress(resolvedTaskId, i, "failed", `${strategy}: ${msg}`);
|
|
72
|
+
}
|
|
73
|
+
// Score with QA pipeline if files were changed
|
|
74
|
+
if (filesChanged.length > 0) {
|
|
75
|
+
try {
|
|
76
|
+
const qa = new QAPipeline({
|
|
77
|
+
projectPath: this.config.projectPath,
|
|
78
|
+
level: "quick",
|
|
79
|
+
...this.config.qaConfig,
|
|
80
|
+
});
|
|
81
|
+
qaResult = await qa.run(filesChanged);
|
|
82
|
+
}
|
|
83
|
+
catch { /* QA failure is non-fatal */ }
|
|
84
|
+
}
|
|
85
|
+
const score = scoreQAResult(qaResult);
|
|
86
|
+
const reason = this.describeScore(qaResult, score, filesChanged.length);
|
|
87
|
+
candidates.push({ index: i, strategy, filesChanged, qaResult, score, reason });
|
|
88
|
+
this.emitProgress(resolvedTaskId, i, "scored", reason);
|
|
89
|
+
}
|
|
90
|
+
// Select winner (highest score; tie-break by fewer files changed)
|
|
91
|
+
let winner = 0;
|
|
92
|
+
for (let i = 1; i < candidates.length; i++) {
|
|
93
|
+
const curr = candidates[i];
|
|
94
|
+
const best = candidates[winner];
|
|
95
|
+
if (curr.score > best.score ||
|
|
96
|
+
(curr.score === best.score && curr.filesChanged.length < best.filesChanged.length)) {
|
|
97
|
+
winner = i;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const result = {
|
|
101
|
+
taskId: resolvedTaskId,
|
|
102
|
+
goal,
|
|
103
|
+
winner,
|
|
104
|
+
candidates,
|
|
105
|
+
qualityScore: candidates[winner]?.score ?? 0,
|
|
106
|
+
timestamp,
|
|
107
|
+
};
|
|
108
|
+
this.emitResult(result);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
// ─── private ───────────────────────────────────────────────────────────────
|
|
112
|
+
defaultStrategies(n) {
|
|
113
|
+
const base = [
|
|
114
|
+
"direct-fix",
|
|
115
|
+
"minimal-change",
|
|
116
|
+
"refactor-first",
|
|
117
|
+
"test-driven",
|
|
118
|
+
"defensive",
|
|
119
|
+
];
|
|
120
|
+
return base.slice(0, n);
|
|
121
|
+
}
|
|
122
|
+
describeScore(qa, score, fileCount) {
|
|
123
|
+
if (!qa)
|
|
124
|
+
return `no QA result (${fileCount} files)`;
|
|
125
|
+
return `${qa.overall}: ${qa.passed}/${qa.totalChecks} checks, score=${score.toFixed(2)} (${fileCount} files)`;
|
|
126
|
+
}
|
|
127
|
+
emitProgress(taskId, candidateIdx, state, detail) {
|
|
128
|
+
const event = {
|
|
129
|
+
kind: "agent:bg_update",
|
|
130
|
+
agentId: `tournament-${taskId}`,
|
|
131
|
+
agentLabel: `Tournament[${candidateIdx}]`,
|
|
132
|
+
eventType: state === "failed" ? "warning" : "info",
|
|
133
|
+
message: `candidate ${candidateIdx} ${state}: ${detail}`,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
this.emit("event", event);
|
|
137
|
+
}
|
|
138
|
+
emitResult(result) {
|
|
139
|
+
const event = {
|
|
140
|
+
kind: "agent:tournament_result",
|
|
141
|
+
taskId: result.taskId,
|
|
142
|
+
winner: result.winner,
|
|
143
|
+
candidates: result.candidates.length,
|
|
144
|
+
qualityScore: result.qualityScore,
|
|
145
|
+
timestamp: result.timestamp,
|
|
146
|
+
};
|
|
147
|
+
this.emit("event", event);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=patch-tournament.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch-tournament.js","sourceRoot":"","sources":["../../src/autonomous/patch-tournament.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module autonomous/research-agent
|
|
3
|
+
* @description Research Agent — combines MCP web search + repo search + docs fetch,
|
|
4
|
+
* ranks sources, and emits a structured research_result event.
|
|
5
|
+
*
|
|
6
|
+
* Design:
|
|
7
|
+
* - Does NOT call the LLM directly (keeps the main loop deterministic)
|
|
8
|
+
* - Orchestrates: MCP search tools + local repo grep/glob via ToolExecutor
|
|
9
|
+
* - Scoring: naive TF-IDF relevance ranking over snippets
|
|
10
|
+
* - Output: agent:research_result event + ResearchResult object
|
|
11
|
+
*
|
|
12
|
+
* Constraints:
|
|
13
|
+
* - All outputs emit events (observable)
|
|
14
|
+
* - Integrates with TraceRecorder via event emission
|
|
15
|
+
* - Goes through OverheadGovernor (caller must check shouldRunResearch())
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from "node:events";
|
|
18
|
+
import type { ToolExecutor } from "../types.js";
|
|
19
|
+
export interface ResearchSource {
|
|
20
|
+
title: string;
|
|
21
|
+
url: string;
|
|
22
|
+
snippet: string;
|
|
23
|
+
/** "web" | "repo" | "docs" | "mcp:<server-name>" */
|
|
24
|
+
source: string;
|
|
25
|
+
/** 0.0–1.0 relevance score */
|
|
26
|
+
relevance: number;
|
|
27
|
+
}
|
|
28
|
+
export interface ResearchResult {
|
|
29
|
+
taskId: string;
|
|
30
|
+
query: string;
|
|
31
|
+
summary: string;
|
|
32
|
+
sources: ResearchSource[];
|
|
33
|
+
/** 0.0–1.0 confidence based on source count + relevance distribution */
|
|
34
|
+
confidence: number;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ResearchAgentConfig {
|
|
38
|
+
/** Max number of MCP search results to collect */
|
|
39
|
+
maxMcpResults?: number;
|
|
40
|
+
/** Max number of repo grep results to collect */
|
|
41
|
+
maxRepoResults?: number;
|
|
42
|
+
/** Project root for local repo search */
|
|
43
|
+
projectPath?: string;
|
|
44
|
+
/** MCP tool names that perform web search (prefix match) */
|
|
45
|
+
mcpSearchTools?: string[];
|
|
46
|
+
}
|
|
47
|
+
export declare class ResearchAgent extends EventEmitter {
|
|
48
|
+
private readonly toolExecutor;
|
|
49
|
+
private readonly config;
|
|
50
|
+
constructor(toolExecutor: ToolExecutor, config?: ResearchAgentConfig);
|
|
51
|
+
/**
|
|
52
|
+
* Run research for a query. Returns structured ResearchResult.
|
|
53
|
+
* Emits agent:research_result when complete.
|
|
54
|
+
*/
|
|
55
|
+
research(query: string, taskId?: string): Promise<ResearchResult>;
|
|
56
|
+
private searchRepo;
|
|
57
|
+
private searchMcp;
|
|
58
|
+
private parseMcpSearchOutput;
|
|
59
|
+
private rankSources;
|
|
60
|
+
/** Naive TF-IDF-like scoring: count query term hits / total terms */
|
|
61
|
+
private scoreRelevance;
|
|
62
|
+
private buildSummary;
|
|
63
|
+
private computeConfidence;
|
|
64
|
+
private emitResult;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=research-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-agent.d.ts","sourceRoot":"","sources":["../../src/autonomous/research-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAK5D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAID,qBAAa,aAAc,SAAQ,YAAY;IAI3C,OAAO,CAAC,QAAQ,CAAC,YAAY;IAH/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,YAAY,EAAE,YAAY,EAC3C,MAAM,GAAE,mBAAwB;IAelC;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;YAsCzD,UAAU;YA2CV,SAAS;IAqBvB,OAAO,CAAC,oBAAoB;IA0B5B,OAAO,CAAC,WAAW;IAanB,qEAAqE;IACrE,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,UAAU;CAWnB"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module autonomous/research-agent
|
|
3
|
+
* @description Research Agent — combines MCP web search + repo search + docs fetch,
|
|
4
|
+
* ranks sources, and emits a structured research_result event.
|
|
5
|
+
*
|
|
6
|
+
* Design:
|
|
7
|
+
* - Does NOT call the LLM directly (keeps the main loop deterministic)
|
|
8
|
+
* - Orchestrates: MCP search tools + local repo grep/glob via ToolExecutor
|
|
9
|
+
* - Scoring: naive TF-IDF relevance ranking over snippets
|
|
10
|
+
* - Output: agent:research_result event + ResearchResult object
|
|
11
|
+
*
|
|
12
|
+
* Constraints:
|
|
13
|
+
* - All outputs emit events (observable)
|
|
14
|
+
* - Integrates with TraceRecorder via event emission
|
|
15
|
+
* - Goes through OverheadGovernor (caller must check shouldRunResearch())
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from "node:events";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { BOUNDS, truncate } from "../safe-bounds.js";
|
|
20
|
+
// ─── ResearchAgent ────────────────────────────────────────────────────────────
|
|
21
|
+
export class ResearchAgent extends EventEmitter {
|
|
22
|
+
toolExecutor;
|
|
23
|
+
config;
|
|
24
|
+
constructor(toolExecutor, config = {}) {
|
|
25
|
+
super();
|
|
26
|
+
this.toolExecutor = toolExecutor;
|
|
27
|
+
this.config = {
|
|
28
|
+
maxMcpResults: config.maxMcpResults ?? 10,
|
|
29
|
+
maxRepoResults: config.maxRepoResults ?? 20,
|
|
30
|
+
projectPath: config.projectPath ?? process.cwd(),
|
|
31
|
+
mcpSearchTools: config.mcpSearchTools ?? [
|
|
32
|
+
"brave_search", "spider_search", "fetch_fetch",
|
|
33
|
+
"github_search_code", "github_search_repositories",
|
|
34
|
+
"search", "web_search",
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run research for a query. Returns structured ResearchResult.
|
|
40
|
+
* Emits agent:research_result when complete.
|
|
41
|
+
*/
|
|
42
|
+
async research(query, taskId) {
|
|
43
|
+
const resolvedTaskId = taskId ?? randomUUID();
|
|
44
|
+
const timestamp = Date.now();
|
|
45
|
+
const sources = [];
|
|
46
|
+
// 1. Repo search — grep for query terms in project
|
|
47
|
+
const repoResults = await this.searchRepo(query);
|
|
48
|
+
sources.push(...repoResults);
|
|
49
|
+
// 2. MCP web/docs search (best-effort — fails gracefully)
|
|
50
|
+
const mcpResults = await this.searchMcp(query);
|
|
51
|
+
sources.push(...mcpResults);
|
|
52
|
+
// 3. Rank by relevance
|
|
53
|
+
const ranked = this.rankSources(query, sources)
|
|
54
|
+
.slice(0, this.config.maxMcpResults + this.config.maxRepoResults);
|
|
55
|
+
// 4. Build summary from top sources
|
|
56
|
+
const summary = this.buildSummary(query, ranked);
|
|
57
|
+
// 5. Compute confidence
|
|
58
|
+
const confidence = this.computeConfidence(ranked);
|
|
59
|
+
const result = {
|
|
60
|
+
taskId: resolvedTaskId,
|
|
61
|
+
query,
|
|
62
|
+
summary,
|
|
63
|
+
sources: ranked,
|
|
64
|
+
confidence,
|
|
65
|
+
timestamp,
|
|
66
|
+
};
|
|
67
|
+
this.emitResult(result);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
// ─── private ───────────────────────────────────────────────────────────────
|
|
71
|
+
async searchRepo(query) {
|
|
72
|
+
const results = [];
|
|
73
|
+
// Extract meaningful terms (strip common words)
|
|
74
|
+
const terms = query.split(/\s+/)
|
|
75
|
+
.filter(t => t.length > 3 && !/^(the|and|for|with|that|this|from|into|when|where)$/i.test(t))
|
|
76
|
+
.slice(0, 4);
|
|
77
|
+
if (terms.length === 0)
|
|
78
|
+
return results;
|
|
79
|
+
const pattern = terms.join("|");
|
|
80
|
+
try {
|
|
81
|
+
const grepResult = await this.toolExecutor.execute({
|
|
82
|
+
id: `research-grep-${Date.now()}`,
|
|
83
|
+
name: "grep",
|
|
84
|
+
arguments: JSON.stringify({
|
|
85
|
+
pattern,
|
|
86
|
+
path: this.config.projectPath,
|
|
87
|
+
recursive: true,
|
|
88
|
+
maxResults: this.config.maxRepoResults,
|
|
89
|
+
include: "*.ts,*.tsx,*.js,*.jsx,*.md",
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
if (grepResult.success && grepResult.output) {
|
|
93
|
+
const lines = grepResult.output.split("\n").filter(Boolean).slice(0, this.config.maxRepoResults);
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const colonIdx = line.indexOf(":");
|
|
96
|
+
if (colonIdx === -1)
|
|
97
|
+
continue;
|
|
98
|
+
const filePath = line.slice(0, colonIdx);
|
|
99
|
+
const snippet = truncate(line.slice(colonIdx + 1).trim(), 200);
|
|
100
|
+
results.push({
|
|
101
|
+
title: filePath,
|
|
102
|
+
url: `file://${filePath}`,
|
|
103
|
+
snippet,
|
|
104
|
+
source: "repo",
|
|
105
|
+
relevance: 0, // will be scored later
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch { /* non-fatal */ }
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
async searchMcp(query) {
|
|
114
|
+
const results = [];
|
|
115
|
+
// Try each MCP search tool — stop at first successful one
|
|
116
|
+
for (const toolName of this.config.mcpSearchTools) {
|
|
117
|
+
try {
|
|
118
|
+
const mcpResult = await this.toolExecutor.execute({
|
|
119
|
+
id: `research-mcp-${toolName}-${Date.now()}`,
|
|
120
|
+
name: toolName,
|
|
121
|
+
arguments: JSON.stringify({ query, q: query, search: query }),
|
|
122
|
+
});
|
|
123
|
+
if (!mcpResult.success || !mcpResult.output)
|
|
124
|
+
continue;
|
|
125
|
+
// Parse result — may be JSON array or plain text
|
|
126
|
+
const parsed = this.parseMcpSearchOutput(mcpResult.output, toolName);
|
|
127
|
+
results.push(...parsed.slice(0, this.config.maxMcpResults));
|
|
128
|
+
if (results.length > 0)
|
|
129
|
+
break; // first working tool is enough
|
|
130
|
+
}
|
|
131
|
+
catch { /* non-fatal — tool may not be loaded */ }
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
parseMcpSearchOutput(output, toolName) {
|
|
136
|
+
// Try JSON array first (structured MCP output)
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(output);
|
|
139
|
+
if (Array.isArray(parsed)) {
|
|
140
|
+
return parsed.slice(0, 10).map((item) => ({
|
|
141
|
+
title: String(item["title"] ?? item["name"] ?? ""),
|
|
142
|
+
url: String(item["url"] ?? item["link"] ?? ""),
|
|
143
|
+
snippet: truncate(String(item["snippet"] ?? item["description"] ?? item["body"] ?? ""), 300),
|
|
144
|
+
source: `mcp:${toolName}`,
|
|
145
|
+
relevance: 0,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch { /* not JSON */ }
|
|
150
|
+
// Fallback: plain text, each non-empty line is a snippet
|
|
151
|
+
const lines = output.split("\n").filter(l => l.trim().length > 20).slice(0, 5);
|
|
152
|
+
return lines.map((line, i) => ({
|
|
153
|
+
title: `Result ${i + 1}`,
|
|
154
|
+
url: "",
|
|
155
|
+
snippet: truncate(line, 300),
|
|
156
|
+
source: `mcp:${toolName}`,
|
|
157
|
+
relevance: 0,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
rankSources(query, sources) {
|
|
161
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
|
|
162
|
+
return sources
|
|
163
|
+
.map(s => ({
|
|
164
|
+
...s,
|
|
165
|
+
relevance: this.scoreRelevance(`${s.title} ${s.snippet}`.toLowerCase(), queryTerms),
|
|
166
|
+
}))
|
|
167
|
+
.sort((a, b) => b.relevance - a.relevance);
|
|
168
|
+
}
|
|
169
|
+
/** Naive TF-IDF-like scoring: count query term hits / total terms */
|
|
170
|
+
scoreRelevance(text, queryTerms) {
|
|
171
|
+
if (queryTerms.length === 0)
|
|
172
|
+
return 0.5;
|
|
173
|
+
const hits = queryTerms.filter(t => text.includes(t)).length;
|
|
174
|
+
return hits / queryTerms.length;
|
|
175
|
+
}
|
|
176
|
+
buildSummary(query, sources) {
|
|
177
|
+
if (sources.length === 0) {
|
|
178
|
+
return `No sources found for: "${query}"`;
|
|
179
|
+
}
|
|
180
|
+
const repoCount = sources.filter(s => s.source === "repo").length;
|
|
181
|
+
const webCount = sources.filter(s => s.source.startsWith("mcp:")).length;
|
|
182
|
+
const top3 = sources.slice(0, 3)
|
|
183
|
+
.map(s => `- ${s.title}: ${s.snippet.slice(0, 100)}`)
|
|
184
|
+
.join("\n");
|
|
185
|
+
return [
|
|
186
|
+
`Research for: "${query}"`,
|
|
187
|
+
`Found ${sources.length} sources (${repoCount} repo, ${webCount} web).`,
|
|
188
|
+
`Top results:\n${top3}`,
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
computeConfidence(sources) {
|
|
192
|
+
if (sources.length === 0)
|
|
193
|
+
return 0;
|
|
194
|
+
const avgRelevance = sources.reduce((s, r) => s + r.relevance, 0) / sources.length;
|
|
195
|
+
const coverageFactor = Math.min(sources.length / 5, 1.0); // 5 sources = max coverage
|
|
196
|
+
return Math.round(avgRelevance * coverageFactor * 100) / 100;
|
|
197
|
+
}
|
|
198
|
+
emitResult(result) {
|
|
199
|
+
const event = {
|
|
200
|
+
kind: "agent:research_result",
|
|
201
|
+
taskId: result.taskId,
|
|
202
|
+
summary: truncate(result.summary, BOUNDS.toolResultPersistence),
|
|
203
|
+
sources: result.sources.slice(0, 20), // cap at 20 sources in event payload
|
|
204
|
+
confidence: result.confidence,
|
|
205
|
+
timestamp: result.timestamp,
|
|
206
|
+
};
|
|
207
|
+
this.emit("event", event);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=research-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-agent.js","sourceRoot":"","sources":["../../src/autonomous/research-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module autonomous/task-memory
|
|
3
|
+
* @description Long Horizon Task Memory — persists task-level state under .yuan/tasks/.
|
|
4
|
+
*
|
|
5
|
+
* Stores the full lifecycle of an engineering task: goal, phase, files touched,
|
|
6
|
+
* evidence history, and final status. Designed for multi-session tasks that
|
|
7
|
+
* span multiple agent runs (e.g. a multi-day refactor).
|
|
8
|
+
*
|
|
9
|
+
* Storage: .yuan/tasks/<taskId>.json (one file per task)
|
|
10
|
+
* All writes are atomic (write to .tmp then rename).
|
|
11
|
+
*/
|
|
12
|
+
import { EventEmitter } from "node:events";
|
|
13
|
+
export type TaskStatus = "running" | "completed" | "failed" | "paused";
|
|
14
|
+
export type TaskPhase = "research" | "planning" | "implement" | "verify" | "finalize";
|
|
15
|
+
export interface EvidenceEntry {
|
|
16
|
+
/** Unix ms */
|
|
17
|
+
timestamp: number;
|
|
18
|
+
filePath: string;
|
|
19
|
+
diffStats: {
|
|
20
|
+
added: number;
|
|
21
|
+
removed: number;
|
|
22
|
+
} | null;
|
|
23
|
+
syntax: "ok" | "error" | "skipped";
|
|
24
|
+
source: "evidence_report" | "qa_result" | "manual";
|
|
25
|
+
}
|
|
26
|
+
export interface TaskState {
|
|
27
|
+
taskId: string;
|
|
28
|
+
goal: string;
|
|
29
|
+
currentPhase: TaskPhase;
|
|
30
|
+
filesTouched: string[];
|
|
31
|
+
evidenceHistory: EvidenceEntry[];
|
|
32
|
+
status: TaskStatus;
|
|
33
|
+
/** ISO timestamp when task was created */
|
|
34
|
+
createdAt: string;
|
|
35
|
+
/** ISO timestamp of last update */
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
/** Optional: path to the plan file */
|
|
38
|
+
planPath?: string;
|
|
39
|
+
/** Optional: final summary on completion */
|
|
40
|
+
summary?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class TaskMemory extends EventEmitter {
|
|
43
|
+
private readonly tasksDir;
|
|
44
|
+
constructor(tasksDir?: string);
|
|
45
|
+
/** Create or overwrite a task. Emits agent:task_memory_update. */
|
|
46
|
+
create(taskId: string, goal: string): TaskState;
|
|
47
|
+
/** Load task state. Returns null if not found. */
|
|
48
|
+
load(taskId: string): TaskState | null;
|
|
49
|
+
/** Transition to a new phase. */
|
|
50
|
+
setPhase(taskId: string, phase: TaskPhase): TaskState | null;
|
|
51
|
+
/** Mark task complete or failed. */
|
|
52
|
+
finish(taskId: string, status: "completed" | "failed", summary?: string): TaskState | null;
|
|
53
|
+
/** Append an evidence entry to the task history. */
|
|
54
|
+
appendEvidence(taskId: string, entry: EvidenceEntry): TaskState | null;
|
|
55
|
+
/** List all tasks, sorted by updatedAt descending. */
|
|
56
|
+
list(): TaskState[];
|
|
57
|
+
/** Remove a task file. */
|
|
58
|
+
delete(taskId: string): boolean;
|
|
59
|
+
private taskPath;
|
|
60
|
+
private save;
|
|
61
|
+
private emitUpdate;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=task-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-memory.d.ts","sourceRoot":"","sources":["../../src/autonomous/task-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACvE,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,cAAc;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;IACnC,MAAM,EAAE,iBAAiB,GAAG,WAAW,GAAG,QAAQ,CAAC;CACpD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,SAAS,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,CAAC,EAAE,MAAM;IAM7B,kEAAkE;IAClE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS;IAgB/C,kDAAkD;IAClD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAStC,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAU5D,oCAAoC;IACpC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAW1F,oDAAoD;IACpD,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;IAgBtE,sDAAsD;IACtD,IAAI,IAAI,SAAS,EAAE;IAgBnB,0BAA0B;IAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAW/B,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,UAAU;CAUnB"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module autonomous/task-memory
|
|
3
|
+
* @description Long Horizon Task Memory — persists task-level state under .yuan/tasks/.
|
|
4
|
+
*
|
|
5
|
+
* Stores the full lifecycle of an engineering task: goal, phase, files touched,
|
|
6
|
+
* evidence history, and final status. Designed for multi-session tasks that
|
|
7
|
+
* span multiple agent runs (e.g. a multi-day refactor).
|
|
8
|
+
*
|
|
9
|
+
* Storage: .yuan/tasks/<taskId>.json (one file per task)
|
|
10
|
+
* All writes are atomic (write to .tmp then rename).
|
|
11
|
+
*/
|
|
12
|
+
import { mkdirSync, writeFileSync, readFileSync, renameSync, readdirSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { EventEmitter } from "node:events";
|
|
16
|
+
// ─── TaskMemory ──────────────────────────────────────────────────────────────
|
|
17
|
+
export class TaskMemory extends EventEmitter {
|
|
18
|
+
tasksDir;
|
|
19
|
+
constructor(tasksDir) {
|
|
20
|
+
super();
|
|
21
|
+
this.tasksDir = tasksDir ?? join(homedir(), ".yuan", "tasks");
|
|
22
|
+
mkdirSync(this.tasksDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
/** Create or overwrite a task. Emits agent:task_memory_update. */
|
|
25
|
+
create(taskId, goal) {
|
|
26
|
+
const state = {
|
|
27
|
+
taskId,
|
|
28
|
+
goal,
|
|
29
|
+
currentPhase: "research",
|
|
30
|
+
filesTouched: [],
|
|
31
|
+
evidenceHistory: [],
|
|
32
|
+
status: "running",
|
|
33
|
+
createdAt: new Date().toISOString(),
|
|
34
|
+
updatedAt: new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
this.save(state);
|
|
37
|
+
this.emitUpdate(state);
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
/** Load task state. Returns null if not found. */
|
|
41
|
+
load(taskId) {
|
|
42
|
+
try {
|
|
43
|
+
const raw = readFileSync(this.taskPath(taskId), "utf-8");
|
|
44
|
+
return JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Transition to a new phase. */
|
|
51
|
+
setPhase(taskId, phase) {
|
|
52
|
+
const state = this.load(taskId);
|
|
53
|
+
if (!state)
|
|
54
|
+
return null;
|
|
55
|
+
state.currentPhase = phase;
|
|
56
|
+
state.updatedAt = new Date().toISOString();
|
|
57
|
+
this.save(state);
|
|
58
|
+
this.emitUpdate(state);
|
|
59
|
+
return state;
|
|
60
|
+
}
|
|
61
|
+
/** Mark task complete or failed. */
|
|
62
|
+
finish(taskId, status, summary) {
|
|
63
|
+
const state = this.load(taskId);
|
|
64
|
+
if (!state)
|
|
65
|
+
return null;
|
|
66
|
+
state.status = status;
|
|
67
|
+
state.summary = summary;
|
|
68
|
+
state.updatedAt = new Date().toISOString();
|
|
69
|
+
this.save(state);
|
|
70
|
+
this.emitUpdate(state);
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
/** Append an evidence entry to the task history. */
|
|
74
|
+
appendEvidence(taskId, entry) {
|
|
75
|
+
const state = this.load(taskId);
|
|
76
|
+
if (!state)
|
|
77
|
+
return null;
|
|
78
|
+
state.evidenceHistory.push(entry);
|
|
79
|
+
// Cap history at 500 entries to prevent unbounded growth
|
|
80
|
+
if (state.evidenceHistory.length > 500) {
|
|
81
|
+
state.evidenceHistory = state.evidenceHistory.slice(-500);
|
|
82
|
+
}
|
|
83
|
+
if (!state.filesTouched.includes(entry.filePath)) {
|
|
84
|
+
state.filesTouched.push(entry.filePath);
|
|
85
|
+
}
|
|
86
|
+
state.updatedAt = new Date().toISOString();
|
|
87
|
+
this.save(state);
|
|
88
|
+
return state;
|
|
89
|
+
}
|
|
90
|
+
/** List all tasks, sorted by updatedAt descending. */
|
|
91
|
+
list() {
|
|
92
|
+
try {
|
|
93
|
+
return readdirSync(this.tasksDir)
|
|
94
|
+
.filter(f => f.endsWith(".json"))
|
|
95
|
+
.map(f => {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(readFileSync(join(this.tasksDir, f), "utf-8"));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.filter((s) => s !== null)
|
|
104
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Remove a task file. */
|
|
111
|
+
delete(taskId) {
|
|
112
|
+
try {
|
|
113
|
+
unlinkSync(this.taskPath(taskId));
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ─── private ───────────────────────────────────────────────────────────────
|
|
121
|
+
taskPath(taskId) {
|
|
122
|
+
// Sanitize taskId to prevent path traversal
|
|
123
|
+
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
|
|
124
|
+
return join(this.tasksDir, `${safe}.json`);
|
|
125
|
+
}
|
|
126
|
+
save(state) {
|
|
127
|
+
const path = this.taskPath(state.taskId);
|
|
128
|
+
const tmp = path + ".tmp";
|
|
129
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
130
|
+
renameSync(tmp, path);
|
|
131
|
+
}
|
|
132
|
+
emitUpdate(state) {
|
|
133
|
+
const event = {
|
|
134
|
+
kind: "agent:task_memory_update",
|
|
135
|
+
taskId: state.taskId,
|
|
136
|
+
phase: state.currentPhase,
|
|
137
|
+
status: state.status,
|
|
138
|
+
timestamp: Date.now(),
|
|
139
|
+
};
|
|
140
|
+
this.emit("event", event);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=task-memory.js.map
|