@bolloon/bolloon-agent 0.1.38 → 0.1.39
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/agents/constraint-layer.js +1 -2
- package/dist/web/client.js +2861 -3746
- package/dist/web/components/p2p/index.js +226 -264
- package/dist/web/ui/message-renderer.js +323 -434
- package/dist/web/ui/step-timeline.js +255 -351
- package/package.json +1 -1
- package/dist/bollharness-integration/llm/pi-ai.js +0 -397
- package/dist/bollharness-integration/pi-ecosystem-colony/index.js +0 -365
- package/dist/bollharness-integration/pi-ecosystem-goals/index.js +0 -458
- package/dist/bollharness-integration/pi-ecosystem-judgment/decision.js +0 -300
- package/dist/bollharness-integration/pi-ecosystem-judgment/distillation.js +0 -291
- package/dist/bollharness-integration/pi-ecosystem-judgment/index.js +0 -445
- package/dist/bollharness-integration/pi-ecosystem-mcp/index.js +0 -331
- package/dist/bollharness-integration/pi-ecosystem-subagents/index.js +0 -303
- package/dist/constraints/commands.js +0 -100
- package/dist/constraints/permissions.js +0 -37
- package/dist/constraints/runtime.js +0 -135
- package/dist/constraints/session.js +0 -48
- package/dist/constraints/system-init.js +0 -51
- package/dist/constraints/tools.js +0 -104
- package/dist/llm/minimax-provider.js +0 -46
- package/dist/llm/minimax.js +0 -45
- package/dist/pi-ecosystem-colony/index.js +0 -365
- package/dist/runtime/context/minimax-prompt.js +0 -178
- package/dist/runtime/context/sys-prompt.js +0 -1
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
- package/dist/social/ant-colony/PheromoneEngine.js +0 -227
- package/dist/social/ant-colony/index.js +0 -6
- package/dist/social/ant-colony/types.js +0 -24
- package/dist/utils/double.js +0 -6
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -657
- package/dist/web/components/p2p/p2p-tools.js +0 -248
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pi Goals Integration for Bolloon
|
|
3
|
-
*
|
|
4
|
-
* Provides persistent goal tracking and workflow orchestration.
|
|
5
|
-
* Based on pi-goals philosophy: goal-oriented agent workflows with budget enforcement.
|
|
6
|
-
*
|
|
7
|
-
* Key features:
|
|
8
|
-
* - Persistent goal state across sessions
|
|
9
|
-
* - Goal queue with FIFO execution
|
|
10
|
-
* - Budget enforcement (max time/tokens, min work thresholds)
|
|
11
|
-
* - Workflow templates with {{args}} placeholders
|
|
12
|
-
* - Churn monitoring to detect drift/stalling
|
|
13
|
-
*/
|
|
14
|
-
import * as fs from 'fs/promises';
|
|
15
|
-
import * as path from 'path';
|
|
16
|
-
// State
|
|
17
|
-
let goalQueue = [];
|
|
18
|
-
let currentIndex = 0;
|
|
19
|
-
const GOALS_DIR = path.join(process.env.HOME || '/tmp', '.bolloon', 'goals');
|
|
20
|
-
const GOALS_FILE = path.join(GOALS_DIR, 'queue.json');
|
|
21
|
-
const CHURN_FILE = path.join(GOALS_DIR, 'churn.json');
|
|
22
|
-
const TEMPLATES_DIR = path.join(process.cwd(), '.pi-goals');
|
|
23
|
-
const TEMPLATES_DIR_ALT = path.join(process.cwd(), '.ai', '.pi-goals');
|
|
24
|
-
// Churn monitoring
|
|
25
|
-
let churnEvents = [];
|
|
26
|
-
let lastGoalSnapshot = null;
|
|
27
|
-
/**
|
|
28
|
-
* Generate a unique goal ID
|
|
29
|
-
*/
|
|
30
|
-
function generateGoalId() {
|
|
31
|
-
return `goal-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Create a new goal
|
|
35
|
-
*/
|
|
36
|
-
export async function createGoal(objective, budget, metadata) {
|
|
37
|
-
const goal = {
|
|
38
|
-
id: generateGoalId(),
|
|
39
|
-
objective,
|
|
40
|
-
status: 'pending',
|
|
41
|
-
createdAt: new Date().toISOString(),
|
|
42
|
-
budget,
|
|
43
|
-
metadata,
|
|
44
|
-
};
|
|
45
|
-
goalQueue.push(goal);
|
|
46
|
-
await persistGoals();
|
|
47
|
-
console.log(`[PiGoals] Created goal: ${goal.id} - ${objective.substring(0, 50)}...`);
|
|
48
|
-
return goal;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Create a queue of goals
|
|
52
|
-
*/
|
|
53
|
-
export async function createGoalQueue(objectives, budgets) {
|
|
54
|
-
const goals = await Promise.all(objectives.map((obj, i) => createGoal(obj, budgets?.[i])));
|
|
55
|
-
return goals;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Get current goal
|
|
59
|
-
*/
|
|
60
|
-
export function getCurrentGoal() {
|
|
61
|
-
return goalQueue[currentIndex];
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Start current goal
|
|
65
|
-
*/
|
|
66
|
-
export async function startCurrentGoal() {
|
|
67
|
-
const goal = getCurrentGoal();
|
|
68
|
-
if (goal) {
|
|
69
|
-
goal.status = 'in_progress';
|
|
70
|
-
goal.startedAt = new Date().toISOString();
|
|
71
|
-
await persistGoals();
|
|
72
|
-
console.log(`[PiGoals] Started goal: ${goal.id}`);
|
|
73
|
-
}
|
|
74
|
-
return goal;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Complete current goal with result
|
|
78
|
-
*/
|
|
79
|
-
export async function completeCurrentGoal(result) {
|
|
80
|
-
const goal = getCurrentGoal();
|
|
81
|
-
if (goal) {
|
|
82
|
-
goal.status = 'completed';
|
|
83
|
-
goal.completedAt = new Date().toISOString();
|
|
84
|
-
goal.result = result;
|
|
85
|
-
// Summarize for context management
|
|
86
|
-
goal.summary = summarizeGoal(goal);
|
|
87
|
-
// Check budget enforcement
|
|
88
|
-
if (goal.budget?.minWorkMs || goal.budget?.minTokens) {
|
|
89
|
-
const meetsMin = checkMinimumWork(goal);
|
|
90
|
-
if (!meetsMin) {
|
|
91
|
-
console.warn(`[PiGoals] Goal ${goal.id} did not meet minimum work threshold`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
currentIndex++;
|
|
95
|
-
await persistGoals();
|
|
96
|
-
await persistChurn();
|
|
97
|
-
console.log(`[PiGoals] Completed goal: ${goal.id} (${currentIndex}/${goalQueue.length})`);
|
|
98
|
-
}
|
|
99
|
-
return goal;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Fail current goal
|
|
103
|
-
*/
|
|
104
|
-
export async function failCurrentGoal(error) {
|
|
105
|
-
const goal = getCurrentGoal();
|
|
106
|
-
if (goal) {
|
|
107
|
-
goal.status = 'failed';
|
|
108
|
-
goal.completedAt = new Date().toISOString();
|
|
109
|
-
goal.error = error;
|
|
110
|
-
goal.summary = summarizeGoal(goal);
|
|
111
|
-
currentIndex++;
|
|
112
|
-
await persistGoals();
|
|
113
|
-
console.log(`[PiGoals] Failed goal: ${goal.id} - ${error}`);
|
|
114
|
-
}
|
|
115
|
-
return goal;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Cutoff current goal (exceeded budget)
|
|
119
|
-
*/
|
|
120
|
-
export async function cutoffCurrentGoal(reason) {
|
|
121
|
-
const goal = getCurrentGoal();
|
|
122
|
-
if (goal) {
|
|
123
|
-
goal.status = 'cutoff';
|
|
124
|
-
goal.completedAt = new Date().toISOString();
|
|
125
|
-
goal.error = reason;
|
|
126
|
-
goal.summary = summarizeGoal(goal);
|
|
127
|
-
currentIndex++;
|
|
128
|
-
await persistGoals();
|
|
129
|
-
addChurnEvent(goal.id, 'drift', `Goal cut off: ${reason}`);
|
|
130
|
-
console.log(`[PiGoals] Cutoff goal: ${goal.id} - ${reason}`);
|
|
131
|
-
}
|
|
132
|
-
return goal;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Pause current goal
|
|
136
|
-
*/
|
|
137
|
-
export async function pauseCurrentGoal() {
|
|
138
|
-
const goal = getCurrentGoal();
|
|
139
|
-
if (goal) {
|
|
140
|
-
goal.status = 'paused';
|
|
141
|
-
goal.completedAt = new Date().toISOString();
|
|
142
|
-
await persistGoals();
|
|
143
|
-
console.log(`[PiGoals] Paused goal: ${goal.id}`);
|
|
144
|
-
}
|
|
145
|
-
return goal;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Check budget for a goal
|
|
149
|
-
*/
|
|
150
|
-
export function checkBudget(goal, elapsedMs, tokensUsed) {
|
|
151
|
-
if (!goal.budget)
|
|
152
|
-
return { exceeded: false };
|
|
153
|
-
if (goal.budget.maxTimeMs && elapsedMs && elapsedMs > goal.budget.maxTimeMs) {
|
|
154
|
-
return { exceeded: true, reason: `Time budget exceeded: ${elapsedMs}ms > ${goal.budget.maxTimeMs}ms` };
|
|
155
|
-
}
|
|
156
|
-
if (goal.budget.maxTokens && tokensUsed && tokensUsed > goal.budget.maxTokens) {
|
|
157
|
-
return { exceeded: true, reason: `Token budget exceeded: ${tokensUsed} > ${goal.budget.maxTokens}` };
|
|
158
|
-
}
|
|
159
|
-
return { exceeded: false };
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Check minimum work threshold
|
|
163
|
-
*/
|
|
164
|
-
function checkMinimumWork(goal) {
|
|
165
|
-
if (!goal.budget)
|
|
166
|
-
return true;
|
|
167
|
-
// Simplified - in practice would track actual time/token usage
|
|
168
|
-
if (goal.budget.minWorkMs && goal.startedAt) {
|
|
169
|
-
const elapsed = Date.now() - new Date(goal.startedAt).getTime();
|
|
170
|
-
if (elapsed < goal.budget.minWorkMs) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Detect churn (goal drift, stall, repeat)
|
|
178
|
-
*/
|
|
179
|
-
function detectChurn(goal) {
|
|
180
|
-
if (!lastGoalSnapshot || lastGoalSnapshot.goalId !== goal.id) {
|
|
181
|
-
lastGoalSnapshot = {
|
|
182
|
-
goalId: goal.id,
|
|
183
|
-
hash: hashGoal(goal),
|
|
184
|
-
timestamp: new Date().toISOString(),
|
|
185
|
-
};
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
const currentHash = hashGoal(goal);
|
|
189
|
-
// Same goal, same state - stall
|
|
190
|
-
if (currentHash === lastGoalSnapshot.hash) {
|
|
191
|
-
const stallTime = Date.now() - new Date(lastGoalSnapshot.timestamp).getTime();
|
|
192
|
-
if (stallTime > 5 * 60 * 1000) {
|
|
193
|
-
// 5 minutes
|
|
194
|
-
return {
|
|
195
|
-
goalId: goal.id,
|
|
196
|
-
type: 'stall',
|
|
197
|
-
message: `Goal stalled for ${Math.floor(stallTime / 60000)} minutes`,
|
|
198
|
-
timestamp: new Date().toISOString(),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
lastGoalSnapshot = {
|
|
203
|
-
goalId: goal.id,
|
|
204
|
-
hash: currentHash,
|
|
205
|
-
timestamp: new Date().toISOString(),
|
|
206
|
-
};
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Hash goal state for churn detection
|
|
211
|
-
*/
|
|
212
|
-
function hashGoal(goal) {
|
|
213
|
-
return `${goal.id}-${goal.status}-${goal.objective.substring(0, 50)}`;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Add a churn event
|
|
217
|
-
*/
|
|
218
|
-
function addChurnEvent(goalId, type, message) {
|
|
219
|
-
const event = {
|
|
220
|
-
goalId,
|
|
221
|
-
type,
|
|
222
|
-
message,
|
|
223
|
-
timestamp: new Date().toISOString(),
|
|
224
|
-
};
|
|
225
|
-
churnEvents.push(event);
|
|
226
|
-
if (churnEvents.length > 50) {
|
|
227
|
-
churnEvents = churnEvents.slice(-25);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Get churn events for current goal
|
|
232
|
-
*/
|
|
233
|
-
export function getChurnForGoal(goalId) {
|
|
234
|
-
return churnEvents.filter((e) => e.goalId === goalId);
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Summarize a goal for context management
|
|
238
|
-
*/
|
|
239
|
-
function summarizeGoal(goal) {
|
|
240
|
-
const duration = goal.startedAt && goal.completedAt
|
|
241
|
-
? Math.round((new Date(goal.completedAt).getTime() - new Date(goal.startedAt).getTime()) / 1000)
|
|
242
|
-
: 0;
|
|
243
|
-
return `[${goal.status.toUpperCase()}] ${goal.objective.substring(0, 100)}${goal.objective.length > 100 ? '...' : ''} | Duration: ${duration}s`;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Get goal statistics
|
|
247
|
-
*/
|
|
248
|
-
export function getGoalStats() {
|
|
249
|
-
return {
|
|
250
|
-
total: goalQueue.length,
|
|
251
|
-
pending: goalQueue.filter((g) => g.status === 'pending').length,
|
|
252
|
-
inProgress: goalQueue.filter((g) => g.status === 'in_progress').length,
|
|
253
|
-
completed: goalQueue.filter((g) => g.status === 'completed').length,
|
|
254
|
-
failed: goalQueue.filter((g) => g.status === 'failed').length,
|
|
255
|
-
paused: goalQueue.filter((g) => g.status === 'paused').length,
|
|
256
|
-
cutoff: goalQueue.filter((g) => g.status === 'cutoff').length,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Get queue summary
|
|
261
|
-
*/
|
|
262
|
-
export function getQueueSummary() {
|
|
263
|
-
const stats = getGoalStats();
|
|
264
|
-
const current = getCurrentGoal();
|
|
265
|
-
let summary = `# Goal Queue\n\n`;
|
|
266
|
-
summary += `Total: ${stats.total} | Pending: ${stats.pending} | Active: ${stats.inProgress} | Done: ${stats.completed + stats.failed + stats.cutoff}\n\n`;
|
|
267
|
-
if (current) {
|
|
268
|
-
summary += `Current: ${current.objective}\n`;
|
|
269
|
-
summary += `Status: ${current.status}\n`;
|
|
270
|
-
if (current.budget) {
|
|
271
|
-
summary += `Budget: maxTime=${current.budget.maxTimeMs}ms, maxTokens=${current.budget.maxTokens}\n`;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
summary += `\n## Recent Goals\n`;
|
|
275
|
-
const recent = goalQueue.slice(-5).reverse();
|
|
276
|
-
for (const g of recent) {
|
|
277
|
-
summary += `- [${g.status}] ${g.objective.substring(0, 60)}${g.objective.length > 60 ? '...' : ''}\n`;
|
|
278
|
-
}
|
|
279
|
-
return summary;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Persist goals to disk
|
|
283
|
-
*/
|
|
284
|
-
async function persistGoals() {
|
|
285
|
-
try {
|
|
286
|
-
await fs.mkdir(GOALS_DIR, { recursive: true });
|
|
287
|
-
await fs.writeFile(GOALS_FILE, JSON.stringify({
|
|
288
|
-
goals: goalQueue,
|
|
289
|
-
currentIndex,
|
|
290
|
-
updatedAt: new Date().toISOString(),
|
|
291
|
-
}, null, 2));
|
|
292
|
-
}
|
|
293
|
-
catch (e) {
|
|
294
|
-
console.warn('[PiGoals] Failed to persist:', e);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Persist churn events
|
|
299
|
-
*/
|
|
300
|
-
async function persistChurn() {
|
|
301
|
-
try {
|
|
302
|
-
await fs.mkdir(GOALS_DIR, { recursive: true });
|
|
303
|
-
await fs.writeFile(CHURN_FILE, JSON.stringify(churnEvents, null, 2));
|
|
304
|
-
}
|
|
305
|
-
catch (e) {
|
|
306
|
-
console.warn('[PiGoals] Failed to persist churn:', e);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Load goals from disk
|
|
311
|
-
*/
|
|
312
|
-
export async function loadGoals() {
|
|
313
|
-
try {
|
|
314
|
-
const data = await fs.readFile(GOALS_FILE, 'utf-8');
|
|
315
|
-
const state = JSON.parse(data);
|
|
316
|
-
goalQueue = state.goals || [];
|
|
317
|
-
currentIndex = state.currentIndex || 0;
|
|
318
|
-
console.log(`[PiGoals] Loaded ${goalQueue.length} goals, current index: ${currentIndex}`);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
goalQueue = [];
|
|
322
|
-
currentIndex = 0;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Load churn events
|
|
327
|
-
*/
|
|
328
|
-
async function loadChurn() {
|
|
329
|
-
try {
|
|
330
|
-
const data = await fs.readFile(CHURN_FILE, 'utf-8');
|
|
331
|
-
churnEvents = JSON.parse(data);
|
|
332
|
-
}
|
|
333
|
-
catch {
|
|
334
|
-
churnEvents = [];
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Clear all goals
|
|
339
|
-
*/
|
|
340
|
-
export async function clearGoals() {
|
|
341
|
-
goalQueue = [];
|
|
342
|
-
currentIndex = 0;
|
|
343
|
-
await persistGoals();
|
|
344
|
-
console.log('[PiGoals] Cleared all goals');
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Remove completed goals and compact queue
|
|
348
|
-
*/
|
|
349
|
-
export async function compactQueue() {
|
|
350
|
-
const completed = goalQueue.filter((g) => ['completed', 'failed', 'cutoff'].includes(g.status));
|
|
351
|
-
// Keep only summaries for completed goals
|
|
352
|
-
goalQueue = goalQueue.filter((g) => ['pending', 'in_progress', 'paused'].includes(g.status));
|
|
353
|
-
// Re-index
|
|
354
|
-
currentIndex = Math.min(currentIndex, goalQueue.length);
|
|
355
|
-
await persistGoals();
|
|
356
|
-
console.log(`[PiGoals] Compacted queue: removed ${completed.length} completed goals`);
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Load workflow templates
|
|
360
|
-
*/
|
|
361
|
-
export async function loadTemplates() {
|
|
362
|
-
const templates = [];
|
|
363
|
-
for (const dir of [TEMPLATES_DIR, TEMPLATES_DIR_ALT]) {
|
|
364
|
-
try {
|
|
365
|
-
const files = await fs.readdir(dir);
|
|
366
|
-
for (const file of files) {
|
|
367
|
-
if (file.endsWith('.md')) {
|
|
368
|
-
const content = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
369
|
-
const template = parseTemplate(file.replace('.md', ''), content);
|
|
370
|
-
if (template) {
|
|
371
|
-
templates.push(template);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
// Directory doesn't exist
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return templates;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Parse a workflow template
|
|
384
|
-
*/
|
|
385
|
-
function parseTemplate(name, content) {
|
|
386
|
-
const lines = content.split('\n');
|
|
387
|
-
// Simple frontmatter parsing
|
|
388
|
-
let description = '';
|
|
389
|
-
const goals = [];
|
|
390
|
-
const args = {};
|
|
391
|
-
let gate;
|
|
392
|
-
let inGoals = false;
|
|
393
|
-
for (const line of lines) {
|
|
394
|
-
if (line.startsWith('description:')) {
|
|
395
|
-
description = line.replace('description:', '').trim();
|
|
396
|
-
}
|
|
397
|
-
else if (line.startsWith('gate:')) {
|
|
398
|
-
gate = line.replace('gate:', '').trim();
|
|
399
|
-
}
|
|
400
|
-
else if (line.startsWith('---')) {
|
|
401
|
-
inGoals = !inGoals;
|
|
402
|
-
}
|
|
403
|
-
else if (inGoals && line.trim().startsWith('-')) {
|
|
404
|
-
goals.push(line.trim().substring(1).trim());
|
|
405
|
-
}
|
|
406
|
-
else if (line.includes(':')) {
|
|
407
|
-
const [key, ...rest] = line.split(':');
|
|
408
|
-
if (rest.length > 0) {
|
|
409
|
-
const value = rest.join(':').trim();
|
|
410
|
-
if (value.startsWith('{{') && value.endsWith('}}')) {
|
|
411
|
-
args[key.trim()] = { default: value };
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
args[key.trim()] = { description: value };
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (goals.length === 0) {
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
return { name, description, goals, args, gate };
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Create goals from a template
|
|
426
|
-
*/
|
|
427
|
-
export async function createFromTemplate(templateName, args) {
|
|
428
|
-
const templates = await loadTemplates();
|
|
429
|
-
const template = templates.find((t) => t.name === templateName);
|
|
430
|
-
if (!template) {
|
|
431
|
-
throw new Error(`Template not found: ${templateName}`);
|
|
432
|
-
}
|
|
433
|
-
const goals = [];
|
|
434
|
-
for (const goalText of template.goals) {
|
|
435
|
-
// Replace {{args}}
|
|
436
|
-
let objective = goalText;
|
|
437
|
-
for (const [key, value] of Object.entries(args)) {
|
|
438
|
-
objective = objective.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
439
|
-
}
|
|
440
|
-
const goal = await createGoal(objective, undefined, { template: templateName });
|
|
441
|
-
goals.push(goal);
|
|
442
|
-
}
|
|
443
|
-
return goals;
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Continue working on current goal (anti-stall)
|
|
447
|
-
*/
|
|
448
|
-
export async function nudgeCurrentGoal() {
|
|
449
|
-
const goal = getCurrentGoal();
|
|
450
|
-
if (goal) {
|
|
451
|
-
const churn = detectChurn(goal);
|
|
452
|
-
if (churn) {
|
|
453
|
-
addChurnEvent(goal.id, churn.type, churn.message);
|
|
454
|
-
console.warn(`[PiGoals] Churn detected: ${churn.message}`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return goal;
|
|
458
|
-
}
|