@kenkaiiii/ggcoder 4.3.217 → 4.3.219
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/dist/cli/command-routing.d.ts +28 -0
- package/dist/cli/command-routing.d.ts.map +1 -0
- package/dist/cli/command-routing.js +51 -0
- package/dist/cli/command-routing.js.map +1 -0
- package/dist/cli/command-routing.test.d.ts +2 -0
- package/dist/cli/command-routing.test.d.ts.map +1 -0
- package/dist/cli/command-routing.test.js +59 -0
- package/dist/cli/command-routing.test.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +48 -104
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-compaction.test.d.ts +2 -0
- package/dist/core/agent-session-compaction.test.d.ts.map +1 -0
- package/dist/core/agent-session-compaction.test.js +121 -0
- package/dist/core/agent-session-compaction.test.js.map +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -7
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/goal-store.d.ts +11 -0
- package/dist/core/goal-store.d.ts.map +1 -1
- package/dist/core/goal-store.js +45 -0
- package/dist/core/goal-store.js.map +1 -1
- package/dist/core/goal-worker.d.ts +7 -0
- package/dist/core/goal-worker.d.ts.map +1 -1
- package/dist/core/goal-worker.js +62 -20
- package/dist/core/goal-worker.js.map +1 -1
- package/dist/core/goal-worker.test.js +92 -2
- package/dist/core/goal-worker.test.js.map +1 -1
- package/dist/core/goal-worktree.d.ts +33 -0
- package/dist/core/goal-worktree.d.ts.map +1 -0
- package/dist/core/goal-worktree.js +67 -0
- package/dist/core/goal-worktree.js.map +1 -0
- package/dist/core/goal-worktree.test.d.ts +2 -0
- package/dist/core/goal-worktree.test.d.ts.map +1 -0
- package/dist/core/goal-worktree.test.js +101 -0
- package/dist/core/goal-worktree.test.js.map +1 -0
- package/dist/core/repomap-budget.d.ts +7 -0
- package/dist/core/repomap-budget.d.ts.map +1 -0
- package/dist/core/repomap-budget.js +10 -0
- package/dist/core/repomap-budget.js.map +1 -0
- package/dist/core/repomap-budget.test.d.ts +2 -0
- package/dist/core/repomap-budget.test.d.ts.map +1 -0
- package/dist/core/repomap-budget.test.js +26 -0
- package/dist/core/repomap-budget.test.js.map +1 -0
- package/dist/core/tasks-store.d.ts +24 -0
- package/dist/core/tasks-store.d.ts.map +1 -0
- package/dist/core/tasks-store.js +81 -0
- package/dist/core/tasks-store.js.map +1 -0
- package/dist/system-prompt.js +2 -2
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +2 -2
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/goals.d.ts.map +1 -1
- package/dist/tools/goals.js +30 -26
- package/dist/tools/goals.js.map +1 -1
- package/dist/tools/goals.test.js +47 -1
- package/dist/tools/goals.test.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tasks.d.ts +16 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +93 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/ui/App.d.ts +12 -381
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +212 -679
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +212 -0
- package/dist/ui/app-items.d.ts.map +1 -0
- package/dist/ui/app-items.js +2 -0
- package/dist/ui/app-items.js.map +1 -0
- package/dist/ui/app-state-persistence.test.js +22 -1
- package/dist/ui/app-state-persistence.test.js.map +1 -1
- package/dist/ui/chat-layout-pinning.test.js +12 -1
- package/dist/ui/chat-layout-pinning.test.js.map +1 -1
- package/dist/ui/components/Banner.js +2 -2
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/GoalPickerMenu.d.ts +9 -0
- package/dist/ui/components/GoalPickerMenu.d.ts.map +1 -0
- package/dist/ui/components/GoalPickerMenu.js +37 -0
- package/dist/ui/components/GoalPickerMenu.js.map +1 -0
- package/dist/ui/components/InputArea.d.ts +16 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +91 -3
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/TaskPickerMenu.d.ts +9 -0
- package/dist/ui/components/TaskPickerMenu.d.ts.map +1 -0
- package/dist/ui/components/TaskPickerMenu.js +33 -0
- package/dist/ui/components/TaskPickerMenu.js.map +1 -0
- package/dist/ui/components/ToolUseLoader.d.ts +3 -1
- package/dist/ui/components/ToolUseLoader.d.ts.map +1 -1
- package/dist/ui/components/ToolUseLoader.js +2 -2
- package/dist/ui/components/ToolUseLoader.js.map +1 -1
- package/dist/ui/goal-events.test.js +2 -1
- package/dist/ui/goal-events.test.js.map +1 -1
- package/dist/ui/goal-lifecycle-orchestration.test.js +20 -1
- package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
- package/dist/ui/goal-progress.d.ts +31 -0
- package/dist/ui/goal-progress.d.ts.map +1 -0
- package/dist/ui/goal-progress.js +152 -0
- package/dist/ui/goal-progress.js.map +1 -0
- package/dist/ui/item-helpers.d.ts +26 -0
- package/dist/ui/item-helpers.d.ts.map +1 -0
- package/dist/ui/item-helpers.js +112 -0
- package/dist/ui/item-helpers.js.map +1 -0
- package/dist/ui/layout-decisions.d.ts +113 -0
- package/dist/ui/layout-decisions.d.ts.map +1 -0
- package/dist/ui/layout-decisions.js +174 -0
- package/dist/ui/layout-decisions.js.map +1 -0
- package/dist/ui/prompt-routing.d.ts +29 -0
- package/dist/ui/prompt-routing.d.ts.map +1 -0
- package/dist/ui/prompt-routing.js +105 -0
- package/dist/ui/prompt-routing.js.map +1 -0
- package/dist/ui/queued-message.test.js +1 -1
- package/dist/ui/queued-message.test.js.map +1 -1
- package/dist/ui/render.d.ts +1 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +1 -0
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/scroll-stabilization.test.js +1 -1
- package/dist/ui/scroll-stabilization.test.js.map +1 -1
- package/dist/ui/slash-command-images.test.js +1 -1
- package/dist/ui/slash-command-images.test.js.map +1 -1
- package/dist/ui/terminal-history-format.d.ts +41 -0
- package/dist/ui/terminal-history-format.d.ts.map +1 -0
- package/dist/ui/terminal-history-format.js +131 -0
- package/dist/ui/terminal-history-format.js.map +1 -0
- package/dist/ui/terminal-history-spacing.d.ts +3 -0
- package/dist/ui/terminal-history-spacing.d.ts.map +1 -0
- package/dist/ui/terminal-history-spacing.js +29 -0
- package/dist/ui/terminal-history-spacing.js.map +1 -0
- package/dist/ui/terminal-history-status-renderers.d.ts +16 -0
- package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -0
- package/dist/ui/terminal-history-status-renderers.js +83 -0
- package/dist/ui/terminal-history-status-renderers.js.map +1 -0
- package/dist/ui/terminal-history.d.ts.map +1 -1
- package/dist/ui/terminal-history.js +23 -236
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +2 -1
- package/dist/ui/terminal-history.test.js.map +1 -1
- package/dist/ui/thinking-level-cycle.test.js +7 -1
- package/dist/ui/thinking-level-cycle.test.js.map +1 -1
- package/dist/ui/thinking-level.d.ts +5 -0
- package/dist/ui/thinking-level.d.ts.map +1 -0
- package/dist/ui/thinking-level.js +30 -0
- package/dist/ui/thinking-level.js.map +1 -0
- package/dist/ui/tui-history-parity.test.js +7 -3
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/package.json +3 -3
package/dist/ui/App.js
CHANGED
|
@@ -4,7 +4,6 @@ import { Box, Text, useStdout } from "ink";
|
|
|
4
4
|
import { useTerminalSize } from "./hooks/useTerminalSize.js";
|
|
5
5
|
import { useDoublePress } from "./hooks/useDoublePress.js";
|
|
6
6
|
import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
|
|
7
|
-
import { writeFileSync } from "node:fs";
|
|
8
7
|
import { playNotificationSound } from "../utils/sound.js";
|
|
9
8
|
import { formatError, } from "@kenkaiiii/gg-ai";
|
|
10
9
|
import { extractImagePaths } from "../utils/image.js";
|
|
@@ -28,16 +27,15 @@ import { GoalStatusBar, reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntr
|
|
|
28
27
|
import { Banner } from "./components/Banner.js";
|
|
29
28
|
import { PlanOverlay } from "./components/PlanOverlay.js";
|
|
30
29
|
import { ModelSelector } from "./components/ModelSelector.js";
|
|
31
|
-
import { GoalOverlay } from "./components/GoalOverlay.js";
|
|
32
30
|
import { PixelOverlay } from "./components/PixelOverlay.js";
|
|
33
|
-
import { buildGoalFinalSummarySections, buildGoalSummaryRows, goalPassedDetail, } from "./goal-summary.js";
|
|
34
31
|
import { SkillsOverlay } from "./components/SkillsOverlay.js";
|
|
35
32
|
import { ThemeSelector } from "./components/ThemeSelector.js";
|
|
36
33
|
import { BackgroundTasksBar, getFooterStatusLayoutDecision, } from "./components/BackgroundTasksBar.js";
|
|
37
34
|
import { useTheme, useSetTheme } from "./theme/theme.js";
|
|
38
35
|
import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
|
|
39
36
|
import { getGitBranch } from "../utils/git.js";
|
|
40
|
-
import { getModel, getContextWindow
|
|
37
|
+
import { getModel, getContextWindow } from "../core/model-registry.js";
|
|
38
|
+
import { BLACK_CIRCLE } from "./constants/figures.js";
|
|
41
39
|
import { SessionManager } from "../core/session-manager.js";
|
|
42
40
|
import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
|
|
43
41
|
import { log } from "../core/logger.js";
|
|
@@ -52,18 +50,30 @@ import { loadCustomCommands } from "../core/custom-commands.js";
|
|
|
52
50
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
53
51
|
import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
|
|
54
52
|
import { detectVerifyCommands } from "../core/verify-commands.js";
|
|
55
|
-
import {
|
|
53
|
+
import { buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
|
|
54
|
+
import { getRepoMapBudgetForContext } from "../core/repomap-budget.js";
|
|
56
55
|
import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
|
|
57
56
|
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
58
57
|
import { getMCPServers } from "../core/mcp/index.js";
|
|
59
58
|
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
|
|
60
59
|
import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
|
|
61
|
-
import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
|
|
62
|
-
import {
|
|
60
|
+
import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, loadGoalRunsSync, reconcileActiveGoalRuns, saveGoalRunsSync, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
|
|
61
|
+
import { getNextPendingTask, loadTasksSync, markTaskInProgress, saveTasksSync, } from "../core/tasks-store.js";
|
|
62
|
+
import { canCompleteGoalRun, decideGoalNextAction } from "../core/goal-controller.js";
|
|
63
63
|
import { runGoalPrerequisiteChecks } from "../core/goal-prerequisites.js";
|
|
64
64
|
import { runGoalVerifierCommand } from "../core/goal-verifier.js";
|
|
65
65
|
import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
|
|
66
66
|
import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
|
|
67
|
+
import { buildUserContentWithAttachments, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
|
|
68
|
+
import { getNextThinkingLevel, isThinkingLevelSupported } from "./thinking-level.js";
|
|
69
|
+
import { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, formatGoalWorkerFinishedTitle, getGoalContinuationChoiceKey, goalTerminalProgressId, routeGoalSyntheticEvent, summarizeGoalCompletion, truncateGoalProgressText, } from "./goal-progress.js";
|
|
70
|
+
import { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalSetupPaneTransitionAfterRun, isAgentSpacingItem, MIN_LIVE_AREA_ROWS, nextGoalModeAfterAgentDone, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
|
|
71
|
+
import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
|
|
72
|
+
export { buildGoalSetupPromptFromPlanner, buildUserContentWithAttachments, collectAssistantTextSince, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
|
|
73
|
+
export { getNextThinkingLevel } from "./thinking-level.js";
|
|
74
|
+
export { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, getGoalContinuationChoiceKey, routeGoalSyntheticEvent, truncateGoalProgressText, } from "./goal-progress.js";
|
|
75
|
+
export { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, getScrollStabilizationDecision, getStaticHistoryKey, hasParagraphBreakLiveUserMessage, isTallLiveUserMessage, nextGoalModeAfterAgentDone, shouldHideHistoryForOverlayView, shouldHideStaticItemsForOverlayView, shouldResetUIForGoalSetupPaneTransition, shouldStabilizeOverlayPaneRerender, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
|
|
76
|
+
export { getNextGeneratedItemId, isActiveItem, partitionCompleted, pinStreamingTextBeforeToolBoundary, } from "./item-helpers.js";
|
|
67
77
|
/** Where ggcoder bugs should be reported. Surfaced in the guidance line. */
|
|
68
78
|
const GGCODER_BUG_REPORT_URL = "github.com/kenkaiiii/gg-framework/issues";
|
|
69
79
|
/**
|
|
@@ -97,114 +107,6 @@ function toErrorItem(err, id, contextPrefix) {
|
|
|
97
107
|
id,
|
|
98
108
|
};
|
|
99
109
|
}
|
|
100
|
-
export function routePromptCommandInput(input, promptCommands = PROMPT_COMMANDS, customCommands = []) {
|
|
101
|
-
const trimmed = input.trim();
|
|
102
|
-
if (!trimmed.startsWith("/"))
|
|
103
|
-
return null;
|
|
104
|
-
const parts = trimmed.slice(1).split(" ");
|
|
105
|
-
const cmdName = parts[0];
|
|
106
|
-
const cmdArgs = parts.slice(1).join(" ").trim();
|
|
107
|
-
const builtinCmd = promptCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
|
|
108
|
-
const customCmd = !builtinCmd ? customCommands.find((c) => c.name === cmdName) : undefined;
|
|
109
|
-
const promptText = builtinCmd?.prompt ?? customCmd?.prompt;
|
|
110
|
-
if (!promptText)
|
|
111
|
-
return null;
|
|
112
|
-
return {
|
|
113
|
-
cmdName,
|
|
114
|
-
cmdArgs,
|
|
115
|
-
promptText,
|
|
116
|
-
fullPrompt: cmdArgs ? `${promptText}\n\n## User Instructions\n\n${cmdArgs}` : promptText,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
const GOAL_PLANNER_OUTPUT_MAX_CHARS = 2400;
|
|
120
|
-
function messageTextContent(message) {
|
|
121
|
-
if (typeof message.content === "string")
|
|
122
|
-
return message.content;
|
|
123
|
-
return message.content
|
|
124
|
-
.filter((part) => part.type === "text")
|
|
125
|
-
.map((part) => part.text)
|
|
126
|
-
.join("\n");
|
|
127
|
-
}
|
|
128
|
-
export function collectAssistantTextSince(messages, startIndex, maxChars = GOAL_PLANNER_OUTPUT_MAX_CHARS) {
|
|
129
|
-
const text = messages
|
|
130
|
-
.slice(startIndex)
|
|
131
|
-
.filter((message) => message.role === "assistant")
|
|
132
|
-
.map(messageTextContent)
|
|
133
|
-
.join("\n")
|
|
134
|
-
.trim();
|
|
135
|
-
if (text.length <= maxChars)
|
|
136
|
-
return text;
|
|
137
|
-
return text.slice(0, maxChars).trimEnd() + "\n[planner output truncated]";
|
|
138
|
-
}
|
|
139
|
-
export function buildGoalSetupPromptFromPlanner({ originalGoalPrompt, plannerOutput, }) {
|
|
140
|
-
const compactPlannerOutput = plannerOutput.trim() || "GOAL_PLAN\nresearch=none\nEND_GOAL_PLAN";
|
|
141
|
-
return (`${originalGoalPrompt.trim()}\n\n` +
|
|
142
|
-
`## Goal Planner Output\n\n${compactPlannerOutput}\n\n` +
|
|
143
|
-
`Use the original objective plus this planner output to create durable Goal setup only. ` +
|
|
144
|
-
`Do not redo planner research unless the planner output is unusable.`);
|
|
145
|
-
}
|
|
146
|
-
export function isGoalPromptCommandName(cmdName) {
|
|
147
|
-
return getPromptCommand(cmdName)?.name === "goal";
|
|
148
|
-
}
|
|
149
|
-
export async function runGoalPromptSetupSequence({ userContent, fullPrompt, messagesRef, setGoalModeAndPrompt, runAgent, onStage, }) {
|
|
150
|
-
onStage?.("Planning Goal setup");
|
|
151
|
-
await setGoalModeAndPrompt("planner");
|
|
152
|
-
const plannerStartIndex = messagesRef.current.length;
|
|
153
|
-
await runAgent(userContent);
|
|
154
|
-
const plannerOutput = collectAssistantTextSince(messagesRef.current, plannerStartIndex);
|
|
155
|
-
const setupPrompt = buildGoalSetupPromptFromPlanner({
|
|
156
|
-
originalGoalPrompt: fullPrompt,
|
|
157
|
-
plannerOutput,
|
|
158
|
-
});
|
|
159
|
-
await setGoalModeAndPrompt("setup");
|
|
160
|
-
onStage?.("Creating Goal run");
|
|
161
|
-
await runAgent(setupPrompt);
|
|
162
|
-
}
|
|
163
|
-
function buildGoalTaskPromptWithReferences(run, taskPrompt) {
|
|
164
|
-
if (taskPrompt.includes("## Goal References (MANDATORY)"))
|
|
165
|
-
return taskPrompt;
|
|
166
|
-
const references = formatGoalReferencesForPrompt(run.references ?? []);
|
|
167
|
-
return references ? `${references}\n\n${taskPrompt}` : taskPrompt;
|
|
168
|
-
}
|
|
169
|
-
export function buildUserContentWithAttachments(text, inputImages, modelSupportsImages) {
|
|
170
|
-
if (inputImages.length === 0)
|
|
171
|
-
return text;
|
|
172
|
-
const parts = [];
|
|
173
|
-
if (text) {
|
|
174
|
-
parts.push({ type: "text", text });
|
|
175
|
-
}
|
|
176
|
-
for (const img of inputImages) {
|
|
177
|
-
if (img.kind === "text") {
|
|
178
|
-
parts.push({
|
|
179
|
-
type: "text",
|
|
180
|
-
text: `<file name="${img.fileName}">\n${img.data}\n</file>`,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
else if (modelSupportsImages) {
|
|
184
|
-
parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
// GLM models: save image to temp file and instruct model to use vision MCP tool
|
|
188
|
-
const ext = img.mediaType.split("/")[1] ?? "png";
|
|
189
|
-
const tmpPath = `/tmp/ggcoder-img-${Date.now()}.${ext}`;
|
|
190
|
-
try {
|
|
191
|
-
writeFileSync(tmpPath, Buffer.from(img.data, "base64"));
|
|
192
|
-
parts.push({
|
|
193
|
-
type: "text",
|
|
194
|
-
text: `[User attached an image saved at: ${tmpPath} — use the image_analysis tool to view and analyze it]`,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
parts.push({
|
|
199
|
-
type: "text",
|
|
200
|
-
text: `[User attached an image but it could not be saved for analysis]`,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
// If only text parts remain after stripping images, simplify to plain string
|
|
206
|
-
return parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
|
|
207
|
-
}
|
|
208
110
|
/** Tools that get aggregated into a single compact group when possible. */
|
|
209
111
|
const AGGREGATABLE_TOOLS = new Set([
|
|
210
112
|
"read",
|
|
@@ -215,66 +117,17 @@ const AGGREGATABLE_TOOLS = new Set([
|
|
|
215
117
|
"mcp__kencode-search__referenceSources",
|
|
216
118
|
"mcp__kencode-search__discoverRepos",
|
|
217
119
|
]);
|
|
218
|
-
const OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
|
|
219
|
-
function isOpenAIGptModel(provider, model) {
|
|
220
|
-
return provider === "openai" && model.startsWith("gpt-");
|
|
221
|
-
}
|
|
222
|
-
export function getNextThinkingLevel(provider, model, current) {
|
|
223
|
-
if (!isOpenAIGptModel(provider, model)) {
|
|
224
|
-
return current ? undefined : getMaxThinkingLevel(model);
|
|
225
|
-
}
|
|
226
|
-
if (!current)
|
|
227
|
-
return "medium";
|
|
228
|
-
const index = OPENAI_GPT_THINKING_LEVELS.indexOf(current);
|
|
229
|
-
if (index === -1)
|
|
230
|
-
return "medium";
|
|
231
|
-
return OPENAI_GPT_THINKING_LEVELS[index + 1];
|
|
232
|
-
}
|
|
233
120
|
const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const MAX_LIVE_HISTORY = 200;
|
|
240
|
-
function compactHistory(items) {
|
|
241
|
-
if (items.length <= MAX_LIVE_HISTORY)
|
|
242
|
-
return items;
|
|
243
|
-
const cutoff = items.length - MAX_LIVE_HISTORY;
|
|
244
|
-
const compacted = new Array(items.length);
|
|
245
|
-
for (let i = 0; i < cutoff; i++) {
|
|
246
|
-
const it = items[i];
|
|
247
|
-
compacted[i] = it.kind === "tombstone" ? it : { kind: "tombstone", id: it.id };
|
|
248
|
-
}
|
|
249
|
-
for (let i = cutoff; i < items.length; i++) {
|
|
250
|
-
compacted[i] = items[i];
|
|
251
|
-
}
|
|
252
|
-
return compacted;
|
|
253
|
-
}
|
|
254
|
-
function summarizeGoalCompletion(summary) {
|
|
255
|
-
const lines = summary
|
|
256
|
-
.split("\n")
|
|
257
|
-
.map((line) => line.trim())
|
|
258
|
-
.filter((line) => line.length > 0 && line !== "[agent_done]");
|
|
259
|
-
const statusLine = lines.find((line) => /^Status:/i.test(line));
|
|
260
|
-
const changedLine = lines.find((line) => /^(Changed|Implemented|Fixed|Added|Key findings|Full verifier)/i.test(line));
|
|
261
|
-
const verificationLine = lines.find((line) => /^(Verification|Verified|Result):/i.test(line));
|
|
262
|
-
return statusLine ?? changedLine ?? verificationLine ?? lines[0];
|
|
263
|
-
}
|
|
264
|
-
const GOAL_PROGRESS_TEXT_LIMIT = 72;
|
|
265
|
-
export function truncateGoalProgressText(text) {
|
|
266
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
267
|
-
if (normalized.length <= GOAL_PROGRESS_TEXT_LIMIT)
|
|
268
|
-
return normalized;
|
|
269
|
-
return `${normalized.slice(0, GOAL_PROGRESS_TEXT_LIMIT - 1).trimEnd()}…`;
|
|
270
|
-
}
|
|
271
|
-
function formatGoalWorkerFinishedTitle(taskTitle, status) {
|
|
272
|
-
const prefix = status === "done" ? "Done" : "Failed";
|
|
273
|
-
return truncateGoalProgressText(`${prefix}: ${taskTitle}`);
|
|
121
|
+
function buildGoalTaskPromptWithReferences(run, taskPrompt) {
|
|
122
|
+
if (taskPrompt.includes("## Goal References (MANDATORY)"))
|
|
123
|
+
return taskPrompt;
|
|
124
|
+
const references = formatGoalReferencesForPrompt(run.references ?? []);
|
|
125
|
+
return references ? `${references}\n\n${taskPrompt}` : taskPrompt;
|
|
274
126
|
}
|
|
275
127
|
function goalProgressLoaderStatus(item) {
|
|
276
|
-
if (item.status === "failed" || item.status === "fail" || item.status === "blocked")
|
|
128
|
+
if (item.status === "failed" || item.status === "fail" || item.status === "blocked") {
|
|
277
129
|
return "error";
|
|
130
|
+
}
|
|
278
131
|
if (item.phase === "worker_finished" ||
|
|
279
132
|
item.phase === "verifier_finished" ||
|
|
280
133
|
item.phase === "terminal") {
|
|
@@ -297,373 +150,6 @@ function goalProgressColor(item, theme) {
|
|
|
297
150
|
return theme.warning;
|
|
298
151
|
return theme.primary;
|
|
299
152
|
}
|
|
300
|
-
function goalTerminalProgressId(run) {
|
|
301
|
-
return `goal-terminal-${run.id}`;
|
|
302
|
-
}
|
|
303
|
-
function goalTerminalRunIdFromItem(item) {
|
|
304
|
-
if (item.kind !== "goal_progress" || item.phase !== "terminal")
|
|
305
|
-
return undefined;
|
|
306
|
-
if (!item.id.startsWith("goal-terminal-"))
|
|
307
|
-
return undefined;
|
|
308
|
-
return item.id.slice("goal-terminal-".length);
|
|
309
|
-
}
|
|
310
|
-
function goalProgressMatchesDraft(item, draft) {
|
|
311
|
-
return (item.phase === draft.phase &&
|
|
312
|
-
item.title === draft.title &&
|
|
313
|
-
item.detail === draft.detail &&
|
|
314
|
-
item.workerId === draft.workerId &&
|
|
315
|
-
item.status === draft.status &&
|
|
316
|
-
JSON.stringify(item.summaryRows ?? []) === JSON.stringify(draft.summaryRows ?? []) &&
|
|
317
|
-
JSON.stringify(item.summarySections ?? []) === JSON.stringify(draft.summarySections ?? []));
|
|
318
|
-
}
|
|
319
|
-
export function appendGoalProgressDraft(items, draft, makeId) {
|
|
320
|
-
const previous = items.at(-1);
|
|
321
|
-
if (previous?.kind === "goal_progress" && goalProgressMatchesDraft(previous, draft)) {
|
|
322
|
-
return items;
|
|
323
|
-
}
|
|
324
|
-
return [...items, { ...draft, id: makeId() }];
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Reconcile terminal Goal cards that are already visible in this UI session.
|
|
328
|
-
*
|
|
329
|
-
* This intentionally does not synthesize missing cards from durable GoalRun
|
|
330
|
-
* state. Goal terminal cards are event notifications: they should appear when
|
|
331
|
-
* the terminal event happens in the current UI, not whenever a fresh session
|
|
332
|
-
* polls old Goal runs from the Goal pane. Callers that just observed a terminal
|
|
333
|
-
* event append that card first, then use this helper to tombstone stale older
|
|
334
|
-
* cards for the same run.
|
|
335
|
-
*/
|
|
336
|
-
export function getNextGeneratedItemId(items) {
|
|
337
|
-
let max = -1;
|
|
338
|
-
for (const item of items) {
|
|
339
|
-
const raw = item.id.startsWith("ui-") ? item.id.slice(3) : item.id;
|
|
340
|
-
const n = Number(raw);
|
|
341
|
-
if (Number.isInteger(n) && n >= 0 && n > max)
|
|
342
|
-
max = n;
|
|
343
|
-
}
|
|
344
|
-
return max + 1;
|
|
345
|
-
}
|
|
346
|
-
export function completedItemsWithDurableGoalTerminalProgress(items, runs) {
|
|
347
|
-
const runIds = new Set(runs.map((run) => run.id));
|
|
348
|
-
const terminalByRun = new Map(runs
|
|
349
|
-
.map((run) => [run.id, formatGoalTerminalProgress(run)])
|
|
350
|
-
.filter((entry) => entry[1] !== null));
|
|
351
|
-
if (runIds.size === 0)
|
|
352
|
-
return items;
|
|
353
|
-
let changed = false;
|
|
354
|
-
const reconciled = items.map((item, index) => {
|
|
355
|
-
const runId = goalTerminalRunIdFromItem(item);
|
|
356
|
-
if (!runId || !runIds.has(runId))
|
|
357
|
-
return item;
|
|
358
|
-
const draft = terminalByRun.get(runId);
|
|
359
|
-
if (draft && goalProgressMatchesDraft(item, draft))
|
|
360
|
-
return item;
|
|
361
|
-
changed = true;
|
|
362
|
-
return { kind: "tombstone", id: `tombstone-${item.id}-${index}` };
|
|
363
|
-
});
|
|
364
|
-
return changed ? reconciled : items;
|
|
365
|
-
}
|
|
366
|
-
export function formatGoalTerminalProgress(run) {
|
|
367
|
-
switch (run.status) {
|
|
368
|
-
case "passed":
|
|
369
|
-
return {
|
|
370
|
-
kind: "goal_progress",
|
|
371
|
-
phase: "terminal",
|
|
372
|
-
title: `Goal passed: ${run.title}`,
|
|
373
|
-
detail: goalPassedDetail(run),
|
|
374
|
-
summaryRows: buildGoalSummaryRows(run),
|
|
375
|
-
summarySections: buildGoalFinalSummarySections(run),
|
|
376
|
-
status: run.status,
|
|
377
|
-
};
|
|
378
|
-
case "failed":
|
|
379
|
-
return {
|
|
380
|
-
kind: "goal_progress",
|
|
381
|
-
phase: "terminal",
|
|
382
|
-
title: `Goal failed: ${run.title}`,
|
|
383
|
-
detail: "Auto-continuation stopped. Check Goal tasks for the failing step.",
|
|
384
|
-
summaryRows: buildGoalSummaryRows(run),
|
|
385
|
-
status: run.status,
|
|
386
|
-
};
|
|
387
|
-
case "blocked":
|
|
388
|
-
return {
|
|
389
|
-
kind: "goal_progress",
|
|
390
|
-
phase: "terminal",
|
|
391
|
-
title: `Goal blocked: ${run.title}`,
|
|
392
|
-
detail: goalHasBlockingPrerequisites(run)
|
|
393
|
-
? formatGoalBlockingPrerequisites(run)
|
|
394
|
-
: (run.blockers[0] ?? "A prerequisite or missing verifier blocked progress."),
|
|
395
|
-
summaryRows: buildGoalSummaryRows(run),
|
|
396
|
-
status: run.status,
|
|
397
|
-
};
|
|
398
|
-
case "paused":
|
|
399
|
-
return {
|
|
400
|
-
kind: "goal_progress",
|
|
401
|
-
phase: "terminal",
|
|
402
|
-
title: `Goal paused: ${run.title}`,
|
|
403
|
-
detail: run.blockers[0] ?? "Auto-continuation paused.",
|
|
404
|
-
summaryRows: buildGoalSummaryRows(run),
|
|
405
|
-
status: run.status,
|
|
406
|
-
};
|
|
407
|
-
case "draft":
|
|
408
|
-
case "ready":
|
|
409
|
-
case "running":
|
|
410
|
-
case "verifying":
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
export function shouldHideHistoryForOverlayView(isOverlayView, _isAgentRunning) {
|
|
415
|
-
// Overlay panes are standalone full-screen states. Finalized chat rows are
|
|
416
|
-
// printed outside Ink, so overlays should never replay transcript UI behind them.
|
|
417
|
-
return isOverlayView;
|
|
418
|
-
}
|
|
419
|
-
export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
|
|
420
|
-
return isAgentRunning && overlayPane === "goal";
|
|
421
|
-
}
|
|
422
|
-
export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender: _stabilizeOverlayPaneRerender, }) {
|
|
423
|
-
return shouldHideHistoryForOverlay;
|
|
424
|
-
}
|
|
425
|
-
export function getDoneFlushDecision({ planOverlayPending, goalMode, goalAutoExpand, }) {
|
|
426
|
-
return {
|
|
427
|
-
showDoneStatus: !(planOverlayPending ||
|
|
428
|
-
goalMode === "planner" ||
|
|
429
|
-
goalMode === "setup" ||
|
|
430
|
-
goalAutoExpand),
|
|
431
|
-
flushLiveItems: true,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
export function getGoalSetupFinishedPaneTransition() {
|
|
435
|
-
return {
|
|
436
|
-
overlay: "goal",
|
|
437
|
-
goalAutoExpand: true,
|
|
438
|
-
planAutoExpand: false,
|
|
439
|
-
suppressDoneStatus: true,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
export function getGoalSetupPaneTransitionAfterRun({ isGoalSetupCommand, setupPanePending, }) {
|
|
443
|
-
return isGoalSetupCommand && setupPanePending ? getGoalSetupFinishedPaneTransition() : null;
|
|
444
|
-
}
|
|
445
|
-
export function shouldResetUIForSetupPaneTransition({ hasResetUI, hasSessionStore, }) {
|
|
446
|
-
// Opening a review pane is a full-screen state transition. A bare React state
|
|
447
|
-
// flip hides history in the virtual tree, but it does not reset Ink/log-update's
|
|
448
|
-
// already-written terminal frame, so the pane can render below prior chat.
|
|
449
|
-
return hasResetUI && hasSessionStore;
|
|
450
|
-
}
|
|
451
|
-
export const shouldResetUIForGoalSetupPaneTransition = shouldResetUIForSetupPaneTransition;
|
|
452
|
-
export function getGoalActivationPaneTransition() {
|
|
453
|
-
return { overlay: null, goalAutoExpand: false, planAutoExpand: false, resetReviewScreen: true };
|
|
454
|
-
}
|
|
455
|
-
export function getGoalContinuationChoiceKey({ runId, decision, }) {
|
|
456
|
-
switch (decision.kind) {
|
|
457
|
-
case "create_task":
|
|
458
|
-
return `${runId}:create_task:${decision.title}:${decision.prompt}`;
|
|
459
|
-
case "start_worker":
|
|
460
|
-
case "pause":
|
|
461
|
-
return `${runId}:${decision.kind}:${decision.task.id}:${decision.attempts}`;
|
|
462
|
-
case "run_verifier":
|
|
463
|
-
return `${runId}:run_verifier:${decision.command}`;
|
|
464
|
-
case "blocked":
|
|
465
|
-
case "complete":
|
|
466
|
-
case "terminal":
|
|
467
|
-
case "wait":
|
|
468
|
-
return `${runId}:${decision.kind}:${decision.reason}`;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, hasTallLiveUserMessage = false, hasParagraphBreakLiveUserMessage = false, }) {
|
|
472
|
-
const shouldPreserveStatic = isUserScrolled || hasTallLiveUserMessage || hasParagraphBreakLiveUserMessage;
|
|
473
|
-
const shouldAutoFollow = !(isUserScrolled || hasTallLiveUserMessage);
|
|
474
|
-
return {
|
|
475
|
-
preserveStatic: shouldPreserveStatic && hasNewOutput,
|
|
476
|
-
autoFollow: shouldAutoFollow,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
export function nextGoalModeAfterAgentDone({ currentMode, runningGoalIds, queuedSyntheticEvents, activeContinuationFlights = 0, wasGoalSetupTurn, }) {
|
|
480
|
-
if (wasGoalSetupTurn)
|
|
481
|
-
return "off";
|
|
482
|
-
if (currentMode === "planner" || currentMode === "setup")
|
|
483
|
-
return currentMode;
|
|
484
|
-
if (queuedSyntheticEvents > 0)
|
|
485
|
-
return "coordinator";
|
|
486
|
-
if (activeContinuationFlights > 0)
|
|
487
|
-
return "coordinator";
|
|
488
|
-
if (currentMode === "coordinator" && runningGoalIds > 0)
|
|
489
|
-
return "coordinator";
|
|
490
|
-
return "off";
|
|
491
|
-
}
|
|
492
|
-
export function hasParagraphBreakLiveUserMessage(text) {
|
|
493
|
-
return /\n[ \t]*\n/.test(text);
|
|
494
|
-
}
|
|
495
|
-
export function isTallLiveUserMessage(text, rows) {
|
|
496
|
-
return text.split("\n").length > Math.max(8, Math.floor(rows * 0.6));
|
|
497
|
-
}
|
|
498
|
-
export function getStaticHistoryKey({ resizeKey }) {
|
|
499
|
-
return `${resizeKey}`;
|
|
500
|
-
}
|
|
501
|
-
const MIN_LIVE_AREA_ROWS = 3;
|
|
502
|
-
const INPUT_AREA_ROWS = 3;
|
|
503
|
-
const STATUS_SLOT_ROWS = 2;
|
|
504
|
-
const FOOTER_ONE_LINE_ROWS = 1;
|
|
505
|
-
const FOOTER_TWO_LINE_ROWS = 2;
|
|
506
|
-
const GOAL_STATUS_ROWS = 1;
|
|
507
|
-
const COLLAPSED_FOOTER_STATUS_ROWS = 1;
|
|
508
|
-
const MAX_EXPANDED_BACKGROUND_TASK_ROWS = 7;
|
|
509
|
-
function isAgentSpacingKind(kind) {
|
|
510
|
-
return [
|
|
511
|
-
"assistant",
|
|
512
|
-
"queued",
|
|
513
|
-
"goal_progress",
|
|
514
|
-
"tool_start",
|
|
515
|
-
"tool_done",
|
|
516
|
-
"tool_group",
|
|
517
|
-
"server_tool_start",
|
|
518
|
-
"server_tool_done",
|
|
519
|
-
"subagent_group",
|
|
520
|
-
"info",
|
|
521
|
-
"error",
|
|
522
|
-
"stopped",
|
|
523
|
-
"plan_transition",
|
|
524
|
-
"goal_agent_transition",
|
|
525
|
-
"model_transition",
|
|
526
|
-
"theme_transition",
|
|
527
|
-
"plan_event",
|
|
528
|
-
"update_notice",
|
|
529
|
-
"compacting",
|
|
530
|
-
"compacted",
|
|
531
|
-
"style_pack",
|
|
532
|
-
"setup_hint",
|
|
533
|
-
].includes(kind);
|
|
534
|
-
}
|
|
535
|
-
function isToolBoundaryKind(kind) {
|
|
536
|
-
return [
|
|
537
|
-
"goal_progress",
|
|
538
|
-
"tool_start",
|
|
539
|
-
"tool_done",
|
|
540
|
-
"tool_group",
|
|
541
|
-
"server_tool_start",
|
|
542
|
-
"server_tool_done",
|
|
543
|
-
"subagent_group",
|
|
544
|
-
].includes(kind);
|
|
545
|
-
}
|
|
546
|
-
function isAgentSpacingItem(item) {
|
|
547
|
-
return isAgentSpacingKind(item.kind);
|
|
548
|
-
}
|
|
549
|
-
export function shouldTopSpaceAfterPrintedAgentBoundary({ currentKind, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
|
|
550
|
-
const needsExternalSpacing = isAgentSpacingKind(currentKind);
|
|
551
|
-
if (!needsExternalSpacing)
|
|
552
|
-
return false;
|
|
553
|
-
if (previousLiveItem !== undefined)
|
|
554
|
-
return false;
|
|
555
|
-
const previousKind = lastPendingHistoryItem?.kind ?? lastHistoryItem?.kind;
|
|
556
|
-
return previousKind !== undefined && isAgentSpacingKind(previousKind);
|
|
557
|
-
}
|
|
558
|
-
export function shouldTopSpaceAssistantAfterToolBoundary({ text, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
|
|
559
|
-
if (text.trim().length === 0)
|
|
560
|
-
return false;
|
|
561
|
-
if (shouldTopSpaceAfterPrintedAgentBoundary({
|
|
562
|
-
currentKind: "assistant",
|
|
563
|
-
previousLiveItem,
|
|
564
|
-
lastPendingHistoryItem,
|
|
565
|
-
lastHistoryItem,
|
|
566
|
-
})) {
|
|
567
|
-
return true;
|
|
568
|
-
}
|
|
569
|
-
const previousKind = previousLiveItem?.kind;
|
|
570
|
-
return previousKind !== undefined && isToolBoundaryKind(previousKind);
|
|
571
|
-
}
|
|
572
|
-
export function shouldTopSpaceStreamingAssistant({ visibleStreamingText, lastLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
|
|
573
|
-
return shouldTopSpaceAssistantAfterToolBoundary({
|
|
574
|
-
text: visibleStreamingText,
|
|
575
|
-
previousLiveItem: lastLiveItem,
|
|
576
|
-
lastPendingHistoryItem,
|
|
577
|
-
lastHistoryItem,
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
export function getChatControlsLayoutDecision({ rows, agentRunning, activityVisible, doneStatusVisible, stallStatusVisible, exitPending, footerStatusLayout, taskBarExpanded, goalStatusEntryCount, footerFitsOnOneLine, }) {
|
|
581
|
-
const statusRows = activityVisible || stallStatusVisible || doneStatusVisible || agentRunning
|
|
582
|
-
? STATUS_SLOT_ROWS
|
|
583
|
-
: 0;
|
|
584
|
-
const footerRows = exitPending || footerFitsOnOneLine ? FOOTER_ONE_LINE_ROWS : FOOTER_TWO_LINE_ROWS;
|
|
585
|
-
const goalRows = !exitPending && goalStatusEntryCount > 0 ? GOAL_STATUS_ROWS : 0;
|
|
586
|
-
const footerStatusRows = footerStatusLayout.stack
|
|
587
|
-
? Number(footerStatusLayout.hasBackgroundTasks) + Number(footerStatusLayout.hasUpdateNotice)
|
|
588
|
-
: footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice
|
|
589
|
-
? COLLAPSED_FOOTER_STATUS_ROWS
|
|
590
|
-
: 0;
|
|
591
|
-
const expandedTaskRows = taskBarExpanded && footerStatusLayout.hasBackgroundTasks
|
|
592
|
-
? MAX_EXPANDED_BACKGROUND_TASK_ROWS - COLLAPSED_FOOTER_STATUS_ROWS
|
|
593
|
-
: 0;
|
|
594
|
-
const controlsRows = statusRows + INPUT_AREA_ROWS + footerRows + goalRows + footerStatusRows + expandedTaskRows;
|
|
595
|
-
const maxControlsRows = Math.max(1, rows - MIN_LIVE_AREA_ROWS);
|
|
596
|
-
const boundedControlsRows = Math.min(controlsRows, maxControlsRows);
|
|
597
|
-
return {
|
|
598
|
-
controlsRows: boundedControlsRows,
|
|
599
|
-
liveAreaRows: Math.max(MIN_LIVE_AREA_ROWS, rows - boundedControlsRows),
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
// flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
|
|
603
|
-
/** Check whether an item is still active (running spinner, pending result). */
|
|
604
|
-
export function isActiveItem(item) {
|
|
605
|
-
switch (item.kind) {
|
|
606
|
-
case "tool_start":
|
|
607
|
-
case "server_tool_start":
|
|
608
|
-
case "queued":
|
|
609
|
-
case "compacting":
|
|
610
|
-
return true;
|
|
611
|
-
case "tool_group":
|
|
612
|
-
return item.tools.some((t) => t.status === "running");
|
|
613
|
-
case "subagent_group":
|
|
614
|
-
return item.agents.some((a) => a.status === "running");
|
|
615
|
-
default:
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Partition live items into completed (flushable to finalized history) and still-active.
|
|
621
|
-
* Completed items precede active ones — we flush the longest contiguous prefix
|
|
622
|
-
* of completed items to keep ordering stable.
|
|
623
|
-
*/
|
|
624
|
-
export function partitionCompleted(items) {
|
|
625
|
-
// Find the first active item — everything before it is safe to flush as a
|
|
626
|
-
// single chronological prefix. Splitting assistant text out of that prefix
|
|
627
|
-
// lets later tool rows print to scrollback above the message that introduced
|
|
628
|
-
// them, so keep the prefix intact.
|
|
629
|
-
const firstActiveIdx = items.findIndex(isActiveItem);
|
|
630
|
-
if (firstActiveIdx === -1) {
|
|
631
|
-
return { flushed: items, remaining: [] };
|
|
632
|
-
}
|
|
633
|
-
if (firstActiveIdx === 0) {
|
|
634
|
-
return { flushed: [], remaining: items };
|
|
635
|
-
}
|
|
636
|
-
return {
|
|
637
|
-
flushed: items.slice(0, firstActiveIdx),
|
|
638
|
-
remaining: items.slice(firstActiveIdx),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function normalizeAssistantText(text) {
|
|
642
|
-
return stripDoneMarkers(text).trim();
|
|
643
|
-
}
|
|
644
|
-
function isReasoningMarkerText(text) {
|
|
645
|
-
return /^(?:currentItem\?\.type\s*=+\s*)?["']?reasoning["']?$/u.test(text.trim());
|
|
646
|
-
}
|
|
647
|
-
function isSameAssistantText(item, text) {
|
|
648
|
-
return item.kind === "assistant" && normalizeAssistantText(item.text) === text;
|
|
649
|
-
}
|
|
650
|
-
export function pinStreamingTextBeforeToolBoundary({ items, visibleStreamingText, thinking, thinkingMs, makeId, }) {
|
|
651
|
-
const text = normalizeAssistantText(visibleStreamingText);
|
|
652
|
-
if (text.length === 0 || isReasoningMarkerText(text))
|
|
653
|
-
return items;
|
|
654
|
-
if (items.some((item) => item.kind === "assistant"))
|
|
655
|
-
return items;
|
|
656
|
-
return [
|
|
657
|
-
...items,
|
|
658
|
-
{
|
|
659
|
-
kind: "assistant",
|
|
660
|
-
text,
|
|
661
|
-
thinking: thinking.length > 0 ? thinking : undefined,
|
|
662
|
-
thinkingMs: thinking.length > 0 ? thinkingMs : undefined,
|
|
663
|
-
id: makeId(),
|
|
664
|
-
},
|
|
665
|
-
];
|
|
666
|
-
}
|
|
667
153
|
// ── Duration summary ─────────────────────────────────────
|
|
668
154
|
function formatDuration(ms) {
|
|
669
155
|
const totalSec = Math.round(ms / 1000);
|
|
@@ -775,7 +261,11 @@ export function App(props) {
|
|
|
775
261
|
// Items from the current/last turn — rendered in the live area so they stay visible.
|
|
776
262
|
// Seed from sessionStore so Goal progress/completion rows and other live output
|
|
777
263
|
// survive pane/overlay/resize remounts before they are finalized.
|
|
778
|
-
const [liveItems, setLiveItems] = useState(() =>
|
|
264
|
+
const [liveItems, setLiveItems] = useState(() => {
|
|
265
|
+
const restoredLiveItems = uniqueItemsById(props.sessionStore?.liveItems ?? []);
|
|
266
|
+
const restoredHistoryIds = new Set(history.map((item) => item.id));
|
|
267
|
+
return removeItemsWithIds(restoredLiveItems, restoredHistoryIds);
|
|
268
|
+
});
|
|
779
269
|
// overlay seeded from sessionStore (lives across remount). Falls back to
|
|
780
270
|
// props.initialOverlay (CLI launched with one), then null.
|
|
781
271
|
const [overlay, setOverlay] = useState(props.sessionStore?.overlay ?? props.initialOverlay ?? null);
|
|
@@ -788,6 +278,13 @@ export function App(props) {
|
|
|
788
278
|
const goalContinuationFlightsRef = useRef(new Set());
|
|
789
279
|
const goalContinuationRecentChoicesRef = useRef(new Map());
|
|
790
280
|
const startGoalRunRef = useRef(() => { });
|
|
281
|
+
const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
|
|
282
|
+
const [taskPickerOpen, setTaskPickerOpen] = useState(false);
|
|
283
|
+
const [taskPickerTasks, setTaskPickerTasks] = useState(() => loadTasksSync(props.cwd));
|
|
284
|
+
const [goalPickerOpen, setGoalPickerOpen] = useState(false);
|
|
285
|
+
const [goalPickerGoals, setGoalPickerGoals] = useState(() => loadGoalRunsSync(props.cwd));
|
|
286
|
+
const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
|
|
287
|
+
const startTaskRef = useRef(() => { });
|
|
791
288
|
const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
|
|
792
289
|
const currentPixelFixRef = useRef(null);
|
|
793
290
|
const startPixelFixRef = useRef(() => { });
|
|
@@ -926,7 +423,7 @@ export function App(props) {
|
|
|
926
423
|
pendingHistoryFlushRef.current = [...pendingHistoryFlushRef.current, ...flushed];
|
|
927
424
|
if (sessionStore) {
|
|
928
425
|
const queuedIds = new Set(items.map((item) => item.id));
|
|
929
|
-
sessionStore.liveItems = (sessionStore.liveItems ?? [])
|
|
426
|
+
sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(sessionStore.liveItems ?? []), queuedIds);
|
|
930
427
|
}
|
|
931
428
|
setHistoryFlushGeneration((generation) => generation + 1);
|
|
932
429
|
}, [sessionStore]);
|
|
@@ -949,11 +446,8 @@ export function App(props) {
|
|
|
949
446
|
onRuntimeStateChange?.({ provider: currentProvider });
|
|
950
447
|
}, [currentProvider, onRuntimeStateChange]);
|
|
951
448
|
useEffect(() => {
|
|
952
|
-
if (thinkingLevel && !
|
|
953
|
-
|
|
954
|
-
if (thinkingLevel !== maxLevel) {
|
|
955
|
-
setThinkingLevel(maxLevel);
|
|
956
|
-
}
|
|
449
|
+
if (thinkingLevel && !isThinkingLevelSupported(currentProvider, currentModel, thinkingLevel)) {
|
|
450
|
+
setThinkingLevel(getNextThinkingLevel(currentProvider, currentModel, undefined));
|
|
957
451
|
}
|
|
958
452
|
}, [currentProvider, currentModel, thinkingLevel]);
|
|
959
453
|
useEffect(() => {
|
|
@@ -994,8 +488,10 @@ export function App(props) {
|
|
|
994
488
|
sessionStore.history = history;
|
|
995
489
|
}, [history, sessionStore]);
|
|
996
490
|
useEffect(() => {
|
|
997
|
-
if (sessionStore)
|
|
998
|
-
|
|
491
|
+
if (!sessionStore)
|
|
492
|
+
return;
|
|
493
|
+
const historyIds = new Set(historyRef.current.map((item) => item.id));
|
|
494
|
+
sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(liveItems), historyIds);
|
|
999
495
|
}, [liveItems, sessionStore]);
|
|
1000
496
|
useEffect(() => {
|
|
1001
497
|
if (sessionStore)
|
|
@@ -1114,6 +610,29 @@ export function App(props) {
|
|
|
1114
610
|
}
|
|
1115
611
|
return newPrompt;
|
|
1116
612
|
}, [rebuildSystemPrompt]);
|
|
613
|
+
useEffect(() => {
|
|
614
|
+
if (!props.connectInitialMcpTools)
|
|
615
|
+
return;
|
|
616
|
+
let cancelled = false;
|
|
617
|
+
void props
|
|
618
|
+
.connectInitialMcpTools()
|
|
619
|
+
.then((mcpTools) => {
|
|
620
|
+
if (cancelled || mcpTools.length === 0)
|
|
621
|
+
return;
|
|
622
|
+
setCurrentTools((prev) => {
|
|
623
|
+
const next = [...prev.filter((tool) => !tool.name.startsWith("mcp__")), ...mcpTools];
|
|
624
|
+
currentToolsRef.current = next;
|
|
625
|
+
void replaceSystemPrompt({ tools: next });
|
|
626
|
+
return next;
|
|
627
|
+
});
|
|
628
|
+
})
|
|
629
|
+
.catch((err) => {
|
|
630
|
+
log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
631
|
+
});
|
|
632
|
+
return () => {
|
|
633
|
+
cancelled = true;
|
|
634
|
+
};
|
|
635
|
+
}, [props.connectInitialMcpTools, replaceSystemPrompt]);
|
|
1117
636
|
const setGoalModeAndPrompt = useCallback(async (nextMode, options) => {
|
|
1118
637
|
goalModeStateRef.current = nextMode;
|
|
1119
638
|
if (props.goalModeRef)
|
|
@@ -1378,13 +897,10 @@ export function App(props) {
|
|
|
1378
897
|
(props.repoMapReadFilesRef?.current.size ?? 0));
|
|
1379
898
|
}, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
|
|
1380
899
|
const getRepoMapBudget = useCallback(() => {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
if (readCount > 0)
|
|
1386
|
-
return FOCUSED_REPO_MAP_MAX_CHARS;
|
|
1387
|
-
return FOCUSED_REPO_MAP_MAX_CHARS + 1000;
|
|
900
|
+
return getRepoMapBudgetForContext({
|
|
901
|
+
messages: messagesRef.current,
|
|
902
|
+
readFileCount: props.repoMapReadFilesRef?.current.size ?? 0,
|
|
903
|
+
});
|
|
1388
904
|
}, [props.repoMapReadFilesRef]);
|
|
1389
905
|
const refreshRepoMap = useCallback(async (latestUserPrompt) => {
|
|
1390
906
|
const rendered = await buildRepoMap({
|
|
@@ -2025,6 +1541,21 @@ export function App(props) {
|
|
|
2025
1541
|
if (nextGoalMode !== goalModeStateRef.current) {
|
|
2026
1542
|
void setGoalModeAndPrompt(nextGoalMode);
|
|
2027
1543
|
}
|
|
1544
|
+
// Run-all: auto-start next pending task after a short delay.
|
|
1545
|
+
if (runAllTasksRef.current) {
|
|
1546
|
+
setTimeout(() => {
|
|
1547
|
+
const cwd = cwdRef.current;
|
|
1548
|
+
const next = getNextPendingTask(cwd);
|
|
1549
|
+
if (next) {
|
|
1550
|
+
markTaskInProgress(cwd, next.id);
|
|
1551
|
+
startTaskRef.current(next.title, next.prompt, next.id);
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
setRunAllTasks(false);
|
|
1555
|
+
log("INFO", "tasks", "Run-all complete — no more pending tasks");
|
|
1556
|
+
}
|
|
1557
|
+
}, 500);
|
|
1558
|
+
}
|
|
2028
1559
|
// Goal loop: after the orchestrator handles a worker/verifier event,
|
|
2029
1560
|
// continue the same Goal automatically until it reaches a terminal state.
|
|
2030
1561
|
for (const runId of [...runningGoalIdsRef.current]) {
|
|
@@ -2446,26 +1977,11 @@ export function App(props) {
|
|
|
2446
1977
|
]);
|
|
2447
1978
|
return;
|
|
2448
1979
|
}
|
|
2449
|
-
// Handle /goals — open goal
|
|
1980
|
+
// Handle /goals — open the input-area goal picker.
|
|
2450
1981
|
if (trimmed === "/goals") {
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
props.sessionStore.goalAutoExpand = false;
|
|
2455
|
-
props.resetUI();
|
|
2456
|
-
}
|
|
2457
|
-
else {
|
|
2458
|
-
if (props.sessionStore) {
|
|
2459
|
-
props.sessionStore.overlay = "goal";
|
|
2460
|
-
props.sessionStore.planAutoExpand = false;
|
|
2461
|
-
props.sessionStore.goalAutoExpand = false;
|
|
2462
|
-
if (agentLoop.isRunning)
|
|
2463
|
-
props.sessionStore.pendingResetUI = true;
|
|
2464
|
-
}
|
|
2465
|
-
setPlanAutoExpand(false);
|
|
2466
|
-
setGoalAutoExpand(false);
|
|
2467
|
-
setOverlay("goal");
|
|
2468
|
-
}
|
|
1982
|
+
setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
|
|
1983
|
+
setTaskPickerOpen(false);
|
|
1984
|
+
setGoalPickerOpen(true);
|
|
2469
1985
|
return;
|
|
2470
1986
|
}
|
|
2471
1987
|
// Handle prompt-template commands (built-in + custom from .gg/commands/)
|
|
@@ -2542,26 +2058,11 @@ export function App(props) {
|
|
|
2542
2058
|
await setGoalModeAndPrompt("off");
|
|
2543
2059
|
}
|
|
2544
2060
|
if (paneTransition) {
|
|
2545
|
-
goalAutoExpandRef.current =
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
hasResetUI: resetUI !== undefined,
|
|
2551
|
-
hasSessionStore: sessionStore !== undefined,
|
|
2552
|
-
}) &&
|
|
2553
|
-
resetUI &&
|
|
2554
|
-
sessionStore) {
|
|
2555
|
-
sessionStore.overlay = paneTransition.overlay;
|
|
2556
|
-
sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
|
|
2557
|
-
sessionStore.planAutoExpand = paneTransition.planAutoExpand;
|
|
2558
|
-
resetUI();
|
|
2559
|
-
return;
|
|
2560
|
-
}
|
|
2561
|
-
setGoalAutoExpand(paneTransition.goalAutoExpand);
|
|
2562
|
-
setPlanAutoExpand(paneTransition.planAutoExpand);
|
|
2563
|
-
setOverlay(paneTransition.overlay);
|
|
2564
|
-
}, 300);
|
|
2061
|
+
goalAutoExpandRef.current = false;
|
|
2062
|
+
setGoalAutoExpand(false);
|
|
2063
|
+
setPlanAutoExpand(false);
|
|
2064
|
+
setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
|
|
2065
|
+
setGoalPickerOpen(true);
|
|
2565
2066
|
}
|
|
2566
2067
|
}
|
|
2567
2068
|
}
|
|
@@ -2879,7 +2380,7 @@ export function App(props) {
|
|
|
2879
2380
|
(item.summaryRows !== undefined && item.summaryRows.length > 0) ||
|
|
2880
2381
|
(item.summarySections !== undefined && item.summarySections.length > 0);
|
|
2881
2382
|
const headerContentWidth = Math.max(10, columns - 3);
|
|
2882
|
-
return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { status: loaderStatus, staticDisplay: true }), _jsx(Box, { flexGrow: 1, width: headerContentWidth, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: truncateGoalProgressText(item.title) }), item.workerId ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] })) : null] }) })] }), hasBody ? (_jsx(MessageResponse, { children: _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: truncateGoalProgressText(item.detail) })) : null, item.summaryRows?.map((row) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.textDim, children: row.label.padEnd(12) }), _jsx(Text, { color: theme.text, children: truncateGoalProgressText(row.value) }), row.detail ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 ", truncateGoalProgressText(row.detail)] })) : null] }, row.label))), item.summarySections?.map((section) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: theme.textDim, bold: true, children: section.title }), section.lines.map((line, sectionLineIndex) => (_jsx(Text, { color: theme.text, wrap: "wrap", children: `• ${truncateGoalProgressText(line)}` }, `${section.title}-${sectionLineIndex}`)))] }, section.title)))] }) })) : null] }, item.id));
|
|
2383
|
+
return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { status: loaderStatus, staticDisplay: true, color: color }), _jsx(Box, { flexGrow: 1, width: headerContentWidth, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: truncateGoalProgressText(item.title) }), item.workerId ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] })) : null] }) })] }), hasBody ? (_jsx(MessageResponse, { children: _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: truncateGoalProgressText(item.detail) })) : null, item.summaryRows?.map((row) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.textDim, children: row.label.padEnd(12) }), _jsx(Text, { color: theme.text, children: truncateGoalProgressText(row.value) }), row.detail ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 ", truncateGoalProgressText(row.detail)] })) : null] }, row.label))), item.summarySections?.map((section) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: theme.textDim, bold: true, children: section.title }), section.lines.map((line, sectionLineIndex) => (_jsx(Text, { color: theme.text, wrap: "wrap", children: `• ${truncateGoalProgressText(line)}` }, `${section.title}-${sectionLineIndex}`)))] }, section.title)))] }) })) : null] }, item.id));
|
|
2883
2384
|
}
|
|
2884
2385
|
case "style_pack": {
|
|
2885
2386
|
const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
|
|
@@ -2909,9 +2410,11 @@ export function App(props) {
|
|
|
2909
2410
|
case "update_notice":
|
|
2910
2411
|
return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsx(Box, { flexShrink: 1, borderStyle: "round", borderColor: theme.commandColor, paddingX: 1, children: _jsxs(Text, { color: theme.commandColor, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }) }, item.id));
|
|
2911
2412
|
case "plan_transition":
|
|
2912
|
-
return renderStatusMessage(item.id,
|
|
2413
|
+
return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
|
|
2913
2414
|
case "goal_agent_transition":
|
|
2914
|
-
return renderStatusMessage(item.id,
|
|
2415
|
+
return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
|
|
2416
|
+
case "task":
|
|
2417
|
+
return withPrintedBoundarySpacing(renderStatusMessage(item.id, "▸ ", _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textDim, children: "Task: " }), _jsx(Text, { color: theme.commandColor, bold: true, children: item.title })] }), theme.commandColor, { bold: true }));
|
|
2915
2418
|
case "model_transition":
|
|
2916
2419
|
return renderStatusMessage(item.id, "▸ ", _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textDim, children: "Switched to " }), _jsx(Text, { color: theme.commandColor, bold: true, children: item.modelName })] }), theme.commandColor, { bold: true });
|
|
2917
2420
|
case "theme_transition":
|
|
@@ -2976,26 +2479,18 @@ export function App(props) {
|
|
|
2976
2479
|
setOverlay(kind);
|
|
2977
2480
|
}
|
|
2978
2481
|
}, [agentLoop.isRunning, props]);
|
|
2979
|
-
const closeOverlay = useCallback(() => {
|
|
2980
|
-
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
2981
|
-
props.sessionStore.overlay = null;
|
|
2982
|
-
props.resetUI();
|
|
2983
|
-
}
|
|
2984
|
-
else {
|
|
2985
|
-
if (props.sessionStore) {
|
|
2986
|
-
props.sessionStore.overlay = null;
|
|
2987
|
-
}
|
|
2988
|
-
setOverlay(null);
|
|
2989
|
-
}
|
|
2990
|
-
}, [agentLoop.isRunning, overlay, props]);
|
|
2991
2482
|
const runGoalSyntheticEvent = useCallback((eventText) => {
|
|
2992
2483
|
const eventInfo = parseGoalSyntheticEvent(eventText);
|
|
2993
2484
|
const detail = eventInfo?.kind === "worker"
|
|
2994
2485
|
? `Inspecting worker result${eventInfo.task ? ` for ${eventInfo.task}` : ""}.`
|
|
2995
2486
|
: `Inspecting verifier result${eventInfo?.status ? ` (${eventInfo.status})` : ""}.`;
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2487
|
+
const route = routeGoalSyntheticEvent({
|
|
2488
|
+
agentRunning: agentRunningRef.current,
|
|
2489
|
+
queuedSyntheticEvents: queuedGoalSyntheticEventsRef.current,
|
|
2490
|
+
});
|
|
2491
|
+
if (route.action === "queue") {
|
|
2492
|
+
queuedGoalSyntheticEventsRef.current = route.nextQueuedSyntheticEvents;
|
|
2493
|
+
void setGoalModeAndPrompt(route.nextGoalMode);
|
|
2999
2494
|
appendGoalProgress({
|
|
3000
2495
|
kind: "goal_progress",
|
|
3001
2496
|
phase: "orchestrator_reviewing",
|
|
@@ -3558,8 +3053,71 @@ export function App(props) {
|
|
|
3558
3053
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
|
|
3559
3054
|
});
|
|
3560
3055
|
}, [appendGoalProgress, clearGoalModeIfIdle, clearGoalStatusEntry, props.cwd]);
|
|
3056
|
+
const startTask = useCallback((title, prompt, taskId) => {
|
|
3057
|
+
const taskCwd = cwdRef.current;
|
|
3058
|
+
const shortId = taskId.slice(0, 8);
|
|
3059
|
+
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
3060
|
+
`tasks({ action: "done", id: "${shortId}" })`;
|
|
3061
|
+
const fullPrompt = prompt + completionHint;
|
|
3062
|
+
if (props.resetUI && props.sessionStore) {
|
|
3063
|
+
const sysMsg = messagesRef.current[0];
|
|
3064
|
+
const newMessages = sysMsg && sysMsg.role === "system" ? [sysMsg] : messagesRef.current.slice(0, 1);
|
|
3065
|
+
const taskItem = { kind: "task", title, id: getId() };
|
|
3066
|
+
const sm = sessionManagerRef.current;
|
|
3067
|
+
void (async () => {
|
|
3068
|
+
let newSessionPath;
|
|
3069
|
+
if (sm) {
|
|
3070
|
+
try {
|
|
3071
|
+
const session = await sm.create(taskCwd, currentProvider, currentModel);
|
|
3072
|
+
newSessionPath = session.path;
|
|
3073
|
+
log("INFO", "tasks", "New session for task", { path: session.path });
|
|
3074
|
+
}
|
|
3075
|
+
catch {
|
|
3076
|
+
// Session creation is best-effort.
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
if (props.sessionStore)
|
|
3080
|
+
props.sessionStore.overlay = null;
|
|
3081
|
+
props.resetUI?.({
|
|
3082
|
+
wipeSession: true,
|
|
3083
|
+
messages: newMessages,
|
|
3084
|
+
history: [{ kind: "banner", id: "banner" }, taskItem],
|
|
3085
|
+
sessionPath: newSessionPath,
|
|
3086
|
+
pendingAction: { prompt: fullPrompt },
|
|
3087
|
+
});
|
|
3088
|
+
})();
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
pendingHistoryFlushRef.current = [];
|
|
3092
|
+
props.terminalHistoryPrinter?.clear();
|
|
3093
|
+
setHistory([{ kind: "banner", id: "banner" }]);
|
|
3094
|
+
setLiveItems([]);
|
|
3095
|
+
messagesRef.current = messagesRef.current.slice(0, 1);
|
|
3096
|
+
agentLoop.reset();
|
|
3097
|
+
persistedIndexRef.current = messagesRef.current.length;
|
|
3098
|
+
const sm = sessionManagerRef.current;
|
|
3099
|
+
if (sm) {
|
|
3100
|
+
void sm.create(taskCwd, currentProvider, currentModel).then((session) => {
|
|
3101
|
+
sessionPathRef.current = session.path;
|
|
3102
|
+
log("INFO", "tasks", "New session for task", { path: session.path });
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
const taskItem = { kind: "task", title, id: getId() };
|
|
3106
|
+
setLastUserMessage(title);
|
|
3107
|
+
setDoneStatus(null);
|
|
3108
|
+
setLiveItems([taskItem]);
|
|
3109
|
+
void agentLoop.run(fullPrompt).catch((err) => {
|
|
3110
|
+
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
3111
|
+
});
|
|
3112
|
+
}, [agentLoop, currentModel, currentProvider, props]);
|
|
3561
3113
|
// Keep refs in sync for access from stale closures (onDone)
|
|
3114
|
+
startTaskRef.current = startTask;
|
|
3562
3115
|
startGoalRunRef.current = startGoalRun;
|
|
3116
|
+
useEffect(() => {
|
|
3117
|
+
runAllTasksRef.current = runAllTasks;
|
|
3118
|
+
if (props.sessionStore)
|
|
3119
|
+
props.sessionStore.runAllTasks = runAllTasks;
|
|
3120
|
+
}, [runAllTasks, props.sessionStore]);
|
|
3563
3121
|
useEffect(() => {
|
|
3564
3122
|
agentRunningRef.current = agentLoop.isRunning;
|
|
3565
3123
|
}, [agentLoop.isRunning]);
|
|
@@ -3659,7 +3217,6 @@ export function App(props) {
|
|
|
3659
3217
|
if (props.sessionStore)
|
|
3660
3218
|
props.sessionStore.runAllPixel = runAllPixel;
|
|
3661
3219
|
}, [runAllPixel, props.sessionStore]);
|
|
3662
|
-
const isGoalView = overlay === "goal";
|
|
3663
3220
|
const isSkillsView = overlay === "skills";
|
|
3664
3221
|
const isPlanView = overlay === "plan";
|
|
3665
3222
|
const footerStatusLayout = getFooterStatusLayoutDecision({
|
|
@@ -3771,71 +3328,7 @@ export function App(props) {
|
|
|
3771
3328
|
lastPendingHistoryItem,
|
|
3772
3329
|
lastHistoryItem,
|
|
3773
3330
|
});
|
|
3774
|
-
return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children:
|
|
3775
|
-
goalAutoExpandRef.current = false;
|
|
3776
|
-
setGoalAutoExpand(false);
|
|
3777
|
-
if (props.sessionStore)
|
|
3778
|
-
props.sessionStore.goalAutoExpand = false;
|
|
3779
|
-
closeOverlay();
|
|
3780
|
-
}, onRunGoal: (run) => {
|
|
3781
|
-
const paneTransition = getGoalActivationPaneTransition();
|
|
3782
|
-
goalAutoExpandRef.current = paneTransition.goalAutoExpand;
|
|
3783
|
-
setGoalAutoExpand(paneTransition.goalAutoExpand);
|
|
3784
|
-
setPlanAutoExpand(paneTransition.planAutoExpand);
|
|
3785
|
-
if (props.sessionStore) {
|
|
3786
|
-
props.sessionStore.overlay = paneTransition.overlay;
|
|
3787
|
-
props.sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
|
|
3788
|
-
props.sessionStore.planAutoExpand = paneTransition.planAutoExpand;
|
|
3789
|
-
}
|
|
3790
|
-
if (paneTransition.resetReviewScreen && props.resetUI && props.sessionStore) {
|
|
3791
|
-
props.sessionStore.pendingGoalRun = run;
|
|
3792
|
-
props.resetUI();
|
|
3793
|
-
return;
|
|
3794
|
-
}
|
|
3795
|
-
setOverlay(paneTransition.overlay);
|
|
3796
|
-
startGoalRun(run);
|
|
3797
|
-
}, onVerifyGoal: (run) => {
|
|
3798
|
-
void verifyGoalRun(run);
|
|
3799
|
-
}, onPauseGoal: (run) => {
|
|
3800
|
-
pauseGoalRun(run);
|
|
3801
|
-
}, onRefineGoal: (run, feedback) => {
|
|
3802
|
-
goalAutoExpandRef.current = true;
|
|
3803
|
-
setGoalAutoExpand(true);
|
|
3804
|
-
void (async () => {
|
|
3805
|
-
try {
|
|
3806
|
-
await setGoalModeAndPrompt("setup");
|
|
3807
|
-
await agentLoop.run(`Refine Goal run ${run.id} (${run.title}) based on this user feedback. Update durable Goal setup only, then stop and reopen the Goal pane for review.\n\nFeedback: ${feedback}`);
|
|
3808
|
-
}
|
|
3809
|
-
catch (err) {
|
|
3810
|
-
log("ERROR", "goal", err instanceof Error ? err.message : String(err));
|
|
3811
|
-
setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
|
|
3812
|
-
}
|
|
3813
|
-
finally {
|
|
3814
|
-
await setGoalModeAndPrompt("off");
|
|
3815
|
-
const paneTransition = getGoalSetupFinishedPaneTransition();
|
|
3816
|
-
goalAutoExpandRef.current = paneTransition.goalAutoExpand;
|
|
3817
|
-
setTimeout(() => {
|
|
3818
|
-
const resetUI = props.resetUI;
|
|
3819
|
-
const sessionStore = props.sessionStore;
|
|
3820
|
-
if (shouldResetUIForGoalSetupPaneTransition({
|
|
3821
|
-
hasResetUI: resetUI !== undefined,
|
|
3822
|
-
hasSessionStore: sessionStore !== undefined,
|
|
3823
|
-
}) &&
|
|
3824
|
-
resetUI &&
|
|
3825
|
-
sessionStore) {
|
|
3826
|
-
sessionStore.overlay = paneTransition.overlay;
|
|
3827
|
-
sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
|
|
3828
|
-
sessionStore.planAutoExpand = paneTransition.planAutoExpand;
|
|
3829
|
-
resetUI();
|
|
3830
|
-
return;
|
|
3831
|
-
}
|
|
3832
|
-
setGoalAutoExpand(paneTransition.goalAutoExpand);
|
|
3833
|
-
setPlanAutoExpand(paneTransition.planAutoExpand);
|
|
3834
|
-
setOverlay(paneTransition.overlay);
|
|
3835
|
-
}, 300);
|
|
3836
|
-
}
|
|
3837
|
-
})();
|
|
3838
|
-
} })) : isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
|
|
3331
|
+
return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
|
|
3839
3332
|
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
3840
3333
|
props.sessionStore.overlay = null;
|
|
3841
3334
|
props.resetUI();
|
|
@@ -3989,8 +3482,48 @@ export function App(props) {
|
|
|
3989
3482
|
log("ERROR", "error", errMsg);
|
|
3990
3483
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
3991
3484
|
});
|
|
3992
|
-
} })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [liveItems.map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking,
|
|
3993
|
-
|
|
3485
|
+
} })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [uniqueItemsById(liveItems).map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
|
|
3486
|
+
setGoalPickerOpen(false);
|
|
3487
|
+
setTaskPickerTasks(loadTasksSync(displayedCwd));
|
|
3488
|
+
setTaskPickerOpen((open) => !open);
|
|
3489
|
+
}, taskPickerOpen: taskPickerOpen, tasks: taskPickerTasks, onCloseTaskPicker: () => setTaskPickerOpen(false), onStartTask: (task) => {
|
|
3490
|
+
setTaskPickerOpen(false);
|
|
3491
|
+
markTaskInProgress(displayedCwd, task.id);
|
|
3492
|
+
setTaskPickerTasks(loadTasksSync(displayedCwd));
|
|
3493
|
+
startTask(task.title, task.prompt, task.id);
|
|
3494
|
+
}, onRunAllTasks: (task) => {
|
|
3495
|
+
setTaskPickerOpen(false);
|
|
3496
|
+
setRunAllTasks(true);
|
|
3497
|
+
const selected = task
|
|
3498
|
+
? {
|
|
3499
|
+
id: task.id,
|
|
3500
|
+
title: task.title,
|
|
3501
|
+
prompt: task.prompt || task.text || task.title,
|
|
3502
|
+
}
|
|
3503
|
+
: getNextPendingTask(displayedCwd);
|
|
3504
|
+
if (selected) {
|
|
3505
|
+
markTaskInProgress(displayedCwd, selected.id);
|
|
3506
|
+
setTaskPickerTasks(loadTasksSync(displayedCwd));
|
|
3507
|
+
startTask(selected.title, selected.prompt, selected.id);
|
|
3508
|
+
}
|
|
3509
|
+
}, onDeleteTask: (task) => {
|
|
3510
|
+
const nextTasks = loadTasksSync(displayedCwd).filter((candidate) => candidate.id !== task.id);
|
|
3511
|
+
saveTasksSync(displayedCwd, nextTasks);
|
|
3512
|
+
setTaskPickerTasks(nextTasks);
|
|
3513
|
+
}, goalPickerOpen: goalPickerOpen, goals: goalPickerGoals, onCloseGoalPicker: () => setGoalPickerOpen(false), onRunGoal: (run) => {
|
|
3514
|
+
setGoalPickerOpen(false);
|
|
3515
|
+
startGoalRun(run);
|
|
3516
|
+
}, onDeleteGoal: (run) => {
|
|
3517
|
+
const nextGoals = loadGoalRunsSync(displayedCwd).filter((candidate) => candidate.id !== run.id);
|
|
3518
|
+
saveGoalRunsSync(displayedCwd, nextGoals);
|
|
3519
|
+
setGoalPickerGoals(nextGoals);
|
|
3520
|
+
}, onPauseGoal: (run) => {
|
|
3521
|
+
setGoalPickerOpen(false);
|
|
3522
|
+
pauseGoalRun(run);
|
|
3523
|
+
}, onToggleGoal: () => {
|
|
3524
|
+
setTaskPickerOpen(false);
|
|
3525
|
+
setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
|
|
3526
|
+
setGoalPickerOpen((open) => !open);
|
|
3994
3527
|
}, onToggleSkills: () => {
|
|
3995
3528
|
openOverlay("skills");
|
|
3996
3529
|
}, onTogglePixel: () => {
|