@pinet/agent-goal 0.2.10 → 0.2.12
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 +8 -4
- package/dist/dashboard.js +1 -1
- package/dist/domain.d.ts +9 -0
- package/dist/goal-window.d.ts +13 -2
- package/dist/goal-window.js +78 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +93 -7
- package/dist/memory-storage.d.ts +1 -0
- package/dist/memory-storage.js +31 -6
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +75 -0
- package/dist/sqlite-storage.d.ts +1 -0
- package/dist/sqlite-storage.js +47 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ pi -e ./agent-goal/index.ts
|
|
|
21
21
|
```text
|
|
22
22
|
/goal <objective> Create and immediately start a goal
|
|
23
23
|
/goal Open the minimal goal window (text in headless modes)
|
|
24
|
+
/goal budget turns=<n> [tokens=<n>] Change total turn/token ceilings
|
|
24
25
|
/goal pause Pause automatic evaluation and continuation
|
|
25
26
|
/goal resume Resume and immediately continue
|
|
26
27
|
/goal complete Mark complete manually
|
|
@@ -29,13 +30,14 @@ pi -e ./agent-goal/index.ts
|
|
|
29
30
|
/goal show Show the compact persistent goal status
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
Pi's footer is the only persistent goal UI and shows compact status and budget usage without duplicating the detailed dashboard. In interactive mode, `/goal` opens the detailed control window with the objective, lifecycle state, budget bars, latest evaluator guidance, and continuation state.
|
|
33
|
+
Pi's footer is the only persistent goal UI and shows compact status and budget usage without duplicating the detailed dashboard. In interactive mode, `/goal` opens the detailed control window with the objective, lifecycle state, budget bars, latest evaluator guidance, and continuation state. Press `b` to edit total turn and token ceilings in place; digits edit the selected field, Tab switches fields, Enter saves, and Escape cancels. Other visible keyboard actions pause or resume the goal, mark it complete, clear it, or close the window; complete and clear require a second keypress, and validation errors remain visible in the open window. Press Escape, `q`, or Ctrl+C to close it. `/goal` retains the detailed textual dashboard in headless sessions.
|
|
33
34
|
|
|
34
35
|
Only one goal may exist per Pi session. Clear the existing goal before creating another.
|
|
35
36
|
|
|
36
|
-
The agent also receives
|
|
37
|
+
The agent also receives four model-visible tools:
|
|
37
38
|
|
|
38
39
|
- `create_goal` — create its own bounded, user-aligned durable goal
|
|
40
|
+
- `update_goal_budget` — change the current goal's total turn or token ceiling
|
|
39
41
|
- `get_goal` — inspect the current objective, status, and budget
|
|
40
42
|
- `update_goal` — optionally attach a `complete` or `blocked` hint for independent verification
|
|
41
43
|
|
|
@@ -51,7 +53,9 @@ PI_AGENT_GOAL_MAX_TOKENS=200000
|
|
|
51
53
|
PI_AGENT_GOAL_MAX_RUNTIME_MS=14400000
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
Iteration and runtime limits are always reliable. Token accounting uses usage reported by Pi providers.
|
|
56
|
+
Iteration and runtime limits are always reliable. Token accounting uses usage reported by Pi providers. Operators and the goal-bearing agent may update total turn and token ceilings without recreating the goal. Changes are optimistic and atomic, cannot reduce a ceiling below accounted usage, must reserve capacity for a currently active turn, and cannot exceed a configured default ceiling when one exists. Increasing an exhausted budget reactivates the same goal when capacity is available; no budget change alters the objective or erases usage.
|
|
57
|
+
|
|
58
|
+
The evaluator reviews every settled run, including the final allowed turn, so a completed goal is not incorrectly classified as budget-limited; only another continuation is prevented. The former `PI_AGENT_GOAL_EVALUATION_INTERVAL` setting is accepted for configuration compatibility but no longer changes evaluation frequency.
|
|
55
59
|
|
|
56
60
|
## Persistence and recovery
|
|
57
61
|
|
|
@@ -114,7 +118,7 @@ A future Pinet integration can use broker storage and evaluation plus RALPH reco
|
|
|
114
118
|
|
|
115
119
|
## Automatic evaluation
|
|
116
120
|
|
|
117
|
-
The extension registers model-visible `create_goal`, `get_goal`, and `update_goal` tools. The worker can establish its own user-aligned goal and
|
|
121
|
+
The extension registers model-visible `create_goal`, `update_goal_budget`, `get_goal`, and `update_goal` tools. The worker can establish its own user-aligned goal, inspect it, and adjust its bounded capacity. `update_goal` is optional: it records a terminal hint rather than mutating goal state directly. Every `agent_settled` event accounts the run and invokes the independent evaluator whether or not the worker supplied that hint.
|
|
118
122
|
|
|
119
123
|
The evaluator returns one of:
|
|
120
124
|
|
package/dist/dashboard.js
CHANGED
|
@@ -37,6 +37,6 @@ export function formatGoalDashboard(goal, claim) {
|
|
|
37
37
|
lines.push(`Reason: ${displayGoalText(goal.blockedReason, 100)}`);
|
|
38
38
|
if (claim)
|
|
39
39
|
lines.push(`Continuation: ${claim.state} · attempt ${claim.attempt}`);
|
|
40
|
-
lines.push("/goal pause · resume · complete · clear · hide");
|
|
40
|
+
lines.push("/goal budget turns=<n> tokens=<n> · pause · resume · complete · clear · hide");
|
|
41
41
|
return lines;
|
|
42
42
|
}
|
package/dist/domain.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export interface GoalUsage {
|
|
|
8
8
|
iterations: number;
|
|
9
9
|
tokens: number;
|
|
10
10
|
}
|
|
11
|
+
export interface GoalBudgetUpdate {
|
|
12
|
+
maxIterations?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
}
|
|
11
15
|
export interface GoalEvaluationRecord {
|
|
12
16
|
id: string;
|
|
13
17
|
outcome: GoalEvaluation["outcome"];
|
|
@@ -86,6 +90,7 @@ export interface GoalStorage {
|
|
|
86
90
|
get(scopeId: string): Promise<AgentGoal | undefined>;
|
|
87
91
|
create(goal: AgentGoal): Promise<void>;
|
|
88
92
|
replace(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
93
|
+
updateBudget(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
89
94
|
delete(scopeId: string, expectedVersion: number): Promise<boolean>;
|
|
90
95
|
getPendingEvaluation(scopeId: string): Promise<GoalPendingEvaluation | undefined>;
|
|
91
96
|
appendPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
|
@@ -143,6 +148,10 @@ export type GoalEvent = {
|
|
|
143
148
|
type: "goal.progress_accounted";
|
|
144
149
|
goal: AgentGoal;
|
|
145
150
|
tokenDelta: number;
|
|
151
|
+
} | {
|
|
152
|
+
type: "goal.budget_changed";
|
|
153
|
+
goal: AgentGoal;
|
|
154
|
+
previousBudget: GoalBudget;
|
|
146
155
|
} | {
|
|
147
156
|
type: "goal.evaluated";
|
|
148
157
|
goal: AgentGoal;
|
package/dist/goal-window.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type Component } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { AgentGoal, GoalContinuationClaim } from "./domain.js";
|
|
4
|
-
export type
|
|
4
|
+
export type GoalWindowLifecycleAction = "pause" | "resume" | "complete" | "clear" | "close";
|
|
5
|
+
export type GoalWindowAction = GoalWindowLifecycleAction | {
|
|
6
|
+
type: "budget";
|
|
7
|
+
maxIterations: number;
|
|
8
|
+
maxTokens?: number;
|
|
9
|
+
};
|
|
5
10
|
export declare class GoalWindow implements Component {
|
|
6
11
|
private readonly goal;
|
|
7
12
|
private readonly claim;
|
|
@@ -9,8 +14,14 @@ export declare class GoalWindow implements Component {
|
|
|
9
14
|
private readonly onAction;
|
|
10
15
|
private readonly requestRender;
|
|
11
16
|
private readonly now;
|
|
17
|
+
private readonly actionError?;
|
|
12
18
|
private pendingConfirmation;
|
|
13
|
-
|
|
19
|
+
private budgetField;
|
|
20
|
+
private budgetTurns;
|
|
21
|
+
private budgetTokens;
|
|
22
|
+
private replaceBudgetValue;
|
|
23
|
+
private budgetInputError;
|
|
24
|
+
constructor(goal: AgentGoal | undefined, claim: GoalContinuationClaim | undefined, theme: Theme, onAction: (action: GoalWindowAction) => void, requestRender?: () => void, now?: () => number, actionError?: string | undefined);
|
|
14
25
|
handleInput(data: string): void;
|
|
15
26
|
render(width: number): string[];
|
|
16
27
|
invalidate(): void;
|
package/dist/goal-window.js
CHANGED
|
@@ -20,14 +20,21 @@ export class GoalWindow {
|
|
|
20
20
|
onAction;
|
|
21
21
|
requestRender;
|
|
22
22
|
now;
|
|
23
|
+
actionError;
|
|
23
24
|
pendingConfirmation;
|
|
24
|
-
|
|
25
|
+
budgetField;
|
|
26
|
+
budgetTurns = "";
|
|
27
|
+
budgetTokens = "";
|
|
28
|
+
replaceBudgetValue = false;
|
|
29
|
+
budgetInputError;
|
|
30
|
+
constructor(goal, claim, theme, onAction, requestRender = () => undefined, now = Date.now, actionError) {
|
|
25
31
|
this.goal = goal;
|
|
26
32
|
this.claim = claim;
|
|
27
33
|
this.theme = theme;
|
|
28
34
|
this.onAction = onAction;
|
|
29
35
|
this.requestRender = requestRender;
|
|
30
36
|
this.now = now;
|
|
37
|
+
this.actionError = actionError;
|
|
31
38
|
}
|
|
32
39
|
handleInput(data) {
|
|
33
40
|
const key = data.toLowerCase();
|
|
@@ -40,6 +47,11 @@ export class GoalWindow {
|
|
|
40
47
|
this.pendingConfirmation = undefined;
|
|
41
48
|
this.requestRender();
|
|
42
49
|
}
|
|
50
|
+
else if (this.budgetField) {
|
|
51
|
+
this.budgetField = undefined;
|
|
52
|
+
this.budgetInputError = undefined;
|
|
53
|
+
this.requestRender();
|
|
54
|
+
}
|
|
43
55
|
else {
|
|
44
56
|
this.onAction("close");
|
|
45
57
|
}
|
|
@@ -47,6 +59,49 @@ export class GoalWindow {
|
|
|
47
59
|
}
|
|
48
60
|
if (!this.goal)
|
|
49
61
|
return;
|
|
62
|
+
if (this.budgetField) {
|
|
63
|
+
if (matchesKey(data, "tab") || matchesKey(data, "up") || matchesKey(data, "down")) {
|
|
64
|
+
this.budgetField = this.budgetField === "turns" ? "tokens" : "turns";
|
|
65
|
+
this.replaceBudgetValue = true;
|
|
66
|
+
this.budgetInputError = undefined;
|
|
67
|
+
}
|
|
68
|
+
else if (matchesKey(data, "backspace")) {
|
|
69
|
+
const value = this.budgetField === "turns" ? this.budgetTurns : this.budgetTokens;
|
|
70
|
+
if (this.budgetField === "turns")
|
|
71
|
+
this.budgetTurns = value.slice(0, -1);
|
|
72
|
+
else
|
|
73
|
+
this.budgetTokens = value.slice(0, -1);
|
|
74
|
+
this.replaceBudgetValue = false;
|
|
75
|
+
this.budgetInputError = undefined;
|
|
76
|
+
}
|
|
77
|
+
else if (matchesKey(data, "enter")) {
|
|
78
|
+
const maxIterations = Number(this.budgetTurns);
|
|
79
|
+
if (!Number.isInteger(maxIterations) || maxIterations <= 0) {
|
|
80
|
+
this.budgetInputError = "Turns must be a positive integer";
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const maxTokens = this.budgetTokens ? Number(this.budgetTokens) : undefined;
|
|
84
|
+
this.onAction({ type: "budget", maxIterations, maxTokens });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (/^\d+$/.test(data)) {
|
|
89
|
+
const value = this.replaceBudgetValue
|
|
90
|
+
? data
|
|
91
|
+
: `${this.budgetField === "turns" ? this.budgetTurns : this.budgetTokens}${data}`;
|
|
92
|
+
if (this.budgetField === "turns")
|
|
93
|
+
this.budgetTurns = value;
|
|
94
|
+
else
|
|
95
|
+
this.budgetTokens = value;
|
|
96
|
+
this.replaceBudgetValue = false;
|
|
97
|
+
this.budgetInputError = undefined;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.requestRender();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
50
105
|
if (this.pendingConfirmation) {
|
|
51
106
|
const confirmationKey = this.pendingConfirmation === "complete" ? "c" : "x";
|
|
52
107
|
if (key === confirmationKey) {
|
|
@@ -58,7 +113,16 @@ export class GoalWindow {
|
|
|
58
113
|
}
|
|
59
114
|
return;
|
|
60
115
|
}
|
|
61
|
-
if (key === "
|
|
116
|
+
if (key === "b" && this.goal.status !== "complete") {
|
|
117
|
+
this.budgetField = "turns";
|
|
118
|
+
this.budgetTurns = String(this.goal.budget.maxIterations);
|
|
119
|
+
this.budgetTokens =
|
|
120
|
+
this.goal.budget.maxTokens === undefined ? "" : String(this.goal.budget.maxTokens);
|
|
121
|
+
this.replaceBudgetValue = true;
|
|
122
|
+
this.budgetInputError = undefined;
|
|
123
|
+
this.requestRender();
|
|
124
|
+
}
|
|
125
|
+
else if (key === "p" && this.goal.status === "active") {
|
|
62
126
|
this.onAction("pause");
|
|
63
127
|
}
|
|
64
128
|
else if (key === "r" && (this.goal.status === "paused" || this.goal.status === "blocked")) {
|
|
@@ -132,16 +196,27 @@ export class GoalWindow {
|
|
|
132
196
|
if (this.claim) {
|
|
133
197
|
lines.push(row(` ${this.theme.fg("muted", "Continuation")} ${this.claim.state} · attempt ${this.claim.attempt}`));
|
|
134
198
|
}
|
|
199
|
+
if (this.budgetField) {
|
|
200
|
+
const turns = `${this.budgetField === "turns" ? "›" : " "} Turns ${this.budgetTurns || "_"}`;
|
|
201
|
+
const tokens = `${this.budgetField === "tokens" ? "›" : " "} Tokens ${this.budgetTokens || "unchanged"}`;
|
|
202
|
+
lines.push(row(), row(` ${this.theme.fg("accent", "Edit budget")}`), row(` ${turns}`), row(` ${tokens}`));
|
|
203
|
+
}
|
|
204
|
+
const error = this.budgetInputError ?? this.actionError;
|
|
205
|
+
if (error)
|
|
206
|
+
lines.push(row(` ${this.theme.fg("error", displayGoalText(error, contentWidth))}`));
|
|
135
207
|
const actions = [
|
|
136
208
|
this.goal.status === "active" ? "p pause" : undefined,
|
|
137
209
|
this.goal.status === "paused" || this.goal.status === "blocked" ? "r resume" : undefined,
|
|
210
|
+
this.goal.status !== "complete" ? "b budget" : undefined,
|
|
138
211
|
this.goal.status !== "complete" ? "c complete" : undefined,
|
|
139
212
|
"x clear",
|
|
140
213
|
"q close",
|
|
141
214
|
].filter((action) => action !== undefined);
|
|
142
215
|
const footer = this.pendingConfirmation
|
|
143
216
|
? `${this.pendingConfirmation === "complete" ? "c" : "x"} again to confirm ${this.pendingConfirmation} · esc cancel`
|
|
144
|
-
:
|
|
217
|
+
: this.budgetField
|
|
218
|
+
? "digits edit · tab field · enter save · esc cancel"
|
|
219
|
+
: actions.join(" · ");
|
|
145
220
|
lines.push(row(), row(` ${this.theme.fg("dim", footer)}`));
|
|
146
221
|
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
147
222
|
return lines;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { GoalBudget, GoalContinuation, GoalEvaluator, GoalEventSink, GoalRetryPolicy, GoalStorage, GoalWakeScheduler } from "./domain.js";
|
|
3
3
|
export type { AgentGoal, GoalBudget, GoalContinuation, GoalContinuationClaim, GoalContinuationRequest, GoalContinuationResult, GoalEvaluation, GoalEvaluationRecord, GoalEvaluator, GoalEvent, GoalEventSink, GoalPendingEvaluation, GoalProgress, GoalRetryPolicy, GoalStatus, GoalStorage, GoalTerminalCandidate, GoalTerminalCandidateRecord, GoalUsage, GoalWakeScheduler, } from "./domain.js";
|
|
4
4
|
export { displayGoalText, formatGoalDashboard, formatGoalStatus } from "./dashboard.js";
|
|
5
|
-
export { GoalWindow, type GoalWindowAction } from "./goal-window.js";
|
|
5
|
+
export { GoalWindow, type GoalWindowAction, type GoalWindowLifecycleAction, } from "./goal-window.js";
|
|
6
6
|
export { MemoryGoalStorage } from "./memory-storage.js";
|
|
7
7
|
export { parseGoalEvaluation, PiGoalEvaluator } from "./pi-evaluator.js";
|
|
8
8
|
export { countGoalProgressTokens, formatGoalProgress, type GoalProgressMessage, } from "./progress.js";
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { formatGoalDashboard, formatGoalStatus } from "./dashboard.js";
|
|
4
|
-
import { GoalWindow } from "./goal-window.js";
|
|
4
|
+
import { GoalWindow, } from "./goal-window.js";
|
|
5
5
|
import { PiGoalEvaluator } from "./pi-evaluator.js";
|
|
6
6
|
import { countGoalProgressTokens, formatGoalProgress, } from "./progress.js";
|
|
7
7
|
import { GoalRuntime } from "./runtime.js";
|
|
8
8
|
import { SqliteGoalStorage } from "./sqlite-storage.js";
|
|
9
9
|
export { displayGoalText, formatGoalDashboard, formatGoalStatus } from "./dashboard.js";
|
|
10
|
-
export { GoalWindow } from "./goal-window.js";
|
|
10
|
+
export { GoalWindow, } from "./goal-window.js";
|
|
11
11
|
export { MemoryGoalStorage } from "./memory-storage.js";
|
|
12
12
|
export { parseGoalEvaluation, PiGoalEvaluator } from "./pi-evaluator.js";
|
|
13
13
|
export { countGoalProgressTokens, formatGoalProgress, } from "./progress.js";
|
|
@@ -44,7 +44,7 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
44
44
|
if (!ctx || ctx.sessionManager.getSessionId() !== goal.scopeId) {
|
|
45
45
|
return { status: "unavailable", reason: "The goal session is not active" };
|
|
46
46
|
}
|
|
47
|
-
if (!ctx.isIdle()
|
|
47
|
+
if (!ctx.isIdle()) {
|
|
48
48
|
return { status: "busy", reason: "The goal session is busy", retryAfterMs: 1_000 };
|
|
49
49
|
}
|
|
50
50
|
api.sendMessage({
|
|
@@ -78,6 +78,13 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
78
78
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
79
79
|
};
|
|
80
80
|
const applyGoalAction = async (scopeId, action) => {
|
|
81
|
+
if (typeof action === "object") {
|
|
82
|
+
await runtime.updateBudget(scopeId, {
|
|
83
|
+
maxIterations: action.maxIterations,
|
|
84
|
+
maxTokens: action.maxTokens,
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
81
88
|
switch (action) {
|
|
82
89
|
case "pause":
|
|
83
90
|
await runtime.setStatus(scopeId, "paused");
|
|
@@ -208,6 +215,53 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
208
215
|
};
|
|
209
216
|
},
|
|
210
217
|
});
|
|
218
|
+
pi.registerTool({
|
|
219
|
+
name: "update_goal_budget",
|
|
220
|
+
label: "Update goal budget",
|
|
221
|
+
description: "Adjust this session goal's bounded turn or token ceiling without replacing the goal. Changes remain constrained by configured limits and cannot discard accounted usage.",
|
|
222
|
+
promptSnippet: "Adjust the active session goal's bounded turn or token budget.",
|
|
223
|
+
promptGuidelines: [
|
|
224
|
+
"Only change a goal budget when more or less capacity is genuinely needed for the existing user-aligned objective.",
|
|
225
|
+
"Never use budget changes to broaden the objective or evade configured hard limits.",
|
|
226
|
+
"Inspect the current goal first and keep requested capacity proportionate to the remaining work.",
|
|
227
|
+
],
|
|
228
|
+
parameters: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
maxTurns: {
|
|
232
|
+
type: "integer",
|
|
233
|
+
minimum: 1,
|
|
234
|
+
maximum: defaultBudget.maxIterations,
|
|
235
|
+
description: "New total settled-turn ceiling, including turns already accounted.",
|
|
236
|
+
},
|
|
237
|
+
maxTokens: {
|
|
238
|
+
type: "number",
|
|
239
|
+
exclusiveMinimum: 0,
|
|
240
|
+
...(defaultBudget.maxTokens === undefined ? {} : { maximum: defaultBudget.maxTokens }),
|
|
241
|
+
description: "New total token ceiling, including tokens already accounted.",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
additionalProperties: false,
|
|
245
|
+
},
|
|
246
|
+
async execute(_toolCallId, rawParams, _signal, _onUpdate, rawCtx) {
|
|
247
|
+
const params = rawParams;
|
|
248
|
+
const ctx = rawCtx;
|
|
249
|
+
const goal = await runtime.updateBudget(ctx.sessionManager.getSessionId(), {
|
|
250
|
+
maxIterations: params.maxTurns,
|
|
251
|
+
maxTokens: params.maxTokens,
|
|
252
|
+
});
|
|
253
|
+
await refreshUi(ctx);
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: `Updated goal budget to ${goal.budget.maxIterations} turns${goal.budget.maxTokens === undefined ? "" : ` and ${goal.budget.maxTokens} tokens`}.`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
details: { goal },
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
});
|
|
211
265
|
pi.registerTool({
|
|
212
266
|
name: "get_goal",
|
|
213
267
|
label: "Get goal",
|
|
@@ -283,7 +337,7 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
283
337
|
},
|
|
284
338
|
});
|
|
285
339
|
pi.registerCommand("goal", {
|
|
286
|
-
description: "Create, inspect, pause, resume, complete, clear, show, or hide this session's goal",
|
|
340
|
+
description: "Create, inspect, adjust budget, pause, resume, complete, clear, show, or hide this session's goal",
|
|
287
341
|
handler: async (args, rawCtx) => {
|
|
288
342
|
const ctx = rawCtx;
|
|
289
343
|
activeContext = ctx;
|
|
@@ -292,12 +346,13 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
292
346
|
try {
|
|
293
347
|
if (!input) {
|
|
294
348
|
let openedWindow = false;
|
|
349
|
+
let actionError;
|
|
295
350
|
while (true) {
|
|
296
351
|
const goal = await runtime.get(scopeId);
|
|
297
352
|
const claim = await runtime.getContinuationClaim(scopeId);
|
|
298
353
|
const action = await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
299
354
|
openedWindow = true;
|
|
300
|
-
return new GoalWindow(goal, claim, theme, done, () => tui.requestRender());
|
|
355
|
+
return new GoalWindow(goal, claim, theme, done, () => tui.requestRender(), Date.now, actionError);
|
|
301
356
|
}, {
|
|
302
357
|
overlay: true,
|
|
303
358
|
overlayOptions: {
|
|
@@ -320,11 +375,42 @@ export function registerAgentGoal(pi, options = {}) {
|
|
|
320
375
|
}
|
|
321
376
|
if (!action || action === "close")
|
|
322
377
|
return;
|
|
323
|
-
|
|
324
|
-
|
|
378
|
+
try {
|
|
379
|
+
await applyGoalAction(scopeId, action);
|
|
380
|
+
actionError = undefined;
|
|
381
|
+
await refreshUi(ctx);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
actionError = error instanceof Error ? error.message : String(error);
|
|
385
|
+
}
|
|
325
386
|
}
|
|
326
387
|
}
|
|
327
388
|
const command = input.toLowerCase();
|
|
389
|
+
if (command === "budget" || command.startsWith("budget ")) {
|
|
390
|
+
const update = {};
|
|
391
|
+
for (const token of input.slice("budget".length).trim().split(/\s+/).filter(Boolean)) {
|
|
392
|
+
const separator = token.indexOf("=");
|
|
393
|
+
if (separator < 1)
|
|
394
|
+
throw new Error(`Invalid goal budget argument: ${token}`);
|
|
395
|
+
const key = token.slice(0, separator).toLowerCase();
|
|
396
|
+
const value = Number(token.slice(separator + 1));
|
|
397
|
+
if (key === "turns" || key === "maxturns" || key === "iterations") {
|
|
398
|
+
update.maxIterations = value;
|
|
399
|
+
}
|
|
400
|
+
else if (key === "tokens" || key === "maxtokens") {
|
|
401
|
+
update.maxTokens = value;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
throw new Error(`Unknown goal budget field: ${key}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const goal = await runtime.updateBudget(scopeId, update);
|
|
408
|
+
await refreshUi(ctx);
|
|
409
|
+
if (ctx.hasUI) {
|
|
410
|
+
ctx.ui.notify(`Goal budget: ${goal.budget.maxIterations} turns${goal.budget.maxTokens === undefined ? "" : ` · ${goal.budget.maxTokens} tokens`}`, "info");
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
328
414
|
switch (command) {
|
|
329
415
|
case "pause":
|
|
330
416
|
case "resume":
|
package/dist/memory-storage.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare class MemoryGoalStorage implements GoalStorage {
|
|
|
7
7
|
get(scopeId: string): Promise<AgentGoal | undefined>;
|
|
8
8
|
create(goal: AgentGoal): Promise<void>;
|
|
9
9
|
replace(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
10
|
+
updateBudget(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
10
11
|
delete(scopeId: string, expectedVersion: number): Promise<boolean>;
|
|
11
12
|
getPendingEvaluation(scopeId: string): Promise<GoalPendingEvaluation | undefined>;
|
|
12
13
|
appendPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
package/dist/memory-storage.js
CHANGED
|
@@ -27,6 +27,25 @@ export class MemoryGoalStorage {
|
|
|
27
27
|
this.goals.set(goal.scopeId, cloneGoal(goal));
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
|
+
async updateBudget(goal, expectedVersion) {
|
|
31
|
+
const current = this.goals.get(goal.scopeId);
|
|
32
|
+
if (!current || current.version !== expectedVersion || current.id !== goal.id)
|
|
33
|
+
return false;
|
|
34
|
+
this.goals.set(goal.scopeId, cloneGoal(goal));
|
|
35
|
+
const pending = this.pendingEvaluations.get(goal.scopeId);
|
|
36
|
+
if (pending?.goalId === goal.id && pending.goalVersion === expectedVersion) {
|
|
37
|
+
this.pendingEvaluations.set(goal.scopeId, { ...pending, goalVersion: goal.version });
|
|
38
|
+
}
|
|
39
|
+
const candidate = this.terminalCandidates.get(goal.scopeId);
|
|
40
|
+
if (candidate?.goalId === goal.id && candidate.goalVersion === expectedVersion) {
|
|
41
|
+
this.terminalCandidates.set(goal.scopeId, { ...candidate, goalVersion: goal.version });
|
|
42
|
+
}
|
|
43
|
+
const claim = this.claims.get(goal.scopeId);
|
|
44
|
+
if (claim?.goalId === goal.id && claim.goalVersion === expectedVersion) {
|
|
45
|
+
this.claims.set(goal.scopeId, { ...claim, goalVersion: goal.version });
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
30
49
|
async delete(scopeId, expectedVersion) {
|
|
31
50
|
const current = this.goals.get(scopeId);
|
|
32
51
|
if (!current || current.version !== expectedVersion)
|
|
@@ -52,14 +71,13 @@ export class MemoryGoalStorage {
|
|
|
52
71
|
}
|
|
53
72
|
async appendPendingEvaluation(pending) {
|
|
54
73
|
const goal = this.goals.get(pending.scopeId);
|
|
55
|
-
if (!goal || goal.id !== pending.goalId || goal.
|
|
74
|
+
if (!goal || goal.id !== pending.goalId || goal.status !== "active")
|
|
56
75
|
return false;
|
|
57
76
|
const stored = this.pendingEvaluations.get(pending.scopeId);
|
|
58
|
-
const existing = stored?.goalId === pending.goalId && stored.goalVersion ===
|
|
59
|
-
? stored
|
|
60
|
-
: undefined;
|
|
77
|
+
const existing = stored?.goalId === pending.goalId && stored.goalVersion === goal.version ? stored : undefined;
|
|
61
78
|
this.pendingEvaluations.set(pending.scopeId, {
|
|
62
79
|
...pending,
|
|
80
|
+
goalVersion: goal.version,
|
|
63
81
|
iterationsDelta: pending.iterationsDelta + (existing?.iterationsDelta ?? 0),
|
|
64
82
|
progress: {
|
|
65
83
|
...pending.progress,
|
|
@@ -149,9 +167,16 @@ export class MemoryGoalStorage {
|
|
|
149
167
|
}
|
|
150
168
|
async replaceContinuationClaim(claim, expectedClaimId) {
|
|
151
169
|
const current = this.claims.get(claim.scopeId);
|
|
152
|
-
|
|
170
|
+
const goal = this.goals.get(claim.scopeId);
|
|
171
|
+
if (!current ||
|
|
172
|
+
current.claimId !== expectedClaimId ||
|
|
173
|
+
current.goalId !== claim.goalId ||
|
|
174
|
+
!goal ||
|
|
175
|
+
goal.id !== claim.goalId ||
|
|
176
|
+
goal.status !== "active") {
|
|
153
177
|
return false;
|
|
154
|
-
|
|
178
|
+
}
|
|
179
|
+
this.claims.set(claim.scopeId, { ...claim, goalVersion: goal.version });
|
|
155
180
|
return true;
|
|
156
181
|
}
|
|
157
182
|
async deleteContinuationClaim(scopeId, expectedClaimId) {
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentGoal, GoalBudget, GoalContinuation, GoalContinuationClaim, GoalEvaluator, GoalEventSink, GoalProgress, GoalRetryPolicy, GoalStatus, GoalStorage, GoalTerminalCandidate, GoalTerminalCandidateRecord, GoalWakeScheduler } from "./domain.js";
|
|
1
|
+
import type { AgentGoal, GoalBudget, GoalBudgetUpdate, GoalContinuation, GoalContinuationClaim, GoalEvaluator, GoalEventSink, GoalProgress, GoalRetryPolicy, GoalStatus, GoalStorage, GoalTerminalCandidate, GoalTerminalCandidateRecord, GoalWakeScheduler } from "./domain.js";
|
|
2
2
|
export interface GoalRuntimeOptions {
|
|
3
3
|
defaultBudget?: GoalBudget;
|
|
4
4
|
retryPolicy?: GoalRetryPolicy;
|
|
@@ -29,6 +29,7 @@ export declare class GoalRuntime {
|
|
|
29
29
|
getContinuationClaim(scopeId: string): Promise<GoalContinuationClaim | undefined>;
|
|
30
30
|
getTerminalCandidate(scopeId: string): Promise<GoalTerminalCandidateRecord | undefined>;
|
|
31
31
|
create(scopeId: string, objective: string, budget?: GoalBudget): Promise<AgentGoal>;
|
|
32
|
+
updateBudget(scopeId: string, update: GoalBudgetUpdate): Promise<AgentGoal>;
|
|
32
33
|
setStatus(scopeId: string, status: Extract<GoalStatus, "active" | "paused" | "complete">): Promise<AgentGoal>;
|
|
33
34
|
clear(scopeId: string): Promise<boolean>;
|
|
34
35
|
requestTerminalCandidate(scopeId: string, candidate: GoalTerminalCandidate): Promise<GoalTerminalCandidateRecord>;
|
package/dist/runtime.js
CHANGED
|
@@ -88,6 +88,65 @@ export class GoalRuntime {
|
|
|
88
88
|
await this.record({ type: "goal.created", goal });
|
|
89
89
|
return goal;
|
|
90
90
|
}
|
|
91
|
+
async updateBudget(scopeId, update) {
|
|
92
|
+
const current = await this.requireGoal(scopeId);
|
|
93
|
+
if (current.status === "complete")
|
|
94
|
+
throw new Error("Cannot change a complete goal budget");
|
|
95
|
+
if (update.maxIterations === undefined && update.maxTokens === undefined) {
|
|
96
|
+
throw new Error("A goal budget update requires maxIterations or maxTokens");
|
|
97
|
+
}
|
|
98
|
+
if (update.maxIterations !== undefined &&
|
|
99
|
+
(!Number.isInteger(update.maxIterations) || update.maxIterations <= 0)) {
|
|
100
|
+
throw new Error("Goal maxIterations must be a positive integer");
|
|
101
|
+
}
|
|
102
|
+
if (update.maxTokens !== undefined &&
|
|
103
|
+
(!Number.isFinite(update.maxTokens) || update.maxTokens <= 0)) {
|
|
104
|
+
throw new Error("Goal maxTokens must be a positive finite number");
|
|
105
|
+
}
|
|
106
|
+
if (update.maxIterations !== undefined && update.maxIterations > this.budget.maxIterations) {
|
|
107
|
+
throw new Error(`Goal maxIterations cannot exceed the configured limit of ${this.budget.maxIterations}`);
|
|
108
|
+
}
|
|
109
|
+
if (update.maxTokens !== undefined &&
|
|
110
|
+
this.budget.maxTokens !== undefined &&
|
|
111
|
+
update.maxTokens > this.budget.maxTokens) {
|
|
112
|
+
throw new Error(`Goal maxTokens cannot exceed the configured limit of ${this.budget.maxTokens}`);
|
|
113
|
+
}
|
|
114
|
+
const minimumIterations = current.status === "active" ? current.usage.iterations + 1 : current.usage.iterations;
|
|
115
|
+
if (update.maxIterations !== undefined && update.maxIterations < minimumIterations) {
|
|
116
|
+
throw new Error(current.status === "active"
|
|
117
|
+
? `Goal maxIterations must leave capacity for the current turn (${minimumIterations} minimum)`
|
|
118
|
+
: `Goal maxIterations cannot be lower than ${current.usage.iterations} accounted turns`);
|
|
119
|
+
}
|
|
120
|
+
const minimumTokens = current.status === "active" ? current.usage.tokens + 1 : current.usage.tokens;
|
|
121
|
+
if (update.maxTokens !== undefined && update.maxTokens < minimumTokens) {
|
|
122
|
+
throw new Error(current.status === "active"
|
|
123
|
+
? `Goal maxTokens must leave capacity beyond ${current.usage.tokens} accounted tokens`
|
|
124
|
+
: `Goal maxTokens cannot be lower than ${current.usage.tokens} accounted tokens`);
|
|
125
|
+
}
|
|
126
|
+
const nextBudget = {
|
|
127
|
+
...current.budget,
|
|
128
|
+
...(update.maxIterations === undefined ? {} : { maxIterations: update.maxIterations }),
|
|
129
|
+
...(update.maxTokens === undefined ? {} : { maxTokens: update.maxTokens }),
|
|
130
|
+
};
|
|
131
|
+
const candidate = {
|
|
132
|
+
...current,
|
|
133
|
+
budget: nextBudget,
|
|
134
|
+
version: current.version + 1,
|
|
135
|
+
updatedAt: this.now().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
const exhausted = this.budgetExhausted(candidate);
|
|
138
|
+
const next = current.status === "budget_limited" && !exhausted
|
|
139
|
+
? { ...candidate, status: "active", blockedReason: undefined }
|
|
140
|
+
: candidate;
|
|
141
|
+
if (!(await this.storage.updateBudget(next, current.version))) {
|
|
142
|
+
throw new Error("Goal changed while its budget was being updated; retry the command");
|
|
143
|
+
}
|
|
144
|
+
await this.record({ type: "goal.budget_changed", goal: next, previousBudget: current.budget });
|
|
145
|
+
if (current.status === "budget_limited" && next.status === "active") {
|
|
146
|
+
await this.continueWithClaim(next, "Continue with the expanded goal budget.");
|
|
147
|
+
}
|
|
148
|
+
return next;
|
|
149
|
+
}
|
|
91
150
|
async setStatus(scopeId, status) {
|
|
92
151
|
const current = await this.requireGoal(scopeId);
|
|
93
152
|
const transitionAllowed = (status === "active" && (current.status === "paused" || current.status === "blocked")) ||
|
|
@@ -459,6 +518,22 @@ export class GoalRuntime {
|
|
|
459
518
|
for (let attempt = claim.attempt + 1; attempt <= this.retryPolicy.maxAttempts; attempt += 1) {
|
|
460
519
|
const currentGoal = await this.storage.get(goal.scopeId);
|
|
461
520
|
const currentClaim = await this.storage.getContinuationClaim(goal.scopeId);
|
|
521
|
+
if (currentGoal?.id === goal.id &&
|
|
522
|
+
currentGoal.status === "active" &&
|
|
523
|
+
currentGoal.version !== goal.version &&
|
|
524
|
+
currentClaim?.claimId === claim.claimId) {
|
|
525
|
+
if (currentClaim.state === "started") {
|
|
526
|
+
this.scheduleRecovery(goal.scopeId, currentClaim.expiresAt);
|
|
527
|
+
}
|
|
528
|
+
else if (currentClaim.state === "deferred" &&
|
|
529
|
+
Date.parse(currentClaim.availableAt) > this.now().getTime()) {
|
|
530
|
+
this.scheduleRecovery(goal.scopeId, currentClaim.availableAt);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
await this.runContinuationClaim(currentGoal, currentClaim);
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
462
537
|
if (!currentGoal ||
|
|
463
538
|
currentGoal.id !== goal.id ||
|
|
464
539
|
currentGoal.version !== goal.version ||
|
package/dist/sqlite-storage.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare class SqliteGoalStorage implements GoalStorage {
|
|
|
5
5
|
get(scopeId: string): Promise<AgentGoal | undefined>;
|
|
6
6
|
create(goal: AgentGoal): Promise<void>;
|
|
7
7
|
replace(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
8
|
+
updateBudget(goal: AgentGoal, expectedVersion: number): Promise<boolean>;
|
|
8
9
|
delete(scopeId: string, expectedVersion: number): Promise<boolean>;
|
|
9
10
|
getPendingEvaluation(scopeId: string): Promise<GoalPendingEvaluation | undefined>;
|
|
10
11
|
appendPendingEvaluation(pending: GoalPendingEvaluation): Promise<boolean>;
|
package/dist/sqlite-storage.js
CHANGED
|
@@ -179,6 +179,41 @@ export class SqliteGoalStorage {
|
|
|
179
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
180
|
return result.changes === 1;
|
|
181
181
|
}
|
|
182
|
+
async updateBudget(goal, expectedVersion) {
|
|
183
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
184
|
+
try {
|
|
185
|
+
const result = this.db
|
|
186
|
+
.prepare(`UPDATE agent_goals SET objective = ?, status = ?, blocked_reason = ?,
|
|
187
|
+
max_iterations = ?, max_tokens = ?, max_runtime_ms = ?, iterations_used = ?,
|
|
188
|
+
tokens_used = ?, last_settled_at = ?, last_evaluation_id = ?,
|
|
189
|
+
last_evaluation_outcome = ?, last_evaluation_reason = ?, last_evaluation_at = ?,
|
|
190
|
+
version = ?, updated_at = ?
|
|
191
|
+
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);
|
|
193
|
+
if (result.changes !== 1) {
|
|
194
|
+
this.db.exec("ROLLBACK");
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
this.db
|
|
198
|
+
.prepare(`UPDATE agent_goal_pending_evaluations SET goal_version = ?
|
|
199
|
+
WHERE scope_id = ? AND goal_id = ? AND goal_version = ?`)
|
|
200
|
+
.run(goal.version, goal.scopeId, goal.id, expectedVersion);
|
|
201
|
+
this.db
|
|
202
|
+
.prepare(`UPDATE agent_goal_terminal_candidates SET goal_version = ?
|
|
203
|
+
WHERE scope_id = ? AND goal_id = ? AND goal_version = ?`)
|
|
204
|
+
.run(goal.version, goal.scopeId, goal.id, expectedVersion);
|
|
205
|
+
this.db
|
|
206
|
+
.prepare(`UPDATE agent_goal_continuations SET goal_version = ?
|
|
207
|
+
WHERE scope_id = ? AND goal_id = ? AND goal_version = ?`)
|
|
208
|
+
.run(goal.version, goal.scopeId, goal.id, expectedVersion);
|
|
209
|
+
this.db.exec("COMMIT");
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.db.exec("ROLLBACK");
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
182
217
|
async delete(scopeId, expectedVersion) {
|
|
183
218
|
const result = this.db
|
|
184
219
|
.prepare("DELETE FROM agent_goals WHERE scope_id = ? AND version = ?")
|
|
@@ -217,10 +252,9 @@ export class SqliteGoalStorage {
|
|
|
217
252
|
(scope_id, goal_id, goal_version, evaluation_id, iterations_delta, latest_output,
|
|
218
253
|
token_delta, candidate_outcome, candidate_reason, attempt, available_at, last_error,
|
|
219
254
|
created_at, updated_at)
|
|
220
|
-
SELECT ?, ?,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
255
|
+
SELECT ?, ?, goal.version, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
256
|
+
FROM agent_goals AS goal
|
|
257
|
+
WHERE goal.scope_id = ? AND goal.id = ? AND goal.status = 'active'
|
|
224
258
|
ON CONFLICT(scope_id) DO UPDATE SET goal_id = excluded.goal_id,
|
|
225
259
|
goal_version = excluded.goal_version, evaluation_id = excluded.evaluation_id,
|
|
226
260
|
iterations_delta = CASE
|
|
@@ -253,7 +287,7 @@ export class SqliteGoalStorage {
|
|
|
253
287
|
AND agent_goal_pending_evaluations.goal_version = excluded.goal_version
|
|
254
288
|
THEN agent_goal_pending_evaluations.created_at ELSE excluded.created_at END,
|
|
255
289
|
updated_at = excluded.updated_at`)
|
|
256
|
-
.run(pending.scopeId, pending.goalId, pending.
|
|
290
|
+
.run(pending.scopeId, pending.goalId, 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);
|
|
257
291
|
return result.changes === 1;
|
|
258
292
|
}
|
|
259
293
|
async putPendingEvaluation(pending) {
|
|
@@ -402,10 +436,14 @@ export class SqliteGoalStorage {
|
|
|
402
436
|
}
|
|
403
437
|
async replaceContinuationClaim(claim, expectedClaimId) {
|
|
404
438
|
const result = this.db
|
|
405
|
-
.prepare(`UPDATE agent_goal_continuations SET goal_id = ?,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
439
|
+
.prepare(`UPDATE agent_goal_continuations SET goal_id = ?,
|
|
440
|
+
goal_version = (SELECT version FROM agent_goals WHERE scope_id = ? AND id = ?),
|
|
441
|
+
claim_id = ?, state = ?, reason = ?, attempt = ?, available_at = ?, expires_at = ?,
|
|
442
|
+
last_error = ?, updated_at = ? WHERE scope_id = ? AND claim_id = ?
|
|
443
|
+
AND goal_id = ? AND EXISTS (
|
|
444
|
+
SELECT 1 FROM agent_goals WHERE scope_id = ? AND id = ? AND status = 'active'
|
|
445
|
+
)`)
|
|
446
|
+
.run(claim.goalId, claim.scopeId, claim.goalId, claim.claimId, claim.state, claim.reason, claim.attempt, claim.availableAt, claim.expiresAt, claim.lastError ?? null, claim.updatedAt, claim.scopeId, expectedClaimId, claim.goalId, claim.scopeId, claim.goalId);
|
|
409
447
|
return result.changes === 1;
|
|
410
448
|
}
|
|
411
449
|
async deleteContinuationClaim(scopeId, expectedClaimId) {
|
package/package.json
CHANGED