@pinet/agent-goal 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -84
- package/dist/dashboard.d.ts +5 -3
- package/dist/dashboard.js +42 -24
- package/dist/domain.d.ts +34 -1
- package/dist/goal-window.d.ts +43 -9
- package/dist/goal-window.js +300 -152
- package/dist/index.d.ts +1 -1
- package/dist/index.js +107 -100
- package/dist/memory-storage.d.ts +4 -1
- package/dist/memory-storage.js +21 -1
- package/dist/runtime.d.ts +15 -2
- package/dist/runtime.js +154 -13
- package/dist/sqlite-storage.d.ts +3 -1
- package/dist/sqlite-storage.js +70 -18
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { TimerGoalWakeScheduler } from "./wake-scheduler.js";
|
|
3
|
-
const DEFAULT_BUDGET = {
|
|
3
|
+
const DEFAULT_BUDGET = {};
|
|
4
4
|
const DEFAULT_RETRY_POLICY = {
|
|
5
5
|
maxAttempts: 3,
|
|
6
6
|
baseDelayMs: 250,
|
|
@@ -54,21 +54,40 @@ export class GoalRuntime {
|
|
|
54
54
|
async getTerminalCandidate(scopeId) {
|
|
55
55
|
return this.storage.getTerminalCandidate(scopeId);
|
|
56
56
|
}
|
|
57
|
-
async
|
|
57
|
+
async listCheckpoints(scopeId) {
|
|
58
|
+
return this.storage.listCheckpoints(scopeId);
|
|
59
|
+
}
|
|
60
|
+
async create(scopeId, objective, budget = this.budget, name) {
|
|
58
61
|
const trimmedObjective = objective.trim();
|
|
59
62
|
if (!trimmedObjective)
|
|
60
63
|
throw new Error("Goal objective cannot be empty");
|
|
61
|
-
if (
|
|
64
|
+
if (budget.maxIterations !== undefined &&
|
|
65
|
+
(!Number.isInteger(budget.maxIterations) || budget.maxIterations <= 0)) {
|
|
62
66
|
throw new Error("Goal maxIterations must be a positive integer");
|
|
63
67
|
}
|
|
68
|
+
if (budget.maxIterations !== undefined &&
|
|
69
|
+
this.budget.maxIterations !== undefined &&
|
|
70
|
+
budget.maxIterations > this.budget.maxIterations) {
|
|
71
|
+
throw new Error(`Goal maxIterations cannot exceed the configured limit of ${this.budget.maxIterations}`);
|
|
72
|
+
}
|
|
64
73
|
if (budget.maxTokens !== undefined &&
|
|
65
74
|
(!Number.isFinite(budget.maxTokens) || budget.maxTokens <= 0)) {
|
|
66
75
|
throw new Error("Goal maxTokens must be a positive finite number");
|
|
67
76
|
}
|
|
77
|
+
if (budget.maxTokens !== undefined &&
|
|
78
|
+
this.budget.maxTokens !== undefined &&
|
|
79
|
+
budget.maxTokens > this.budget.maxTokens) {
|
|
80
|
+
throw new Error(`Goal maxTokens cannot exceed the configured limit of ${this.budget.maxTokens}`);
|
|
81
|
+
}
|
|
68
82
|
if (budget.maxRuntimeMs !== undefined &&
|
|
69
83
|
(!Number.isFinite(budget.maxRuntimeMs) || budget.maxRuntimeMs <= 0)) {
|
|
70
84
|
throw new Error("Goal maxRuntimeMs must be a positive finite number");
|
|
71
85
|
}
|
|
86
|
+
if (budget.maxRuntimeMs !== undefined &&
|
|
87
|
+
this.budget.maxRuntimeMs !== undefined &&
|
|
88
|
+
budget.maxRuntimeMs > this.budget.maxRuntimeMs) {
|
|
89
|
+
throw new Error(`Goal maxRuntimeMs cannot exceed the configured limit of ${this.budget.maxRuntimeMs}`);
|
|
90
|
+
}
|
|
72
91
|
if (await this.storage.get(scopeId)) {
|
|
73
92
|
throw new Error("This session already has a goal; clear it before creating another");
|
|
74
93
|
}
|
|
@@ -76,6 +95,7 @@ export class GoalRuntime {
|
|
|
76
95
|
const goal = {
|
|
77
96
|
id: randomUUID(),
|
|
78
97
|
scopeId,
|
|
98
|
+
name: name?.trim() || trimmedObjective.slice(0, 72),
|
|
79
99
|
objective: trimmedObjective,
|
|
80
100
|
status: "active",
|
|
81
101
|
budget: { ...budget },
|
|
@@ -92,18 +112,27 @@ export class GoalRuntime {
|
|
|
92
112
|
const current = await this.requireGoal(scopeId);
|
|
93
113
|
if (current.status === "complete")
|
|
94
114
|
throw new Error("Cannot change a complete goal budget");
|
|
95
|
-
if (update.maxIterations === undefined &&
|
|
96
|
-
|
|
115
|
+
if (update.maxIterations === undefined &&
|
|
116
|
+
update.maxRuntimeMs === undefined &&
|
|
117
|
+
update.maxTokens === undefined &&
|
|
118
|
+
!update.disabled) {
|
|
119
|
+
throw new Error("A goal limit update requires turns, runtime, or off");
|
|
97
120
|
}
|
|
98
121
|
if (update.maxIterations !== undefined &&
|
|
99
122
|
(!Number.isInteger(update.maxIterations) || update.maxIterations <= 0)) {
|
|
100
123
|
throw new Error("Goal maxIterations must be a positive integer");
|
|
101
124
|
}
|
|
125
|
+
if (update.maxRuntimeMs !== undefined &&
|
|
126
|
+
(!Number.isFinite(update.maxRuntimeMs) || update.maxRuntimeMs <= 0)) {
|
|
127
|
+
throw new Error("Goal maxRuntimeMs must be a positive finite number");
|
|
128
|
+
}
|
|
102
129
|
if (update.maxTokens !== undefined &&
|
|
103
130
|
(!Number.isFinite(update.maxTokens) || update.maxTokens <= 0)) {
|
|
104
131
|
throw new Error("Goal maxTokens must be a positive finite number");
|
|
105
132
|
}
|
|
106
|
-
if (update.maxIterations !== undefined &&
|
|
133
|
+
if (update.maxIterations !== undefined &&
|
|
134
|
+
this.budget.maxIterations !== undefined &&
|
|
135
|
+
update.maxIterations > this.budget.maxIterations) {
|
|
107
136
|
throw new Error(`Goal maxIterations cannot exceed the configured limit of ${this.budget.maxIterations}`);
|
|
108
137
|
}
|
|
109
138
|
if (update.maxTokens !== undefined &&
|
|
@@ -111,6 +140,11 @@ export class GoalRuntime {
|
|
|
111
140
|
update.maxTokens > this.budget.maxTokens) {
|
|
112
141
|
throw new Error(`Goal maxTokens cannot exceed the configured limit of ${this.budget.maxTokens}`);
|
|
113
142
|
}
|
|
143
|
+
if (update.maxRuntimeMs !== undefined &&
|
|
144
|
+
this.budget.maxRuntimeMs !== undefined &&
|
|
145
|
+
update.maxRuntimeMs > this.budget.maxRuntimeMs) {
|
|
146
|
+
throw new Error(`Goal maxRuntimeMs cannot exceed the configured limit of ${this.budget.maxRuntimeMs}`);
|
|
147
|
+
}
|
|
114
148
|
const minimumIterations = current.status === "active" ? current.usage.iterations + 1 : current.usage.iterations;
|
|
115
149
|
if (update.maxIterations !== undefined && update.maxIterations < minimumIterations) {
|
|
116
150
|
throw new Error(current.status === "active"
|
|
@@ -123,11 +157,14 @@ export class GoalRuntime {
|
|
|
123
157
|
? `Goal maxTokens must leave capacity beyond ${current.usage.tokens} accounted tokens`
|
|
124
158
|
: `Goal maxTokens cannot be lower than ${current.usage.tokens} accounted tokens`);
|
|
125
159
|
}
|
|
126
|
-
const nextBudget =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
160
|
+
const nextBudget = update.disabled
|
|
161
|
+
? {}
|
|
162
|
+
: {
|
|
163
|
+
...current.budget,
|
|
164
|
+
...(update.maxIterations === undefined ? {} : { maxIterations: update.maxIterations }),
|
|
165
|
+
...(update.maxRuntimeMs === undefined ? {} : { maxRuntimeMs: update.maxRuntimeMs }),
|
|
166
|
+
...(update.maxTokens === undefined ? {} : { maxTokens: update.maxTokens }),
|
|
167
|
+
};
|
|
131
168
|
const candidate = {
|
|
132
169
|
...current,
|
|
133
170
|
budget: nextBudget,
|
|
@@ -147,6 +184,88 @@ export class GoalRuntime {
|
|
|
147
184
|
}
|
|
148
185
|
return next;
|
|
149
186
|
}
|
|
187
|
+
async updateDetails(scopeId, update) {
|
|
188
|
+
const current = await this.requireGoal(scopeId);
|
|
189
|
+
if (current.status === "complete")
|
|
190
|
+
throw new Error("Cannot edit a closed goal");
|
|
191
|
+
const name = update.name?.trim();
|
|
192
|
+
const objective = update.objective?.trim();
|
|
193
|
+
if (update.name !== undefined && !name)
|
|
194
|
+
throw new Error("Goal name cannot be empty");
|
|
195
|
+
if (update.objective !== undefined && !objective)
|
|
196
|
+
throw new Error("Goal objective cannot be empty");
|
|
197
|
+
if (name === undefined && objective === undefined)
|
|
198
|
+
throw new Error("A goal update requires name or objective");
|
|
199
|
+
const next = {
|
|
200
|
+
...current,
|
|
201
|
+
...(name === undefined ? {} : { name }),
|
|
202
|
+
...(objective === undefined ? {} : { objective }),
|
|
203
|
+
version: current.version + 1,
|
|
204
|
+
updatedAt: this.now().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
if (!(await this.storage.updateBudget(next, current.version))) {
|
|
207
|
+
throw new Error("Goal changed while it was being edited; retry the command");
|
|
208
|
+
}
|
|
209
|
+
await this.record({ type: "goal.updated", goal: next });
|
|
210
|
+
if (await this.storage.getPendingEvaluation(scopeId)) {
|
|
211
|
+
await this.processPendingEvaluation(scopeId);
|
|
212
|
+
}
|
|
213
|
+
return (await this.storage.get(scopeId)) ?? next;
|
|
214
|
+
}
|
|
215
|
+
async addCheckpoint(scopeId, input) {
|
|
216
|
+
const goal = await this.requireGoal(scopeId);
|
|
217
|
+
if (goal.status === "complete")
|
|
218
|
+
throw new Error("Cannot checkpoint a closed goal");
|
|
219
|
+
const summary = input.summary.trim();
|
|
220
|
+
if (!summary)
|
|
221
|
+
throw new Error("A checkpoint summary is required");
|
|
222
|
+
const checkpoint = {
|
|
223
|
+
id: randomUUID(),
|
|
224
|
+
scopeId,
|
|
225
|
+
goalId: goal.id,
|
|
226
|
+
summary,
|
|
227
|
+
evidence: input.evidence?.trim() || undefined,
|
|
228
|
+
nextStep: input.nextStep?.trim() || undefined,
|
|
229
|
+
blocker: input.blocker?.trim() || undefined,
|
|
230
|
+
createdAt: this.now().toISOString(),
|
|
231
|
+
};
|
|
232
|
+
if (!(await this.storage.addCheckpoint(checkpoint))) {
|
|
233
|
+
throw new Error("Goal changed while its checkpoint was being recorded; retry");
|
|
234
|
+
}
|
|
235
|
+
await this.record({ type: "goal.checkpoint_added", goal, checkpoint });
|
|
236
|
+
return checkpoint;
|
|
237
|
+
}
|
|
238
|
+
async snooze(scopeId, durationMs) {
|
|
239
|
+
if (!Number.isFinite(durationMs) || durationMs <= 0)
|
|
240
|
+
throw new Error("Goal snooze duration must be positive");
|
|
241
|
+
const current = await this.requireGoal(scopeId);
|
|
242
|
+
if (current.status === "complete")
|
|
243
|
+
throw new Error("Cannot snooze a closed goal");
|
|
244
|
+
const snoozedUntil = new Date(this.now().getTime() + durationMs).toISOString();
|
|
245
|
+
const next = {
|
|
246
|
+
...current,
|
|
247
|
+
status: "active",
|
|
248
|
+
blockedReason: undefined,
|
|
249
|
+
snoozedUntil,
|
|
250
|
+
version: current.version + 1,
|
|
251
|
+
updatedAt: this.now().toISOString(),
|
|
252
|
+
};
|
|
253
|
+
if (!(await this.storage.updateBudget(next, current.version))) {
|
|
254
|
+
throw new Error("Goal changed while it was being snoozed; retry the command");
|
|
255
|
+
}
|
|
256
|
+
const claim = await this.storage.getContinuationClaim(scopeId);
|
|
257
|
+
if (claim)
|
|
258
|
+
await this.storage.deleteContinuationClaim(scopeId, claim.claimId);
|
|
259
|
+
this.scheduleRecovery(scopeId, snoozedUntil);
|
|
260
|
+
await this.record({ type: "goal.snoozed", goal: next, snoozedUntil });
|
|
261
|
+
return next;
|
|
262
|
+
}
|
|
263
|
+
async closeGoal(scopeId) {
|
|
264
|
+
const current = await this.requireGoal(scopeId);
|
|
265
|
+
if (current.status === "complete")
|
|
266
|
+
return current;
|
|
267
|
+
return this.setStatus(scopeId, "complete");
|
|
268
|
+
}
|
|
150
269
|
async setStatus(scopeId, status) {
|
|
151
270
|
const current = await this.requireGoal(scopeId);
|
|
152
271
|
const transitionAllowed = (status === "active" && (current.status === "paused" || current.status === "blocked")) ||
|
|
@@ -158,6 +277,7 @@ export class GoalRuntime {
|
|
|
158
277
|
...current,
|
|
159
278
|
status,
|
|
160
279
|
blockedReason: undefined,
|
|
280
|
+
snoozedUntil: undefined,
|
|
161
281
|
version: current.version + 1,
|
|
162
282
|
updatedAt: this.now().toISOString(),
|
|
163
283
|
};
|
|
@@ -234,9 +354,25 @@ export class GoalRuntime {
|
|
|
234
354
|
try {
|
|
235
355
|
if (await this.storage.getPendingEvaluation(scopeId))
|
|
236
356
|
await this.processPendingEvaluation(scopeId);
|
|
237
|
-
|
|
357
|
+
let goal = await this.storage.get(scopeId);
|
|
238
358
|
if (!goal || goal.status !== "active")
|
|
239
359
|
return;
|
|
360
|
+
if (goal.snoozedUntil) {
|
|
361
|
+
if (Date.parse(goal.snoozedUntil) > this.now().getTime()) {
|
|
362
|
+
this.scheduleRecovery(scopeId, goal.snoozedUntil);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const awakened = {
|
|
366
|
+
...goal,
|
|
367
|
+
snoozedUntil: undefined,
|
|
368
|
+
version: goal.version + 1,
|
|
369
|
+
updatedAt: this.now().toISOString(),
|
|
370
|
+
};
|
|
371
|
+
if (!(await this.storage.updateBudget(awakened, goal.version)))
|
|
372
|
+
return;
|
|
373
|
+
goal = awakened;
|
|
374
|
+
await this.record({ type: "goal.snooze_expired", goal });
|
|
375
|
+
}
|
|
240
376
|
if (this.budgetExhausted(goal)) {
|
|
241
377
|
await this.markBudgetLimited(goal);
|
|
242
378
|
return;
|
|
@@ -489,6 +625,10 @@ export class GoalRuntime {
|
|
|
489
625
|
}
|
|
490
626
|
}
|
|
491
627
|
async continueWithClaim(goal, reason) {
|
|
628
|
+
if (goal.snoozedUntil && Date.parse(goal.snoozedUntil) > this.now().getTime()) {
|
|
629
|
+
this.scheduleRecovery(goal.scopeId, goal.snoozedUntil);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
492
632
|
const existingClaim = await this.storage.getContinuationClaim(goal.scopeId);
|
|
493
633
|
if (existingClaim) {
|
|
494
634
|
if (existingClaim.goalId === goal.id && existingClaim.goalVersion === goal.version)
|
|
@@ -647,7 +787,8 @@ export class GoalRuntime {
|
|
|
647
787
|
await this.record({ type: "goal.status_changed", goal: next, previousStatus: current.status });
|
|
648
788
|
}
|
|
649
789
|
budgetExhausted(goal) {
|
|
650
|
-
return (goal.
|
|
790
|
+
return ((goal.budget.maxIterations !== undefined &&
|
|
791
|
+
goal.usage.iterations >= goal.budget.maxIterations) ||
|
|
651
792
|
(goal.budget.maxTokens !== undefined && goal.usage.tokens >= goal.budget.maxTokens) ||
|
|
652
793
|
(goal.budget.maxRuntimeMs !== undefined &&
|
|
653
794
|
this.now().getTime() - Date.parse(goal.createdAt) >= goal.budget.maxRuntimeMs));
|
package/dist/sqlite-storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentGoal, GoalContinuationClaim, GoalPendingEvaluation, GoalStorage, GoalTerminalCandidateRecord } from "./domain.js";
|
|
1
|
+
import type { AgentGoal, GoalCheckpoint, GoalContinuationClaim, GoalPendingEvaluation, GoalStorage, GoalTerminalCandidateRecord } from "./domain.js";
|
|
2
2
|
export declare class SqliteGoalStorage implements GoalStorage {
|
|
3
3
|
private readonly db;
|
|
4
4
|
constructor(path: string);
|
|
@@ -6,6 +6,8 @@ export declare class SqliteGoalStorage implements GoalStorage {
|
|
|
6
6
|
create(goal: AgentGoal): Promise<void>;
|
|
7
7
|
replace(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
8
8
|
updateBudget(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
9
|
+
addCheckpoint(checkpoint: GoalCheckpoint): Promise<boolean>;
|
|
10
|
+
listCheckpoints(scopeId: string): Promise<GoalCheckpoint[]>;
|
|
9
11
|
delete(scopeId: string, expectedVersion: number): Promise<boolean>;
|
|
10
12
|
getPendingEvaluation(scopeId: string): Promise<GoalPendingEvaluation | undefined>;
|
|
11
13
|
appendPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
package/dist/sqlite-storage.js
CHANGED
|
@@ -9,9 +9,11 @@ export class SqliteGoalStorage {
|
|
|
9
9
|
const goalTableSql = `CREATE TABLE IF NOT EXISTS agent_goals (
|
|
10
10
|
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
11
11
|
id TEXT UNIQUE NOT NULL,
|
|
12
|
+
name TEXT,
|
|
12
13
|
objective TEXT NOT NULL,
|
|
13
14
|
status TEXT NOT NULL CHECK (status IN ('active', 'paused', 'blocked', 'budget_limited', 'complete')),
|
|
14
15
|
blocked_reason TEXT,
|
|
16
|
+
snoozed_until TEXT,
|
|
15
17
|
max_iterations INTEGER NOT NULL DEFAULT 25,
|
|
16
18
|
max_tokens INTEGER,
|
|
17
19
|
max_runtime_ms INTEGER,
|
|
@@ -29,6 +31,8 @@ export class SqliteGoalStorage {
|
|
|
29
31
|
this.db.exec(`PRAGMA journal_mode = WAL; ${goalTableSql};`);
|
|
30
32
|
const columns = new Set(this.db.prepare("PRAGMA table_info(agent_goals)").all().map(({ name }) => name));
|
|
31
33
|
for (const [name, definition] of [
|
|
34
|
+
["name", "TEXT"],
|
|
35
|
+
["snoozed_until", "TEXT"],
|
|
32
36
|
["max_iterations", "INTEGER NOT NULL DEFAULT 25"],
|
|
33
37
|
["max_tokens", "INTEGER"],
|
|
34
38
|
["max_runtime_ms", "INTEGER"],
|
|
@@ -71,6 +75,19 @@ export class SqliteGoalStorage {
|
|
|
71
75
|
}
|
|
72
76
|
this.db.exec(`
|
|
73
77
|
PRAGMA foreign_keys = ON;
|
|
78
|
+
CREATE TABLE IF NOT EXISTS agent_goal_checkpoints (
|
|
79
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
80
|
+
scope_id TEXT NOT NULL,
|
|
81
|
+
goal_id TEXT NOT NULL,
|
|
82
|
+
summary TEXT NOT NULL,
|
|
83
|
+
evidence TEXT,
|
|
84
|
+
next_step TEXT,
|
|
85
|
+
blocker TEXT,
|
|
86
|
+
created_at TEXT NOT NULL,
|
|
87
|
+
FOREIGN KEY (scope_id) REFERENCES agent_goals(scope_id) ON DELETE CASCADE
|
|
88
|
+
);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS agent_goal_checkpoints_scope_created
|
|
90
|
+
ON agent_goal_checkpoints(scope_id, created_at DESC);
|
|
74
91
|
CREATE TABLE IF NOT EXISTS agent_goal_terminal_candidates (
|
|
75
92
|
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
76
93
|
goal_id TEXT NOT NULL,
|
|
@@ -132,11 +149,13 @@ export class SqliteGoalStorage {
|
|
|
132
149
|
return {
|
|
133
150
|
id: row.id,
|
|
134
151
|
scopeId: row.scope_id,
|
|
152
|
+
name: row.name ?? undefined,
|
|
135
153
|
objective: row.objective,
|
|
136
154
|
status: row.status,
|
|
137
155
|
blockedReason: row.blocked_reason ?? undefined,
|
|
156
|
+
snoozedUntil: row.snoozed_until ?? undefined,
|
|
138
157
|
budget: {
|
|
139
|
-
maxIterations: row.max_iterations,
|
|
158
|
+
maxIterations: row.max_iterations > 0 ? row.max_iterations : undefined,
|
|
140
159
|
maxTokens: row.max_tokens ?? undefined,
|
|
141
160
|
maxRuntimeMs: row.max_runtime_ms ?? undefined,
|
|
142
161
|
},
|
|
@@ -161,35 +180,35 @@ export class SqliteGoalStorage {
|
|
|
161
180
|
async create(goal) {
|
|
162
181
|
this.db
|
|
163
182
|
.prepare(`INSERT INTO agent_goals
|
|
164
|
-
(scope_id, id, objective, status, blocked_reason,
|
|
165
|
-
max_runtime_ms, iterations_used, tokens_used,
|
|
166
|
-
last_evaluation_id, last_evaluation_outcome,
|
|
167
|
-
version, created_at, updated_at)
|
|
168
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
169
|
-
.run(goal.scopeId, goal.id, goal.objective, goal.status, goal.blockedReason ?? null, goal.budget.maxIterations, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.createdAt, goal.updatedAt);
|
|
183
|
+
(scope_id, id, name, objective, status, blocked_reason, snoozed_until,
|
|
184
|
+
max_iterations, max_tokens, max_runtime_ms, iterations_used, tokens_used,
|
|
185
|
+
last_settled_at, last_evaluation_id, last_evaluation_outcome,
|
|
186
|
+
last_evaluation_reason, last_evaluation_at, version, created_at, updated_at)
|
|
187
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
188
|
+
.run(goal.scopeId, goal.id, goal.name ?? null, goal.objective, goal.status, goal.blockedReason ?? null, goal.snoozedUntil ?? null, goal.budget.maxIterations ?? 0, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.createdAt, goal.updatedAt);
|
|
170
189
|
}
|
|
171
190
|
async replace(goal, expectedVersion) {
|
|
172
191
|
const result = this.db
|
|
173
|
-
.prepare(`UPDATE agent_goals SET objective = ?, status = ?, blocked_reason = ?,
|
|
174
|
-
|
|
175
|
-
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
192
|
+
.prepare(`UPDATE agent_goals SET name = ?, objective = ?, status = ?, blocked_reason = ?,
|
|
193
|
+
snoozed_until = ?, max_iterations = ?, max_tokens = ?, max_runtime_ms = ?,
|
|
194
|
+
iterations_used = ?, tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
176
195
|
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
177
196
|
version = ?, updated_at = ?
|
|
178
197
|
WHERE scope_id = ? AND id = ? AND version = ?`)
|
|
179
|
-
.run(goal.objective, goal.status, goal.blockedReason ?? null, goal.budget.maxIterations, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.updatedAt, goal.scopeId, goal.id, expectedVersion);
|
|
198
|
+
.run(goal.name ?? null, goal.objective, goal.status, goal.blockedReason ?? null, goal.snoozedUntil ?? null, goal.budget.maxIterations ?? 0, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.updatedAt, goal.scopeId, goal.id, expectedVersion);
|
|
180
199
|
return result.changes === 1;
|
|
181
200
|
}
|
|
182
201
|
async updateBudget(goal, expectedVersion) {
|
|
183
202
|
this.db.exec("BEGIN IMMEDIATE");
|
|
184
203
|
try {
|
|
185
204
|
const result = this.db
|
|
186
|
-
.prepare(`UPDATE agent_goals SET objective = ?, status = ?, blocked_reason = ?,
|
|
187
|
-
|
|
188
|
-
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
205
|
+
.prepare(`UPDATE agent_goals SET name = ?, objective = ?, status = ?, blocked_reason = ?,
|
|
206
|
+
snoozed_until = ?, max_iterations = ?, max_tokens = ?, max_runtime_ms = ?,
|
|
207
|
+
iterations_used = ?, tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
189
208
|
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
190
209
|
version = ?, updated_at = ?
|
|
191
210
|
WHERE scope_id = ? AND id = ? AND version = ?`)
|
|
192
|
-
.run(goal.objective, goal.status, goal.blockedReason ?? null, goal.budget.maxIterations, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.updatedAt, goal.scopeId, goal.id, expectedVersion);
|
|
211
|
+
.run(goal.name ?? null, goal.objective, goal.status, goal.blockedReason ?? null, goal.snoozedUntil ?? null, goal.budget.maxIterations ?? 0, goal.budget.maxTokens ?? null, goal.budget.maxRuntimeMs ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.updatedAt, goal.scopeId, goal.id, expectedVersion);
|
|
193
212
|
if (result.changes !== 1) {
|
|
194
213
|
this.db.exec("ROLLBACK");
|
|
195
214
|
return false;
|
|
@@ -214,6 +233,40 @@ export class SqliteGoalStorage {
|
|
|
214
233
|
throw error;
|
|
215
234
|
}
|
|
216
235
|
}
|
|
236
|
+
async addCheckpoint(checkpoint) {
|
|
237
|
+
const result = this.db
|
|
238
|
+
.prepare(`INSERT INTO agent_goal_checkpoints
|
|
239
|
+
(id, scope_id, goal_id, summary, evidence, next_step, blocker, created_at)
|
|
240
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (
|
|
241
|
+
SELECT 1 FROM agent_goals WHERE scope_id = ? AND id = ? AND status != 'complete'
|
|
242
|
+
)`)
|
|
243
|
+
.run(checkpoint.id, checkpoint.scopeId, checkpoint.goalId, checkpoint.summary, checkpoint.evidence ?? null, checkpoint.nextStep ?? null, checkpoint.blocker ?? null, checkpoint.createdAt, checkpoint.scopeId, checkpoint.goalId);
|
|
244
|
+
return result.changes === 1;
|
|
245
|
+
}
|
|
246
|
+
async listCheckpoints(scopeId) {
|
|
247
|
+
const rows = this.db
|
|
248
|
+
.prepare("SELECT * FROM agent_goal_checkpoints WHERE scope_id = ? ORDER BY created_at DESC, id DESC")
|
|
249
|
+
.all(scopeId);
|
|
250
|
+
return rows.map((row) => {
|
|
251
|
+
if (typeof row.id !== "string" ||
|
|
252
|
+
typeof row.scope_id !== "string" ||
|
|
253
|
+
typeof row.goal_id !== "string" ||
|
|
254
|
+
typeof row.summary !== "string" ||
|
|
255
|
+
typeof row.created_at !== "string") {
|
|
256
|
+
throw new Error("Stored goal checkpoint is malformed");
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
id: row.id,
|
|
260
|
+
scopeId: row.scope_id,
|
|
261
|
+
goalId: row.goal_id,
|
|
262
|
+
summary: row.summary,
|
|
263
|
+
evidence: typeof row.evidence === "string" ? row.evidence : undefined,
|
|
264
|
+
nextStep: typeof row.next_step === "string" ? row.next_step : undefined,
|
|
265
|
+
blocker: typeof row.blocker === "string" ? row.blocker : undefined,
|
|
266
|
+
createdAt: row.created_at,
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
}
|
|
217
270
|
async delete(scopeId, expectedVersion) {
|
|
218
271
|
const result = this.db
|
|
219
272
|
.prepare("DELETE FROM agent_goals WHERE scope_id = ? AND version = ?")
|
|
@@ -334,8 +387,7 @@ export class SqliteGoalStorage {
|
|
|
334
387
|
this.db.exec("BEGIN IMMEDIATE");
|
|
335
388
|
try {
|
|
336
389
|
const updated = this.db
|
|
337
|
-
.prepare(`UPDATE agent_goals SET
|
|
338
|
-
max_iterations = ?, max_tokens = ?, max_runtime_ms = ?, iterations_used = ?,
|
|
390
|
+
.prepare(`UPDATE agent_goals SET status = ?, blocked_reason = ?, iterations_used = ?,
|
|
339
391
|
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
340
392
|
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
341
393
|
version = ?, updated_at = ?
|
|
@@ -343,7 +395,7 @@ export class SqliteGoalStorage {
|
|
|
343
395
|
SELECT 1 FROM agent_goal_pending_evaluations
|
|
344
396
|
WHERE scope_id = ? AND evaluation_id = ?
|
|
345
397
|
)`)
|
|
346
|
-
.run(goal.
|
|
398
|
+
.run(goal.status, goal.blockedReason ?? null, goal.usage.iterations, goal.usage.tokens, goal.lastSettledAt ?? null, goal.lastEvaluation?.id ?? null, goal.lastEvaluation?.outcome ?? null, goal.lastEvaluation?.reason ?? null, goal.lastEvaluation?.at ?? null, goal.version, goal.updatedAt, goal.scopeId, goal.id, expectedGoalVersion, goal.scopeId, expectedEvaluationId);
|
|
347
399
|
if (updated.changes !== 1) {
|
|
348
400
|
this.db.exec("ROLLBACK");
|
|
349
401
|
return false;
|
package/package.json
CHANGED