@pinet/agent-goal 0.2.13 → 0.2.15
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 +44 -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 +46 -9
- package/dist/goal-window.js +340 -150
- package/dist/index.d.ts +1 -1
- package/dist/index.js +202 -112
- 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/goal-window.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
2
|
-
import { displayGoalText } from "./dashboard.js";
|
|
3
|
-
function progressBar(value, maximum, width) {
|
|
4
|
-
const ratio = Math.min(1, Math.max(0, value / maximum));
|
|
5
|
-
const filled = Math.round(ratio * width);
|
|
6
|
-
return `${"━".repeat(filled)}${"─".repeat(width - filled)}`;
|
|
7
|
-
}
|
|
8
|
-
function compactNumber(value) {
|
|
9
|
-
if (value < 1_000)
|
|
10
|
-
return String(value);
|
|
11
|
-
const divisor = value < 1_000_000 ? 1_000 : 1_000_000;
|
|
12
|
-
const suffix = value < 1_000_000 ? "k" : "m";
|
|
13
|
-
const scaled = value / divisor;
|
|
14
|
-
return `${scaled.toFixed(scaled < 100 ? 1 : 0).replace(/\.0$/, "")}${suffix}`;
|
|
15
|
-
}
|
|
2
|
+
import { displayGoalText, formatElapsed, goalDisplayName } from "./dashboard.js";
|
|
16
3
|
export class GoalWindow {
|
|
17
4
|
goal;
|
|
18
5
|
claim;
|
|
@@ -21,13 +8,24 @@ export class GoalWindow {
|
|
|
21
8
|
requestRender;
|
|
22
9
|
now;
|
|
23
10
|
actionError;
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
checkpoints;
|
|
12
|
+
mode = "details";
|
|
13
|
+
textField = "name";
|
|
14
|
+
name = "";
|
|
15
|
+
objective = "";
|
|
16
|
+
initialName = "";
|
|
17
|
+
initialObjective = "";
|
|
18
|
+
budgetField = "turns";
|
|
26
19
|
budgetTurns = "";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
budgetRuntime = "";
|
|
21
|
+
snoozeDuration = "30m";
|
|
22
|
+
inputError;
|
|
23
|
+
confirmClose = false;
|
|
24
|
+
selectedCheckpoint = -1;
|
|
25
|
+
checkpointOffset = 0;
|
|
26
|
+
checkpointLineCount = 0;
|
|
27
|
+
refreshTimer;
|
|
28
|
+
constructor(goal, claim, theme, onAction, requestRender = () => undefined, now = Date.now, actionError, checkpoints = [], initialMode = "details") {
|
|
31
29
|
this.goal = goal;
|
|
32
30
|
this.claim = claim;
|
|
33
31
|
this.theme = theme;
|
|
@@ -35,191 +33,383 @@ export class GoalWindow {
|
|
|
35
33
|
this.requestRender = requestRender;
|
|
36
34
|
this.now = now;
|
|
37
35
|
this.actionError = actionError;
|
|
36
|
+
this.checkpoints = checkpoints;
|
|
37
|
+
if (initialMode === "edit" && goal)
|
|
38
|
+
this.openTextForm("edit");
|
|
39
|
+
if (goal?.status === "active") {
|
|
40
|
+
this.refreshTimer = setInterval(this.requestRender, 1_000);
|
|
41
|
+
this.refreshTimer.unref();
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
handleInput(data) {
|
|
40
45
|
const key = data.toLowerCase();
|
|
41
|
-
if (matchesKey(data, "ctrl+c")
|
|
46
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
42
47
|
this.onAction("close");
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
if (matchesKey(data, "escape")) {
|
|
46
|
-
if (this.
|
|
47
|
-
this.
|
|
51
|
+
if (this.mode !== "details") {
|
|
52
|
+
this.mode = "details";
|
|
53
|
+
this.inputError = undefined;
|
|
48
54
|
this.requestRender();
|
|
49
55
|
}
|
|
50
|
-
else if (this.
|
|
51
|
-
this.
|
|
52
|
-
this.budgetInputError = undefined;
|
|
56
|
+
else if (this.confirmClose) {
|
|
57
|
+
this.confirmClose = false;
|
|
53
58
|
this.requestRender();
|
|
54
59
|
}
|
|
55
|
-
else
|
|
60
|
+
else
|
|
56
61
|
this.onAction("close");
|
|
57
|
-
}
|
|
58
62
|
return;
|
|
59
63
|
}
|
|
60
|
-
if (
|
|
64
|
+
if (this.mode === "create" || this.mode === "edit") {
|
|
65
|
+
this.handleTextForm(data);
|
|
61
66
|
return;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
}
|
|
67
|
+
}
|
|
68
|
+
if (this.mode === "budget") {
|
|
69
|
+
this.handleBudgetForm(data);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (this.mode === "snooze") {
|
|
73
|
+
this.handleSnoozeForm(data);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!this.goal) {
|
|
77
|
+
if (key === "n" || matchesKey(data, "enter"))
|
|
78
|
+
this.openTextForm("create");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.mode === "checkpoint") {
|
|
82
|
+
if (matchesKey(data, "down"))
|
|
83
|
+
this.checkpointOffset = Math.min(Math.max(0, this.checkpointLineCount - 8), this.checkpointOffset + 1);
|
|
84
|
+
else if (matchesKey(data, "up"))
|
|
85
|
+
this.checkpointOffset = Math.max(0, this.checkpointOffset - 1);
|
|
102
86
|
this.requestRender();
|
|
103
87
|
return;
|
|
104
88
|
}
|
|
105
|
-
if (this.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.
|
|
89
|
+
if (!this.confirmClose && this.checkpoints.length) {
|
|
90
|
+
if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
|
|
91
|
+
const backwards = matchesKey(data, "shift+tab");
|
|
92
|
+
this.selectedCheckpoint =
|
|
93
|
+
this.selectedCheckpoint < 0
|
|
94
|
+
? backwards
|
|
95
|
+
? this.checkpoints.length - 1
|
|
96
|
+
: 0
|
|
97
|
+
: (this.selectedCheckpoint + (backwards ? -1 : 1) + this.checkpoints.length) %
|
|
98
|
+
this.checkpoints.length;
|
|
99
|
+
this.requestRender();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (matchesKey(data, "enter") && this.selectedCheckpoint >= 0) {
|
|
103
|
+
this.mode = "checkpoint";
|
|
104
|
+
this.checkpointOffset = 0;
|
|
105
|
+
this.requestRender();
|
|
106
|
+
return;
|
|
109
107
|
}
|
|
108
|
+
}
|
|
109
|
+
if (this.confirmClose) {
|
|
110
|
+
if (key === "x")
|
|
111
|
+
this.onAction("closeGoal");
|
|
110
112
|
else {
|
|
111
|
-
this.
|
|
113
|
+
this.confirmClose = false;
|
|
112
114
|
this.requestRender();
|
|
113
115
|
}
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
|
-
if (key === "
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
this.
|
|
118
|
+
if (key === "e")
|
|
119
|
+
this.openTextForm("edit");
|
|
120
|
+
else if (key === "b" && this.goal.status !== "complete")
|
|
121
|
+
this.openBudgetForm();
|
|
122
|
+
else if (key === "s" && this.goal.status !== "complete") {
|
|
123
|
+
this.mode = "snooze";
|
|
124
|
+
this.inputError = undefined;
|
|
123
125
|
this.requestRender();
|
|
124
126
|
}
|
|
125
|
-
else if (key === "
|
|
126
|
-
this.
|
|
127
|
+
else if (key === "x") {
|
|
128
|
+
this.confirmClose = true;
|
|
129
|
+
this.requestRender();
|
|
127
130
|
}
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
}
|
|
132
|
+
openTextForm(mode) {
|
|
133
|
+
this.mode = mode;
|
|
134
|
+
this.textField = "name";
|
|
135
|
+
this.name = mode === "edit" && this.goal ? (this.goal.name ?? this.goal.objective) : "";
|
|
136
|
+
this.objective = mode === "edit" && this.goal ? this.goal.objective : "";
|
|
137
|
+
this.initialName = this.name;
|
|
138
|
+
this.initialObjective = this.objective;
|
|
139
|
+
if (mode === "create") {
|
|
140
|
+
this.budgetTurns = "";
|
|
141
|
+
this.budgetRuntime = "";
|
|
142
|
+
}
|
|
143
|
+
this.inputError = undefined;
|
|
144
|
+
this.requestRender();
|
|
145
|
+
}
|
|
146
|
+
handleTextForm(data) {
|
|
147
|
+
if (matchesKey(data, "tab") || matchesKey(data, "down")) {
|
|
148
|
+
const fields = this.mode === "create" ? ["name", "objective", "turns", "runtime"] : ["name", "objective"];
|
|
149
|
+
this.textField = fields[(fields.indexOf(this.textField) + 1) % fields.length];
|
|
150
|
+
}
|
|
151
|
+
else if (matchesKey(data, "up")) {
|
|
152
|
+
const fields = this.mode === "create" ? ["name", "objective", "turns", "runtime"] : ["name", "objective"];
|
|
153
|
+
this.textField =
|
|
154
|
+
fields[(fields.indexOf(this.textField) + fields.length - 1) % fields.length];
|
|
155
|
+
}
|
|
156
|
+
else if (matchesKey(data, "backspace")) {
|
|
157
|
+
if (this.textField === "name")
|
|
158
|
+
this.name = this.name.slice(0, -1);
|
|
159
|
+
else if (this.textField === "objective")
|
|
160
|
+
this.objective = this.objective.slice(0, -1);
|
|
161
|
+
else if (this.textField === "turns")
|
|
162
|
+
this.budgetTurns = this.budgetTurns.slice(0, -1);
|
|
163
|
+
else
|
|
164
|
+
this.budgetRuntime = this.budgetRuntime.slice(0, -1);
|
|
130
165
|
}
|
|
131
|
-
else if (
|
|
132
|
-
|
|
166
|
+
else if (matchesKey(data, "enter")) {
|
|
167
|
+
const name = this.name.trim();
|
|
168
|
+
const objective = this.objective.trim();
|
|
169
|
+
const maxIterations = this.budgetTurns ? Number(this.budgetTurns) : undefined;
|
|
170
|
+
const maxRuntimeMs = this.budgetRuntime ? parseDuration(this.budgetRuntime) : undefined;
|
|
171
|
+
if (!name || !objective)
|
|
172
|
+
this.inputError = "Name and objective are required";
|
|
173
|
+
else if (maxIterations !== undefined &&
|
|
174
|
+
(!Number.isInteger(maxIterations) || maxIterations <= 0))
|
|
175
|
+
this.inputError = "Turns must be a positive integer";
|
|
176
|
+
else if (this.budgetRuntime && maxRuntimeMs === undefined)
|
|
177
|
+
this.inputError = "Runtime must use m, h, or d";
|
|
178
|
+
else if (this.mode === "create")
|
|
179
|
+
this.onAction({ type: "create", name, objective, maxIterations, maxRuntimeMs });
|
|
180
|
+
else {
|
|
181
|
+
const update = {
|
|
182
|
+
...(this.name === this.initialName ? {} : { name }),
|
|
183
|
+
...(this.objective === this.initialObjective ? {} : { objective }),
|
|
184
|
+
};
|
|
185
|
+
if (update.name === undefined && update.objective === undefined)
|
|
186
|
+
this.inputError = "Change the name or objective before saving";
|
|
187
|
+
else
|
|
188
|
+
this.onAction({ type: "edit", ...update });
|
|
189
|
+
}
|
|
133
190
|
this.requestRender();
|
|
191
|
+
return;
|
|
134
192
|
}
|
|
135
|
-
else if (
|
|
136
|
-
|
|
193
|
+
else if (data.length > 0 &&
|
|
194
|
+
Array.from(data).every((character) => {
|
|
195
|
+
const code = character.codePointAt(0) ?? 0;
|
|
196
|
+
return code > 31 && code !== 127;
|
|
197
|
+
})) {
|
|
198
|
+
if (this.textField === "name")
|
|
199
|
+
this.name += data;
|
|
200
|
+
else if (this.textField === "objective")
|
|
201
|
+
this.objective += data;
|
|
202
|
+
else if (this.textField === "turns" && /^\d+$/.test(data))
|
|
203
|
+
this.budgetTurns += data;
|
|
204
|
+
else if (this.textField === "runtime" && /^[0-9mhd]+$/i.test(data))
|
|
205
|
+
this.budgetRuntime += data;
|
|
206
|
+
else
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
return;
|
|
211
|
+
this.inputError = undefined;
|
|
212
|
+
this.requestRender();
|
|
213
|
+
}
|
|
214
|
+
openBudgetForm() {
|
|
215
|
+
this.mode = "budget";
|
|
216
|
+
this.budgetField = "turns";
|
|
217
|
+
this.budgetTurns = this.goal?.budget.maxIterations?.toString() ?? "";
|
|
218
|
+
this.budgetRuntime = this.goal?.budget.maxRuntimeMs
|
|
219
|
+
? `${Math.round(this.goal.budget.maxRuntimeMs / 60_000)}m`
|
|
220
|
+
: "";
|
|
221
|
+
this.inputError = undefined;
|
|
222
|
+
this.requestRender();
|
|
223
|
+
}
|
|
224
|
+
handleBudgetForm(data) {
|
|
225
|
+
if (data.toLowerCase() === "o") {
|
|
226
|
+
this.onAction({ type: "budget", disabled: true });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (matchesKey(data, "tab") || matchesKey(data, "up") || matchesKey(data, "down")) {
|
|
230
|
+
this.budgetField = this.budgetField === "turns" ? "runtime" : "turns";
|
|
231
|
+
}
|
|
232
|
+
else if (matchesKey(data, "backspace")) {
|
|
233
|
+
if (this.budgetField === "turns")
|
|
234
|
+
this.budgetTurns = this.budgetTurns.slice(0, -1);
|
|
235
|
+
else
|
|
236
|
+
this.budgetRuntime = this.budgetRuntime.slice(0, -1);
|
|
237
|
+
}
|
|
238
|
+
else if (matchesKey(data, "enter")) {
|
|
239
|
+
const maxIterations = this.budgetTurns ? Number(this.budgetTurns) : undefined;
|
|
240
|
+
const maxRuntimeMs = this.budgetRuntime ? parseDuration(this.budgetRuntime) : undefined;
|
|
241
|
+
if (maxIterations !== undefined && (!Number.isInteger(maxIterations) || maxIterations <= 0))
|
|
242
|
+
this.inputError = "Turns must be a positive integer";
|
|
243
|
+
else if (this.budgetRuntime && maxRuntimeMs === undefined)
|
|
244
|
+
this.inputError = "Runtime must use m, h, or d (for example 2h)";
|
|
245
|
+
else if (maxIterations === undefined && maxRuntimeMs === undefined)
|
|
246
|
+
this.inputError = "Set a limit or press o to turn limits off";
|
|
247
|
+
else
|
|
248
|
+
this.onAction({ type: "budget", maxIterations, maxRuntimeMs });
|
|
137
249
|
this.requestRender();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
else if (/^[0-9mhd]+$/i.test(data)) {
|
|
253
|
+
if (this.budgetField === "turns" && /^\d+$/.test(data))
|
|
254
|
+
this.budgetTurns += data;
|
|
255
|
+
else if (this.budgetField === "runtime")
|
|
256
|
+
this.budgetRuntime += data;
|
|
257
|
+
else
|
|
258
|
+
return;
|
|
138
259
|
}
|
|
260
|
+
else
|
|
261
|
+
return;
|
|
262
|
+
this.inputError = undefined;
|
|
263
|
+
this.requestRender();
|
|
264
|
+
}
|
|
265
|
+
handleSnoozeForm(data) {
|
|
266
|
+
if (matchesKey(data, "backspace"))
|
|
267
|
+
this.snoozeDuration = this.snoozeDuration.slice(0, -1);
|
|
268
|
+
else if (matchesKey(data, "enter")) {
|
|
269
|
+
const durationMs = parseDuration(this.snoozeDuration);
|
|
270
|
+
if (durationMs === undefined)
|
|
271
|
+
this.inputError = "Duration must use m, h, or d";
|
|
272
|
+
else
|
|
273
|
+
this.onAction({ type: "snooze", durationMs });
|
|
274
|
+
this.requestRender();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
else if (/^[0-9mhd]+$/i.test(data))
|
|
278
|
+
this.snoozeDuration += data;
|
|
279
|
+
else
|
|
280
|
+
return;
|
|
281
|
+
this.inputError = undefined;
|
|
282
|
+
this.requestRender();
|
|
139
283
|
}
|
|
140
284
|
render(width) {
|
|
141
285
|
if (width < 8)
|
|
142
286
|
return [truncateToWidth("Goal", Math.max(0, width), "")];
|
|
143
287
|
const innerWidth = width - 2;
|
|
144
288
|
const contentWidth = Math.max(1, innerWidth - 2);
|
|
145
|
-
const
|
|
146
|
-
const border = (text) => this.theme.fg(borderColor, text);
|
|
289
|
+
const border = (text) => this.theme.fg("borderAccent", text);
|
|
147
290
|
const row = (content = "") => {
|
|
148
291
|
const truncated = truncateToWidth(content, innerWidth, "", true);
|
|
149
292
|
return `${border("│")}${truncated}${" ".repeat(Math.max(0, innerWidth - visibleWidth(truncated)))}${border("│")}`;
|
|
150
293
|
};
|
|
151
|
-
const title = this.theme.fg("accent", this.theme.bold(
|
|
152
|
-
const titleWidth = visibleWidth(title);
|
|
294
|
+
const title = this.theme.fg("accent", this.theme.bold(` Goal${this.mode === "details" ? "" : ` · ${this.mode}`} `));
|
|
153
295
|
const lines = [
|
|
154
|
-
`${border("╭")}${title}${border(`${"─".repeat(Math.max(0, innerWidth -
|
|
296
|
+
`${border("╭")}${title}${border(`${"─".repeat(Math.max(0, innerWidth - visibleWidth(title)))}╮`)}`,
|
|
155
297
|
];
|
|
156
|
-
if (!this.goal) {
|
|
298
|
+
if (!this.goal && this.mode === "details") {
|
|
157
299
|
lines.push(row(), row(` ${this.theme.fg("muted", "No goal for this session.")}`));
|
|
158
|
-
lines.push(row(` ${this.theme.fg("dim", "
|
|
159
|
-
lines.push(row(` ${this.theme.fg("dim", "esc · q close")}`));
|
|
300
|
+
lines.push(row(` ${this.theme.fg("dim", "n · enter create · esc close")}`));
|
|
160
301
|
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
161
302
|
return lines;
|
|
162
303
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
lines.push(row(` Tokens ${compactNumber(this.goal.usage.tokens)}`));
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
const tokenBar = progressBar(this.goal.usage.tokens, this.goal.budget.maxTokens, Math.min(12, Math.max(4, contentWidth - 23)));
|
|
182
|
-
lines.push(row(` Tokens ${this.theme.fg("accent", tokenBar)} ${compactNumber(this.goal.usage.tokens)}/${compactNumber(this.goal.budget.maxTokens)}`));
|
|
183
|
-
}
|
|
184
|
-
if (this.goal.budget.maxRuntimeMs !== undefined) {
|
|
185
|
-
const end = this.goal.status === "active" ? this.now() : Date.parse(this.goal.updatedAt);
|
|
186
|
-
const elapsed = Math.max(0, end - Date.parse(this.goal.createdAt));
|
|
187
|
-
const runtimeBar = progressBar(elapsed, this.goal.budget.maxRuntimeMs, Math.min(12, Math.max(4, contentWidth - 23)));
|
|
188
|
-
lines.push(row(` Time ${this.theme.fg("accent", runtimeBar)} ${Math.floor(elapsed / 60_000)}m/${Math.ceil(this.goal.budget.maxRuntimeMs / 60_000)}m`));
|
|
304
|
+
if (this.mode === "create" || this.mode === "edit") {
|
|
305
|
+
const displayedName = displayGoalText(this.name, 500);
|
|
306
|
+
const displayedObjective = displayGoalText(this.objective, 500);
|
|
307
|
+
const objectivePrefix = ` ${this.textField === "objective" ? "›" : " "} Objective `;
|
|
308
|
+
const objectiveLines = wrapTextWithAnsi(displayedObjective || "_", Math.max(1, innerWidth - visibleWidth(objectivePrefix))).slice(0, 4);
|
|
309
|
+
lines.push(row(` ${this.textField === "name" ? "›" : " "} Name ${displayedName || "_"}`), ...objectiveLines.map((line, index) => row(`${index === 0 ? objectivePrefix : " ".repeat(visibleWidth(objectivePrefix))}${line}`)), ...(this.mode === "create"
|
|
310
|
+
? [
|
|
311
|
+
row(` ${this.textField === "turns" ? "›" : " "} Turns ${this.budgetTurns || "off"}`),
|
|
312
|
+
row(` ${this.textField === "runtime" ? "›" : " "} Runtime ${this.budgetRuntime || "off"}`),
|
|
313
|
+
]
|
|
314
|
+
: []), row(), row(` ${this.theme.fg("dim", "tab field · enter save · esc cancel")}`));
|
|
315
|
+
if (this.mode === "edit")
|
|
316
|
+
lines.push(row(` ${this.theme.fg("warning", "Objective applies on the next continuation.")}`));
|
|
317
|
+
this.finish(lines, row, border, innerWidth);
|
|
318
|
+
return lines;
|
|
189
319
|
}
|
|
190
|
-
if (this.
|
|
191
|
-
|
|
320
|
+
if (this.mode === "checkpoint") {
|
|
321
|
+
const checkpoint = this.checkpoints[this.selectedCheckpoint];
|
|
322
|
+
const details = [];
|
|
323
|
+
for (const [label, value] of [
|
|
324
|
+
["Summary", checkpoint.summary],
|
|
325
|
+
["Evidence", checkpoint.evidence],
|
|
326
|
+
["Next", checkpoint.nextStep],
|
|
327
|
+
["Blocker", checkpoint.blocker],
|
|
328
|
+
]) {
|
|
329
|
+
if (value)
|
|
330
|
+
details.push(...wrapTextWithAnsi(`${label}: ${displayGoalText(value, value.length)}`, contentWidth));
|
|
331
|
+
}
|
|
332
|
+
this.checkpointLineCount = details.length;
|
|
333
|
+
this.checkpointOffset = Math.min(this.checkpointOffset, Math.max(0, details.length - 8));
|
|
334
|
+
lines.push(...details
|
|
335
|
+
.slice(this.checkpointOffset, this.checkpointOffset + 8)
|
|
336
|
+
.map((line) => row(` ${line}`)));
|
|
337
|
+
lines.push(row(), row(" ↑↓ scroll · esc back"));
|
|
338
|
+
this.finish(lines, row, border, innerWidth);
|
|
339
|
+
return lines;
|
|
192
340
|
}
|
|
193
|
-
|
|
194
|
-
lines.push(row(), row(` ${this.
|
|
341
|
+
if (this.mode === "budget") {
|
|
342
|
+
lines.push(row(` › Limits are opt-in and stop continuation, not in-flight work.`), row(` ${this.budgetField === "turns" ? "›" : " "} Turns ${this.budgetTurns || "off"}`), row(` ${this.budgetField === "runtime" ? "›" : " "} Runtime ${this.budgetRuntime || "off"}`), row(), row(` ${this.theme.fg("dim", "tab field · enter save · o off · esc cancel")}`));
|
|
343
|
+
this.finish(lines, row, border, innerWidth);
|
|
344
|
+
return lines;
|
|
195
345
|
}
|
|
196
|
-
if (this.
|
|
197
|
-
lines.push(row(` ${this.
|
|
346
|
+
if (this.mode === "snooze") {
|
|
347
|
+
lines.push(row(` Snooze for ${this.snoozeDuration || "_"}`), row(` ${this.theme.fg("dim", "Automatically continues when due · enter save · esc cancel")}`));
|
|
348
|
+
this.finish(lines, row, border, innerWidth);
|
|
349
|
+
return lines;
|
|
198
350
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
351
|
+
const goal = this.goal;
|
|
352
|
+
const status = goal.snoozedUntil
|
|
353
|
+
? `SNOOZED UNTIL ${goal.snoozedUntil}`
|
|
354
|
+
: goal.status.toUpperCase();
|
|
355
|
+
lines.push(row(` ${this.theme.fg(goal.status === "complete" ? "success" : "accent", `● ${status}`)}`));
|
|
356
|
+
lines.push(row(` ${this.theme.bold(goalDisplayName(goal))}`));
|
|
357
|
+
for (const objectiveLine of wrapTextWithAnsi(displayGoalText(goal.objective, 500), contentWidth).slice(0, 3))
|
|
358
|
+
lines.push(row(` ${objectiveLine}`));
|
|
359
|
+
const elapsedEnd = goal.status === "active" ? this.now() : Date.parse(goal.updatedAt);
|
|
360
|
+
lines.push(row(` ${this.theme.fg("muted", `Elapsed ${formatElapsed(elapsedEnd - Date.parse(goal.createdAt))}`)}`));
|
|
361
|
+
const limits = [
|
|
362
|
+
goal.budget.maxIterations === undefined
|
|
363
|
+
? undefined
|
|
364
|
+
: `${goal.usage.iterations}/${goal.budget.maxIterations} turns`,
|
|
365
|
+
goal.budget.maxRuntimeMs === undefined
|
|
366
|
+
? undefined
|
|
367
|
+
: `${Math.round(goal.budget.maxRuntimeMs / 60_000)}m runtime`,
|
|
368
|
+
].filter((value) => value !== undefined);
|
|
369
|
+
lines.push(row(` ${this.theme.fg("muted", `Limits ${limits.length ? limits.join(" · ") : "off"}`)}`));
|
|
370
|
+
if (this.claim)
|
|
371
|
+
lines.push(row(` ${this.theme.fg("muted", `Continuation ${this.claim.state}`)}`));
|
|
372
|
+
if (this.checkpoints.length) {
|
|
373
|
+
lines.push(row(), row(` ${this.theme.fg("accent", "Checkpoints · agent-reported")}`));
|
|
374
|
+
const start = Math.max(0, this.selectedCheckpoint - 2);
|
|
375
|
+
const shown = this.checkpoints.slice(start, start + 3);
|
|
376
|
+
for (const checkpoint of shown) {
|
|
377
|
+
lines.push(row(` ${checkpoint === this.checkpoints[this.selectedCheckpoint] ? "›" : " "} ${checkpoint.createdAt.slice(11, 16)} · ${displayGoalText(checkpoint.summary, contentWidth - 12)}`));
|
|
378
|
+
}
|
|
379
|
+
lines.push(row(` ${this.theme.fg("dim", "tab/shift+tab select · enter open")}`));
|
|
203
380
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
this.goal.status === "paused" || this.goal.status === "blocked" ? "r resume" : undefined,
|
|
210
|
-
this.goal.status !== "complete" ? "b budget" : undefined,
|
|
211
|
-
this.goal.status !== "complete" ? "c complete" : undefined,
|
|
212
|
-
"x clear",
|
|
213
|
-
"q close",
|
|
214
|
-
].filter((action) => action !== undefined);
|
|
215
|
-
const footer = this.pendingConfirmation
|
|
216
|
-
? `${this.pendingConfirmation === "complete" ? "c" : "x"} again to confirm ${this.pendingConfirmation} · esc cancel`
|
|
217
|
-
: this.budgetField
|
|
218
|
-
? "digits edit · tab field · enter save · esc cancel"
|
|
219
|
-
: actions.join(" · ");
|
|
381
|
+
const footer = this.confirmClose
|
|
382
|
+
? "x again to close goal · esc cancel"
|
|
383
|
+
: goal.status === "complete"
|
|
384
|
+
? "x close goal · esc close overlay"
|
|
385
|
+
: "e edit · b limits · s snooze · x close goal · esc close overlay";
|
|
220
386
|
lines.push(row(), row(` ${this.theme.fg("dim", footer)}`));
|
|
221
|
-
|
|
387
|
+
this.finish(lines, row, border, innerWidth);
|
|
222
388
|
return lines;
|
|
223
389
|
}
|
|
390
|
+
finish(lines, row, border, innerWidth) {
|
|
391
|
+
const error = this.inputError ?? this.actionError;
|
|
392
|
+
if (error)
|
|
393
|
+
lines.push(row(` ${this.theme.fg("error", displayGoalText(error, innerWidth - 2))}`));
|
|
394
|
+
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
395
|
+
}
|
|
224
396
|
invalidate() { }
|
|
397
|
+
dispose() {
|
|
398
|
+
if (!this.refreshTimer)
|
|
399
|
+
return;
|
|
400
|
+
clearInterval(this.refreshTimer);
|
|
401
|
+
this.refreshTimer = undefined;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
export function parseDuration(value) {
|
|
405
|
+
const match = value
|
|
406
|
+
.trim()
|
|
407
|
+
.toLowerCase()
|
|
408
|
+
.match(/^(\d+)(m|h|d)$/);
|
|
409
|
+
if (!match)
|
|
410
|
+
return undefined;
|
|
411
|
+
const amount = Number(match[1]);
|
|
412
|
+
if (!Number.isSafeInteger(amount) || amount <= 0)
|
|
413
|
+
return undefined;
|
|
414
|
+
return amount * (match[2] === "m" ? 60_000 : match[2] === "h" ? 3_600_000 : 86_400_000);
|
|
225
415
|
}
|
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,
|
|
5
|
+
export { GoalWindow, parseDuration, type GoalWindowAction } 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";
|