pi-goal-x 0.25.4 → 0.26.0
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/docs/architecture.md
CHANGED
|
@@ -19,7 +19,7 @@ handlers from their dedicated modules:
|
|
|
19
19
|
| `goal-task-tools.ts` | `set_goal_tasks` / `update_goal_task` executors plus flat parent-linked conversion, id-stable merge, `countTasks` |
|
|
20
20
|
| `goal-task-confirmation.ts` | Task-only result boundary (`{decision}`, no auditor toggle) with neutral Confirm task list / Keep current tasks labels |
|
|
21
21
|
| `goal-commands.ts` | The curated fourteen-command palette and its handlers |
|
|
22
|
-
| `goal-events.ts` | The
|
|
22
|
+
| `goal-events.ts` | The 14 lifecycle event handlers (`context`, `turn_start`, `tool_call`, `tool_execution_end`, `turn_end`, `message_end`, `session_start`, `session_before_compact`, `session_compact`, `session_tree`, `before_agent_start`, `agent_end`, `agent_settled`, `session_shutdown`) |
|
|
23
23
|
| `goal-widget.ts` | Terminal input keybindings (Esc pause / abort-audit, Ctrl+Shift+T overlay) and the hidden debug helpers |
|
|
24
24
|
| `goal-format.ts` | Pure formatting/message-introspection helpers and renderers |
|
|
25
25
|
| `goal-service.ts` | `GoalService` — the sole mutation boundary: ordered reconcile → id/focus-revision validation → clone-mutate → write/archive → ledger → memory commit → returned effects |
|
package/extensions/goal-draft.ts
CHANGED
|
@@ -150,6 +150,7 @@ export function goalDraftingPrompt(topic: string, focus: GoalDraftingFocus): str
|
|
|
150
150
|
"- For simple single-step goals, no task list is required. The `tasks` parameter can be omitted.",
|
|
151
151
|
"- After goal creation, `propose_task_list` is still available for user-requested task additions or structural changes.",
|
|
152
152
|
"- propose_goal_draft opens the user's Confirm / Continue Chatting dialog. Confirm creates and focuses the goal; Continue Chatting means keep refining through normal proposal cycles.",
|
|
153
|
+
"- Before proposing, write the COMPLETE goal into your own message — every section (objective, success criteria, boundaries, constraints, verification contract) and the full task list — so the user can scroll up and re-read the whole goal while the confirmation dialog is open. Nothing may be omitted from the presented goal.",
|
|
153
154
|
"- create_goal is not a shortcut. Direct create_goal calls are rejected so the user keeps explicit say in goal creation.",
|
|
154
155
|
];
|
|
155
156
|
|
|
@@ -454,7 +454,30 @@ export function registerDraftingTools(core: GoalCore): void {
|
|
|
454
454
|
clearGoalDrafting(core, ctx);
|
|
455
455
|
return { content: [{ type: "text", text: `${summary}\n\nGoal tweak confirmed and applied.` }], details: goalDetails(result.goal), terminate: true };
|
|
456
456
|
},
|
|
457
|
-
renderCall(args, theme) {
|
|
457
|
+
renderCall(args, theme) {
|
|
458
|
+
// §proposal-presentation: the tool-call display lands in the terminal
|
|
459
|
+
// buffer the moment the tool call starts — BEFORE the confirmation
|
|
460
|
+
// dialog opens — so it is the scrollable "complete presentation" of
|
|
461
|
+
// the goal. It must show the full objective contract AND every task
|
|
462
|
+
// line; the dialog frame alone cannot (its churn-guard bound keeps
|
|
463
|
+
// it within the terminal height). The user can scroll up and read
|
|
464
|
+
// everything while the dialog is open ("the user can just scroll").
|
|
465
|
+
const objective = String(args?.objective ?? "");
|
|
466
|
+
let text = theme.fg("toolTitle", "propose_goal_draft ") + theme.fg("muted", objective);
|
|
467
|
+
const flatTasks = Array.isArray(args?.tasks) ? (args.tasks as FlatTaskInput[]) : [];
|
|
468
|
+
const derived = flatTasks.length === 0 ? (deriveTasksFromObjective(objective) ?? []) : [];
|
|
469
|
+
const taskBlock = flatTasks.length > 0
|
|
470
|
+
? { header: "Tasks proposed for confirmation:", lines: flatTasks.map((t) => `[ ] ${t.id}: ${t.title}`) }
|
|
471
|
+
: derived.length > 0
|
|
472
|
+
? { header: "Tasks derived from the objective (confirm or ask the agent to adjust):", lines: renderConfirmationTasks(derived, 0) }
|
|
473
|
+
: null;
|
|
474
|
+
if (taskBlock) {
|
|
475
|
+
text += "\n\n" + theme.fg("toolTitle", theme.bold(taskBlock.header)) + "\n" + theme.fg("toolOutput", taskBlock.lines.join("\n"));
|
|
476
|
+
}
|
|
477
|
+
if (args?.auto_continue === false) text += "\n\n" + theme.fg("muted", "Automatic continuation: disabled");
|
|
478
|
+
if (args?.block_completion === true) text += "\n\n" + theme.fg("muted", "Block completion on pending tasks: enabled");
|
|
479
|
+
return new Text(text, 0, 0);
|
|
480
|
+
},
|
|
458
481
|
renderResult(result, _opts, theme) { return renderGoalResult(result, _opts, theme); },
|
|
459
482
|
}));
|
|
460
483
|
}
|
|
@@ -34,10 +34,12 @@ import type { GoalMutationOutcome } from "./goal-service.ts";
|
|
|
34
34
|
* The goal extension's lifecycle event handlers (context, turn_start,
|
|
35
35
|
* tool_call, tool_execution_end, turn_end, message_end, session_start,
|
|
36
36
|
* session_before_compact, session_compact, session_tree, before_agent_start,
|
|
37
|
-
* agent_end, session_shutdown). All state flows through the
|
|
37
|
+
* agent_end, agent_settled, session_shutdown). All state flows through the
|
|
38
|
+
* GoalCore.
|
|
38
39
|
*/
|
|
39
40
|
export function registerGoalEvents(core: GoalCore): void {
|
|
40
41
|
const { pi } = core;
|
|
42
|
+
let continuationAfterSettleFor: string | null = null;
|
|
41
43
|
|
|
42
44
|
pi.on("context", async (event): Promise<{ messages: typeof event.messages } | undefined> => {
|
|
43
45
|
let changed = false;
|
|
@@ -418,6 +420,7 @@ export function registerGoalEvents(core: GoalCore): void {
|
|
|
418
420
|
pi.on("agent_end", async (event, ctx) => {
|
|
419
421
|
const endedGoalId = core.runningGoalId;
|
|
420
422
|
core.runningGoalId = null;
|
|
423
|
+
continuationAfterSettleFor = null;
|
|
421
424
|
|
|
422
425
|
// Account for any tokens from aborted in-flight assistant messages so
|
|
423
426
|
// they are not silently lost (but charge them to the original goal).
|
|
@@ -446,10 +449,23 @@ export function registerGoalEvents(core: GoalCore): void {
|
|
|
446
449
|
}
|
|
447
450
|
core.persist(ctx);
|
|
448
451
|
core.updateUI(ctx);
|
|
449
|
-
|
|
452
|
+
// agent_end runs before pi finishes retries, compaction, terminating-tool
|
|
453
|
+
// settlement, and queued messages. Starting the continuation timer here
|
|
454
|
+
// can poll a stale busy context for minutes on pi 0.84. agent_settled is
|
|
455
|
+
// available in both supported SDK lines (0.83 and 0.84) and is the first
|
|
456
|
+
// point where pi guarantees no automatic work remains.
|
|
457
|
+
continuationAfterSettleFor = core.state.goal.id;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
461
|
+
const goalId = continuationAfterSettleFor;
|
|
462
|
+
continuationAfterSettleFor = null;
|
|
463
|
+
if (!goalId || !core.isActionableContinuationGoal(goalId)) return;
|
|
464
|
+
core.queueContinuation(ctx, true);
|
|
450
465
|
});
|
|
451
466
|
|
|
452
467
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
468
|
+
continuationAfterSettleFor = null;
|
|
453
469
|
core.accountProgress(ctx);
|
|
454
470
|
core.clearContinuationTimer();
|
|
455
471
|
core.terminalInputUnsubscribe?.();
|
|
@@ -29,6 +29,139 @@ export interface GoalQuestionnaireResult {
|
|
|
29
29
|
|
|
30
30
|
export type ProposalDecision = "confirm" | "continue" | "cancel";
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Bound a custom dialog using the regular renderer's frame cache when
|
|
34
|
+
* available. The pi 0.84 fullscreen renderer does not expose that cache, so
|
|
35
|
+
* reserve four rows for host chrome and use the remaining terminal height.
|
|
36
|
+
*/
|
|
37
|
+
export function computeDialogLineLimit(args: { terminalRows?: number; baseFrameLines?: number }): number | undefined {
|
|
38
|
+
const rows = args.terminalRows;
|
|
39
|
+
if (!rows || rows <= 0) return undefined;
|
|
40
|
+
if (args.baseFrameLines && args.baseFrameLines > 0) {
|
|
41
|
+
return Math.min(rows, Math.max(10, rows - args.baseFrameLines + 1));
|
|
42
|
+
}
|
|
43
|
+
return Math.min(rows, Math.max(4, rows - 4));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Proposal-confirmation segment descriptor: absolute line indices of the
|
|
48
|
+
* tasks section (header + every task line) and the start of the options tail
|
|
49
|
+
* inside a rendered questionnaire dialog.
|
|
50
|
+
*/
|
|
51
|
+
export interface ProposalPresentationSegments {
|
|
52
|
+
tasksStart: number;
|
|
53
|
+
tasksEnd: number;
|
|
54
|
+
tailStart: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Locate the proposal tasks segment and the options tail in a rendered dialog.
|
|
59
|
+
* Returns null when the dialog is not a proposal-style confirmation (no
|
|
60
|
+
* "Tasks proposed for confirmation:" header / "┌─ TASKS ─" box).
|
|
61
|
+
*/
|
|
62
|
+
export function findProposalPresentationSegments(lines: string[], tailStart: number): ProposalPresentationSegments | null {
|
|
63
|
+
if (tailStart < 0) return null;
|
|
64
|
+
const tasksStart = lines.findIndex((l) => l.includes("Tasks proposed for confirmation:") || l.includes("┌─ TASKS ─"));
|
|
65
|
+
if (tasksStart < 0 || tasksStart >= tailStart) return null;
|
|
66
|
+
let tasksEnd = tasksStart;
|
|
67
|
+
for (let i = tasksStart + 1; i < tailStart; i++) {
|
|
68
|
+
if (/^\s*\[[ x~]\]/.test(lines[i])) tasksEnd = i;
|
|
69
|
+
else break;
|
|
70
|
+
}
|
|
71
|
+
return { tasksStart, tasksEnd, tailStart };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fit a rendered dialog to the terminal-height bound without ever hiding the
|
|
76
|
+
* question. The protected head (top border + tabs + question line) is always
|
|
77
|
+
* kept; the remaining budget is spent on the tail — options/footer/bottom
|
|
78
|
+
* border — so long context blocks (proposal confirmations) are sliced from
|
|
79
|
+
* their head exactly as the pre-fix tail-slice did (383ae52 surface). When the
|
|
80
|
+
* options block starts immediately after the head (plain agent questions with
|
|
81
|
+
* no context block), the TOP options are kept instead of the tail so the
|
|
82
|
+
* recommended/first option stays visible; the footer hint and bottom border
|
|
83
|
+
* are always the last rendered lines. Never returns more than maxDialogLines.
|
|
84
|
+
*
|
|
85
|
+
* Proposal confirmations (proposal segments given) keep the head, the tasks
|
|
86
|
+
* section (header + every task line), and the options/footer/bottom border in
|
|
87
|
+
* frame; only the objective-box middle is sacrificed in-frame — the full
|
|
88
|
+
* objective is always present in the scrollable transcript presentation
|
|
89
|
+
* (propose_goal_draft renderCall), so nothing of the goal is ever omitted.
|
|
90
|
+
*/
|
|
91
|
+
export function fitDialogLines(
|
|
92
|
+
lines: string[],
|
|
93
|
+
maxDialogLines: number,
|
|
94
|
+
protectedHead: number,
|
|
95
|
+
optionsImmediatelyAfterHead = false,
|
|
96
|
+
proposal: ProposalPresentationSegments | null = null,
|
|
97
|
+
): string[] {
|
|
98
|
+
if (maxDialogLines <= 0 || lines.length <= maxDialogLines) return lines;
|
|
99
|
+
const keepHead = Math.min(protectedHead, maxDialogLines);
|
|
100
|
+
const budget = maxDialogLines - keepHead;
|
|
101
|
+
if (budget <= 0) return lines.slice(0, keepHead);
|
|
102
|
+
const rest = lines.slice(protectedHead);
|
|
103
|
+
if (rest.length <= budget) return [...lines.slice(0, keepHead), ...rest];
|
|
104
|
+
if (proposal) {
|
|
105
|
+
return fitProposalPresentation(lines, maxDialogLines, keepHead, proposal);
|
|
106
|
+
}
|
|
107
|
+
if (optionsImmediatelyAfterHead) {
|
|
108
|
+
// Plain select-mode question: keep the footer hint + bottom border (as
|
|
109
|
+
// many as fit), then spend the remaining room on the TOP options so the
|
|
110
|
+
// recommended/first option stays visible; leading blank separators are
|
|
111
|
+
// dropped first when room is short. Never exceeds maxDialogLines.
|
|
112
|
+
const headLines = lines.slice(0, keepHead);
|
|
113
|
+
const firstContent = rest.findIndex((l) => l.trim().length > 0);
|
|
114
|
+
const lead = firstContent > 0 ? rest.slice(0, firstContent) : [];
|
|
115
|
+
const contentStart = firstContent > 0 ? firstContent : 0;
|
|
116
|
+
const tailBudget = Math.min(2, budget); // footer hint + bottom border
|
|
117
|
+
const tail = rest.slice(-tailBudget);
|
|
118
|
+
const room = budget - tailBudget;
|
|
119
|
+
const keepContent = Math.max(0, Math.min(rest.length - contentStart, room));
|
|
120
|
+
const keepLead = Math.max(0, Math.min(lead.length, room - keepContent));
|
|
121
|
+
const content = rest.slice(contentStart, contentStart + keepContent);
|
|
122
|
+
return [...headLines, ...lead.slice(0, keepLead), ...content, ...tail];
|
|
123
|
+
}
|
|
124
|
+
// Context-heavy / input / submit dialogs: keep the head and the tail
|
|
125
|
+
// (options, footer, bottom border) exactly as the pre-fix tail-slice did.
|
|
126
|
+
return [...lines.slice(0, keepHead), ...rest.slice(rest.length - budget)];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Proposal confirmation fit: keep the protected head, the tasks section, and
|
|
131
|
+
* the options/footer/bottom border; the objective-box middle is sacrificed
|
|
132
|
+
* in-frame (it stays fully readable in the scrollback presentation). Interior
|
|
133
|
+
* blank spacing lines are dropped first when room is short; task lines are
|
|
134
|
+
* only dropped after that, from the end, when the bound is exhausted (those
|
|
135
|
+
* lines remain in the scrollback presentation). Never exceeds maxDialogLines.
|
|
136
|
+
*/
|
|
137
|
+
function fitProposalPresentation(
|
|
138
|
+
lines: string[],
|
|
139
|
+
maxDialogLines: number,
|
|
140
|
+
keepHead: number,
|
|
141
|
+
proposal: ProposalPresentationSegments,
|
|
142
|
+
): string[] {
|
|
143
|
+
const { tasksStart, tasksEnd, tailStart } = proposal;
|
|
144
|
+
const head = lines.slice(0, keepHead);
|
|
145
|
+
const tasks = lines.slice(Math.max(tasksStart, keepHead), Math.min(tasksEnd, tailStart) + 1);
|
|
146
|
+
const tail = lines.slice(Math.max(tailStart, keepHead));
|
|
147
|
+
const candidate = [...head, ...tasks, ...tail];
|
|
148
|
+
if (candidate.length <= maxDialogLines) return candidate;
|
|
149
|
+
// Tight: drop blank spacing lines below the head (e.g. the blank between
|
|
150
|
+
// the options and the footer hint) before touching any content line.
|
|
151
|
+
const stripped = candidate.filter((l, i) => i < keepHead || l.trim() !== "");
|
|
152
|
+
if (stripped.length <= maxDialogLines) return stripped;
|
|
153
|
+
// Bound still exhausted: keep the head, then spend the room on the tail
|
|
154
|
+
// first (options/footer/bottom border — the actionable decision surface —
|
|
155
|
+
// kept from its end so the border and footer never drop), then on the
|
|
156
|
+
// tasks from the start. The dropped task lines remain fully readable in
|
|
157
|
+
// the scrollback presentation. Never exceeds maxDialogLines.
|
|
158
|
+
const tailNoBlanks = tail.filter((l) => l.trim() !== "");
|
|
159
|
+
const keepTail = Math.min(tailNoBlanks.length, Math.max(0, maxDialogLines - keepHead));
|
|
160
|
+
const tailKept = tailNoBlanks.slice(tailNoBlanks.length - keepTail);
|
|
161
|
+
const keepTasks = Math.min(tasks.length, Math.max(0, maxDialogLines - keepHead - keepTail));
|
|
162
|
+
return [...head, ...tasks.slice(0, keepTasks), ...tailKept];
|
|
163
|
+
}
|
|
164
|
+
|
|
32
165
|
export function normalizeQuestionnaireQuestions(rawQuestions: GoalQuestionnaireQuestion[]): GoalQuestionnaireQuestion[] {
|
|
33
166
|
const seenIds = new Set<string>();
|
|
34
167
|
return rawQuestions.map((q, i) => {
|
|
@@ -108,18 +241,20 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
108
241
|
// the frame never exceeds the terminal height — without this, closing a dialog
|
|
109
242
|
// taller than the terminal triggers pi-tui's generic shrink full-render
|
|
110
243
|
// (\x1b[2J\x1b[H\x1b[3J), erasing terminal scrollback and yanking the viewport
|
|
111
|
-
// so the window takes ~10s to scroll back to the bottom. The
|
|
112
|
-
// the actionable options/footer in view
|
|
113
|
-
//
|
|
244
|
+
// so the window takes ~10s to scroll back to the bottom. The slice keeps
|
|
245
|
+
// the question and the actionable options/footer in view (see
|
|
246
|
+
// fitDialogLines); content that fits renders exactly as the
|
|
247
|
+
// pre-regression (383ae52) UI. Only applies with real TUI dimensions.
|
|
114
248
|
const tuiInfo = tui as unknown as { terminal?: { rows?: number }; previousLines?: string[] };
|
|
115
249
|
const terminalRows = tuiInfo.terminal?.rows;
|
|
116
250
|
const baseFrame = tuiInfo.previousLines?.length;
|
|
117
|
-
const maxDialogLines =
|
|
251
|
+
const maxDialogLines = computeDialogLineLimit({ terminalRows, baseFrameLines: baseFrame });
|
|
118
252
|
let currentTab = 0;
|
|
119
253
|
let optionIndex = 0;
|
|
120
254
|
let inputMode = false;
|
|
121
255
|
let inputQuestionId: string | null = null;
|
|
122
256
|
let cachedLines: string[] | undefined;
|
|
257
|
+
let optionsStartIndex = -1;
|
|
123
258
|
let auditorEnabled = auditorToggleInit?.defaultEnabled ?? true;
|
|
124
259
|
const answers = new Map<string, GoalQuestionnaireAnswer>();
|
|
125
260
|
const drafts = new Map<string, string>();
|
|
@@ -439,6 +574,9 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
439
574
|
};
|
|
440
575
|
|
|
441
576
|
add(theme.fg("accent", "─".repeat(safeWidth)));
|
|
577
|
+
// Lines up to the question line (top border, tabs, question incl.
|
|
578
|
+
// wraps) are the protected head — the slice below must never hide them.
|
|
579
|
+
let protectedCount = lines.length;
|
|
442
580
|
if (isMulti) {
|
|
443
581
|
const tabs: string[] = ["← "];
|
|
444
582
|
for (let i = 0; i < questions.length; i++) {
|
|
@@ -453,9 +591,11 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
453
591
|
tabs.push(" →");
|
|
454
592
|
add(` ${tabs.join("")}`);
|
|
455
593
|
lines.push("");
|
|
594
|
+
protectedCount = lines.length;
|
|
456
595
|
}
|
|
457
596
|
|
|
458
597
|
function renderOptions() {
|
|
598
|
+
optionsStartIndex = lines.length;
|
|
459
599
|
for (let i = 0; i < opts.length; i++) {
|
|
460
600
|
const opt = opts[i];
|
|
461
601
|
const selected = i === optionIndex;
|
|
@@ -467,6 +607,7 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
467
607
|
|
|
468
608
|
if (inputMode && q) {
|
|
469
609
|
addWrapped(theme.fg("text", ` ${q.question}`));
|
|
610
|
+
protectedCount = lines.length;
|
|
470
611
|
if (q.context) renderContextLines(q.context);
|
|
471
612
|
lines.push("");
|
|
472
613
|
if (q.options.length > 0) {
|
|
@@ -479,6 +620,7 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
479
620
|
add(theme.fg("dim", " Enter to submit • Esc to cancel"));
|
|
480
621
|
} else if (currentTab === questions.length) {
|
|
481
622
|
add(theme.fg("accent", theme.bold(" Ready to submit")));
|
|
623
|
+
protectedCount = lines.length;
|
|
482
624
|
lines.push("");
|
|
483
625
|
for (const question of questions) {
|
|
484
626
|
const answer = answers.get(question.id);
|
|
@@ -488,6 +630,7 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
488
630
|
add(allAnswered() ? theme.fg("success", " Press Enter to submit") : theme.fg("warning", ` Unanswered: ${questions.filter((qq) => !answers.has(qq.id)).map((qq) => qq.id).join(", ")}`));
|
|
489
631
|
} else if (q) {
|
|
490
632
|
addWrapped(theme.fg("text", ` ${q.question}`));
|
|
633
|
+
protectedCount = lines.length;
|
|
491
634
|
if (q.context) renderContextLines(q.context);
|
|
492
635
|
// Auditor toggle line between context and options
|
|
493
636
|
if (auditorToggleInit) {
|
|
@@ -516,9 +659,21 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
516
659
|
lines[i] = truncateToWidth(lines[i], safeWidth);
|
|
517
660
|
}
|
|
518
661
|
}
|
|
519
|
-
// Churn guard:
|
|
662
|
+
// Churn guard: bound to the terminal height (see factory top) so the
|
|
663
|
+
// opened frame never exceeds the screen. Never slice the question:
|
|
664
|
+
// keep the protected head, then spend the rest of the budget on the
|
|
665
|
+
// tail (options/footer/bottom border) — or, for plain select-mode
|
|
666
|
+
// questions where the options start right after the head, keep the
|
|
667
|
+
// top options so the recommended one stays visible. Proposal
|
|
668
|
+
// confirmations additionally keep the tasks section in frame; the
|
|
669
|
+
// objective-box middle is sacrificed there because the full objective
|
|
670
|
+
// is always in the scrollable transcript presentation (renderCall).
|
|
520
671
|
if (maxDialogLines !== undefined && lines.length > maxDialogLines) {
|
|
521
|
-
|
|
672
|
+
const optionsImmediatelyAfterHead = !inputMode && currentTab !== questions.length && !!q && !q.context && opts.length > 0;
|
|
673
|
+
const proposalSegments = !inputMode && currentTab !== questions.length && !!q && q.context
|
|
674
|
+
? findProposalPresentationSegments(lines, optionsStartIndex)
|
|
675
|
+
: null;
|
|
676
|
+
lines = fitDialogLines(lines, maxDialogLines, protectedCount, optionsImmediatelyAfterHead, proposalSegments);
|
|
522
677
|
}
|
|
523
678
|
cachedLines = lines;
|
|
524
679
|
return lines;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"typebox": "*"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@earendil-works/pi-ai": "^0.
|
|
66
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
67
|
-
"@earendil-works/pi-tui": "^0.
|
|
65
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
66
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
67
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
68
68
|
"typebox": "^1.0.58",
|
|
69
69
|
"typescript": "^5.9.3"
|
|
70
70
|
},
|