pi-goal-x 0.19.0 → 0.20.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 +3 -1
- package/extensions/goal-auditor.ts +26 -2
- package/extensions/goal-pool.ts +9 -2
- package/extensions/goal-settings.ts +6 -0
- package/extensions/goal.ts +31 -67
- package/extensions/widgets/goal-widget.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ Pressing `Esc` or aborting an active run pauses the goal so it does not remain f
|
|
|
104
104
|
- **Branch-local focus**: because focus is reconstructed from the current session branch, `/tree` navigation can restore a different focus for a different branch.
|
|
105
105
|
- **One continuation chain**: auto-continue only schedules work for the focused goal in the current session.
|
|
106
106
|
|
|
107
|
-
Creating a goal with `/goals`, `/sisyphus`, `/goals-set`, or `/sisyphus-set` no longer clears other open goals. It creates a new active goal file and focuses it. Use `/goal-list` to inspect open goals and `/goal-focus` to switch the session focus. If the latest focus entry explicitly clears focus, or points at a missing/stale goal, a remaining single open goal is not auto-focused
|
|
107
|
+
Creating a goal with `/goals`, `/sisyphus`, `/goals-set`, or `/sisyphus-set` no longer clears other open goals. It creates a new active goal file and focuses it. Use `/goal-list` to inspect open goals and `/goal-focus` to switch the session focus. If the latest focus entry explicitly clears focus, or points at a missing/stale goal, a remaining single open goal is not auto-focused. By default (`autoSelectSingleGoal: false`) sessions start unfocused so goals stay session-scoped — useful when multiple sessions share the same `.pi/goals/` directory. Set `autoSelectSingleGoal: true` to restore the old behavior where a single open goal is auto-focused when no focus entry exists at all. If multiple open goals exist and the session has no valid focus, `/goal-resume`, `/goal-clear`, `/goal-abort`, `/goal-pause`, and `/goal-tweak` ask the user to choose a goal instead of acting on all of them.
|
|
108
108
|
|
|
109
109
|
## Agent tools
|
|
110
110
|
|
|
@@ -216,6 +216,7 @@ Configured interactively via `/goal-settings`, or edited directly:
|
|
|
216
216
|
"disableTasks": false,
|
|
217
217
|
"disableContracts": false,
|
|
218
218
|
"subtaskDepth": 1,
|
|
219
|
+
"autoSelectSingleGoal": false,
|
|
219
220
|
"provider": "fireworks",
|
|
220
221
|
"model": "accounts/fireworks/models/deepseek-v4-flash",
|
|
221
222
|
"thinkingLevel": "high",
|
|
@@ -228,6 +229,7 @@ Configured interactively via `/goal-settings`, or edited directly:
|
|
|
228
229
|
| `disableTasks` | `false` | Suppress task list features entirely when `true` |
|
|
229
230
|
| `disableContracts` | `false` | Suppress verification contract enforcement when `true` |
|
|
230
231
|
| `subtaskDepth` | `1` | Maximum nesting depth for subtasks |
|
|
232
|
+
| `autoSelectSingleGoal` | `false` | When `true`, auto-focus the single open goal when a session has no focus entry (default keeps goals session-scoped) |
|
|
231
233
|
| `provider` | system default | Provider name for the auditor agent |
|
|
232
234
|
| `model` | system default | Model name for the auditor agent |
|
|
233
235
|
| `thinkingLevel` | system default | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
|
|
@@ -242,6 +242,30 @@ function resolveAuditorModel(ctx: ExtensionContext, config: GoalSettings): { mod
|
|
|
242
242
|
return { model: undefined, error: `Configured auditor model is ambiguous or unavailable: ${config.model}` };
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Options that reuse the parent session's auth + registered providers for the nested auditor.
|
|
247
|
+
*
|
|
248
|
+
* Pi 0.81+ `createAgentSession` accepts `modelRuntime` (and ignores `modelRegistry`).
|
|
249
|
+
* ExtensionContext still exposes `modelRegistry`, which wraps the live ModelRuntime.
|
|
250
|
+
* Sharing that runtime keeps extension providers such as `pi-cursor-sdk`'s `cursor`
|
|
251
|
+
* provider (and its auth) available. Without this, the auditor builds a fresh runtime
|
|
252
|
+
* with an empty resource loader, so Cursor models fail with "No API key found for cursor"
|
|
253
|
+
* even when `~/.pi/agent/auth.json` has a Cursor key.
|
|
254
|
+
*
|
|
255
|
+
* Older SDKs still accept `modelRegistry`; pass both for compatibility.
|
|
256
|
+
*/
|
|
257
|
+
export function resolveAuditorSessionModelOptions(ctx: ExtensionContext): {
|
|
258
|
+
modelRegistry: ExtensionContext["modelRegistry"];
|
|
259
|
+
modelRuntime?: unknown;
|
|
260
|
+
} {
|
|
261
|
+
const registry = ctx.modelRegistry as ExtensionContext["modelRegistry"] & { runtime?: unknown };
|
|
262
|
+
const runtime = registry?.runtime;
|
|
263
|
+
if (runtime) {
|
|
264
|
+
return { modelRegistry: ctx.modelRegistry, modelRuntime: runtime };
|
|
265
|
+
}
|
|
266
|
+
return { modelRegistry: ctx.modelRegistry };
|
|
267
|
+
}
|
|
268
|
+
|
|
245
269
|
function modelLabel(model: Model<any> | undefined): string | undefined {
|
|
246
270
|
return model ? `${model.provider}/${model.id}` : undefined;
|
|
247
271
|
}
|
|
@@ -318,13 +342,13 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
318
342
|
cwd: args.ctx.cwd,
|
|
319
343
|
model,
|
|
320
344
|
thinkingLevel,
|
|
321
|
-
|
|
345
|
+
...resolveAuditorSessionModelOptions(args.ctx),
|
|
322
346
|
resourceLoader: makeAuditorResourceLoader(),
|
|
323
347
|
sessionManager: SessionManager.inMemory(args.ctx.cwd),
|
|
324
348
|
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
|
|
325
349
|
tools: ["read", "grep", "find", "ls", "bash", REPORT_AUDITOR_PROGRESS_TOOL_NAME],
|
|
326
350
|
customTools: [reportProgressTool],
|
|
327
|
-
});
|
|
351
|
+
} as Parameters<typeof createAgentSession>[0]);
|
|
328
352
|
const unsubscribe = session.subscribe((event) => {
|
|
329
353
|
if (event.type === "tool_execution_start") {
|
|
330
354
|
progress.currentTool = event.toolName;
|
package/extensions/goal-pool.ts
CHANGED
|
@@ -38,6 +38,7 @@ export function resolveSessionFocus(args: {
|
|
|
38
38
|
pool: Map<string, GoalRecord>;
|
|
39
39
|
focusEntry?: GoalFocusEntry | null;
|
|
40
40
|
legacyGoal?: GoalRecord | null;
|
|
41
|
+
autoSelectSingleGoal?: boolean;
|
|
41
42
|
}): string | null {
|
|
42
43
|
const focusedGoalId = args.focusEntry?.focusedGoalId ?? null;
|
|
43
44
|
const focused = focusedGoalId ? focusedGoalFromPool(args.pool, focusedGoalId) : null;
|
|
@@ -52,8 +53,14 @@ export function resolveSessionFocus(args: {
|
|
|
52
53
|
args.pool.set(args.legacyGoal.id, cloneGoal(args.legacyGoal));
|
|
53
54
|
return args.legacyGoal.id;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// Auto-select single open goal only when explicitly enabled.
|
|
57
|
+
// Default (false): sessions start unfocused to prevent goals from
|
|
58
|
+
// bleeding across sessions that share the same .pi/goals/ directory.
|
|
59
|
+
if (args.autoSelectSingleGoal) {
|
|
60
|
+
const open = openGoalsFromPool(args.pool);
|
|
61
|
+
return open.length === 1 ? open[0]?.id ?? null : null;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
export function goalSelectorLabel(goal: GoalRecord, focusedGoalId: string | null): string {
|
|
@@ -26,6 +26,7 @@ export interface GoalSettings {
|
|
|
26
26
|
model?: string;
|
|
27
27
|
thinkingLevel?: ThinkingLevel;
|
|
28
28
|
disabled?: boolean;
|
|
29
|
+
autoSelectSingleGoal?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export const PI_GOAL_SETTINGS_FILE_ENV = "PI_GOAL_SETTINGS_FILE";
|
|
@@ -41,6 +42,7 @@ const ALLOWED_SETTINGS_KEYS = new Set([
|
|
|
41
42
|
"thinkingLevel",
|
|
42
43
|
"thinking_level",
|
|
43
44
|
"disabled",
|
|
45
|
+
"autoSelectSingleGoal",
|
|
44
46
|
]);
|
|
45
47
|
|
|
46
48
|
/**
|
|
@@ -105,6 +107,7 @@ export function parseGoalSettings(raw: unknown): GoalSettings {
|
|
|
105
107
|
if (model !== undefined) settings.model = model;
|
|
106
108
|
if (thinkingLevel !== undefined) settings.thinkingLevel = thinkingLevel;
|
|
107
109
|
if (record.disabled === true || record.disabled === "true") settings.disabled = true;
|
|
110
|
+
if (record.autoSelectSingleGoal === true || record.autoSelectSingleGoal === "true") settings.autoSelectSingleGoal = true;
|
|
108
111
|
return settings;
|
|
109
112
|
}
|
|
110
113
|
|
|
@@ -136,6 +139,7 @@ export function loadGoalSettings(cwd: string, env: NodeJS.ProcessEnv = process.e
|
|
|
136
139
|
model: fileConfig.model,
|
|
137
140
|
thinkingLevel: fileConfig.thinkingLevel,
|
|
138
141
|
disabled: fileConfig.disabled,
|
|
142
|
+
autoSelectSingleGoal: fileConfig.autoSelectSingleGoal ?? false,
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -166,6 +170,7 @@ export function saveGoalSettingsFileConfig(cwd: string, settings: GoalSettings):
|
|
|
166
170
|
if (disableTasks === true) clean.disableTasks = true;
|
|
167
171
|
if (disableContracts === true) clean.disableContracts = true;
|
|
168
172
|
if (subtaskDepth !== undefined) clean.subtaskDepth = subtaskDepth;
|
|
173
|
+
if (settings.autoSelectSingleGoal === true) clean.autoSelectSingleGoal = true;
|
|
169
174
|
const configPath = goalSettingsPath(cwd);
|
|
170
175
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
171
176
|
const persisted: Record<string, unknown> = {};
|
|
@@ -176,6 +181,7 @@ export function saveGoalSettingsFileConfig(cwd: string, settings: GoalSettings):
|
|
|
176
181
|
if (clean.disableTasks) persisted.disableTasks = true;
|
|
177
182
|
if (clean.disableContracts) persisted.disableContracts = true;
|
|
178
183
|
if (clean.subtaskDepth !== undefined) persisted.subtaskDepth = clean.subtaskDepth;
|
|
184
|
+
if (settings.autoSelectSingleGoal === true) persisted.autoSelectSingleGoal = true;
|
|
179
185
|
fs.writeFileSync(configPath, `${JSON.stringify(persisted, null, 2)}\n`, "utf8");
|
|
180
186
|
return clean;
|
|
181
187
|
}
|
package/extensions/goal.ts
CHANGED
|
@@ -147,7 +147,6 @@ const GOAL_EVENT_ENTRY = "pi-goal-event";
|
|
|
147
147
|
const GOAL_AUDIT_ENTRY = "pi-goal-audit-event";
|
|
148
148
|
const COMPLETE_STATUS = "complete";
|
|
149
149
|
const CONTINUATION_IDLE_RETRY_MS = 50;
|
|
150
|
-
const STATUS_REFRESH_MS = 1000;
|
|
151
150
|
/**
|
|
152
151
|
* Tools that count as "real work" toward the active goal. If a non-tool-use
|
|
153
152
|
* turn ends without any of these having been called, we DO NOT queue the next
|
|
@@ -411,8 +410,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
411
410
|
let continuationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
412
411
|
let runningGoalId: string | null = null;
|
|
413
412
|
let terminalInputUnsubscribe: (() => void) | null = null;
|
|
414
|
-
let statusRefreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
415
|
-
let statusRefreshCtx: ExtensionContext | null = null;
|
|
416
413
|
let auditProgress: AuditorWidgetProgress | null = null;
|
|
417
414
|
let auditAnimationTimer: ReturnType<typeof setInterval> | null = null;
|
|
418
415
|
let auditAbortController: AbortController | null = null;
|
|
@@ -535,37 +532,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
535
532
|
}
|
|
536
533
|
}
|
|
537
534
|
|
|
538
|
-
function stopStatusRefresh(): void {
|
|
539
|
-
if (statusRefreshTimer) {
|
|
540
|
-
clearInterval(statusRefreshTimer);
|
|
541
|
-
statusRefreshTimer = null;
|
|
542
|
-
}
|
|
543
|
-
statusRefreshCtx = null;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function syncStatusRefresh(ctx: ExtensionContext): void {
|
|
547
|
-
if (!ctx.hasUI || state.goal?.status !== "active") {
|
|
548
|
-
stopStatusRefresh();
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
statusRefreshCtx = ctx;
|
|
552
|
-
if (statusRefreshTimer) return;
|
|
553
|
-
statusRefreshTimer = setInterval(() => {
|
|
554
|
-
if (!statusRefreshCtx || state.goal?.status !== "active") {
|
|
555
|
-
stopStatusRefresh();
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const displayGoal = goalForDisplay();
|
|
559
|
-
if (displayGoal) {
|
|
560
|
-
const otherCount = otherOpenGoalCount(goalsById, focusedGoalId);
|
|
561
|
-
statusRefreshCtx.ui.setStatus("goal", `${footerStatus(displayGoal)}${otherCount > 0 ? ` (+${otherCount} open)` : ""}`);
|
|
562
|
-
}
|
|
563
|
-
// Live-tick the above-editor widget so duration/tokens update.
|
|
564
|
-
goalWidgetComponent?.update();
|
|
565
|
-
}, STATUS_REFRESH_MS);
|
|
566
|
-
statusRefreshTimer.unref?.();
|
|
567
|
-
}
|
|
568
|
-
|
|
569
535
|
function clearContinuationTimer(): void {
|
|
570
536
|
if (continuationTimer) {
|
|
571
537
|
clearTimeout(continuationTimer);
|
|
@@ -785,7 +751,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
785
751
|
if (next) state.goal = next.status === "complete" ? archiveGoalFile(ctx, next) : writeActiveGoalFile(ctx, next);
|
|
786
752
|
}
|
|
787
753
|
}
|
|
788
|
-
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
789
754
|
syncGoalTools();
|
|
790
755
|
if (ctx) updateUI(ctx);
|
|
791
756
|
}
|
|
@@ -794,7 +759,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
794
759
|
if (!state.goal || state.goal.status === "complete") return;
|
|
795
760
|
if (syncGoalPromptFromDisk(ctx)) {
|
|
796
761
|
state.goal = { ...state.goal, updatedAt: nowIso() };
|
|
797
|
-
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
798
762
|
}
|
|
799
763
|
syncGoalTools();
|
|
800
764
|
updateUI(ctx);
|
|
@@ -804,7 +768,10 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
804
768
|
* Live above-editor widget for the active goal. Inspired by rpiv-todo's
|
|
805
769
|
* TodoOverlay: register the widget once with a factory, read live state
|
|
806
770
|
* via the closure at render time, and call `tui.requestRender()` on every
|
|
807
|
-
* state change so the overlay refreshes without re-registration.
|
|
771
|
+
* state change so the overlay refreshes without re-registration. Normal pi
|
|
772
|
+
* renders still read current values through the closure; do not request
|
|
773
|
+
* periodic renders just to tick elapsed time because terminal redraws pull
|
|
774
|
+
* users out of scrollback while they review long goals and earlier context.
|
|
808
775
|
*
|
|
809
776
|
* Layout (sisyphus, running):
|
|
810
777
|
* ◆ Sisyphus [▰▰▰▱▱] 3/5
|
|
@@ -835,7 +802,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
835
802
|
const totalOpen = openGoals().length;
|
|
836
803
|
if (!state.goal && totalOpen === 0) {
|
|
837
804
|
clearGoalWidget(ctx);
|
|
838
|
-
stopStatusRefresh();
|
|
839
805
|
return;
|
|
840
806
|
}
|
|
841
807
|
if (!state.goal) {
|
|
@@ -861,7 +827,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
861
827
|
} else {
|
|
862
828
|
goalWidgetComponent?.update();
|
|
863
829
|
}
|
|
864
|
-
stopStatusRefresh();
|
|
865
830
|
return;
|
|
866
831
|
}
|
|
867
832
|
|
|
@@ -890,12 +855,6 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
890
855
|
} else {
|
|
891
856
|
goalWidgetComponent?.update();
|
|
892
857
|
}
|
|
893
|
-
|
|
894
|
-
if (state.goal.status === "complete") {
|
|
895
|
-
stopStatusRefresh();
|
|
896
|
-
} else {
|
|
897
|
-
syncStatusRefresh(ctx);
|
|
898
|
-
}
|
|
899
858
|
}
|
|
900
859
|
|
|
901
860
|
function loadState(ctx: ExtensionContext): void {
|
|
@@ -920,7 +879,8 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
920
879
|
if (legacyGoal && legacyGoal.status !== "complete") {
|
|
921
880
|
legacyGoal = sanitizeGoalPaths(ctx, mergeGoalPromptFromDisk(ctx, legacyGoal));
|
|
922
881
|
}
|
|
923
|
-
|
|
882
|
+
const settings = loadGoalSettings(ctx.cwd);
|
|
883
|
+
focusedGoalId = resolveSessionFocus({ pool: goalsById, focusEntry, legacyGoal, autoSelectSingleGoal: settings.autoSelectSingleGoal });
|
|
924
884
|
if (!focusEntry && focusedGoalId) {
|
|
925
885
|
try {
|
|
926
886
|
appendFocusEntry(focusedGoalId, legacyGoal?.id === focusedGoalId ? "migrated" : "selected");
|
|
@@ -1336,16 +1296,18 @@ Verification contract:
|
|
|
1336
1296
|
}
|
|
1337
1297
|
continuationQueuedFor = goalId;
|
|
1338
1298
|
const settings = loadGoalSettings(ctx.cwd);
|
|
1299
|
+
const goal = state.goal;
|
|
1300
|
+
if (!goal) return;
|
|
1339
1301
|
pi.sendMessage<GoalEventDetails>(
|
|
1340
1302
|
{
|
|
1341
1303
|
customType: GOAL_EVENT_ENTRY,
|
|
1342
|
-
content: continuationPrompt(
|
|
1304
|
+
content: continuationPrompt(goal, settings),
|
|
1343
1305
|
display: false,
|
|
1344
1306
|
details: {
|
|
1345
1307
|
kind: "checkpoint",
|
|
1346
|
-
goalId:
|
|
1347
|
-
status:
|
|
1348
|
-
objective:
|
|
1308
|
+
goalId: goal.id,
|
|
1309
|
+
status: goal.status,
|
|
1310
|
+
objective: goal.objective,
|
|
1349
1311
|
timestamp: Date.now(),
|
|
1350
1312
|
},
|
|
1351
1313
|
},
|
|
@@ -1652,6 +1614,7 @@ Verification contract:
|
|
|
1652
1614
|
if (key === "disabled") return config.disabled === true ? "true" : "false";
|
|
1653
1615
|
if (key === "disableTasks") return config.disableTasks === true ? "true" : "false";
|
|
1654
1616
|
if (key === "disableContracts") return config.disableContracts === true ? "true" : "false";
|
|
1617
|
+
if (key === "autoSelectSingleGoal") return config.autoSelectSingleGoal === true ? "true" : "false";
|
|
1655
1618
|
if (key === "subtaskDepth") return config.subtaskDepth !== undefined ? String(config.subtaskDepth) : "1";
|
|
1656
1619
|
return config[key] ?? "(default)";
|
|
1657
1620
|
}
|
|
@@ -1673,7 +1636,7 @@ Verification contract:
|
|
|
1673
1636
|
ctx.ui.notify(`Settings file: ${goalSettingsPath(ctx.cwd)}`, "info");
|
|
1674
1637
|
return;
|
|
1675
1638
|
}
|
|
1676
|
-
const editorKeys = ["disabled", "provider", "model", "thinking_level", "subtaskDepth"] as const;
|
|
1639
|
+
const editorKeys = ["disabled", "provider", "model", "thinking_level", "subtaskDepth", "autoSelectSingleGoal"] as const;
|
|
1677
1640
|
while (true) {
|
|
1678
1641
|
const config = loadGoalSettingsFileConfig(ctx.cwd);
|
|
1679
1642
|
const options = settingsLines(config).map((line) => ` ${line}`);
|
|
@@ -1695,6 +1658,12 @@ Verification contract:
|
|
|
1695
1658
|
ctx.ui.notify(`Settings saved:\n${settingsLines(loadGoalSettingsFileConfig(ctx.cwd)).join("\n")}`, "info");
|
|
1696
1659
|
continue;
|
|
1697
1660
|
}
|
|
1661
|
+
if (key === "autoSelectSingleGoal") {
|
|
1662
|
+
const next = { ...config, autoSelectSingleGoal: config.autoSelectSingleGoal !== true };
|
|
1663
|
+
saveGoalSettingsFileConfig(ctx.cwd, next);
|
|
1664
|
+
ctx.ui.notify(`Settings saved:\n${settingsLines(loadGoalSettingsFileConfig(ctx.cwd)).join("\n")}`, "info");
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1698
1667
|
if (key === "subtaskDepth") {
|
|
1699
1668
|
const input = await ctx.ui.input("Set subtaskDepth", String(config.subtaskDepth ?? 1));
|
|
1700
1669
|
if (input === undefined) continue;
|
|
@@ -2316,15 +2285,14 @@ ${objective}` : objective,
|
|
|
2316
2285
|
// minimal state update manually:
|
|
2317
2286
|
// 1) write the new record to disk authoritatively
|
|
2318
2287
|
// 2) update in-memory `goal` to the canonical post-write record
|
|
2319
|
-
// 3)
|
|
2288
|
+
// 3) re-sync tools
|
|
2320
2289
|
// 4) clear the tweak drafting gate so propose_goal_tweak can't be re-used
|
|
2321
2290
|
state.goal = writeActiveGoalFile(ctx, next);
|
|
2322
|
-
pi.appendEntry(STATE_ENTRY, goalDetails(state.goal));
|
|
2323
2291
|
tweakDraftingFor = null;
|
|
2324
2292
|
// Reset autoContinue counter — plan changed, agent gets a fresh chain.
|
|
2325
2293
|
resetGetGoalNudgeState(state.goal.id);
|
|
2326
2294
|
// Mark turn-stopped so subsequent in-turn tool calls are blocked.
|
|
2327
|
-
turnStoppedFor = state.goal.id;
|
|
2295
|
+
turnStoppedFor = { goalId: state.goal.id, turnSeq };
|
|
2328
2296
|
syncGoalTools();
|
|
2329
2297
|
updateUI(ctx);
|
|
2330
2298
|
ctx.ui.notify(`Goal tweaked: ${truncateText(changeSummary, 160)}`, "info");
|
|
@@ -2497,8 +2465,7 @@ ${objective}` : objective,
|
|
|
2497
2465
|
updatedAt: nowIso(),
|
|
2498
2466
|
};
|
|
2499
2467
|
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
2500
|
-
|
|
2501
|
-
turnStoppedFor = state.goal?.id ?? null;
|
|
2468
|
+
turnStoppedFor = state.goal ? { goalId: state.goal.id, turnSeq } : null;
|
|
2502
2469
|
resetGetGoalNudgeState(state.goal?.id);
|
|
2503
2470
|
syncGoalTools();
|
|
2504
2471
|
updateUI(ctx);
|
|
@@ -2564,8 +2531,7 @@ ${objective}` : objective,
|
|
|
2564
2531
|
updatedAt: nowIso(),
|
|
2565
2532
|
};
|
|
2566
2533
|
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
2567
|
-
|
|
2568
|
-
turnStoppedFor = state.goal?.id ?? null;
|
|
2534
|
+
turnStoppedFor = state.goal ? { goalId: state.goal.id, turnSeq } : null;
|
|
2569
2535
|
resetGetGoalNudgeState(state.goal?.id);
|
|
2570
2536
|
syncGoalTools();
|
|
2571
2537
|
updateUI(ctx);
|
|
@@ -2694,8 +2660,7 @@ ${objective}` : objective,
|
|
|
2694
2660
|
updatedAt: nowIso(),
|
|
2695
2661
|
};
|
|
2696
2662
|
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
2697
|
-
|
|
2698
|
-
turnStoppedFor = state.goal?.id ?? null;
|
|
2663
|
+
turnStoppedFor = state.goal ? { goalId: state.goal.id, turnSeq } : null;
|
|
2699
2664
|
resetGetGoalNudgeState(state.goal?.id);
|
|
2700
2665
|
syncGoalTools();
|
|
2701
2666
|
updateUI(ctx);
|
|
@@ -2713,7 +2678,7 @@ ${objective}` : objective,
|
|
|
2713
2678
|
} else {
|
|
2714
2679
|
// ── Continue working → pause the goal ──────────────
|
|
2715
2680
|
pauseActiveGoal(ctx);
|
|
2716
|
-
turnStoppedFor = state.goal
|
|
2681
|
+
turnStoppedFor = state.goal ? { goalId: state.goal.id, turnSeq } : null;
|
|
2717
2682
|
return {
|
|
2718
2683
|
content: [{ type: "text", text: "Goal paused — user chose to continue working after skipping audit." }],
|
|
2719
2684
|
details: state.goal ? goalDetails(state.goal) : undefined,
|
|
@@ -2795,8 +2760,7 @@ ${objective}` : objective,
|
|
|
2795
2760
|
updatedAt: nowIso(),
|
|
2796
2761
|
};
|
|
2797
2762
|
state.goal = writeActiveGoalFile(ctx, state.goal);
|
|
2798
|
-
|
|
2799
|
-
turnStoppedFor = state.goal?.id ?? null;
|
|
2763
|
+
turnStoppedFor = state.goal ? { goalId: state.goal.id, turnSeq } : null;
|
|
2800
2764
|
resetGetGoalNudgeState(state.goal?.id);
|
|
2801
2765
|
syncGoalTools();
|
|
2802
2766
|
updateUI(ctx);
|
|
@@ -2863,7 +2827,7 @@ ${objective}` : objective,
|
|
|
2863
2827
|
resetGetGoalNudgeState(next.id);
|
|
2864
2828
|
// C9 fix: mark turn-stopped so subsequent in-turn tool calls are blocked.
|
|
2865
2829
|
// This is the schema-level closure of "agent kept writing files after pause_goal".
|
|
2866
|
-
turnStoppedFor = state.goal.id;
|
|
2830
|
+
turnStoppedFor = { goalId: state.goal.id, turnSeq };
|
|
2867
2831
|
|
|
2868
2832
|
const suggestionLine = suggested ? `\nSuggested: ${truncateText(suggested, 160)}` : "";
|
|
2869
2833
|
ctx.ui.notify(
|
|
@@ -2923,7 +2887,7 @@ ${objective}` : objective,
|
|
|
2923
2887
|
const archived = archiveCurrentGoal(ctx, "agent");
|
|
2924
2888
|
resetGetGoalNudgeState(abortedGoalId);
|
|
2925
2889
|
setGoal(null, ctx, true, "aborted");
|
|
2926
|
-
turnStoppedFor = abortedGoalId;
|
|
2890
|
+
turnStoppedFor = { goalId: abortedGoalId, turnSeq };
|
|
2927
2891
|
|
|
2928
2892
|
const archiveLine = archived?.archivedPath ? `\nArchive: ${archived.archivedPath}` : "";
|
|
2929
2893
|
ctx.ui.notify(
|
|
@@ -3112,7 +3076,7 @@ ${objective}` : objective,
|
|
|
3112
3076
|
}
|
|
3113
3077
|
state.goal = { ...state.goal, taskList, updatedAt: now };
|
|
3114
3078
|
setGoal(state.goal, ctx);
|
|
3115
|
-
turnStoppedFor = state.goal.id;
|
|
3079
|
+
turnStoppedFor = { goalId: state.goal.id, turnSeq };
|
|
3116
3080
|
resetGetGoalNudgeState(state.goal.id);
|
|
3117
3081
|
syncGoalTools();
|
|
3118
3082
|
updateUI(ctx);
|
|
@@ -3501,6 +3465,7 @@ promptGuidelines: [
|
|
|
3501
3465
|
});
|
|
3502
3466
|
|
|
3503
3467
|
pi.on("session_start", async (event, ctx) => {
|
|
3468
|
+
syncGoalTools();
|
|
3504
3469
|
loadState(ctx);
|
|
3505
3470
|
syncGoalTools();
|
|
3506
3471
|
syncTerminalInputPause(ctx);
|
|
@@ -3689,7 +3654,6 @@ promptGuidelines: [
|
|
|
3689
3654
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
3690
3655
|
accountProgress(ctx);
|
|
3691
3656
|
clearContinuationTimer();
|
|
3692
|
-
stopStatusRefresh();
|
|
3693
3657
|
terminalInputUnsubscribe?.();
|
|
3694
3658
|
terminalInputUnsubscribe = null;
|
|
3695
3659
|
if (state.goal) persist(ctx);
|
|
@@ -179,7 +179,7 @@ export function renderAuditorWidgetLines(progress: AuditorWidgetProgress, theme:
|
|
|
179
179
|
? truncateText(progress.currentToolArgs, Math.max(10, safeWidth - 24))
|
|
180
180
|
: "";
|
|
181
181
|
const toolDuration = progress.currentToolStartedAt
|
|
182
|
-
? ` ${theme.fg("dim", formatDuration(Date.now() - progress.currentToolStartedAt))}`
|
|
182
|
+
? ` ${theme.fg("dim", formatDuration(Math.floor((Date.now() - progress.currentToolStartedAt) / 1000)))}`
|
|
183
183
|
: "";
|
|
184
184
|
lines.push(branchLine(
|
|
185
185
|
theme,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Goal mode extension for pi: persistent long-running objectives, /goal-set drafting, Sisyphus prompt style, autoContinue, and an above-editor status overlay. Fork of @capyup/pi-goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pi-goal-x contributors",
|