pi-goal-x 0.18.8 → 0.18.9
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/extensions/goal.ts +56 -6
- package/package.json +1 -1
package/extensions/goal.ts
CHANGED
|
@@ -429,8 +429,15 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
429
429
|
// after their successful execute. Once set, pi.on("tool_call") blocks all
|
|
430
430
|
// subsequent in-turn tool calls except POST_STOP_ALLOWED_TOOLS. This is the
|
|
431
431
|
// schema fix for "agent keeps writing files after pause_goal".
|
|
432
|
+
// turnSeq: lightweight generation counter, incremented at each turn start.
|
|
433
|
+
// Used to scope turnStoppedFor so stale markers from prior turns or sessions
|
|
434
|
+
// cannot poison a resumed active goal.
|
|
435
|
+
// checkpointGoalId: remembers the incoming goal id from queued continuations.
|
|
436
|
+
// When set but the goal is no longer active/autoContinue, work tools are blocked.
|
|
432
437
|
let goalWorkToolCalledThisTurn = false;
|
|
433
|
-
let
|
|
438
|
+
let turnSeq = 0;
|
|
439
|
+
let turnStoppedFor: { goalId: string; turnSeq: number } | null = null;
|
|
440
|
+
let checkpointGoalId: string | null = null;
|
|
434
441
|
|
|
435
442
|
// #5 post-compaction resync: when a compaction just happened, the next agent
|
|
436
443
|
// turn gets an extra reminder block. Set in session_compact, consumed
|
|
@@ -576,6 +583,28 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
576
583
|
accounting.lastAccountedAt = null;
|
|
577
584
|
}
|
|
578
585
|
|
|
586
|
+
function advanceTurnSeq(): void {
|
|
587
|
+
turnSeq += 1;
|
|
588
|
+
if (turnStoppedFor?.turnSeq !== turnSeq) turnStoppedFor = null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function currentTurnStoppedGoalId(): string | null {
|
|
592
|
+
if (!turnStoppedFor) return null;
|
|
593
|
+
if (turnStoppedFor.turnSeq !== turnSeq) {
|
|
594
|
+
turnStoppedFor = null;
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
return turnStoppedFor.goalId;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function isActionableContinuationGoal(goalId: string | null | undefined): goalId is string {
|
|
601
|
+
return !!goalId && state.goal?.id === goalId && state.goal.status === "active" && state.goal.autoContinue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function isStaleCheckpointBlockedToolCall(toolName: string): boolean {
|
|
605
|
+
return !POST_STOP_ALLOWED_TOOL_SET.has(toolName);
|
|
606
|
+
}
|
|
607
|
+
|
|
579
608
|
function clearStoppedRuntimeState(): void {
|
|
580
609
|
clearContinuationState();
|
|
581
610
|
clearActiveAccounting();
|
|
@@ -1285,7 +1314,7 @@ Verification contract:
|
|
|
1285
1314
|
continuationTimer = null;
|
|
1286
1315
|
continuationScheduledFor = null;
|
|
1287
1316
|
syncGoalTools();
|
|
1288
|
-
if (!
|
|
1317
|
+
if (!isActionableContinuationGoal(goalId)) {
|
|
1289
1318
|
if (continuationQueuedFor === goalId) continuationQueuedFor = null;
|
|
1290
1319
|
return;
|
|
1291
1320
|
}
|
|
@@ -3307,8 +3336,8 @@ promptGuidelines: [
|
|
|
3307
3336
|
|
|
3308
3337
|
pi.on("turn_start", async (_event, ctx) => {
|
|
3309
3338
|
// Per-turn flag resets (#4 + C9 fix).
|
|
3339
|
+
advanceTurnSeq();
|
|
3310
3340
|
goalWorkToolCalledThisTurn = false;
|
|
3311
|
-
turnStoppedFor = null;
|
|
3312
3341
|
syncGoalTools();
|
|
3313
3342
|
beginAccounting();
|
|
3314
3343
|
updateUI(ctx);
|
|
@@ -3316,17 +3345,29 @@ promptGuidelines: [
|
|
|
3316
3345
|
|
|
3317
3346
|
// #4 + C9 fix + Phase 5 C3: gate in-turn tool calls based on lifecycle state.
|
|
3318
3347
|
pi.on("tool_call", async (event, ctx) => {
|
|
3348
|
+
const stoppedGoalId = currentTurnStoppedGoalId();
|
|
3319
3349
|
// Post-stop in-turn block (C9 0ad8 fix): after pause_goal / abort_goal /
|
|
3320
3350
|
// complete_goal / propose_goal_tweak fires in this turn, block all subsequent tool calls except
|
|
3321
3351
|
// read-only inspection. Forces the agent to yield the turn instead of "fixing"
|
|
3322
3352
|
// the situation by creating extra files etc.
|
|
3323
|
-
if (
|
|
3353
|
+
if (stoppedGoalId !== null && !POST_STOP_ALLOWED_TOOL_SET.has(event.toolName)) {
|
|
3324
3354
|
return {
|
|
3325
3355
|
block: true,
|
|
3326
|
-
reason: `The goal was already stopped earlier in this turn (goalId=${
|
|
3356
|
+
reason: `The goal was already stopped earlier in this turn (goalId=${stoppedGoalId}). ` +
|
|
3327
3357
|
`Do not call more tools; end the turn with a brief summary and yield to the user.`,
|
|
3328
3358
|
};
|
|
3329
3359
|
}
|
|
3360
|
+
// Stale checkpoint guard: if the turn was triggered by a queued continuation
|
|
3361
|
+
// for a goal that is no longer active/autoContinue, block work tools.
|
|
3362
|
+
if (checkpointGoalId !== null && !isActionableContinuationGoal(checkpointGoalId) && isStaleCheckpointBlockedToolCall(event.toolName)) {
|
|
3363
|
+
// Block the tool call with a stale-checkpoint message.
|
|
3364
|
+
return {
|
|
3365
|
+
block: true,
|
|
3366
|
+
reason: `Cannot call ${event.toolName}: the goal checkpoint that triggered this turn is no longer active. ` +
|
|
3367
|
+
`Goal ${checkpointGoalId} has been paused, cleared, or replaced. ` +
|
|
3368
|
+
`End the turn with a brief summary and yield to the user.`,
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3330
3371
|
// Phase 5 soft gate relaxation: active-goal question block and repeated get_goal
|
|
3331
3372
|
// block are removed. The agent is trusted to prefer work tools; prompts nudge
|
|
3332
3373
|
// toward concrete work without hard-stopping the turn.
|
|
@@ -3448,11 +3489,13 @@ promptGuidelines: [
|
|
|
3448
3489
|
});
|
|
3449
3490
|
|
|
3450
3491
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
3492
|
+
advanceTurnSeq();
|
|
3451
3493
|
syncGoalTools();
|
|
3452
3494
|
const currentSystemPrompt = () => ctx.getSystemPrompt?.() || event.systemPrompt;
|
|
3453
3495
|
const incomingGoalId = extractGoalIdFromInjectedMessage(event.prompt ?? "");
|
|
3454
3496
|
|
|
3455
3497
|
if (confirmationIntent !== null) {
|
|
3498
|
+
checkpointGoalId = null;
|
|
3456
3499
|
clearContinuationState();
|
|
3457
3500
|
clearActiveAccounting();
|
|
3458
3501
|
runningGoalId = null;
|
|
@@ -3460,6 +3503,7 @@ promptGuidelines: [
|
|
|
3460
3503
|
}
|
|
3461
3504
|
|
|
3462
3505
|
if (tweakDraftingFor !== null) {
|
|
3506
|
+
checkpointGoalId = null;
|
|
3463
3507
|
clearContinuationState();
|
|
3464
3508
|
clearActiveAccounting();
|
|
3465
3509
|
runningGoalId = null;
|
|
@@ -3470,8 +3514,12 @@ promptGuidelines: [
|
|
|
3470
3514
|
// matches the active goal, abort the whole turn instead of letting the
|
|
3471
3515
|
// model act on a stale instruction.
|
|
3472
3516
|
if (incomingGoalId !== null) {
|
|
3517
|
+
// Reconcile from disk to pick up any external state changes before
|
|
3518
|
+
// evaluating whether the checkpoint is actionable.
|
|
3519
|
+
reconcileFocusedGoalFromDisk(ctx);
|
|
3520
|
+
checkpointGoalId = incomingGoalId;
|
|
3473
3521
|
clearContinuationState();
|
|
3474
|
-
if (!
|
|
3522
|
+
if (!isActionableContinuationGoal(incomingGoalId)) {
|
|
3475
3523
|
try {
|
|
3476
3524
|
ctx.abort?.();
|
|
3477
3525
|
} catch {}
|
|
@@ -3480,10 +3528,12 @@ promptGuidelines: [
|
|
|
3480
3528
|
systemPrompt: `${currentSystemPrompt()}\n\n${staleContinuationPrompt(incomingGoalId, state.goal)}`,
|
|
3481
3529
|
};
|
|
3482
3530
|
}
|
|
3531
|
+
checkpointGoalId = null;
|
|
3483
3532
|
} else {
|
|
3484
3533
|
// A user-driven turn — clear any queued continuation so we don't
|
|
3485
3534
|
// double-fire after the user's own message returns. Also reset the
|
|
3486
3535
|
// autoContinue nudge state so the user always gets a fresh chain.
|
|
3536
|
+
checkpointGoalId = null;
|
|
3487
3537
|
clearContinuationState();
|
|
3488
3538
|
resetGetGoalNudgeState(state.goal?.id);
|
|
3489
3539
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.9",
|
|
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",
|