pi-goal-x 0.26.3 → 0.27.1
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 +24 -0
- package/docs/unified-dashboard.md +5 -0
- package/extensions/goal-auditor.ts +5 -3
- package/extensions/goal-commands.ts +113 -4
- package/extensions/goal-compaction.ts +20 -6
- package/extensions/goal-contract.ts +1 -1
- package/extensions/goal-core.ts +1 -1
- package/extensions/goal-drafting.ts +1 -1
- package/extensions/goal-events.ts +2 -2
- package/extensions/goal-format.ts +1 -1
- package/extensions/goal-ledger.ts +465 -20
- package/extensions/goal-questionnaire.ts +160 -42
- package/extensions/goal-recovery.ts +252 -0
- package/extensions/goal-settings.ts +78 -1
- package/extensions/goal-task-confirmation.ts +1 -1
- package/extensions/goal-task-derive.ts +2 -2
- package/extensions/goal-widget.ts +10 -8
- package/extensions/storage/goal-files.ts +39 -3
- package/extensions/widgets/goal-dashboard-renderer.ts +19 -9
- package/extensions/widgets/goal-escape-dialog.ts +1 -1
- package/extensions/widgets/goal-widget.ts +5 -4
- package/extensions/widgets/task-list-overlay.ts +4 -4
- package/package.json +16 -9
- package/docs/goal-ts-refactor-test-strategy.md +0 -88
package/README.md
CHANGED
|
@@ -183,6 +183,12 @@ Run read-only storage/runtime health checks:
|
|
|
183
183
|
/goal-status health
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
Re-read goal storage caches (pool, ledger, settings) from disk and report what changed — picks up external edits to `.pi` files without file watchers:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
/goal-refresh
|
|
190
|
+
```
|
|
191
|
+
|
|
186
192
|
Select an open goal for the current session:
|
|
187
193
|
|
|
188
194
|
```text
|
|
@@ -348,6 +354,8 @@ Each session can focus on one goal while the project keeps other goals open.
|
|
|
348
354
|
/goal-status Show the focused goal (unified dashboard)
|
|
349
355
|
/goal-status verbose Show the focused goal with full diagnostic detail
|
|
350
356
|
/goal-status health Check goal storage/runtime health (read-only)
|
|
357
|
+
/goal-refresh Re-read storage caches and report external changes
|
|
358
|
+
/goal-recovery Read-only recovery report; `/goal-recovery repair` fixes stale locks + snapshot after confirmation
|
|
351
359
|
/goal-focus Select an open goal
|
|
352
360
|
/goal-unfocus Remove the session’s focus
|
|
353
361
|
/goal-tweak <change> Revise the focused goal
|
|
@@ -368,6 +376,22 @@ Settings are stored in:
|
|
|
368
376
|
|
|
369
377
|
Use `/goal-settings` to configure task lists, verification contracts, subtask depth, automatic goal selection, and completion auditing. Goal objectives have no hard length limit by default; set `objectiveMaxChars` (or `PI_GOAL_OBJECTIVE_MAX_CHARS`, `0` = no limit) to cap objective length across `create_goal`, `propose_goal_draft`, and `/goal-tweak`.
|
|
370
378
|
|
|
379
|
+
Configure the task shortcuts in the same file when the terminal captures the defaults:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"keybindings": {
|
|
384
|
+
"dashboard": {
|
|
385
|
+
"toggleExpand": "ctrl+shift+t",
|
|
386
|
+
"scrollUp": "ctrl+shift+up",
|
|
387
|
+
"scrollDown": "ctrl+shift+down"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The default task bindings are `ctrl+shift+t`, `ctrl+shift+up`, and `ctrl+shift+down`. Use pi key names such as `ctrl+shift+up`.
|
|
394
|
+
|
|
371
395
|
## License
|
|
372
396
|
|
|
373
397
|
MIT
|
|
@@ -227,6 +227,11 @@ auditor's findings, and returns to the normal dashboard so work can continue.
|
|
|
227
227
|
| `Esc` | Collapse the expanded dashboard; otherwise pause the goal |
|
|
228
228
|
| `Esc` (during audit) | Stop the audit and choose to continue working or complete without audit |
|
|
229
229
|
|
|
230
|
+
Configure the three task shortcuts with `keybindings.dashboard.toggleExpand`,
|
|
231
|
+
`keybindings.dashboard.scrollUp`, and `keybindings.dashboard.scrollDown` in
|
|
232
|
+
`.pi/pi-goal-x-settings.json`. The defaults are `ctrl+shift+t`,
|
|
233
|
+
`ctrl+shift+up`, and `ctrl+shift+down`.
|
|
234
|
+
|
|
230
235
|
## Width behavior
|
|
231
236
|
|
|
232
237
|
Layout modes (§5.5):
|
|
@@ -217,6 +217,7 @@ function makeAuditorResourceLoader(): ResourceLoader {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- pi SDK types Model<any> idiomatically (pi-coding-agent sdk.d.ts)
|
|
220
221
|
export function resolveAuditorModel(ctx: ExtensionContext, config: GoalSettings): { model: Model<any> | undefined; error?: string } {
|
|
221
222
|
if (!config.model && !config.provider) return { model: ctx.model };
|
|
222
223
|
if (config.provider && config.model) {
|
|
@@ -268,6 +269,7 @@ export function resolveAuditorSessionModelOptions(ctx: ExtensionContext): {
|
|
|
268
269
|
return { modelRegistry: ctx.modelRegistry };
|
|
269
270
|
}
|
|
270
271
|
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- pi SDK types Model<any> idiomatically (pi-coding-agent sdk.d.ts)
|
|
271
273
|
function modelLabel(model: Model<any> | undefined): string | undefined {
|
|
272
274
|
return model ? `${model.provider}/${model.id}` : undefined;
|
|
273
275
|
}
|
|
@@ -379,7 +381,7 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
379
381
|
}
|
|
380
382
|
if (event.type === "message_update") {
|
|
381
383
|
// Check for thinking events from the assistant stream
|
|
382
|
-
const streamEvent = (event as
|
|
384
|
+
const streamEvent = (event as { assistantMessageEvent?: { type?: string } }).assistantMessageEvent;
|
|
383
385
|
if (streamEvent?.type === "thinking_start") {
|
|
384
386
|
progress.phase = "thinking";
|
|
385
387
|
if (!progress.label) progress.label = "Analyzing goal...";
|
|
@@ -393,7 +395,7 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
393
395
|
}
|
|
394
396
|
// For text content, show producing_report phase
|
|
395
397
|
progress.phase = "producing_report";
|
|
396
|
-
const message = event.message as
|
|
398
|
+
const message = event.message as { role?: string; content?: Array<{ type?: string; text?: string }> };
|
|
397
399
|
if (message?.role === "assistant") {
|
|
398
400
|
for (const part of message.content ?? []) {
|
|
399
401
|
if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
@@ -407,7 +409,7 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
407
409
|
return;
|
|
408
410
|
}
|
|
409
411
|
if (event.type !== "message_end") return;
|
|
410
|
-
const message = event.message as
|
|
412
|
+
const message = event.message as { role?: string; content?: Array<{ type?: string; text?: string }> };
|
|
411
413
|
if (message.role !== "assistant") return;
|
|
412
414
|
for (const part of message.content ?? []) {
|
|
413
415
|
if (part.type === "text" && typeof part.text === "string") outputParts.push(part.text);
|
|
@@ -17,12 +17,45 @@ import {
|
|
|
17
17
|
otherOpenGoalCount,
|
|
18
18
|
} from "./goal-pool.ts";
|
|
19
19
|
import { clearGoalCommandMessage, validateResumeGoal } from "./goal-policy.ts";
|
|
20
|
-
import { readGoalLedger } from "./goal-ledger.ts";
|
|
20
|
+
import { invalidateGoalLedgerCache, readGoalLedger } from "./goal-ledger.ts";
|
|
21
21
|
import { buildGoalStatusText } from "./goal-status.ts";
|
|
22
|
-
import { effectiveSettingsReport, envOverrideFor } from "./goal-settings.ts";
|
|
23
|
-
import { mergeGoalPromptFromDisk } from "./storage/goal-files.ts";
|
|
22
|
+
import { effectiveSettingsReport, envOverrideFor, invalidateGoalSettingsCache } from "./goal-settings.ts";
|
|
23
|
+
import { invalidateGoalPoolCache, mergeGoalPromptFromDisk, readActiveGoalPool } from "./storage/goal-files.ts";
|
|
24
24
|
import { nowIso, type GoalMode, type GoalRecord } from "./goal-record.ts";
|
|
25
25
|
import { clearGoalDrafting, hasActiveDraft, startGoalDrafting } from "./goal-drafting.ts";
|
|
26
|
+
import { formatRecoveryReport, runRecoveryReport, runRecoveryRepair } from "./goal-recovery.ts";
|
|
27
|
+
|
|
28
|
+
export interface GoalRefreshState {
|
|
29
|
+
poolIds: Iterable<string>;
|
|
30
|
+
ledgerEvents: number;
|
|
31
|
+
ledgerMalformed: number;
|
|
32
|
+
/** Stable fingerprint of the effective settings (JSON of the parsed file). */
|
|
33
|
+
settings: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pure diff of a goal-refresh cycle (before invalidation vs after re-read).
|
|
38
|
+
* Unit-testable without the command harness; the command renders the changes.
|
|
39
|
+
*/
|
|
40
|
+
export function diffGoalRefreshState(before: GoalRefreshState, after: GoalRefreshState): string[] {
|
|
41
|
+
const changes: string[] = [];
|
|
42
|
+
const beforeIds = new Set(before.poolIds);
|
|
43
|
+
const afterIds = new Set(after.poolIds);
|
|
44
|
+
const added = [...afterIds].filter((id) => !beforeIds.has(id)).sort();
|
|
45
|
+
const removed = [...beforeIds].filter((id) => !afterIds.has(id)).sort();
|
|
46
|
+
if (added.length > 0) changes.push(`pool: ${added.length} goal(s) added — ${added.join(", ")}`);
|
|
47
|
+
if (removed.length > 0) changes.push(`pool: ${removed.length} goal(s) removed — ${removed.join(", ")}`);
|
|
48
|
+
if (after.ledgerEvents !== before.ledgerEvents) {
|
|
49
|
+
changes.push(`ledger: ${before.ledgerEvents} -> ${after.ledgerEvents} events`);
|
|
50
|
+
}
|
|
51
|
+
if (after.ledgerMalformed !== before.ledgerMalformed) {
|
|
52
|
+
changes.push(`ledger: malformed entries ${before.ledgerMalformed} -> ${after.ledgerMalformed}`);
|
|
53
|
+
}
|
|
54
|
+
if (before.settings !== after.settings) {
|
|
55
|
+
changes.push("settings: effective settings changed (external edit)");
|
|
56
|
+
}
|
|
57
|
+
return changes;
|
|
58
|
+
}
|
|
26
59
|
import {
|
|
27
60
|
AUDITOR_THINKING_LEVELS,
|
|
28
61
|
buildAuditorModelChoices,
|
|
@@ -157,6 +190,68 @@ export function registerGoalCommands(core: GoalCore): void {
|
|
|
157
190
|
core.replaceGoal({ objective, autoContinue: true, sisyphus: mode === "sisyphus" }, ctx, true, verificationContract);
|
|
158
191
|
}
|
|
159
192
|
|
|
193
|
+
async function runGoalRecovery(rawArgs: string, ctx: ExtensionContext): Promise<void> {
|
|
194
|
+
const report = runRecoveryReport({ cwd: ctx.cwd });
|
|
195
|
+
if (/^repair$/i.test(rawArgs)) {
|
|
196
|
+
const result = await runRecoveryRepair({ cwd: ctx.cwd }, report, async () => {
|
|
197
|
+
const confirmed = await ctx.ui.confirm(`Remove ${report.staleLocks.length} stale lock(s) and refresh the pool snapshot?`, `Files are backed up to .pi/goals/.recovery-backup first.`);
|
|
198
|
+
return confirmed === true;
|
|
199
|
+
});
|
|
200
|
+
if (result.confirmed) {
|
|
201
|
+
ctx.ui.notify(result.applied.length > 0
|
|
202
|
+
? `goal-recovery repair: ${result.applied.length} operation(s) applied.\n${result.applied.map((a) => ` - ${a}`).join("\n")}\nBackup: ${result.backupDir}`
|
|
203
|
+
: "goal-recovery repair: nothing to repair.", "info");
|
|
204
|
+
} else {
|
|
205
|
+
ctx.ui.notify("goal-recovery repair: cancelled — nothing changed.", "info");
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
ctx.ui.notify(formatRecoveryReport(report), report.healthy ? "info" : "warning");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function runGoalRefresh(ctx: ExtensionContext): Promise<void> {
|
|
213
|
+
// Pre-state snapshots are cache-served (0 fs ops); the re-reads after
|
|
214
|
+
// invalidation go cold. This is the explicit user-owned path for picking
|
|
215
|
+
// up external edits — no watchers, no per-turn polling.
|
|
216
|
+
const beforePool = readActiveGoalPool(ctx);
|
|
217
|
+
const beforeLedger = readGoalLedger(ctx);
|
|
218
|
+
// Settings: the before fingerprint is cache-served; the after one is a
|
|
219
|
+
// cold re-read after invalidation — so an external settings edit shows
|
|
220
|
+
// up as a fingerprint difference.
|
|
221
|
+
const beforeSettings = settingsFingerprint(ctx);
|
|
222
|
+
|
|
223
|
+
invalidateGoalPoolCache();
|
|
224
|
+
invalidateGoalLedgerCache();
|
|
225
|
+
invalidateGoalSettingsCache();
|
|
226
|
+
|
|
227
|
+
const afterPool = readActiveGoalPool(ctx);
|
|
228
|
+
const afterLedger = readGoalLedger(ctx);
|
|
229
|
+
const afterSettings = settingsFingerprint(ctx);
|
|
230
|
+
|
|
231
|
+
const changes = diffGoalRefreshState({
|
|
232
|
+
poolIds: beforePool.keys(),
|
|
233
|
+
ledgerEvents: beforeLedger.events.length,
|
|
234
|
+
ledgerMalformed: beforeLedger.malformed,
|
|
235
|
+
settings: beforeSettings,
|
|
236
|
+
}, {
|
|
237
|
+
poolIds: afterPool.keys(),
|
|
238
|
+
ledgerEvents: afterLedger.events.length,
|
|
239
|
+
ledgerMalformed: afterLedger.malformed,
|
|
240
|
+
settings: afterSettings,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const text = changes.length > 0
|
|
244
|
+
? `goal-refresh: re-read caches from disk — ${changes.length} change(s):\n${changes.map((c) => ` - ${c}`).join("\n")}`
|
|
245
|
+
: "goal-refresh: no changes detected — caches were already current.";
|
|
246
|
+
ctx.ui.notify(text, "info");
|
|
247
|
+
core.updateUI(ctx);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Stable fingerprint of the effective settings (cache-served before invalidation). */
|
|
251
|
+
function settingsFingerprint(ctx: ExtensionContext): string {
|
|
252
|
+
return JSON.stringify(loadGoalSettingsFileConfig(ctx.cwd));
|
|
253
|
+
}
|
|
254
|
+
|
|
160
255
|
async function showGoalStatus(rawArgs: string, ctx: ExtensionContext): Promise<void> {
|
|
161
256
|
core.reconcileFocusedGoalFromDisk(ctx);
|
|
162
257
|
if (core.state.goal) core.syncGoalPromptFromDisk(ctx);
|
|
@@ -287,7 +382,9 @@ export function registerGoalCommands(core: GoalCore): void {
|
|
|
287
382
|
if (key === "subtaskDepth") return config.subtaskDepth !== undefined ? String(config.subtaskDepth) : "1";
|
|
288
383
|
if (key === "stallTimeoutMinutes") return config.stallTimeoutMinutes !== undefined ? String(config.stallTimeoutMinutes) : "0";
|
|
289
384
|
if (key === "objectiveMaxChars") return config.objectiveMaxChars !== undefined ? String(config.objectiveMaxChars) : "0";
|
|
290
|
-
return config
|
|
385
|
+
if (key === "keybindings") return config.keybindings ? `${config.keybindings.dashboard.toggleExpand}, ${config.keybindings.dashboard.scrollUp}, ${config.keybindings.dashboard.scrollDown}` : "(default)";
|
|
386
|
+
const value = config[key];
|
|
387
|
+
return typeof value === "string" ? value : "(default)";
|
|
291
388
|
}
|
|
292
389
|
|
|
293
390
|
function settingsLines(config: GoalSettings): string[] {
|
|
@@ -547,6 +644,18 @@ export function registerGoalCommands(core: GoalCore): void {
|
|
|
547
644
|
await showGoalStatus(rawArgs ?? "", ctx);
|
|
548
645
|
},
|
|
549
646
|
});
|
|
647
|
+
pi.registerCommand("goal-refresh", {
|
|
648
|
+
description: "Re-read goal storage caches (pool, ledger, settings) from disk and report what changed. Picks up external edits to .pi files — no file watchers needed.",
|
|
649
|
+
handler: async (_rawArgs, ctx) => {
|
|
650
|
+
await runGoalRefresh(ctx);
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
pi.registerCommand("goal-recovery", {
|
|
654
|
+
description: "Read-only storage/recovery report (malformed goal files, malformed ledger lines, stale locks, orphaned snapshot data). Append \"repair\" to remove stale locks and refresh the pool snapshot after confirmation (with backup).",
|
|
655
|
+
handler: async (rawArgs, ctx) => {
|
|
656
|
+
await runGoalRecovery((rawArgs ?? "").trim(), ctx);
|
|
657
|
+
},
|
|
658
|
+
});
|
|
550
659
|
pi.registerCommand("goal-focus", {
|
|
551
660
|
description: "Choose which open goal this session focuses on.",
|
|
552
661
|
handler: async (_rawArgs, ctx) => {
|
|
@@ -9,10 +9,15 @@ import {
|
|
|
9
9
|
latestEventsForGoal,
|
|
10
10
|
reconstructGoalLedger,
|
|
11
11
|
type GoalLedgerEvent,
|
|
12
|
+
type LedgerStateReadResult,
|
|
12
13
|
} from "./goal-ledger.ts";
|
|
13
14
|
import { type GoalRecord } from "./goal-record.ts";
|
|
14
15
|
|
|
15
|
-
export function buildGoalCompactSummary(
|
|
16
|
+
export function buildGoalCompactSummary(
|
|
17
|
+
goal: GoalRecord,
|
|
18
|
+
events: GoalLedgerEvent[],
|
|
19
|
+
auditorOverride?: { verdict: "approved" | "disapproved" | "error"; report: string; at: string },
|
|
20
|
+
): string {
|
|
16
21
|
const lines: string[] = [];
|
|
17
22
|
lines.push(`Goal ${goal.id} — ${statusLabel(goal)}`);
|
|
18
23
|
lines.push(` Objective: ${truncateText(goal.objective, 200)}`);
|
|
@@ -70,7 +75,7 @@ export function buildGoalCompactSummary(goal: GoalRecord, events: GoalLedgerEven
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
const auditor = latestAuditorResultForGoal(events, goal.id);
|
|
78
|
+
const auditor = auditorOverride ?? latestAuditorResultForGoal(events, goal.id);
|
|
74
79
|
if (auditor && auditor.verdict === "disapproved") {
|
|
75
80
|
lines.push(` Auditor rejection (latest): ${truncateText(auditor.report, 120)}`);
|
|
76
81
|
}
|
|
@@ -88,20 +93,29 @@ export function buildGoalCompactSummary(goal: GoalRecord, events: GoalLedgerEven
|
|
|
88
93
|
export function buildCompactionSummary(args: {
|
|
89
94
|
goalsById: Map<string, GoalRecord>;
|
|
90
95
|
focusedGoalId: string | null;
|
|
91
|
-
ledgerEvents
|
|
96
|
+
ledgerEvents?: GoalLedgerEvent[];
|
|
97
|
+
/** Checkpoint-fed view: uses reconstructed state + per-goal recent tails instead of a full event list. */
|
|
98
|
+
ledgerState?: LedgerStateReadResult;
|
|
92
99
|
capOpenGoals?: number;
|
|
93
100
|
capEventsPerGoal?: number;
|
|
94
101
|
}): string {
|
|
95
|
-
const { goalsById, focusedGoalId, ledgerEvents, capOpenGoals = 20, capEventsPerGoal = 5 } = args;
|
|
102
|
+
const { goalsById, focusedGoalId, ledgerEvents = [], ledgerState, capOpenGoals = 20, capEventsPerGoal = 5 } = args;
|
|
96
103
|
|
|
97
104
|
const lines: string[] = [];
|
|
98
105
|
const openGoals = Array.from(goalsById.values()).filter((g) => g.status !== "complete");
|
|
99
|
-
const reconstructed = reconstructGoalLedger(ledgerEvents);
|
|
106
|
+
const reconstructed = ledgerState?.state ?? reconstructGoalLedger(ledgerEvents);
|
|
107
|
+
const recentEventsFor = (goalId: string, cap: number): GoalLedgerEvent[] => {
|
|
108
|
+
if (ledgerState) return (ledgerState.recentEventsByGoal.get(goalId) ?? []).slice(0, cap);
|
|
109
|
+
return latestEventsForGoal(ledgerEvents, goalId, cap);
|
|
110
|
+
};
|
|
100
111
|
|
|
101
112
|
if (focusedGoalId && goalsById.has(focusedGoalId)) {
|
|
102
113
|
const focused = goalsById.get(focusedGoalId)!;
|
|
114
|
+
const auditor = ledgerState
|
|
115
|
+
? reconstructed.goals.get(focusedGoalId)?.latestAuditorResult ?? reconstructed.terminalGoals.get(focusedGoalId)?.latestAuditorResult
|
|
116
|
+
: undefined;
|
|
103
117
|
lines.push(`[FOCUSED GOAL]`);
|
|
104
|
-
lines.push(buildGoalCompactSummary(focused,
|
|
118
|
+
lines.push(buildGoalCompactSummary(focused, recentEventsFor(focusedGoalId, capEventsPerGoal), auditor));
|
|
105
119
|
lines.push("");
|
|
106
120
|
}
|
|
107
121
|
|
|
@@ -27,7 +27,7 @@ export function extractVerificationContract(objective: string): { objective: str
|
|
|
27
27
|
const trimmed = line.trim();
|
|
28
28
|
const m = VERIFICATION_CONTRACT_RE.exec(trimmed);
|
|
29
29
|
if (m) {
|
|
30
|
-
contract = m[1]
|
|
30
|
+
contract = m[1]!.trim();
|
|
31
31
|
// Skip this line — don't add it to the cleaned objective
|
|
32
32
|
} else {
|
|
33
33
|
filtered.push(line);
|
package/extensions/goal-core.ts
CHANGED
|
@@ -71,7 +71,7 @@ export function statusLabel(goal: Pick<GoalDisplayRecordLike, "sisyphus" | "stat
|
|
|
71
71
|
export function footerStatus(goal: GoalDisplayRecordLike): string {
|
|
72
72
|
const usageBits: string[] = [];
|
|
73
73
|
if (goal.usage.activeSeconds > 0) usageBits.push(formatDuration(goal.usage.activeSeconds));
|
|
74
|
-
if (goal.usage.tokensUsed > 0) usageBits.push(formatTokenValue(goal.usage.tokensUsed).split(" ")[0]);
|
|
74
|
+
if (goal.usage.tokensUsed > 0) usageBits.push(formatTokenValue(goal.usage.tokensUsed).split(" ")[0]!);
|
|
75
75
|
const usage = usageBits.length > 0 ? ` [${usageBits.join(" ")}]` : "";
|
|
76
76
|
const prefix = goal.sisyphus ? "goal✊" : "goal";
|
|
77
77
|
return `${prefix}: ${statusLabel(goal)}${usage} - ${truncateText(goal.objective, 60)}`;
|
|
@@ -276,7 +276,7 @@ export function registerDraftingTools(core: GoalCore): void {
|
|
|
276
276
|
}, { additionalProperties: false }),
|
|
277
277
|
async execute(_id, params, _signal, _update, ctx) {
|
|
278
278
|
if (!activeDraft(core)) return { content: [{ type: "text", text: "No guided goal draft is active." }], details: goalDetails(core.state.goal) };
|
|
279
|
-
const questions: GoalQuestionnaireQuestion[] = params.questions.map((q:
|
|
279
|
+
const questions: GoalQuestionnaireQuestion[] = params.questions.map((q: GoalQuestionnaireQuestion & { allow_custom?: boolean }) => ({ ...q, allowCustom: q.allow_custom }));
|
|
280
280
|
core.enterGoalModal();
|
|
281
281
|
try {
|
|
282
282
|
const result = await runGoalQuestionnaire(ctx, questions);
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isToolUseAssistantMessage,
|
|
13
13
|
} from "./goal-format.ts";
|
|
14
14
|
import { buildCompactionSummary } from "./goal-compaction.ts";
|
|
15
|
-
import { latestAuditorResultForGoal, readGoalLedger, invalidateGoalLedgerCache } from "./goal-ledger.ts";
|
|
15
|
+
import { latestAuditorResultForGoal, loadLedgerState, readGoalLedger, invalidateGoalLedgerCache } from "./goal-ledger.ts";
|
|
16
16
|
import { shouldArmPostCompactReminder, shouldInjectPostCompactReminder } from "./goal-policy.ts";
|
|
17
17
|
import { formatTokenValue } from "./goal-core.ts";
|
|
18
18
|
import { loadGoalSettings, invalidateGoalSettingsCache } from "./goal-settings.ts";
|
|
@@ -413,7 +413,7 @@ export function registerGoalEvents(core: GoalCore): void {
|
|
|
413
413
|
// Use deterministic compaction summary instead of generic reminder
|
|
414
414
|
try {
|
|
415
415
|
const ledger = getPromptLedger();
|
|
416
|
-
const compaction = buildCompactionSummary({ goalsById: core.goalsById, focusedGoalId: core.focusedGoalId,
|
|
416
|
+
const compaction = buildCompactionSummary({ goalsById: core.goalsById, focusedGoalId: core.focusedGoalId, ledgerState: loadLedgerState(ctx) });
|
|
417
417
|
prompt = `${prompt}\n\n[POST-COMPACTION RESYNC goalId=${activeGoal.id}]\n${compaction}`;
|
|
418
418
|
} catch {
|
|
419
419
|
prompt = `${prompt}\n\n[POST-COMPACTION RESYNC goalId=${core.state.goal.id}]\nThe conversation was just compacted. Re-read the objective and continue from the actual artifacts/state; do not rely on memory of the prior chat.`;
|
|
@@ -181,7 +181,7 @@ export function extractGoalIdFromInjectedMessage(text: string): string | null {
|
|
|
181
181
|
// Borrowed from pi-codex-goal. More robust than bare bracket text because
|
|
182
182
|
// the angle brackets + attributes are nearly impossible for users to type
|
|
183
183
|
// by accident, and the structure is grep-able / parse-able by external tooling.
|
|
184
|
-
const xmlMatch = text.match(/^<pi_goal_continuation\s+goal_id
|
|
184
|
+
const xmlMatch = text.match(/^<pi_goal_continuation\s+goal_id="([^"]+)"/);
|
|
185
185
|
if (xmlMatch) return xmlMatch[1] ?? null;
|
|
186
186
|
const match = text.match(/^\[(?:GOAL CHECKPOINT|GOAL CONTINUATION|GOAL STALE) goalId=([^\]\s]+)\]/);
|
|
187
187
|
return match?.[1] ?? null;
|