agentic-qe 2.7.1 → 2.7.3
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/CHANGELOG.md +179 -0
- package/README.md +2 -2
- package/dist/agents/CodeIntelligenceAgent.d.ts.map +1 -1
- package/dist/agents/CodeIntelligenceAgent.js +7 -5
- package/dist/agents/CodeIntelligenceAgent.js.map +1 -1
- package/dist/cli/commands/knowledge-graph.d.ts.map +1 -1
- package/dist/cli/commands/knowledge-graph.js +4 -2
- package/dist/cli/commands/knowledge-graph.js.map +1 -1
- package/dist/cli/commands/migrate/index.d.ts +14 -0
- package/dist/cli/commands/migrate/index.d.ts.map +1 -0
- package/dist/cli/commands/migrate/index.js +283 -0
- package/dist/cli/commands/migrate/index.js.map +1 -0
- package/dist/cli/formatters/KGOutputFormatter.d.ts.map +1 -1
- package/dist/cli/formatters/KGOutputFormatter.js +15 -6
- package/dist/cli/formatters/KGOutputFormatter.js.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/database-init.d.ts.map +1 -1
- package/dist/cli/init/database-init.js +105 -0
- package/dist/cli/init/database-init.js.map +1 -1
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.d.ts +9 -3
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.d.ts.map +1 -1
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.js +41 -3
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.js.map +1 -1
- package/dist/code-intelligence/service/CodeIntelligenceService.js +1 -1
- package/dist/code-intelligence/service/CodeIntelligenceService.js.map +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/learning/QLearning.d.ts +110 -2
- package/dist/learning/QLearning.d.ts.map +1 -1
- package/dist/learning/QLearning.js +218 -1
- package/dist/learning/QLearning.js.map +1 -1
- package/dist/learning/metrics/LearningMetrics.d.ts +37 -0
- package/dist/learning/metrics/LearningMetrics.d.ts.map +1 -1
- package/dist/learning/metrics/LearningMetrics.js +73 -0
- package/dist/learning/metrics/LearningMetrics.js.map +1 -1
- package/dist/mcp/handlers/fleet-init.d.ts +10 -0
- package/dist/mcp/handlers/fleet-init.d.ts.map +1 -1
- package/dist/mcp/handlers/fleet-init.js +61 -0
- package/dist/mcp/handlers/fleet-init.js.map +1 -1
- package/dist/mcp/handlers/learning/learning-store-pattern.d.ts +13 -0
- package/dist/mcp/handlers/learning/learning-store-pattern.d.ts.map +1 -1
- package/dist/mcp/handlers/learning/learning-store-pattern.js +38 -0
- package/dist/mcp/handlers/learning/learning-store-pattern.js.map +1 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.d.ts +89 -0
- package/dist/mcp/handlers/phase3/Phase3DomainTools.d.ts.map +1 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.js +110 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.js.map +1 -1
- package/dist/mcp/handlers/task-orchestrate.d.ts +27 -1
- package/dist/mcp/handlers/task-orchestrate.d.ts.map +1 -1
- package/dist/mcp/handlers/task-orchestrate.js +188 -8
- package/dist/mcp/handlers/task-orchestrate.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +92 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/services/AgentRegistry.d.ts +13 -0
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +66 -0
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.d.ts +55 -0
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.d.ts.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.js +233 -0
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.js.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/index.d.ts +5 -2
- package/dist/mcp/tools/qe/quality-gates/index.d.ts.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/index.js +10 -1
- package/dist/mcp/tools/qe/quality-gates/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +156 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/persistence/migrations/all-migrations.d.ts +18 -0
- package/dist/persistence/migrations/all-migrations.d.ts.map +1 -0
- package/dist/persistence/migrations/all-migrations.js +624 -0
- package/dist/persistence/migrations/all-migrations.js.map +1 -0
- package/dist/persistence/migrations/index.d.ts +110 -0
- package/dist/persistence/migrations/index.d.ts.map +1 -0
- package/dist/persistence/migrations/index.js +303 -0
- package/dist/persistence/migrations/index.js.map +1 -0
- package/dist/planning/GOAPPlanner.d.ts +170 -0
- package/dist/planning/GOAPPlanner.d.ts.map +1 -0
- package/dist/planning/GOAPPlanner.js +781 -0
- package/dist/planning/GOAPPlanner.js.map +1 -0
- package/dist/planning/PlanLearning.d.ts +184 -0
- package/dist/planning/PlanLearning.d.ts.map +1 -0
- package/dist/planning/PlanLearning.js +526 -0
- package/dist/planning/PlanLearning.js.map +1 -0
- package/dist/planning/PlanSimilarity.d.ts +148 -0
- package/dist/planning/PlanSimilarity.d.ts.map +1 -0
- package/dist/planning/PlanSimilarity.js +463 -0
- package/dist/planning/PlanSimilarity.js.map +1 -0
- package/dist/planning/WorldStateBuilder.d.ts +150 -0
- package/dist/planning/WorldStateBuilder.d.ts.map +1 -0
- package/dist/planning/WorldStateBuilder.js +267 -0
- package/dist/planning/WorldStateBuilder.js.map +1 -0
- package/dist/planning/actions/fleet-actions.d.ts +78 -0
- package/dist/planning/actions/fleet-actions.d.ts.map +1 -0
- package/dist/planning/actions/fleet-actions.js +329 -0
- package/dist/planning/actions/fleet-actions.js.map +1 -0
- package/dist/planning/actions/index.d.ts +61 -0
- package/dist/planning/actions/index.d.ts.map +1 -0
- package/dist/planning/actions/index.js +159 -0
- package/dist/planning/actions/index.js.map +1 -0
- package/dist/planning/actions/orchestration-actions.d.ts +61 -0
- package/dist/planning/actions/orchestration-actions.d.ts.map +1 -0
- package/dist/planning/actions/orchestration-actions.js +395 -0
- package/dist/planning/actions/orchestration-actions.js.map +1 -0
- package/dist/planning/actions/quality-gate-actions.d.ts +160 -0
- package/dist/planning/actions/quality-gate-actions.d.ts.map +1 -0
- package/dist/planning/actions/quality-gate-actions.js +639 -0
- package/dist/planning/actions/quality-gate-actions.js.map +1 -0
- package/dist/planning/actions/test-strategy-actions.d.ts +70 -0
- package/dist/planning/actions/test-strategy-actions.d.ts.map +1 -0
- package/dist/planning/actions/test-strategy-actions.js +278 -0
- package/dist/planning/actions/test-strategy-actions.js.map +1 -0
- package/dist/planning/execution/PlanExecutor.d.ts +223 -0
- package/dist/planning/execution/PlanExecutor.d.ts.map +1 -0
- package/dist/planning/execution/PlanExecutor.js +978 -0
- package/dist/planning/execution/PlanExecutor.js.map +1 -0
- package/dist/planning/execution/index.d.ts +12 -0
- package/dist/planning/execution/index.d.ts.map +1 -0
- package/dist/planning/execution/index.js +18 -0
- package/dist/planning/execution/index.js.map +1 -0
- package/dist/planning/goals/TaskWorkflowGoals.d.ts +67 -0
- package/dist/planning/goals/TaskWorkflowGoals.d.ts.map +1 -0
- package/dist/planning/goals/TaskWorkflowGoals.js +208 -0
- package/dist/planning/goals/TaskWorkflowGoals.js.map +1 -0
- package/dist/planning/goals/index.d.ts +10 -0
- package/dist/planning/goals/index.d.ts.map +1 -0
- package/dist/planning/goals/index.js +19 -0
- package/dist/planning/goals/index.js.map +1 -0
- package/dist/planning/index.d.ts +36 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/index.js +187 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts +235 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts.map +1 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.js +589 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.js.map +1 -0
- package/dist/planning/integration/GOAPTaskOrchestration.d.ts +165 -0
- package/dist/planning/integration/GOAPTaskOrchestration.d.ts.map +1 -0
- package/dist/planning/integration/GOAPTaskOrchestration.js +490 -0
- package/dist/planning/integration/GOAPTaskOrchestration.js.map +1 -0
- package/dist/planning/integration/index.d.ts +14 -0
- package/dist/planning/integration/index.d.ts.map +1 -0
- package/dist/planning/integration/index.js +23 -0
- package/dist/planning/integration/index.js.map +1 -0
- package/dist/planning/types.d.ts +266 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/planning/types.js +63 -0
- package/dist/planning/types.js.map +1 -0
- package/dist/utils/Database.d.ts.map +1 -1
- package/dist/utils/Database.js +81 -2
- package/dist/utils/Database.js.map +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GOAP Planner - A* Search Algorithm for Goal-Oriented Action Planning
|
|
4
|
+
*
|
|
5
|
+
* Implements optimal plan finding using A* search with:
|
|
6
|
+
* - Precondition checking
|
|
7
|
+
* - Effect application
|
|
8
|
+
* - Heuristic calculation
|
|
9
|
+
* - Plan reconstruction
|
|
10
|
+
*
|
|
11
|
+
* @module planning/GOAPPlanner
|
|
12
|
+
* @version 1.0.0
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.GOAPPlanner = void 0;
|
|
16
|
+
exports.getSharedGOAPPlanner = getSharedGOAPPlanner;
|
|
17
|
+
exports.resetSharedGOAPPlanner = resetSharedGOAPPlanner;
|
|
18
|
+
const Logger_1 = require("../utils/Logger");
|
|
19
|
+
const PlanSimilarity_1 = require("./PlanSimilarity");
|
|
20
|
+
/**
|
|
21
|
+
* GOAP Planner using A* search algorithm
|
|
22
|
+
*/
|
|
23
|
+
class GOAPPlanner {
|
|
24
|
+
constructor(db) {
|
|
25
|
+
this.actionLibrary = [];
|
|
26
|
+
this.actionsLoaded = false;
|
|
27
|
+
this.enablePlanReuse = true; // Can be disabled for benchmarking A* directly
|
|
28
|
+
this.db = db;
|
|
29
|
+
this.logger = Logger_1.Logger.getInstance();
|
|
30
|
+
this.planSimilarity = new PlanSimilarity_1.PlanSimilarity(db);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get PlanSimilarity instance for direct access
|
|
34
|
+
*/
|
|
35
|
+
getPlanSimilarity() {
|
|
36
|
+
return this.planSimilarity;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Enable or disable plan reuse (useful for benchmarking)
|
|
40
|
+
*/
|
|
41
|
+
setPlanReuseEnabled(enabled) {
|
|
42
|
+
this.enablePlanReuse = enabled;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if plan reuse is enabled
|
|
46
|
+
*/
|
|
47
|
+
isPlanReuseEnabled() {
|
|
48
|
+
return this.enablePlanReuse;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Load action library from database (merges with programmatic actions)
|
|
52
|
+
*/
|
|
53
|
+
async loadActionsFromDatabase() {
|
|
54
|
+
if (this.actionsLoaded)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const rows = this.db.prepare(`
|
|
58
|
+
SELECT * FROM goap_actions ORDER BY category, cost ASC
|
|
59
|
+
`).all();
|
|
60
|
+
// Merge DB actions with existing programmatic actions (avoid duplicates)
|
|
61
|
+
const existingIds = new Set(this.actionLibrary.map(a => a.id));
|
|
62
|
+
const dbActions = rows
|
|
63
|
+
.filter(row => !existingIds.has(row.id))
|
|
64
|
+
.map(row => ({
|
|
65
|
+
id: row.id,
|
|
66
|
+
name: row.name,
|
|
67
|
+
description: row.description || undefined,
|
|
68
|
+
agentType: row.agent_type,
|
|
69
|
+
preconditions: JSON.parse(row.preconditions),
|
|
70
|
+
effects: JSON.parse(row.effects),
|
|
71
|
+
cost: row.cost,
|
|
72
|
+
durationEstimate: row.duration_estimate || undefined,
|
|
73
|
+
successRate: row.success_rate,
|
|
74
|
+
executionCount: row.execution_count,
|
|
75
|
+
category: row.category
|
|
76
|
+
}));
|
|
77
|
+
this.actionLibrary.push(...dbActions);
|
|
78
|
+
this.actionsLoaded = true;
|
|
79
|
+
this.logger.debug(`[GOAPPlanner] Loaded ${dbActions.length} actions from database (total: ${this.actionLibrary.length})`);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.logger.warn('[GOAPPlanner] Failed to load actions from database, keeping existing library', { error });
|
|
83
|
+
// Keep existing actions instead of clearing
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Add actions programmatically (for testing or bootstrap)
|
|
88
|
+
*/
|
|
89
|
+
addActions(actions) {
|
|
90
|
+
this.actionLibrary.push(...actions);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Clear action library
|
|
94
|
+
*/
|
|
95
|
+
clearActions() {
|
|
96
|
+
this.actionLibrary = [];
|
|
97
|
+
this.actionsLoaded = false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get current action library
|
|
101
|
+
*/
|
|
102
|
+
getActionLibrary() {
|
|
103
|
+
return [...this.actionLibrary];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Ensure GOAP schema exists (creates missing tables)
|
|
107
|
+
*/
|
|
108
|
+
ensureSchema() {
|
|
109
|
+
// Create goap_execution_steps if missing
|
|
110
|
+
this.db.exec(`
|
|
111
|
+
CREATE TABLE IF NOT EXISTS goap_execution_steps (
|
|
112
|
+
id TEXT PRIMARY KEY,
|
|
113
|
+
plan_id TEXT NOT NULL,
|
|
114
|
+
action_id TEXT NOT NULL,
|
|
115
|
+
step_order INTEGER NOT NULL,
|
|
116
|
+
world_state_before TEXT,
|
|
117
|
+
world_state_after TEXT,
|
|
118
|
+
status TEXT DEFAULT 'pending',
|
|
119
|
+
started_at DATETIME,
|
|
120
|
+
completed_at DATETIME,
|
|
121
|
+
error_message TEXT,
|
|
122
|
+
agent_id TEXT,
|
|
123
|
+
FOREIGN KEY (plan_id) REFERENCES goap_plans (id),
|
|
124
|
+
FOREIGN KEY (action_id) REFERENCES goap_actions (id)
|
|
125
|
+
)
|
|
126
|
+
`);
|
|
127
|
+
// Create indexes if missing
|
|
128
|
+
try {
|
|
129
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_goap_execution_plan ON goap_execution_steps (plan_id)');
|
|
130
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_goap_execution_action ON goap_execution_steps (action_id)');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Indexes may already exist
|
|
134
|
+
}
|
|
135
|
+
this.logger.debug('[GOAPPlanner] Schema ensured');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Seed actions to database (upsert - won't duplicate)
|
|
139
|
+
* Call this with allActions to persist the action library
|
|
140
|
+
*/
|
|
141
|
+
seedActions(actions) {
|
|
142
|
+
this.ensureSchema();
|
|
143
|
+
const upsert = this.db.prepare(`
|
|
144
|
+
INSERT INTO goap_actions (
|
|
145
|
+
id, name, description, agent_type, preconditions, effects,
|
|
146
|
+
cost, duration_estimate, success_rate, execution_count, category, created_at
|
|
147
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
148
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
149
|
+
name = excluded.name,
|
|
150
|
+
description = excluded.description,
|
|
151
|
+
agent_type = excluded.agent_type,
|
|
152
|
+
preconditions = excluded.preconditions,
|
|
153
|
+
effects = excluded.effects,
|
|
154
|
+
cost = excluded.cost,
|
|
155
|
+
duration_estimate = excluded.duration_estimate,
|
|
156
|
+
category = excluded.category,
|
|
157
|
+
updated_at = CURRENT_TIMESTAMP
|
|
158
|
+
`);
|
|
159
|
+
let seeded = 0;
|
|
160
|
+
const insertMany = this.db.transaction((actionsToSeed) => {
|
|
161
|
+
for (const action of actionsToSeed) {
|
|
162
|
+
upsert.run(action.id, action.name, action.description || null, action.agentType, JSON.stringify(action.preconditions), JSON.stringify(action.effects), action.cost, action.durationEstimate || null, action.successRate ?? 1.0, action.executionCount ?? 0, action.category);
|
|
163
|
+
seeded++;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
insertMany(actions);
|
|
167
|
+
this.logger.info(`[GOAPPlanner] Seeded ${seeded} actions to database`);
|
|
168
|
+
return seeded;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get action count from database
|
|
172
|
+
*/
|
|
173
|
+
getActionCountFromDatabase() {
|
|
174
|
+
const result = this.db.prepare('SELECT COUNT(*) as count FROM goap_actions').get();
|
|
175
|
+
return result.count;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A* search to find optimal plan from current to goal state
|
|
179
|
+
* Phase 5: First checks for reusable similar plans before running A*
|
|
180
|
+
*/
|
|
181
|
+
async findPlan(currentState, goalConditions, constraints) {
|
|
182
|
+
await this.loadActionsFromDatabase();
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
// Phase 5: Check for reusable similar plans first (O(log n) vs O(n) A* search)
|
|
185
|
+
if (this.enablePlanReuse) {
|
|
186
|
+
const reusedPlan = await this.tryReuseSimilarPlan(currentState, goalConditions, constraints);
|
|
187
|
+
if (reusedPlan) {
|
|
188
|
+
this.logger.info('[GOAPPlanner] Reused similar plan', {
|
|
189
|
+
planId: reusedPlan.id,
|
|
190
|
+
actions: reusedPlan.actions.length,
|
|
191
|
+
totalCost: reusedPlan.totalCost,
|
|
192
|
+
elapsedMs: Date.now() - startTime
|
|
193
|
+
});
|
|
194
|
+
return reusedPlan;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const openSet = [];
|
|
198
|
+
const closedSet = new Set();
|
|
199
|
+
// Start node
|
|
200
|
+
const startNode = {
|
|
201
|
+
state: this.cloneState(currentState),
|
|
202
|
+
gCost: 0,
|
|
203
|
+
hCost: this.calculateHeuristic(currentState, goalConditions),
|
|
204
|
+
fCost: 0,
|
|
205
|
+
action: null,
|
|
206
|
+
parent: null,
|
|
207
|
+
depth: 0
|
|
208
|
+
};
|
|
209
|
+
startNode.fCost = startNode.gCost + startNode.hCost;
|
|
210
|
+
openSet.push(startNode);
|
|
211
|
+
let iterations = 0;
|
|
212
|
+
const maxIterations = constraints?.maxIterations ?? 10000;
|
|
213
|
+
const timeoutMs = constraints?.timeoutMs ?? 5000;
|
|
214
|
+
const maxPlanLength = constraints?.maxPlanLength ?? 20;
|
|
215
|
+
while (openSet.length > 0 && iterations < maxIterations) {
|
|
216
|
+
iterations++;
|
|
217
|
+
// Check timeout
|
|
218
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
219
|
+
this.logger.warn('[GOAPPlanner] Planning timeout exceeded', {
|
|
220
|
+
timeoutMs,
|
|
221
|
+
iterations,
|
|
222
|
+
elapsed: Date.now() - startTime
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
// Get node with lowest fCost
|
|
227
|
+
openSet.sort((a, b) => a.fCost - b.fCost);
|
|
228
|
+
const current = openSet.shift();
|
|
229
|
+
// Check if goal reached
|
|
230
|
+
if (this.goalMet(current.state, goalConditions)) {
|
|
231
|
+
const plan = this.reconstructPlan(current, goalConditions);
|
|
232
|
+
this.logger.info('[GOAPPlanner] Plan found', {
|
|
233
|
+
iterations,
|
|
234
|
+
actions: plan.actions.length,
|
|
235
|
+
totalCost: plan.totalCost,
|
|
236
|
+
elapsedMs: Date.now() - startTime
|
|
237
|
+
});
|
|
238
|
+
return plan;
|
|
239
|
+
}
|
|
240
|
+
// Check max depth
|
|
241
|
+
if (current.depth >= maxPlanLength) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const stateKey = this.stateHash(current.state);
|
|
245
|
+
if (closedSet.has(stateKey)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
closedSet.add(stateKey);
|
|
249
|
+
// Expand neighbors (applicable actions)
|
|
250
|
+
const applicableActions = this.getApplicableActions(current.state, constraints);
|
|
251
|
+
for (const action of applicableActions) {
|
|
252
|
+
const nextState = this.applyAction(current.state, action);
|
|
253
|
+
const nextStateKey = this.stateHash(nextState);
|
|
254
|
+
if (closedSet.has(nextStateKey))
|
|
255
|
+
continue;
|
|
256
|
+
const gCost = current.gCost + this.getActionCost(action, current.state);
|
|
257
|
+
const hCost = this.calculateHeuristic(nextState, goalConditions);
|
|
258
|
+
const existingNode = openSet.find(n => this.stateHash(n.state) === nextStateKey);
|
|
259
|
+
if (!existingNode || gCost < existingNode.gCost) {
|
|
260
|
+
const newNode = {
|
|
261
|
+
state: nextState,
|
|
262
|
+
gCost,
|
|
263
|
+
hCost,
|
|
264
|
+
fCost: gCost + hCost,
|
|
265
|
+
action,
|
|
266
|
+
parent: current,
|
|
267
|
+
depth: current.depth + 1
|
|
268
|
+
};
|
|
269
|
+
if (existingNode) {
|
|
270
|
+
Object.assign(existingNode, newNode);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
openSet.push(newNode);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.logger.warn('[GOAPPlanner] No plan found', {
|
|
279
|
+
iterations,
|
|
280
|
+
closedSetSize: closedSet.size,
|
|
281
|
+
elapsedMs: Date.now() - startTime
|
|
282
|
+
});
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Phase 5: Try to reuse a similar plan from the signature cache
|
|
287
|
+
* Returns null if no suitable plan found, otherwise returns reconstructed plan
|
|
288
|
+
*/
|
|
289
|
+
async tryReuseSimilarPlan(currentState, goalConditions, constraints) {
|
|
290
|
+
try {
|
|
291
|
+
// Find similar plans (target: <100ms)
|
|
292
|
+
const similarPlans = await this.planSimilarity.findSimilarPlans(goalConditions, currentState, { minSimilarity: 0.75, maxCandidates: 3 });
|
|
293
|
+
if (similarPlans.length === 0) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
// Try best match first (sorted by goal match, then similarity)
|
|
297
|
+
for (const similar of similarPlans) {
|
|
298
|
+
// Validate the action sequence is still valid
|
|
299
|
+
const actions = this.reconstructActionsFromSequence(similar.signature.actionSequence, constraints);
|
|
300
|
+
if (actions.length === 0) {
|
|
301
|
+
this.logger.debug('[GOAPPlanner] Similar plan has no valid actions', {
|
|
302
|
+
planId: similar.planId
|
|
303
|
+
});
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// Verify preconditions can be met from current state
|
|
307
|
+
if (!this.validateActionSequence(currentState, actions)) {
|
|
308
|
+
this.logger.debug('[GOAPPlanner] Similar plan action sequence invalid for current state', {
|
|
309
|
+
planId: similar.planId
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
// Create reused plan with new ID
|
|
314
|
+
const reusedPlanId = `plan-reuse-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
315
|
+
const plan = {
|
|
316
|
+
id: reusedPlanId,
|
|
317
|
+
actions,
|
|
318
|
+
totalCost: similar.signature.totalCost,
|
|
319
|
+
estimatedDuration: actions.reduce((sum, a) => sum + (a.durationEstimate ?? 0), 0),
|
|
320
|
+
goalConditions,
|
|
321
|
+
reusedFromPlanId: similar.planId, // Track provenance
|
|
322
|
+
similarityScore: similar.similarityScore
|
|
323
|
+
};
|
|
324
|
+
this.logger.info('[GOAPPlanner] Found reusable plan', {
|
|
325
|
+
originalPlanId: similar.planId,
|
|
326
|
+
reusedPlanId,
|
|
327
|
+
similarity: similar.similarityScore.toFixed(3),
|
|
328
|
+
goalMatch: similar.goalMatch,
|
|
329
|
+
actions: actions.length
|
|
330
|
+
});
|
|
331
|
+
return plan;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
this.logger.warn('[GOAPPlanner] Error in plan reuse check, falling back to A*', { error });
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Reconstruct action objects from action ID sequence
|
|
342
|
+
*/
|
|
343
|
+
reconstructActionsFromSequence(actionSequence, constraints) {
|
|
344
|
+
const actions = [];
|
|
345
|
+
const excludedActions = new Set(constraints?.excludedActions || []);
|
|
346
|
+
const allowedCategories = constraints?.allowedCategories
|
|
347
|
+
? new Set(constraints.allowedCategories)
|
|
348
|
+
: null;
|
|
349
|
+
for (const actionId of actionSequence) {
|
|
350
|
+
if (excludedActions.has(actionId))
|
|
351
|
+
continue;
|
|
352
|
+
const action = this.actionLibrary.find(a => a.id === actionId);
|
|
353
|
+
if (!action)
|
|
354
|
+
continue;
|
|
355
|
+
if (allowedCategories && !allowedCategories.has(action.category))
|
|
356
|
+
continue;
|
|
357
|
+
actions.push(action);
|
|
358
|
+
}
|
|
359
|
+
return actions;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Validate that action sequence can be executed from current state
|
|
363
|
+
*/
|
|
364
|
+
validateActionSequence(initialState, actions) {
|
|
365
|
+
let currentState = this.cloneState(initialState);
|
|
366
|
+
for (const action of actions) {
|
|
367
|
+
if (!this.preconditionsMet(currentState, action.preconditions)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
currentState = this.applyAction(currentState, action);
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Store plan signature for future reuse
|
|
376
|
+
* Call this after successful plan execution
|
|
377
|
+
*/
|
|
378
|
+
storePlanSignature(plan, initialState) {
|
|
379
|
+
try {
|
|
380
|
+
this.planSimilarity.storePlanSignature(plan.id, plan.goalConditions, initialState, plan.actions, plan.totalCost);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
this.logger.warn('[GOAPPlanner] Failed to store plan signature', {
|
|
384
|
+
planId: plan.id,
|
|
385
|
+
error
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Record plan reuse outcome (for learning)
|
|
391
|
+
*/
|
|
392
|
+
recordPlanReuseOutcome(planId, success) {
|
|
393
|
+
this.planSimilarity.recordPlanReuse(planId, success);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get plan reuse statistics
|
|
397
|
+
*/
|
|
398
|
+
getPlanReuseStats() {
|
|
399
|
+
return this.planSimilarity.getReuseStats();
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Calculate heuristic distance to goal (admissible)
|
|
403
|
+
*/
|
|
404
|
+
calculateHeuristic(state, goal) {
|
|
405
|
+
let distance = 0;
|
|
406
|
+
for (const [key, condition] of Object.entries(goal)) {
|
|
407
|
+
const currentValue = this.getStateValue(state, key);
|
|
408
|
+
const condObj = condition;
|
|
409
|
+
if (condObj.gte !== undefined) {
|
|
410
|
+
if (typeof currentValue === 'number' && currentValue < condObj.gte) {
|
|
411
|
+
distance += (condObj.gte - currentValue) / 100; // Normalize
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (condObj.gt !== undefined) {
|
|
415
|
+
if (typeof currentValue === 'number' && currentValue <= condObj.gt) {
|
|
416
|
+
distance += (condObj.gt - currentValue + 1) / 100;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (condObj.lte !== undefined) {
|
|
420
|
+
if (typeof currentValue === 'number' && currentValue > condObj.lte) {
|
|
421
|
+
distance += (currentValue - condObj.lte) / 100;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (condObj.lt !== undefined) {
|
|
425
|
+
if (typeof currentValue === 'number' && currentValue >= condObj.lt) {
|
|
426
|
+
distance += (currentValue - condObj.lt + 1) / 100;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (condObj.eq !== undefined) {
|
|
430
|
+
if (currentValue !== condObj.eq) {
|
|
431
|
+
distance += 1;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (condObj.ne !== undefined) {
|
|
435
|
+
if (currentValue === condObj.ne) {
|
|
436
|
+
distance += 1;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (condObj.contains !== undefined) {
|
|
440
|
+
if (Array.isArray(currentValue) && !currentValue.includes(condObj.contains)) {
|
|
441
|
+
distance += 1;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (condObj.exists !== undefined) {
|
|
445
|
+
const exists = currentValue !== undefined && currentValue !== null;
|
|
446
|
+
if (condObj.exists !== exists) {
|
|
447
|
+
distance += 1;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return distance;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Check if goal conditions are met
|
|
455
|
+
*/
|
|
456
|
+
goalMet(state, goal) {
|
|
457
|
+
return this.conditionsMet(state, goal);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Check if all conditions are satisfied
|
|
461
|
+
*/
|
|
462
|
+
conditionsMet(state, conditions) {
|
|
463
|
+
for (const [key, condition] of Object.entries(conditions)) {
|
|
464
|
+
if (!this.checkCondition(state, key, condition)) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Check a single condition
|
|
472
|
+
*/
|
|
473
|
+
checkCondition(state, key, condition) {
|
|
474
|
+
const value = this.getStateValue(state, key);
|
|
475
|
+
if (condition.gte !== undefined && (typeof value !== 'number' || value < condition.gte)) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
if (condition.gt !== undefined && (typeof value !== 'number' || value <= condition.gt)) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
if (condition.lte !== undefined && (typeof value !== 'number' || value > condition.lte)) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
if (condition.lt !== undefined && (typeof value !== 'number' || value >= condition.lt)) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
if (condition.eq !== undefined && value !== condition.eq) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
if (condition.ne !== undefined && value === condition.ne) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
if (condition.contains !== undefined) {
|
|
494
|
+
if (!Array.isArray(value) || !value.includes(condition.contains)) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (condition.exists !== undefined) {
|
|
499
|
+
const exists = value !== undefined && value !== null;
|
|
500
|
+
if (condition.exists !== exists) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (condition.in !== undefined) {
|
|
505
|
+
if (!condition.in.includes(value)) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Check if action preconditions are met
|
|
513
|
+
*/
|
|
514
|
+
preconditionsMet(state, preconditions) {
|
|
515
|
+
return this.conditionsMet(state, preconditions);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get actions whose preconditions are satisfied
|
|
519
|
+
*/
|
|
520
|
+
getApplicableActions(state, constraints) {
|
|
521
|
+
return this.actionLibrary.filter(action => {
|
|
522
|
+
// Check preconditions
|
|
523
|
+
if (!this.preconditionsMet(state, action.preconditions)) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
// Check category constraints
|
|
527
|
+
if (constraints?.allowedCategories) {
|
|
528
|
+
if (!constraints.allowedCategories.includes(action.category)) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Check excluded actions
|
|
533
|
+
if (constraints?.excludedActions) {
|
|
534
|
+
if (constraints.excludedActions.includes(action.id)) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Check preferred agent types (boost, don't exclude)
|
|
539
|
+
// This is handled in cost calculation instead
|
|
540
|
+
return true;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Calculate effective action cost
|
|
545
|
+
*/
|
|
546
|
+
getActionCost(action, state) {
|
|
547
|
+
let cost = action.cost;
|
|
548
|
+
// Adjust based on success rate (prefer reliable actions)
|
|
549
|
+
if (action.successRate !== undefined && action.successRate < 1) {
|
|
550
|
+
cost = cost / action.successRate;
|
|
551
|
+
}
|
|
552
|
+
// Adjust for critical risk level (increase cost of risky actions)
|
|
553
|
+
if (state.context.riskLevel === 'critical') {
|
|
554
|
+
if (action.category === 'process') {
|
|
555
|
+
cost *= 2; // Discourage process shortcuts in critical situations
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return cost;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Apply action effects to state
|
|
562
|
+
*/
|
|
563
|
+
applyAction(state, action) {
|
|
564
|
+
const newState = this.cloneState(state);
|
|
565
|
+
for (const [key, effect] of Object.entries(action.effects)) {
|
|
566
|
+
this.applyEffect(newState, key, effect);
|
|
567
|
+
}
|
|
568
|
+
return newState;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Apply a single effect to state
|
|
572
|
+
*/
|
|
573
|
+
applyEffect(state, key, effect) {
|
|
574
|
+
if (effect.set !== undefined) {
|
|
575
|
+
this.setStateValue(state, key, effect.set);
|
|
576
|
+
}
|
|
577
|
+
if (effect.increase !== undefined) {
|
|
578
|
+
const current = this.getStateValue(state, key) ?? 0;
|
|
579
|
+
if (typeof current === 'number') {
|
|
580
|
+
this.setStateValue(state, key, Math.min(100, current + effect.increase));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (effect.decrease !== undefined) {
|
|
584
|
+
const current = this.getStateValue(state, key) ?? 0;
|
|
585
|
+
if (typeof current === 'number') {
|
|
586
|
+
this.setStateValue(state, key, Math.max(0, current - effect.decrease));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (effect.increment !== undefined) {
|
|
590
|
+
const current = this.getStateValue(state, key) ?? 0;
|
|
591
|
+
if (typeof current === 'number') {
|
|
592
|
+
this.setStateValue(state, key, current + effect.increment);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (effect.decrement !== undefined) {
|
|
596
|
+
const current = this.getStateValue(state, key) ?? 0;
|
|
597
|
+
if (typeof current === 'number') {
|
|
598
|
+
this.setStateValue(state, key, Math.max(0, current - effect.decrement));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (effect.add !== undefined) {
|
|
602
|
+
const current = this.getStateValue(state, key) ?? [];
|
|
603
|
+
if (Array.isArray(current) && !current.includes(effect.add)) {
|
|
604
|
+
this.setStateValue(state, key, [...current, effect.add]);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (effect.remove !== undefined) {
|
|
608
|
+
const current = this.getStateValue(state, key) ?? [];
|
|
609
|
+
if (Array.isArray(current)) {
|
|
610
|
+
this.setStateValue(state, key, current.filter(v => v !== effect.remove));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// NOTE: { update: 'measured' } was removed as it was a semantic hack
|
|
614
|
+
// that allowed the same action to repeat infinitely. We now use proper
|
|
615
|
+
// flag-based effects where measurement actions set boolean flags that
|
|
616
|
+
// enable improvement actions. This ensures each measurement action
|
|
617
|
+
// can only run once (precondition: flag == false, effect: flag = true).
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Get value from nested state using dot notation
|
|
621
|
+
*/
|
|
622
|
+
getStateValue(state, key) {
|
|
623
|
+
const parts = key.split('.');
|
|
624
|
+
let current = state;
|
|
625
|
+
for (const part of parts) {
|
|
626
|
+
if (current === undefined || current === null) {
|
|
627
|
+
return undefined;
|
|
628
|
+
}
|
|
629
|
+
current = current[part];
|
|
630
|
+
}
|
|
631
|
+
return current;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Set value in nested state using dot notation
|
|
635
|
+
*/
|
|
636
|
+
setStateValue(state, key, value) {
|
|
637
|
+
const parts = key.split('.');
|
|
638
|
+
let current = state;
|
|
639
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
640
|
+
const part = parts[i];
|
|
641
|
+
if (current[part] === undefined) {
|
|
642
|
+
current[part] = {};
|
|
643
|
+
}
|
|
644
|
+
current = current[part];
|
|
645
|
+
}
|
|
646
|
+
current[parts[parts.length - 1]] = value;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Create hash of state for deduplication
|
|
650
|
+
*/
|
|
651
|
+
stateHash(state) {
|
|
652
|
+
// Hash all metrics that affect planning, including measurement flags
|
|
653
|
+
const key = {
|
|
654
|
+
coverage: state.coverage.line,
|
|
655
|
+
coverageMeasured: state.coverage.measured ?? false,
|
|
656
|
+
tests: state.quality.testsPassing,
|
|
657
|
+
testsMeasured: state.quality.testsMeasured ?? false,
|
|
658
|
+
integrationTested: state.quality.integrationTested ?? false,
|
|
659
|
+
security: state.quality.securityScore,
|
|
660
|
+
securityMeasured: state.quality.securityMeasured ?? false,
|
|
661
|
+
performance: state.quality.performanceScore,
|
|
662
|
+
performanceMeasured: state.quality.performanceMeasured ?? false,
|
|
663
|
+
complexityMeasured: state.quality.complexityMeasured ?? false,
|
|
664
|
+
gateEvaluated: state.quality.gateEvaluated ?? false,
|
|
665
|
+
gateStatus: state.quality.gateStatus,
|
|
666
|
+
agents: state.fleet.activeAgents,
|
|
667
|
+
availableTypes: state.fleet.availableAgents.sort().join(','),
|
|
668
|
+
time: Math.floor(state.resources.timeRemaining / 60) // Round to minutes
|
|
669
|
+
};
|
|
670
|
+
return JSON.stringify(key);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Deep clone world state
|
|
674
|
+
*/
|
|
675
|
+
cloneState(state) {
|
|
676
|
+
return JSON.parse(JSON.stringify(state));
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Reconstruct plan from goal node
|
|
680
|
+
*/
|
|
681
|
+
reconstructPlan(goalNode, goalConditions) {
|
|
682
|
+
const actions = [];
|
|
683
|
+
let current = goalNode;
|
|
684
|
+
while (current && current.action) {
|
|
685
|
+
actions.unshift(current.action);
|
|
686
|
+
current = current.parent;
|
|
687
|
+
}
|
|
688
|
+
const planId = `plan-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
689
|
+
return {
|
|
690
|
+
id: planId,
|
|
691
|
+
actions,
|
|
692
|
+
totalCost: goalNode.gCost,
|
|
693
|
+
estimatedDuration: actions.reduce((sum, a) => sum + (a.durationEstimate ?? 0), 0),
|
|
694
|
+
goalConditions
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Persist plan to database
|
|
699
|
+
*/
|
|
700
|
+
async persistPlan(plan, initialState, goalId) {
|
|
701
|
+
try {
|
|
702
|
+
this.db.prepare(`
|
|
703
|
+
INSERT INTO goap_plans (
|
|
704
|
+
id, goal_id, initial_state, goal_state, action_sequence,
|
|
705
|
+
total_cost, estimated_duration, status, created_at
|
|
706
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)
|
|
707
|
+
`).run(plan.id, goalId || null, JSON.stringify(initialState), JSON.stringify(plan.goalConditions), JSON.stringify(plan.actions.map(a => a.id)), plan.totalCost, plan.estimatedDuration);
|
|
708
|
+
this.logger.info('[GOAPPlanner] Plan persisted', { planId: plan.id });
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
this.logger.error('[GOAPPlanner] Failed to persist plan', { planId: plan.id, error });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Update action success rate based on execution outcome
|
|
716
|
+
*/
|
|
717
|
+
async updateActionSuccessRate(actionId, success) {
|
|
718
|
+
try {
|
|
719
|
+
const action = this.db.prepare(`
|
|
720
|
+
SELECT success_rate, execution_count FROM goap_actions WHERE id = ?
|
|
721
|
+
`).get(actionId);
|
|
722
|
+
if (!action)
|
|
723
|
+
return;
|
|
724
|
+
const newCount = action.execution_count + 1;
|
|
725
|
+
const newRate = (action.success_rate * action.execution_count + (success ? 1 : 0)) / newCount;
|
|
726
|
+
this.db.prepare(`
|
|
727
|
+
UPDATE goap_actions
|
|
728
|
+
SET success_rate = ?, execution_count = ?, updated_at = CURRENT_TIMESTAMP
|
|
729
|
+
WHERE id = ?
|
|
730
|
+
`).run(newRate, newCount, actionId);
|
|
731
|
+
// Update in-memory library
|
|
732
|
+
const memAction = this.actionLibrary.find(a => a.id === actionId);
|
|
733
|
+
if (memAction) {
|
|
734
|
+
memAction.successRate = newRate;
|
|
735
|
+
memAction.executionCount = newCount;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
this.logger.warn('[GOAPPlanner] Failed to update action success rate', { actionId, error });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Find alternative plans (for presenting options)
|
|
744
|
+
*/
|
|
745
|
+
async findAlternativePlans(currentState, goalConditions, constraints, maxAlternatives = 3) {
|
|
746
|
+
const plans = [];
|
|
747
|
+
const usedActions = new Set();
|
|
748
|
+
for (let i = 0; i < maxAlternatives + 1; i++) {
|
|
749
|
+
const plan = await this.findPlan(currentState, goalConditions, {
|
|
750
|
+
...constraints,
|
|
751
|
+
excludedActions: [...(constraints?.excludedActions || []), ...Array.from(usedActions)]
|
|
752
|
+
});
|
|
753
|
+
if (plan) {
|
|
754
|
+
plans.push(plan);
|
|
755
|
+
// Exclude first action of this plan for next iteration
|
|
756
|
+
if (plan.actions.length > 0) {
|
|
757
|
+
usedActions.add(plan.actions[0].id);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return plans;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
exports.GOAPPlanner = GOAPPlanner;
|
|
768
|
+
/**
|
|
769
|
+
* Get shared GOAPPlanner instance
|
|
770
|
+
*/
|
|
771
|
+
let sharedPlanner = null;
|
|
772
|
+
function getSharedGOAPPlanner(db) {
|
|
773
|
+
if (!sharedPlanner) {
|
|
774
|
+
sharedPlanner = new GOAPPlanner(db);
|
|
775
|
+
}
|
|
776
|
+
return sharedPlanner;
|
|
777
|
+
}
|
|
778
|
+
function resetSharedGOAPPlanner() {
|
|
779
|
+
sharedPlanner = null;
|
|
780
|
+
}
|
|
781
|
+
//# sourceMappingURL=GOAPPlanner.js.map
|