pi-goal-x 0.26.0 → 0.26.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.
|
@@ -211,7 +211,10 @@ export function proposalText(draft: ActiveGoalDraft, objective: string, autoCont
|
|
|
211
211
|
} else if (taskList && taskList.tasks.length > 0) {
|
|
212
212
|
tasksText = "\n\nTasks proposed for confirmation:\n" + renderConfirmationTasks(taskList.tasks, 0).join("\n");
|
|
213
213
|
} else {
|
|
214
|
-
|
|
214
|
+
// §single-task-set: the derived preview must derive from the SAME
|
|
215
|
+
// objective text the apply path persists (extracted — the Verification
|
|
216
|
+
// contract line removed), so shown == persisted.
|
|
217
|
+
const derived = deriveTasksFromObjective(extractVerificationContract(objective).objective);
|
|
215
218
|
if (derived && derived.length > 0) {
|
|
216
219
|
tasksText = "\n\nTasks derived from the objective (confirm or ask the agent to adjust):\n" + renderConfirmationTasks(derived, 0).join("\n");
|
|
217
220
|
}
|
|
@@ -465,12 +468,26 @@ export function registerDraftingTools(core: GoalCore): void {
|
|
|
465
468
|
const objective = String(args?.objective ?? "");
|
|
466
469
|
let text = theme.fg("toolTitle", "propose_goal_draft ") + theme.fg("muted", objective);
|
|
467
470
|
const flatTasks = Array.isArray(args?.tasks) ? (args.tasks as FlatTaskInput[]) : [];
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
// §single-task-set: a proposal shows exactly ONE task set — the set
|
|
472
|
+
// that will actually be persisted. Explicit tasks are that set; a
|
|
473
|
+
// tweak without explicit tasks retains the current list (mirror
|
|
474
|
+
// proposalText — never a derived-from-objective phantom); a new
|
|
475
|
+
// draft without explicit tasks shows the F2-derived preview derived
|
|
476
|
+
// from the SAME objective text the apply path persists (extracted).
|
|
477
|
+
let taskBlock: { header: string; lines: string[] } | null = null;
|
|
478
|
+
if (flatTasks.length > 0) {
|
|
479
|
+
taskBlock = { header: "Tasks proposed for confirmation:", lines: flatTasks.map((t) => `[ ] ${t.id}: ${t.title}`) };
|
|
480
|
+
} else if (activeDraft(core)?.mode === "tweak") {
|
|
481
|
+
const retained = core.state.goal?.taskList?.tasks;
|
|
482
|
+
if (retained && retained.length > 0) {
|
|
483
|
+
taskBlock = { header: "Current task list (retained unchanged):", lines: renderConfirmationTasks(retained, 0) };
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
const derived = deriveTasksFromObjective(extractVerificationContract(objective).objective) ?? [];
|
|
487
|
+
if (derived.length > 0) {
|
|
488
|
+
taskBlock = { header: "Tasks derived from the objective (confirm or ask the agent to adjust):", lines: renderConfirmationTasks(derived, 0) };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
474
491
|
if (taskBlock) {
|
|
475
492
|
text += "\n\n" + theme.fg("toolTitle", theme.bold(taskBlock.header)) + "\n" + theme.fg("toolOutput", taskBlock.lines.join("\n"));
|
|
476
493
|
}
|
|
@@ -54,10 +54,27 @@ export interface ProposalPresentationSegments {
|
|
|
54
54
|
tailStart: number;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Strip ANSI SGR sequences so structural scans see the plain text. */
|
|
58
|
+
function stripAnsiCodes(value: string): string {
|
|
59
|
+
return value.replace(/\x1b\[[0-9;]*m/g, "");
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
/**
|
|
58
63
|
* Locate the proposal tasks segment and the options tail in a rendered dialog.
|
|
59
64
|
* Returns null when the dialog is not a proposal-style confirmation (no
|
|
60
65
|
* "Tasks proposed for confirmation:" header / "┌─ TASKS ─" box).
|
|
66
|
+
*
|
|
67
|
+
* The scan is ANSI-aware: every rendered line carries `theme.fg(...)` escape
|
|
68
|
+
* sequences in the real TUI (the mock theme does not), so plain-text regexes
|
|
69
|
+
* must be run against the stripped line or the tasks section collapses to its
|
|
70
|
+
* header and every styled task line is sliced out of the bounded frame. The
|
|
71
|
+
* box-drawn bottom border (`└───┘`) after the last task line is kept so the
|
|
72
|
+
* `┌─ TASKS ─┐` box renders complete.
|
|
73
|
+
*
|
|
74
|
+
* The interactive auditor toggle line ("press 'a' to toggle") renders between
|
|
75
|
+
* the context and the options; `tailStart` is pulled back to it so the bounded
|
|
76
|
+
* fit always keeps the auditor status visible (its status text is the only
|
|
77
|
+
* visible toggle feedback in the dialog).
|
|
61
78
|
*/
|
|
62
79
|
export function findProposalPresentationSegments(lines: string[], tailStart: number): ProposalPresentationSegments | null {
|
|
63
80
|
if (tailStart < 0) return null;
|
|
@@ -65,10 +82,25 @@ export function findProposalPresentationSegments(lines: string[], tailStart: num
|
|
|
65
82
|
if (tasksStart < 0 || tasksStart >= tailStart) return null;
|
|
66
83
|
let tasksEnd = tasksStart;
|
|
67
84
|
for (let i = tasksStart + 1; i < tailStart; i++) {
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
const plain = stripAnsiCodes(lines[i]);
|
|
86
|
+
if (/^\s*\[[ x~]\]/.test(plain)) {
|
|
87
|
+
tasksEnd = i;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// Box-drawn TASKS section bottom border after the last task line.
|
|
91
|
+
if (tasksEnd > tasksStart && /^[└┐┤┘─]/.test(plain.trim())) tasksEnd = i;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
// Protect the auditor toggle line: it renders between context and options,
|
|
95
|
+
// so pull the tail start back to it when present.
|
|
96
|
+
let effectiveTail = tailStart;
|
|
97
|
+
for (let i = tailStart - 1; i > tasksEnd; i--) {
|
|
98
|
+
if (lines[i].includes("press 'a' to toggle")) {
|
|
99
|
+
effectiveTail = i;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
70
102
|
}
|
|
71
|
-
return { tasksStart, tasksEnd, tailStart };
|
|
103
|
+
return { tasksStart, tasksEnd, tailStart: effectiveTail };
|
|
72
104
|
}
|
|
73
105
|
|
|
74
106
|
/**
|
|
@@ -127,12 +159,13 @@ export function fitDialogLines(
|
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
/**
|
|
130
|
-
* Proposal confirmation fit: keep the protected head, the tasks section,
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
162
|
+
* Proposal confirmation fit: keep the protected head, the tasks section, the
|
|
163
|
+
* interactive auditor toggle line (when present), and the options/footer/
|
|
164
|
+
* bottom border; the objective-box middle is sacrificed in-frame (it stays
|
|
165
|
+
* fully readable in the scrollback presentation). Interior blank spacing lines
|
|
166
|
+
* are dropped first when room is short; task lines are only dropped after
|
|
167
|
+
* that, from the end, when the bound is exhausted (those lines remain in the
|
|
168
|
+
* scrollback presentation). Never exceeds maxDialogLines.
|
|
136
169
|
*/
|
|
137
170
|
function fitProposalPresentation(
|
|
138
171
|
lines: string[],
|
|
@@ -150,15 +183,18 @@ function fitProposalPresentation(
|
|
|
150
183
|
// the options and the footer hint) before touching any content line.
|
|
151
184
|
const stripped = candidate.filter((l, i) => i < keepHead || l.trim() !== "");
|
|
152
185
|
if (stripped.length <= maxDialogLines) return stripped;
|
|
153
|
-
// Bound still exhausted: keep the head, then
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
186
|
+
// Bound still exhausted: keep the head, then the auditor toggle line (the
|
|
187
|
+
// actionable status control — its feedback must stay visible), then spend
|
|
188
|
+
// the room on the tail (options/footer/bottom border — the decision
|
|
189
|
+
// surface — kept from its end so the border and footer never drop), then on
|
|
190
|
+
// the tasks from the start. The dropped task lines remain fully readable in
|
|
157
191
|
// the scrollback presentation. Never exceeds maxDialogLines.
|
|
158
192
|
const tailNoBlanks = tail.filter((l) => l.trim() !== "");
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
193
|
+
const hasAuditor = tail.length > 0 && tail[0].includes("press 'a' to toggle");
|
|
194
|
+
const restTail = hasAuditor ? tailNoBlanks.slice(1) : tailNoBlanks;
|
|
195
|
+
const keepRest = Math.min(restTail.length, Math.max(0, maxDialogLines - keepHead - (hasAuditor ? 1 : 0)));
|
|
196
|
+
const tailKept = [...(hasAuditor ? [tail[0]] : []), ...restTail.slice(restTail.length - keepRest)];
|
|
197
|
+
const keepTasks = Math.min(tasks.length, Math.max(0, maxDialogLines - keepHead - tailKept.length));
|
|
162
198
|
return [...head, ...tasks.slice(0, keepTasks), ...tailKept];
|
|
163
199
|
}
|
|
164
200
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Goal mode extension for pi: durable long-running objectives, five model tools, structured tasks, independent completion audit, Sisyphus mode, auto-continue, and a status overlay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pi-goal-x contributors",
|