@pinet/agent-goal 0.2.6
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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/dashboard.d.ts +3 -0
- package/dist/dashboard.js +42 -0
- package/dist/domain.d.ts +199 -0
- package/dist/domain.js +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +337 -0
- package/dist/memory-storage.d.ts +25 -0
- package/dist/memory-storage.js +164 -0
- package/dist/pi-evaluator.d.ts +8 -0
- package/dist/pi-evaluator.js +74 -0
- package/dist/progress.d.ts +18 -0
- package/dist/progress.js +34 -0
- package/dist/runtime.d.ts +49 -0
- package/dist/runtime.js +629 -0
- package/dist/sqlite-storage.d.ts +23 -0
- package/dist/sqlite-storage.js +420 -0
- package/dist/wake-scheduler.d.ts +10 -0
- package/dist/wake-scheduler.js +34 -0
- package/package.json +53 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AgentGoal, GoalContinuationClaim, GoalPendingEvaluation, GoalStorage, GoalTerminalCandidateRecord } from "./domain.js";
|
|
2
|
+
export declare class SqliteGoalStorage implements GoalStorage {
|
|
3
|
+
private readonly db;
|
|
4
|
+
constructor(path: string);
|
|
5
|
+
get(scopeId: string): Promise<AgentGoal | undefined>;
|
|
6
|
+
create(goal: AgentGoal): Promise<void>;
|
|
7
|
+
replace(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
8
|
+
delete(scopeId: string, expectedVersion: number): Promise<boolean>;
|
|
9
|
+
getPendingEvaluation(scopeId: string): Promise<GoalPendingEvaluation | undefined>;
|
|
10
|
+
appendPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
|
11
|
+
putPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
|
12
|
+
replacePendingEvaluation(pending: GoalPendingEvaluation, expectedEvaluationId: string): Promise<boolean>;
|
|
13
|
+
deletePendingEvaluation(scopeId: string, expectedEvaluationId: string): Promise<boolean>;
|
|
14
|
+
commitEvaluation(goal: AgentGoal, expectedGoalVersion: number, expectedEvaluationId: string): Promise<boolean>;
|
|
15
|
+
getTerminalCandidate(scopeId: string): Promise<GoalTerminalCandidateRecord | undefined>;
|
|
16
|
+
putTerminalCandidate(candidate: GoalTerminalCandidateRecord): Promise<boolean>;
|
|
17
|
+
deleteTerminalCandidate(scopeId: string, expectedCandidateId: string): Promise<boolean>;
|
|
18
|
+
getContinuationClaim(scopeId: string): Promise<GoalContinuationClaim | undefined>;
|
|
19
|
+
createContinuationClaim(claim: GoalContinuationClaim): Promise<boolean>;
|
|
20
|
+
replaceContinuationClaim(claim: GoalContinuationClaim, expectedClaimId: string): Promise<boolean>;
|
|
21
|
+
deleteContinuationClaim(scopeId: string, expectedClaimId: string): Promise<boolean>;
|
|
22
|
+
close(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
export class SqliteGoalStorage {
|
|
5
|
+
db;
|
|
6
|
+
constructor(path) {
|
|
7
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
8
|
+
this.db = new DatabaseSync(path, { timeout: 5000 });
|
|
9
|
+
const goalTableSql = `CREATE TABLE IF NOT EXISTS agent_goals (
|
|
10
|
+
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
11
|
+
id TEXT UNIQUE NOT NULL,
|
|
12
|
+
objective TEXT NOT NULL,
|
|
13
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'paused', 'blocked', 'budget_limited', 'complete')),
|
|
14
|
+
blocked_reason TEXT,
|
|
15
|
+
max_iterations INTEGER NOT NULL DEFAULT 25,
|
|
16
|
+
max_tokens INTEGER,
|
|
17
|
+
max_runtime_ms INTEGER,
|
|
18
|
+
iterations_used INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
tokens_used INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
last_settled_at TEXT,
|
|
21
|
+
last_evaluation_id TEXT,
|
|
22
|
+
last_evaluation_outcome TEXT,
|
|
23
|
+
last_evaluation_reason TEXT,
|
|
24
|
+
last_evaluation_at TEXT,
|
|
25
|
+
version INTEGER NOT NULL,
|
|
26
|
+
created_at TEXT NOT NULL,
|
|
27
|
+
updated_at TEXT NOT NULL
|
|
28
|
+
)`;
|
|
29
|
+
this.db.exec(`PRAGMA journal_mode = WAL; ${goalTableSql};`);
|
|
30
|
+
const columns = new Set(this.db.prepare("PRAGMA table_info(agent_goals)").all().map(({ name }) => name));
|
|
31
|
+
for (const [name, definition] of [
|
|
32
|
+
["max_iterations", "INTEGER NOT NULL DEFAULT 25"],
|
|
33
|
+
["max_tokens", "INTEGER"],
|
|
34
|
+
["max_runtime_ms", "INTEGER"],
|
|
35
|
+
["iterations_used", "INTEGER NOT NULL DEFAULT 0"],
|
|
36
|
+
["tokens_used", "INTEGER NOT NULL DEFAULT 0"],
|
|
37
|
+
["last_settled_at", "TEXT"],
|
|
38
|
+
["last_evaluation_id", "TEXT"],
|
|
39
|
+
["last_evaluation_outcome", "TEXT"],
|
|
40
|
+
["last_evaluation_reason", "TEXT"],
|
|
41
|
+
["last_evaluation_at", "TEXT"],
|
|
42
|
+
]) {
|
|
43
|
+
if (!columns.has(name))
|
|
44
|
+
this.db.exec(`ALTER TABLE agent_goals ADD COLUMN ${name} ${definition}`);
|
|
45
|
+
}
|
|
46
|
+
const tableDefinition = this.db
|
|
47
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'agent_goals'")
|
|
48
|
+
.get();
|
|
49
|
+
if (!tableDefinition.sql.includes("budget_limited")) {
|
|
50
|
+
this.db.exec(`
|
|
51
|
+
PRAGMA foreign_keys = OFF;
|
|
52
|
+
BEGIN IMMEDIATE;
|
|
53
|
+
DROP TABLE IF EXISTS agent_goal_pending_evaluations;
|
|
54
|
+
DROP TABLE IF EXISTS agent_goal_continuations;
|
|
55
|
+
ALTER TABLE agent_goals RENAME TO agent_goals_legacy;
|
|
56
|
+
${goalTableSql};
|
|
57
|
+
INSERT INTO agent_goals
|
|
58
|
+
(scope_id, id, objective, status, blocked_reason, max_iterations, max_tokens,
|
|
59
|
+
max_runtime_ms, iterations_used, tokens_used, last_settled_at,
|
|
60
|
+
last_evaluation_id, last_evaluation_outcome, last_evaluation_reason, last_evaluation_at,
|
|
61
|
+
version, created_at, updated_at)
|
|
62
|
+
SELECT scope_id, id, objective, status, blocked_reason, max_iterations, max_tokens,
|
|
63
|
+
max_runtime_ms, iterations_used, tokens_used, last_settled_at,
|
|
64
|
+
last_evaluation_id, last_evaluation_outcome, last_evaluation_reason, last_evaluation_at,
|
|
65
|
+
version, created_at, updated_at
|
|
66
|
+
FROM agent_goals_legacy;
|
|
67
|
+
DROP TABLE agent_goals_legacy;
|
|
68
|
+
COMMIT;
|
|
69
|
+
PRAGMA foreign_keys = ON;
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
this.db.exec(`
|
|
73
|
+
PRAGMA foreign_keys = ON;
|
|
74
|
+
CREATE TABLE IF NOT EXISTS agent_goal_terminal_candidates (
|
|
75
|
+
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
76
|
+
goal_id TEXT NOT NULL,
|
|
77
|
+
goal_version INTEGER NOT NULL,
|
|
78
|
+
candidate_id TEXT UNIQUE NOT NULL,
|
|
79
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('complete', 'blocked')),
|
|
80
|
+
reason TEXT NOT NULL,
|
|
81
|
+
created_at TEXT NOT NULL,
|
|
82
|
+
FOREIGN KEY (scope_id) REFERENCES agent_goals(scope_id) ON DELETE CASCADE
|
|
83
|
+
);
|
|
84
|
+
CREATE TABLE IF NOT EXISTS agent_goal_pending_evaluations (
|
|
85
|
+
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
86
|
+
goal_id TEXT NOT NULL,
|
|
87
|
+
goal_version INTEGER NOT NULL,
|
|
88
|
+
evaluation_id TEXT UNIQUE NOT NULL,
|
|
89
|
+
iterations_delta INTEGER NOT NULL DEFAULT 1,
|
|
90
|
+
latest_output TEXT NOT NULL,
|
|
91
|
+
token_delta INTEGER NOT NULL,
|
|
92
|
+
candidate_outcome TEXT CHECK (candidate_outcome IN ('complete', 'blocked')),
|
|
93
|
+
candidate_reason TEXT,
|
|
94
|
+
attempt INTEGER NOT NULL,
|
|
95
|
+
available_at TEXT NOT NULL,
|
|
96
|
+
last_error TEXT,
|
|
97
|
+
created_at TEXT NOT NULL,
|
|
98
|
+
updated_at TEXT NOT NULL,
|
|
99
|
+
FOREIGN KEY (scope_id) REFERENCES agent_goals(scope_id) ON DELETE CASCADE
|
|
100
|
+
);
|
|
101
|
+
CREATE TABLE IF NOT EXISTS agent_goal_continuations (
|
|
102
|
+
scope_id TEXT PRIMARY KEY NOT NULL,
|
|
103
|
+
goal_id TEXT NOT NULL,
|
|
104
|
+
goal_version INTEGER NOT NULL,
|
|
105
|
+
claim_id TEXT UNIQUE NOT NULL,
|
|
106
|
+
state TEXT NOT NULL CHECK (state IN ('claimed', 'deferred', 'started')),
|
|
107
|
+
reason TEXT NOT NULL,
|
|
108
|
+
attempt INTEGER NOT NULL,
|
|
109
|
+
available_at TEXT NOT NULL,
|
|
110
|
+
expires_at TEXT NOT NULL,
|
|
111
|
+
last_error TEXT,
|
|
112
|
+
created_at TEXT NOT NULL,
|
|
113
|
+
updated_at TEXT NOT NULL,
|
|
114
|
+
FOREIGN KEY (scope_id) REFERENCES agent_goals(scope_id) ON DELETE CASCADE
|
|
115
|
+
);
|
|
116
|
+
`);
|
|
117
|
+
const pendingColumns = new Set(this.db.prepare("PRAGMA table_info(agent_goal_pending_evaluations)").all().map(({ name }) => name));
|
|
118
|
+
if (!pendingColumns.has("iterations_delta")) {
|
|
119
|
+
this.db.exec("ALTER TABLE agent_goal_pending_evaluations ADD COLUMN iterations_delta INTEGER NOT NULL DEFAULT 1");
|
|
120
|
+
}
|
|
121
|
+
if (!pendingColumns.has("candidate_outcome")) {
|
|
122
|
+
this.db.exec("ALTER TABLE agent_goal_pending_evaluations ADD COLUMN candidate_outcome TEXT CHECK (candidate_outcome IN ('complete', 'blocked'))");
|
|
123
|
+
}
|
|
124
|
+
if (!pendingColumns.has("candidate_reason")) {
|
|
125
|
+
this.db.exec("ALTER TABLE agent_goal_pending_evaluations ADD COLUMN candidate_reason TEXT");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async get(scopeId) {
|
|
129
|
+
const row = this.db.prepare("SELECT * FROM agent_goals WHERE scope_id = ?").get(scopeId);
|
|
130
|
+
if (!row)
|
|
131
|
+
return undefined;
|
|
132
|
+
return {
|
|
133
|
+
id: row.id,
|
|
134
|
+
scopeId: row.scope_id,
|
|
135
|
+
objective: row.objective,
|
|
136
|
+
status: row.status,
|
|
137
|
+
blockedReason: row.blocked_reason ?? undefined,
|
|
138
|
+
budget: {
|
|
139
|
+
maxIterations: row.max_iterations,
|
|
140
|
+
maxTokens: row.max_tokens ?? undefined,
|
|
141
|
+
maxRuntimeMs: row.max_runtime_ms ?? undefined,
|
|
142
|
+
},
|
|
143
|
+
usage: { iterations: row.iterations_used, tokens: row.tokens_used },
|
|
144
|
+
lastSettledAt: row.last_settled_at ?? undefined,
|
|
145
|
+
lastEvaluation: row.last_evaluation_id &&
|
|
146
|
+
row.last_evaluation_outcome &&
|
|
147
|
+
row.last_evaluation_reason &&
|
|
148
|
+
row.last_evaluation_at
|
|
149
|
+
? {
|
|
150
|
+
id: row.last_evaluation_id,
|
|
151
|
+
outcome: row.last_evaluation_outcome,
|
|
152
|
+
reason: row.last_evaluation_reason,
|
|
153
|
+
at: row.last_evaluation_at,
|
|
154
|
+
}
|
|
155
|
+
: undefined,
|
|
156
|
+
version: row.version,
|
|
157
|
+
createdAt: row.created_at,
|
|
158
|
+
updatedAt: row.updated_at,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async create(goal) {
|
|
162
|
+
this.db
|
|
163
|
+
.prepare(`INSERT INTO agent_goals
|
|
164
|
+
(scope_id, id, objective, status, blocked_reason, max_iterations, max_tokens,
|
|
165
|
+
max_runtime_ms, iterations_used, tokens_used, last_settled_at,
|
|
166
|
+
last_evaluation_id, last_evaluation_outcome, last_evaluation_reason, last_evaluation_at,
|
|
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);
|
|
170
|
+
}
|
|
171
|
+
async replace(goal, expectedVersion) {
|
|
172
|
+
const result = this.db
|
|
173
|
+
.prepare(`UPDATE agent_goals SET objective = ?, status = ?, blocked_reason = ?,
|
|
174
|
+
max_iterations = ?, max_tokens = ?, max_runtime_ms = ?, iterations_used = ?,
|
|
175
|
+
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
176
|
+
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
177
|
+
version = ?, updated_at = ?
|
|
178
|
+
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);
|
|
180
|
+
return result.changes === 1;
|
|
181
|
+
}
|
|
182
|
+
async delete(scopeId, expectedVersion) {
|
|
183
|
+
const result = this.db
|
|
184
|
+
.prepare("DELETE FROM agent_goals WHERE scope_id = ? AND version = ?")
|
|
185
|
+
.run(scopeId, expectedVersion);
|
|
186
|
+
return result.changes === 1;
|
|
187
|
+
}
|
|
188
|
+
async getPendingEvaluation(scopeId) {
|
|
189
|
+
const row = this.db
|
|
190
|
+
.prepare("SELECT * FROM agent_goal_pending_evaluations WHERE scope_id = ?")
|
|
191
|
+
.get(scopeId);
|
|
192
|
+
return row
|
|
193
|
+
? {
|
|
194
|
+
scopeId: row.scope_id,
|
|
195
|
+
goalId: row.goal_id,
|
|
196
|
+
goalVersion: row.goal_version,
|
|
197
|
+
evaluationId: row.evaluation_id,
|
|
198
|
+
iterationsDelta: row.iterations_delta,
|
|
199
|
+
progress: {
|
|
200
|
+
latestOutput: row.latest_output,
|
|
201
|
+
tokenDelta: row.token_delta,
|
|
202
|
+
terminalCandidate: row.candidate_outcome && row.candidate_reason
|
|
203
|
+
? { outcome: row.candidate_outcome, reason: row.candidate_reason }
|
|
204
|
+
: undefined,
|
|
205
|
+
},
|
|
206
|
+
attempt: row.attempt,
|
|
207
|
+
availableAt: row.available_at,
|
|
208
|
+
lastError: row.last_error ?? undefined,
|
|
209
|
+
createdAt: row.created_at,
|
|
210
|
+
updatedAt: row.updated_at,
|
|
211
|
+
}
|
|
212
|
+
: undefined;
|
|
213
|
+
}
|
|
214
|
+
async appendPendingEvaluation(pending) {
|
|
215
|
+
const result = this.db
|
|
216
|
+
.prepare(`INSERT INTO agent_goal_pending_evaluations
|
|
217
|
+
(scope_id, goal_id, goal_version, evaluation_id, iterations_delta, latest_output,
|
|
218
|
+
token_delta, candidate_outcome, candidate_reason, attempt, available_at, last_error,
|
|
219
|
+
created_at, updated_at)
|
|
220
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
221
|
+
WHERE EXISTS (
|
|
222
|
+
SELECT 1 FROM agent_goals WHERE scope_id = ? AND id = ? AND version = ?
|
|
223
|
+
)
|
|
224
|
+
ON CONFLICT(scope_id) DO UPDATE SET goal_id = excluded.goal_id,
|
|
225
|
+
goal_version = excluded.goal_version, evaluation_id = excluded.evaluation_id,
|
|
226
|
+
iterations_delta = CASE
|
|
227
|
+
WHEN agent_goal_pending_evaluations.goal_id = excluded.goal_id
|
|
228
|
+
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
229
|
+
THEN agent_goal_pending_evaluations.iterations_delta + excluded.iterations_delta
|
|
230
|
+
ELSE excluded.iterations_delta END,
|
|
231
|
+
latest_output = excluded.latest_output,
|
|
232
|
+
token_delta = CASE
|
|
233
|
+
WHEN agent_goal_pending_evaluations.goal_id = excluded.goal_id
|
|
234
|
+
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
235
|
+
THEN agent_goal_pending_evaluations.token_delta + excluded.token_delta
|
|
236
|
+
ELSE excluded.token_delta END,
|
|
237
|
+
candidate_outcome = CASE
|
|
238
|
+
WHEN agent_goal_pending_evaluations.goal_id = excluded.goal_id
|
|
239
|
+
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
240
|
+
THEN COALESCE(excluded.candidate_outcome,
|
|
241
|
+
agent_goal_pending_evaluations.candidate_outcome)
|
|
242
|
+
ELSE excluded.candidate_outcome END,
|
|
243
|
+
candidate_reason = CASE
|
|
244
|
+
WHEN agent_goal_pending_evaluations.goal_id = excluded.goal_id
|
|
245
|
+
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
246
|
+
THEN COALESCE(excluded.candidate_reason,
|
|
247
|
+
agent_goal_pending_evaluations.candidate_reason)
|
|
248
|
+
ELSE excluded.candidate_reason END,
|
|
249
|
+
attempt = excluded.attempt, available_at = excluded.available_at,
|
|
250
|
+
last_error = excluded.last_error,
|
|
251
|
+
created_at = CASE
|
|
252
|
+
WHEN agent_goal_pending_evaluations.goal_id = excluded.goal_id
|
|
253
|
+
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
254
|
+
THEN agent_goal_pending_evaluations.created_at ELSE excluded.created_at END,
|
|
255
|
+
updated_at = excluded.updated_at`)
|
|
256
|
+
.run(pending.scopeId, pending.goalId, pending.goalVersion, pending.evaluationId, pending.iterationsDelta, pending.progress.latestOutput, pending.progress.tokenDelta ?? 0, pending.progress.terminalCandidate?.outcome ?? null, pending.progress.terminalCandidate?.reason ?? null, pending.attempt, pending.availableAt, pending.lastError ?? null, pending.createdAt, pending.updatedAt, pending.scopeId, pending.goalId, pending.goalVersion);
|
|
257
|
+
return result.changes === 1;
|
|
258
|
+
}
|
|
259
|
+
async putPendingEvaluation(pending) {
|
|
260
|
+
const result = this.db
|
|
261
|
+
.prepare(`INSERT INTO agent_goal_pending_evaluations
|
|
262
|
+
(scope_id, goal_id, goal_version, evaluation_id, iterations_delta, latest_output,
|
|
263
|
+
token_delta, candidate_outcome, candidate_reason, attempt, available_at, last_error,
|
|
264
|
+
created_at, updated_at)
|
|
265
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
266
|
+
WHERE EXISTS (
|
|
267
|
+
SELECT 1 FROM agent_goals WHERE scope_id = ? AND id = ? AND version = ?
|
|
268
|
+
)
|
|
269
|
+
ON CONFLICT(scope_id) DO UPDATE SET goal_id = excluded.goal_id,
|
|
270
|
+
goal_version = excluded.goal_version, evaluation_id = excluded.evaluation_id,
|
|
271
|
+
iterations_delta = excluded.iterations_delta,
|
|
272
|
+
latest_output = excluded.latest_output, token_delta = excluded.token_delta,
|
|
273
|
+
candidate_outcome = excluded.candidate_outcome,
|
|
274
|
+
candidate_reason = excluded.candidate_reason, attempt = excluded.attempt,
|
|
275
|
+
available_at = excluded.available_at,
|
|
276
|
+
last_error = excluded.last_error, created_at = excluded.created_at,
|
|
277
|
+
updated_at = excluded.updated_at`)
|
|
278
|
+
.run(pending.scopeId, pending.goalId, pending.goalVersion, pending.evaluationId, pending.iterationsDelta, pending.progress.latestOutput, pending.progress.tokenDelta ?? 0, pending.progress.terminalCandidate?.outcome ?? null, pending.progress.terminalCandidate?.reason ?? null, pending.attempt, pending.availableAt, pending.lastError ?? null, pending.createdAt, pending.updatedAt, pending.scopeId, pending.goalId, pending.goalVersion);
|
|
279
|
+
return result.changes === 1;
|
|
280
|
+
}
|
|
281
|
+
async replacePendingEvaluation(pending, expectedEvaluationId) {
|
|
282
|
+
const result = this.db
|
|
283
|
+
.prepare(`UPDATE agent_goal_pending_evaluations SET goal_id = ?, goal_version = ?,
|
|
284
|
+
evaluation_id = ?, iterations_delta = ?, latest_output = ?, token_delta = ?,
|
|
285
|
+
candidate_outcome = ?, candidate_reason = ?, attempt = ?, available_at = ?,
|
|
286
|
+
last_error = ?, created_at = ?, updated_at = ?
|
|
287
|
+
WHERE scope_id = ? AND evaluation_id = ? AND EXISTS (
|
|
288
|
+
SELECT 1 FROM agent_goals WHERE scope_id = ? AND id = ? AND version = ?
|
|
289
|
+
)`)
|
|
290
|
+
.run(pending.goalId, pending.goalVersion, pending.evaluationId, pending.iterationsDelta, pending.progress.latestOutput, pending.progress.tokenDelta ?? 0, pending.progress.terminalCandidate?.outcome ?? null, pending.progress.terminalCandidate?.reason ?? null, pending.attempt, pending.availableAt, pending.lastError ?? null, pending.createdAt, pending.updatedAt, pending.scopeId, expectedEvaluationId, pending.scopeId, pending.goalId, pending.goalVersion);
|
|
291
|
+
return result.changes === 1;
|
|
292
|
+
}
|
|
293
|
+
async deletePendingEvaluation(scopeId, expectedEvaluationId) {
|
|
294
|
+
const result = this.db
|
|
295
|
+
.prepare("DELETE FROM agent_goal_pending_evaluations WHERE scope_id = ? AND evaluation_id = ?")
|
|
296
|
+
.run(scopeId, expectedEvaluationId);
|
|
297
|
+
return result.changes === 1;
|
|
298
|
+
}
|
|
299
|
+
async commitEvaluation(goal, expectedGoalVersion, expectedEvaluationId) {
|
|
300
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
301
|
+
try {
|
|
302
|
+
const updated = this.db
|
|
303
|
+
.prepare(`UPDATE agent_goals SET objective = ?, status = ?, blocked_reason = ?,
|
|
304
|
+
max_iterations = ?, max_tokens = ?, max_runtime_ms = ?, iterations_used = ?,
|
|
305
|
+
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
306
|
+
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
307
|
+
version = ?, updated_at = ?
|
|
308
|
+
WHERE scope_id = ? AND id = ? AND version = ? AND EXISTS (
|
|
309
|
+
SELECT 1 FROM agent_goal_pending_evaluations
|
|
310
|
+
WHERE scope_id = ? AND evaluation_id = ?
|
|
311
|
+
)`)
|
|
312
|
+
.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, expectedGoalVersion, goal.scopeId, expectedEvaluationId);
|
|
313
|
+
if (updated.changes !== 1) {
|
|
314
|
+
this.db.exec("ROLLBACK");
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
const deleted = this.db
|
|
318
|
+
.prepare("DELETE FROM agent_goal_pending_evaluations WHERE scope_id = ? AND evaluation_id = ?")
|
|
319
|
+
.run(goal.scopeId, expectedEvaluationId);
|
|
320
|
+
if (deleted.changes !== 1) {
|
|
321
|
+
this.db.exec("ROLLBACK");
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
this.db.exec("COMMIT");
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
this.db.exec("ROLLBACK");
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async getTerminalCandidate(scopeId) {
|
|
333
|
+
const row = this.db
|
|
334
|
+
.prepare("SELECT * FROM agent_goal_terminal_candidates WHERE scope_id = ?")
|
|
335
|
+
.get(scopeId);
|
|
336
|
+
return row
|
|
337
|
+
? {
|
|
338
|
+
scopeId: row.scope_id,
|
|
339
|
+
goalId: row.goal_id,
|
|
340
|
+
goalVersion: row.goal_version,
|
|
341
|
+
candidateId: row.candidate_id,
|
|
342
|
+
outcome: row.outcome,
|
|
343
|
+
reason: row.reason,
|
|
344
|
+
createdAt: row.created_at,
|
|
345
|
+
}
|
|
346
|
+
: undefined;
|
|
347
|
+
}
|
|
348
|
+
async putTerminalCandidate(candidate) {
|
|
349
|
+
const result = this.db
|
|
350
|
+
.prepare(`INSERT INTO agent_goal_terminal_candidates
|
|
351
|
+
(scope_id, goal_id, goal_version, candidate_id, outcome, reason, created_at)
|
|
352
|
+
SELECT ?, ?, ?, ?, ?, ?, ?
|
|
353
|
+
WHERE EXISTS (
|
|
354
|
+
SELECT 1 FROM agent_goals
|
|
355
|
+
WHERE scope_id = ? AND id = ? AND version = ? AND status = 'active'
|
|
356
|
+
)
|
|
357
|
+
ON CONFLICT(scope_id) DO UPDATE SET goal_id = excluded.goal_id,
|
|
358
|
+
goal_version = excluded.goal_version, candidate_id = excluded.candidate_id,
|
|
359
|
+
outcome = excluded.outcome, reason = excluded.reason, created_at = excluded.created_at`)
|
|
360
|
+
.run(candidate.scopeId, candidate.goalId, candidate.goalVersion, candidate.candidateId, candidate.outcome, candidate.reason, candidate.createdAt, candidate.scopeId, candidate.goalId, candidate.goalVersion);
|
|
361
|
+
return result.changes === 1;
|
|
362
|
+
}
|
|
363
|
+
async deleteTerminalCandidate(scopeId, expectedCandidateId) {
|
|
364
|
+
const result = this.db
|
|
365
|
+
.prepare("DELETE FROM agent_goal_terminal_candidates WHERE scope_id = ? AND candidate_id = ?")
|
|
366
|
+
.run(scopeId, expectedCandidateId);
|
|
367
|
+
return result.changes === 1;
|
|
368
|
+
}
|
|
369
|
+
async getContinuationClaim(scopeId) {
|
|
370
|
+
const row = this.db
|
|
371
|
+
.prepare("SELECT * FROM agent_goal_continuations WHERE scope_id = ?")
|
|
372
|
+
.get(scopeId);
|
|
373
|
+
return row
|
|
374
|
+
? {
|
|
375
|
+
scopeId: row.scope_id,
|
|
376
|
+
goalId: row.goal_id,
|
|
377
|
+
goalVersion: row.goal_version,
|
|
378
|
+
claimId: row.claim_id,
|
|
379
|
+
state: row.state,
|
|
380
|
+
reason: row.reason,
|
|
381
|
+
attempt: row.attempt,
|
|
382
|
+
availableAt: row.available_at,
|
|
383
|
+
expiresAt: row.expires_at,
|
|
384
|
+
lastError: row.last_error ?? undefined,
|
|
385
|
+
createdAt: row.created_at,
|
|
386
|
+
updatedAt: row.updated_at,
|
|
387
|
+
}
|
|
388
|
+
: undefined;
|
|
389
|
+
}
|
|
390
|
+
async createContinuationClaim(claim) {
|
|
391
|
+
const result = this.db
|
|
392
|
+
.prepare(`INSERT OR IGNORE INTO agent_goal_continuations
|
|
393
|
+
(scope_id, goal_id, goal_version, claim_id, state, reason, attempt,
|
|
394
|
+
available_at, expires_at, last_error, created_at, updated_at)
|
|
395
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
396
|
+
WHERE EXISTS (
|
|
397
|
+
SELECT 1 FROM agent_goals
|
|
398
|
+
WHERE scope_id = ? AND id = ? AND version = ? AND status = 'active'
|
|
399
|
+
)`)
|
|
400
|
+
.run(claim.scopeId, claim.goalId, claim.goalVersion, claim.claimId, claim.state, claim.reason, claim.attempt, claim.availableAt, claim.expiresAt, claim.lastError ?? null, claim.createdAt, claim.updatedAt, claim.scopeId, claim.goalId, claim.goalVersion);
|
|
401
|
+
return result.changes === 1;
|
|
402
|
+
}
|
|
403
|
+
async replaceContinuationClaim(claim, expectedClaimId) {
|
|
404
|
+
const result = this.db
|
|
405
|
+
.prepare(`UPDATE agent_goal_continuations SET goal_id = ?, goal_version = ?, claim_id = ?,
|
|
406
|
+
state = ?, reason = ?, attempt = ?, available_at = ?, expires_at = ?,
|
|
407
|
+
last_error = ?, updated_at = ? WHERE scope_id = ? AND claim_id = ?`)
|
|
408
|
+
.run(claim.goalId, claim.goalVersion, claim.claimId, claim.state, claim.reason, claim.attempt, claim.availableAt, claim.expiresAt, claim.lastError ?? null, claim.updatedAt, claim.scopeId, expectedClaimId);
|
|
409
|
+
return result.changes === 1;
|
|
410
|
+
}
|
|
411
|
+
async deleteContinuationClaim(scopeId, expectedClaimId) {
|
|
412
|
+
const result = this.db
|
|
413
|
+
.prepare("DELETE FROM agent_goal_continuations WHERE scope_id = ? AND claim_id = ?")
|
|
414
|
+
.run(scopeId, expectedClaimId);
|
|
415
|
+
return result.changes === 1;
|
|
416
|
+
}
|
|
417
|
+
close() {
|
|
418
|
+
this.db.close();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GoalWakeScheduler } from "./domain.js";
|
|
2
|
+
export declare class TimerGoalWakeScheduler implements GoalWakeScheduler {
|
|
3
|
+
private readonly now;
|
|
4
|
+
private readonly timers;
|
|
5
|
+
private closed;
|
|
6
|
+
constructor(now?: () => number);
|
|
7
|
+
schedule(scopeId: string, wakeAt: string, wake: () => void): void;
|
|
8
|
+
cancel(scopeId: string): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
2
|
+
export class TimerGoalWakeScheduler {
|
|
3
|
+
now;
|
|
4
|
+
timers = new Map();
|
|
5
|
+
closed = false;
|
|
6
|
+
constructor(now = Date.now) {
|
|
7
|
+
this.now = now;
|
|
8
|
+
}
|
|
9
|
+
schedule(scopeId, wakeAt, wake) {
|
|
10
|
+
if (this.closed)
|
|
11
|
+
return;
|
|
12
|
+
this.cancel(scopeId);
|
|
13
|
+
const delay = Math.min(MAX_TIMER_DELAY_MS, Math.max(0, Date.parse(wakeAt) - this.now()));
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
this.timers.delete(scopeId);
|
|
16
|
+
wake();
|
|
17
|
+
}, delay);
|
|
18
|
+
timer.unref?.();
|
|
19
|
+
this.timers.set(scopeId, timer);
|
|
20
|
+
}
|
|
21
|
+
cancel(scopeId) {
|
|
22
|
+
const timer = this.timers.get(scopeId);
|
|
23
|
+
if (!timer)
|
|
24
|
+
return;
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
this.timers.delete(scopeId);
|
|
27
|
+
}
|
|
28
|
+
close() {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
for (const timer of this.timers.values())
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
this.timers.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pinet/agent-goal",
|
|
3
|
+
"version": "0.2.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Standalone single-agent durable goal loop for Pi",
|
|
6
|
+
"author": "Will Porcellini <5994936+gugu91@users.noreply.github.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/gugu91/pinet.git",
|
|
11
|
+
"directory": "agent-goal"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"dist/"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"pi-package",
|
|
32
|
+
"goal",
|
|
33
|
+
"agent",
|
|
34
|
+
"autonomous"
|
|
35
|
+
],
|
|
36
|
+
"pi": {
|
|
37
|
+
"extensions": [
|
|
38
|
+
"./dist/index.js"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "node ../scripts/build-package.mjs",
|
|
43
|
+
"prepack": "pnpm run build",
|
|
44
|
+
"lint": "oxlint .",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run --config ../vitest.config.ts"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@earendil-works/pi-ai": ">=0.74.0",
|
|
51
|
+
"@earendil-works/pi-coding-agent": ">=0.74.0"
|
|
52
|
+
}
|
|
53
|
+
}
|