@gajae-code/coding-agent 0.2.2 → 0.2.4
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/CHANGELOG.md +45 -8600
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +56 -0
- package/dist/types/defaults/gjc-defaults.d.ts +19 -6
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +18 -0
- package/dist/types/hooks/skill-state.d.ts +5 -0
- package/dist/types/memories/index.d.ts +1 -1
- package/dist/types/memory-backend/local-backend.d.ts +3 -3
- package/dist/types/modes/components/hook-selector.d.ts +7 -0
- package/dist/types/modes/components/settings-selector.d.ts +3 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +126 -0
- package/dist/types/modes/theme/theme.d.ts +5 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -2
- package/dist/types/sdk.d.ts +6 -2
- package/dist/types/session/agent-session.d.ts +45 -1
- package/dist/types/session/session-manager.d.ts +3 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -0
- package/dist/types/setup/provider-onboarding.d.ts +29 -5
- package/dist/types/skill-state/active-state.d.ts +26 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/types.d.ts +11 -0
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/skill.d.ts +47 -0
- package/dist/types/utils/changelog.d.ts +18 -2
- package/package.json +7 -7
- package/src/cli/setup-cli.ts +26 -12
- package/src/cli/update-cli.ts +67 -16
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +25 -2
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +1 -0
- package/src/config/settings-schema.ts +63 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +58 -5
- package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -0
- package/src/defaults/gjc/skills/team/SKILL.md +10 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +19 -6
- package/src/defaults/gjc-defaults.ts +68 -16
- package/src/discovery/helpers.ts +24 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +312 -1
- package/src/gjc-runtime/state-runtime.ts +175 -5
- package/src/goals/tools/goal-tool.ts +5 -1
- package/src/hooks/skill-state.ts +8 -6
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/internal-urls/memory-protocol.ts +3 -2
- package/src/main.ts +2 -3
- package/src/memories/index.ts +6 -4
- package/src/memory-backend/local-backend.ts +14 -6
- package/src/modes/components/hook-selector.ts +156 -1
- package/src/modes/components/settings-selector.ts +16 -12
- package/src/modes/controllers/command-controller.ts +3 -4
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/selector-controller.ts +69 -9
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/theme/defaults/blue-crab.json +126 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +40 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/context-usage.ts +66 -17
- package/src/prompts/agents/architect.md +3 -0
- package/src/prompts/agents/executor.md +2 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/memories/unavailable.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +6 -0
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +54 -10
- package/src/session/agent-session.ts +204 -21
- package/src/session/session-manager.ts +9 -1
- package/src/setup/model-onboarding-guidance.ts +6 -3
- package/src/setup/provider-onboarding.ts +177 -16
- package/src/skill-state/active-state.ts +150 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +11 -24
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/slash-commands/builtin-registry.ts +62 -14
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +91 -4
- package/src/task/types.ts +6 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/index.ts +23 -1
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
|
@@ -15,11 +15,12 @@ const MEMORY_NAMESPACE = "root";
|
|
|
15
15
|
* may see different roots.
|
|
16
16
|
*/
|
|
17
17
|
export function memoryRootsFromRegistry(): string[] {
|
|
18
|
-
const agentDir = getAgentDir();
|
|
19
18
|
const roots: string[] = [];
|
|
20
19
|
for (const ref of AgentRegistry.global().list()) {
|
|
21
|
-
const
|
|
20
|
+
const session = ref.session;
|
|
21
|
+
const sm = session?.sessionManager;
|
|
22
22
|
if (!sm) continue;
|
|
23
|
+
const agentDir = session.settings?.getAgentDir() ?? getAgentDir();
|
|
23
24
|
const root = getMemoryRoot(agentDir, sm.getCwd());
|
|
24
25
|
if (root && !roots.includes(root)) roots.push(root);
|
|
25
26
|
}
|
package/src/main.ts
CHANGED
|
@@ -49,7 +49,7 @@ import { formatModelOnboardingGuidance } from "./setup/model-onboarding-guidance
|
|
|
49
49
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
50
50
|
import { resolvePromptInput } from "./system-prompt";
|
|
51
51
|
import type { LspStartupServerInfo } from "./tools";
|
|
52
|
-
import {
|
|
52
|
+
import { getDisplayChangelogEntries, getNewEntries } from "./utils/changelog";
|
|
53
53
|
import type { EventBus } from "./utils/event-bus";
|
|
54
54
|
|
|
55
55
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
@@ -341,8 +341,7 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
|
|
|
341
341
|
return undefined;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
const
|
|
345
|
-
const entries = await parseChangelog(changelogPath);
|
|
344
|
+
const entries = getDisplayChangelogEntries();
|
|
346
345
|
|
|
347
346
|
if (!lastVersion) {
|
|
348
347
|
if (entries.length > 0) {
|
package/src/memories/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import consolidationTemplate from "../prompts/memories/consolidation.md" with {
|
|
|
12
12
|
import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
|
|
13
13
|
import stageOneInputTemplate from "../prompts/memories/stage_one_input.md" with { type: "text" };
|
|
14
14
|
import stageOneSystemTemplate from "../prompts/memories/stage_one_system.md" with { type: "text" };
|
|
15
|
+
import unavailableTemplate from "../prompts/memories/unavailable.md" with { type: "text" };
|
|
15
16
|
import type { AgentSession } from "../session/agent-session";
|
|
16
17
|
import {
|
|
17
18
|
claimStage1Jobs,
|
|
@@ -149,23 +150,24 @@ export function startMemoryStartupTask(options: {
|
|
|
149
150
|
export async function buildMemoryToolDeveloperInstructions(
|
|
150
151
|
agentDir: string,
|
|
151
152
|
settings: Settings,
|
|
153
|
+
session?: AgentSession,
|
|
152
154
|
): Promise<string | undefined> {
|
|
153
155
|
const cfg = loadMemoryConfig(settings);
|
|
154
156
|
if (!cfg.enabled) return undefined;
|
|
155
|
-
const memoryRoot = getMemoryRoot(agentDir, settings.getCwd());
|
|
157
|
+
const memoryRoot = getMemoryRoot(agentDir, session?.sessionManager?.getCwd() ?? settings.getCwd());
|
|
156
158
|
const summaryPath = path.join(memoryRoot, "memory_summary.md");
|
|
157
159
|
|
|
158
160
|
let text: string;
|
|
159
161
|
try {
|
|
160
162
|
text = await Bun.file(summaryPath).text();
|
|
161
163
|
} catch {
|
|
162
|
-
return
|
|
164
|
+
return unavailableTemplate;
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
const summary = text.trim();
|
|
166
|
-
if (!summary) return
|
|
168
|
+
if (!summary) return unavailableTemplate;
|
|
167
169
|
const truncated = truncateByApproxTokens(summary, cfg.summaryInjectionTokenLimit);
|
|
168
|
-
if (!truncated.trim()) return
|
|
170
|
+
if (!truncated.trim()) return unavailableTemplate;
|
|
169
171
|
|
|
170
172
|
return prompt.render(readPathTemplate, {
|
|
171
173
|
memory_summary: truncated,
|
|
@@ -9,22 +9,30 @@ import type { MemoryBackend } from "./types";
|
|
|
9
9
|
/**
|
|
10
10
|
* Wraps the existing `memories/` module as a `MemoryBackend`.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* The local pipeline owns rollout summarisation, SQLite retention, and
|
|
13
|
+
* `memory_summary.md`. Prompt reads use the live session cwd when available so
|
|
14
|
+
* manual enqueue/rebuild and startup hydration address the same memory root.
|
|
15
15
|
*/
|
|
16
16
|
export const localBackend: MemoryBackend = {
|
|
17
17
|
id: "local",
|
|
18
18
|
start(options) {
|
|
19
19
|
startMemoryStartupTask(options);
|
|
20
20
|
},
|
|
21
|
-
async buildDeveloperInstructions(agentDir, settings) {
|
|
22
|
-
return buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
21
|
+
async buildDeveloperInstructions(agentDir, settings, session) {
|
|
22
|
+
return buildMemoryToolDeveloperInstructions(agentDir, settings, session);
|
|
23
23
|
},
|
|
24
24
|
async clear(agentDir, cwd) {
|
|
25
25
|
await clearMemoryData(agentDir, cwd);
|
|
26
26
|
},
|
|
27
|
-
async enqueue(agentDir, cwd) {
|
|
27
|
+
async enqueue(agentDir, cwd, session) {
|
|
28
28
|
enqueueMemoryConsolidation(agentDir, cwd);
|
|
29
|
+
if (!session) return;
|
|
30
|
+
startMemoryStartupTask({
|
|
31
|
+
session,
|
|
32
|
+
settings: session.settings,
|
|
33
|
+
modelRegistry: session.modelRegistry,
|
|
34
|
+
agentDir,
|
|
35
|
+
taskDepth: session.taskDepth,
|
|
36
|
+
});
|
|
29
37
|
},
|
|
30
38
|
};
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type TUI,
|
|
15
15
|
truncateToWidth,
|
|
16
16
|
visibleWidth,
|
|
17
|
+
wrapTextWithAnsi,
|
|
17
18
|
} from "@gajae-code/tui";
|
|
18
19
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
19
20
|
import { matchesAppExternalEditor, matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
@@ -31,6 +32,13 @@ export interface HookSelectorOptions {
|
|
|
31
32
|
onRight?: () => void;
|
|
32
33
|
onExternalEditor?: () => void;
|
|
33
34
|
helpText?: string;
|
|
35
|
+
/**
|
|
36
|
+
* When true, the focused option's label wraps across multiple rows so the
|
|
37
|
+
* full text is visible. Non-focused options remain single-row with the
|
|
38
|
+
* existing `…` truncation hint. When unset/false, rendering is
|
|
39
|
+
* byte-identical to the previous implementation for all consumers.
|
|
40
|
+
*/
|
|
41
|
+
wrapFocused?: boolean;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
class OutlinedList extends Container {
|
|
@@ -55,12 +63,140 @@ class OutlinedList extends Container {
|
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Width-aware list child that owns wrapped focused-option layout.
|
|
68
|
+
*
|
|
69
|
+
* Single layout owner for the `wrapFocused` branch: row budgeting, sibling
|
|
70
|
+
* selection, marker placement, and finalized row construction all happen
|
|
71
|
+
* inside `render(width)` using the actual incoming width. The outer host
|
|
72
|
+
* (`HookSelectorComponent`) feeds it `options`, `selectedIndex`, and
|
|
73
|
+
* `maxVisibleRows`; everything that depends on terminal width is recomputed
|
|
74
|
+
* on each render so resize Just Works.
|
|
75
|
+
*
|
|
76
|
+
* `maxVisibleRows` is a sibling budget before it is a hard cap: surrounding
|
|
77
|
+
* options shrink first so the focused option is never clipped. The single
|
|
78
|
+
* allowed overflow exception is when the focused option's wrapped block
|
|
79
|
+
* alone exceeds the budget — in that case the focused option is rendered
|
|
80
|
+
* fully with zero siblings.
|
|
81
|
+
*/
|
|
82
|
+
class FocusAwareList extends Container {
|
|
83
|
+
#options: string[] = [];
|
|
84
|
+
#selectedIndex = 0;
|
|
85
|
+
#maxVisibleRows = 0;
|
|
86
|
+
#outline: boolean;
|
|
87
|
+
|
|
88
|
+
constructor(outline: boolean) {
|
|
89
|
+
super();
|
|
90
|
+
this.#outline = outline;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setState(options: string[], selectedIndex: number, maxVisibleRows: number): void {
|
|
94
|
+
this.#options = options;
|
|
95
|
+
this.#selectedIndex = Math.max(0, Math.min(selectedIndex, options.length - 1));
|
|
96
|
+
this.#maxVisibleRows = Math.max(1, maxVisibleRows);
|
|
97
|
+
this.invalidate();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render(width: number): string[] {
|
|
101
|
+
if (this.#options.length === 0) return this.#outline ? this.#wrapOutline([], width) : [];
|
|
102
|
+
|
|
103
|
+
const mdTheme = getMarkdownTheme();
|
|
104
|
+
const innerWidth = this.#outline ? Math.max(1, width - 2) : Math.max(1, width);
|
|
105
|
+
|
|
106
|
+
// Selected/non-selected prefixes mirror the legacy `#updateList` shape.
|
|
107
|
+
const styledSelectedPrefix = theme.fg("accent", `${theme.nav.cursor} `);
|
|
108
|
+
const nonSelectedPrefix = " ";
|
|
109
|
+
const prefixWidth = visibleWidth(styledSelectedPrefix);
|
|
110
|
+
const continuationPrefix = " ".repeat(prefixWidth);
|
|
111
|
+
const availableLabelWidth = Math.max(1, innerWidth - prefixWidth);
|
|
112
|
+
|
|
113
|
+
// Render the focused label up front so we can measure how many rows it
|
|
114
|
+
// will consume at the current width and budget siblings accordingly.
|
|
115
|
+
const focusedLabel = renderInlineMarkdown(this.#options[this.#selectedIndex] ?? "", mdTheme, t =>
|
|
116
|
+
theme.fg("accent", t),
|
|
117
|
+
);
|
|
118
|
+
const focusedWrappedSegments = wrapTextWithAnsi(focusedLabel, availableLabelWidth);
|
|
119
|
+
const focusedRows = Math.max(1, focusedWrappedSegments.length);
|
|
120
|
+
|
|
121
|
+
// Decide whether the position marker is going to be shown. We make a
|
|
122
|
+
// pessimistic first pass assuming the marker is needed; if the window
|
|
123
|
+
// ends up covering every option we drop it.
|
|
124
|
+
const totalOptions = this.#options.length;
|
|
125
|
+
const willHaveSiblings = totalOptions > 1;
|
|
126
|
+
const wouldNeedMarker = willHaveSiblings; // tentative; refined below
|
|
127
|
+
const markerSlot = wouldNeedMarker ? 1 : 0;
|
|
128
|
+
|
|
129
|
+
// Sibling budget. If the focused block alone is over budget, render it
|
|
130
|
+
// fully with zero siblings (only allowed overflow exception).
|
|
131
|
+
const siblingBudget = Math.max(0, this.#maxVisibleRows - focusedRows - markerSlot);
|
|
132
|
+
|
|
133
|
+
// Distribute sibling slots around focus, preferring closest options.
|
|
134
|
+
const availableAbove = this.#selectedIndex;
|
|
135
|
+
const availableBelow = totalOptions - this.#selectedIndex - 1;
|
|
136
|
+
let above = Math.min(availableAbove, Math.floor(siblingBudget / 2));
|
|
137
|
+
let below = Math.min(availableBelow, siblingBudget - above);
|
|
138
|
+
// Transfer unused quota across the focus when one side has fewer
|
|
139
|
+
// options than its share.
|
|
140
|
+
const unusedBelow = siblingBudget - above - below;
|
|
141
|
+
if (unusedBelow > 0) above = Math.min(availableAbove, above + unusedBelow);
|
|
142
|
+
const unusedAbove = siblingBudget - above - below;
|
|
143
|
+
if (unusedAbove > 0) below = Math.min(availableBelow, below + unusedAbove);
|
|
144
|
+
|
|
145
|
+
const startIndex = this.#selectedIndex - above;
|
|
146
|
+
const endIndex = this.#selectedIndex + below + 1;
|
|
147
|
+
const showMarker = startIndex > 0 || endIndex < totalOptions;
|
|
148
|
+
|
|
149
|
+
const rows: string[] = [];
|
|
150
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
151
|
+
if (i === this.#selectedIndex) {
|
|
152
|
+
// Emit focused wrapped rows. Cursor only on row 0; continuation
|
|
153
|
+
// rows are whitespace-aligned under the label start.
|
|
154
|
+
for (let r = 0; r < focusedWrappedSegments.length; r++) {
|
|
155
|
+
const segment = focusedWrappedSegments[r] ?? "";
|
|
156
|
+
rows.push(r === 0 ? styledSelectedPrefix + segment : continuationPrefix + segment);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const label = renderInlineMarkdown(this.#options[i] ?? "", mdTheme, t => theme.fg("text", t));
|
|
160
|
+
// Non-focused rows stay single-line. Truncate here so the
|
|
161
|
+
// outline (post-padded by `#wrapOutline`) and non-outline
|
|
162
|
+
// paths render the same `…` hint for over-wide labels.
|
|
163
|
+
const fittedLabel = truncateToWidth(label, availableLabelWidth);
|
|
164
|
+
rows.push(nonSelectedPrefix + fittedLabel);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (showMarker) {
|
|
169
|
+
rows.push(theme.fg("dim", ` (${this.#selectedIndex + 1}/${totalOptions})`));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return this.#outline ? this.#wrapOutline(rows, width) : rows;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#wrapOutline(rows: string[], width: number): string[] {
|
|
176
|
+
// Mirror the outline border drawn by `OutlinedList.render(width)`. The
|
|
177
|
+
// rows passed in are already constrained to `innerWidth` by
|
|
178
|
+
// `wrapTextWithAnsi`, so we only normalize tabs and pad — no further
|
|
179
|
+
// truncation, which would clip wrapped focused labels.
|
|
180
|
+
const borderColor = (text: string) => theme.fg("border", text);
|
|
181
|
+
const horizontal = borderColor(theme.boxSharp.horizontal.repeat(Math.max(1, width)));
|
|
182
|
+
const innerWidth = Math.max(1, width - 2);
|
|
183
|
+
const content = rows.map(line => {
|
|
184
|
+
const normalized = replaceTabs(line);
|
|
185
|
+
const fitted = truncateToWidth(normalized, innerWidth);
|
|
186
|
+
const pad = Math.max(0, innerWidth - visibleWidth(fitted));
|
|
187
|
+
return `${borderColor(theme.boxSharp.vertical)}${fitted}${padding(pad)}${borderColor(theme.boxSharp.vertical)}`;
|
|
188
|
+
});
|
|
189
|
+
return [horizontal, ...content, horizontal];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
58
193
|
export class HookSelectorComponent extends Container {
|
|
59
194
|
#options: string[];
|
|
60
195
|
#selectedIndex: number;
|
|
61
196
|
#maxVisible: number;
|
|
62
197
|
#listContainer: Container | undefined;
|
|
63
198
|
#outlinedList: OutlinedList | undefined;
|
|
199
|
+
#focusAwareList: FocusAwareList | undefined;
|
|
64
200
|
#onSelectCallback: (option: string) => void;
|
|
65
201
|
#onCancelCallback: () => void;
|
|
66
202
|
#titleComponent: Markdown;
|
|
@@ -69,6 +205,8 @@ export class HookSelectorComponent extends Container {
|
|
|
69
205
|
#onLeftCallback: (() => void) | undefined;
|
|
70
206
|
#onRightCallback: (() => void) | undefined;
|
|
71
207
|
#onExternalEditorCallback: (() => void) | undefined;
|
|
208
|
+
#wrapFocused: boolean;
|
|
209
|
+
#outline: boolean;
|
|
72
210
|
constructor(
|
|
73
211
|
title: string,
|
|
74
212
|
options: string[],
|
|
@@ -87,6 +225,8 @@ export class HookSelectorComponent extends Container {
|
|
|
87
225
|
this.#onLeftCallback = opts?.onLeft;
|
|
88
226
|
this.#onRightCallback = opts?.onRight;
|
|
89
227
|
this.#onExternalEditorCallback = opts?.onExternalEditor;
|
|
228
|
+
this.#wrapFocused = opts?.wrapFocused === true;
|
|
229
|
+
this.#outline = opts?.outline === true;
|
|
90
230
|
|
|
91
231
|
this.addChild(new DynamicBorder());
|
|
92
232
|
this.addChild(new Spacer(1));
|
|
@@ -113,7 +253,13 @@ export class HookSelectorComponent extends Container {
|
|
|
113
253
|
);
|
|
114
254
|
}
|
|
115
255
|
|
|
116
|
-
if (
|
|
256
|
+
if (this.#wrapFocused) {
|
|
257
|
+
// Width-aware child owns wrapped layout. It handles both outline
|
|
258
|
+
// and non-outline rendering paths internally so the cursor signal
|
|
259
|
+
// + continuation indent are identical across branches.
|
|
260
|
+
this.#focusAwareList = new FocusAwareList(this.#outline);
|
|
261
|
+
this.addChild(this.#focusAwareList);
|
|
262
|
+
} else if (this.#outline) {
|
|
117
263
|
this.#outlinedList = new OutlinedList();
|
|
118
264
|
this.addChild(this.#outlinedList);
|
|
119
265
|
} else {
|
|
@@ -130,6 +276,15 @@ export class HookSelectorComponent extends Container {
|
|
|
130
276
|
}
|
|
131
277
|
|
|
132
278
|
#updateList(): void {
|
|
279
|
+
if (this.#wrapFocused && this.#focusAwareList) {
|
|
280
|
+
this.#focusAwareList.setState(this.#options, this.#selectedIndex, this.#maxVisible);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Legacy branch — byte-identical to the previous implementation. Any
|
|
285
|
+
// change here is a regression against
|
|
286
|
+
// `BASELINE_OUTLINED_RENDER_80_STRIPPED` in
|
|
287
|
+
// `packages/coding-agent/test/hook-selector-overflow.test.ts`.
|
|
133
288
|
const lines: string[] = [];
|
|
134
289
|
const startIndex = Math.max(
|
|
135
290
|
0,
|
|
@@ -200,8 +200,10 @@ export interface StatusLinePreviewSettings {
|
|
|
200
200
|
export interface SettingsCallbacks {
|
|
201
201
|
/** Called when any setting value changes */
|
|
202
202
|
onChange: (path: SettingPath, newValue: unknown) => void;
|
|
203
|
-
/** Called for theme preview while browsing */
|
|
203
|
+
/** Called for theme preview while browsing theme settings */
|
|
204
204
|
onThemePreview?: (theme: string) => void | Promise<void>;
|
|
205
|
+
/** Called to restore the rendered theme when theme settings preview is cancelled */
|
|
206
|
+
onThemePreviewCancel?: (theme: string) => void | Promise<void>;
|
|
205
207
|
/** Called for status line preview while configuring */
|
|
206
208
|
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
207
209
|
/** Get current rendered status line for inline preview */
|
|
@@ -219,7 +221,6 @@ export interface SettingsCallbacks {
|
|
|
219
221
|
export class SettingsSelectorComponent extends Container {
|
|
220
222
|
#tabBar: TabBar;
|
|
221
223
|
#currentList: SettingsList | null = null;
|
|
222
|
-
#currentSubmenu: Container | null = null;
|
|
223
224
|
#pluginComponent: PluginSettingsComponent | null = null;
|
|
224
225
|
#statusPreviewContainer: Container | null = null;
|
|
225
226
|
#statusPreviewText: Text | null = null;
|
|
@@ -376,13 +377,13 @@ export class SettingsSelectorComponent extends Container {
|
|
|
376
377
|
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
377
378
|
let onPreviewCancel: (() => void) | undefined;
|
|
378
379
|
|
|
379
|
-
const activeThemeBeforePreview = getCurrentThemeName() ?? currentValue;
|
|
380
380
|
if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
381
|
+
const activeThemeBeforePreview = getCurrentThemeName() ?? currentValue;
|
|
381
382
|
onPreview = value => {
|
|
382
383
|
return this.callbacks.onThemePreview?.(value);
|
|
383
384
|
};
|
|
384
385
|
onPreviewCancel = () => {
|
|
385
|
-
this.callbacks.
|
|
386
|
+
return this.callbacks.onThemePreviewCancel?.(activeThemeBeforePreview);
|
|
386
387
|
};
|
|
387
388
|
} else if (def.path === "statusLine.preset") {
|
|
388
389
|
onPreview = value => {
|
|
@@ -619,17 +620,20 @@ export class SettingsSelectorComponent extends Container {
|
|
|
619
620
|
return;
|
|
620
621
|
}
|
|
621
622
|
|
|
622
|
-
// Escape
|
|
623
|
-
|
|
624
|
-
this.callbacks.onCancel();
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Pass to current content
|
|
623
|
+
// Pass to current content. SettingsList owns Escape routing so open
|
|
624
|
+
// submenus can run their cancel/restore callbacks before closing.
|
|
629
625
|
if (this.#currentList) {
|
|
630
626
|
this.#currentList.handleInput(data);
|
|
631
|
-
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (this.#pluginComponent) {
|
|
632
630
|
this.#pluginComponent.handleInput(data);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Fallback for future top-level content that does not own cancellation.
|
|
635
|
+
if (matchesAppInterrupt(data)) {
|
|
636
|
+
this.callbacks.onCancel();
|
|
633
637
|
}
|
|
634
638
|
}
|
|
635
639
|
}
|
|
@@ -42,7 +42,7 @@ import type { NewSessionOptions } from "../../session/session-manager";
|
|
|
42
42
|
import { outputMeta } from "../../tools/output-meta";
|
|
43
43
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
44
44
|
import { replaceTabs } from "../../tools/render-utils";
|
|
45
|
-
import {
|
|
45
|
+
import { getDisplayChangelogEntries } from "../../utils/changelog";
|
|
46
46
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
47
47
|
import { openPath } from "../../utils/open";
|
|
48
48
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
@@ -525,8 +525,7 @@ export class CommandController {
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
async handleChangelogCommand(showFull = false): Promise<void> {
|
|
528
|
-
const
|
|
529
|
-
const allEntries = await parseChangelog(changelogPath);
|
|
528
|
+
const allEntries = getDisplayChangelogEntries();
|
|
530
529
|
// Default to showing only the latest 3 versions unless --full is specified
|
|
531
530
|
// allEntries comes from parseChangelog with newest first, reverse to show oldest->newest
|
|
532
531
|
const entriesToShow = showFull ? allEntries : allEntries.slice(0, 3);
|
|
@@ -586,7 +585,7 @@ export class CommandController {
|
|
|
586
585
|
if (action === "view") {
|
|
587
586
|
const payload = await backend.buildDeveloperInstructions(agentDir, this.ctx.settings, this.ctx.session);
|
|
588
587
|
if (!payload) {
|
|
589
|
-
this.ctx.showWarning("Memory payload is empty
|
|
588
|
+
this.ctx.showWarning("Memory payload is empty; durable memory is unavailable or unconfirmed.");
|
|
590
589
|
return;
|
|
591
590
|
}
|
|
592
591
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -17,8 +17,11 @@ import {
|
|
|
17
17
|
} from "../../extensibility/plugins/marketplace";
|
|
18
18
|
import {
|
|
19
19
|
getAvailableThemes,
|
|
20
|
+
getCurrentThemeName,
|
|
21
|
+
getDetectedThemeSettingsPath,
|
|
20
22
|
getSymbolTheme,
|
|
21
23
|
previewTheme,
|
|
24
|
+
restoreThemePreview,
|
|
22
25
|
setColorBlindMode,
|
|
23
26
|
setSymbolPreset,
|
|
24
27
|
setTheme,
|
|
@@ -29,6 +32,7 @@ import { type SessionInfo, SessionManager } from "../../session/session-manager"
|
|
|
29
32
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
30
33
|
import {
|
|
31
34
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
35
|
+
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
32
36
|
MODEL_ONBOARDING_SETUP_COMMAND,
|
|
33
37
|
} from "../../setup/model-onboarding-guidance";
|
|
34
38
|
import { isSearchProviderPreference, setPreferredImageProvider, setPreferredSearchProvider } from "../../tools";
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
47
51
|
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
48
52
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
49
53
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
54
|
+
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
50
55
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
51
56
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
52
57
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
@@ -62,9 +67,15 @@ const CALLBACK_SERVER_PROVIDERS = new Set<OAuthProvider>([
|
|
|
62
67
|
|
|
63
68
|
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
|
64
69
|
|
|
70
|
+
function isThemePreviewSuperseded(result: { success: boolean; error?: string }): boolean {
|
|
71
|
+
return !result.success && result.error?.includes("superseded by a newer request") === true;
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
function formatProviderOnboardingCommandGuide(): string {
|
|
66
75
|
return [
|
|
67
|
-
"
|
|
76
|
+
"Provider preset setup:",
|
|
77
|
+
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
78
|
+
"Custom API-compatible provider setup:",
|
|
68
79
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
69
80
|
MODEL_ONBOARDING_SETUP_COMMAND,
|
|
70
81
|
].join("\n");
|
|
@@ -132,14 +143,21 @@ export class SelectorController {
|
|
|
132
143
|
},
|
|
133
144
|
{
|
|
134
145
|
onChange: (id, value) => this.handleSettingChange(id, value),
|
|
135
|
-
onThemePreview:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
onThemePreview: themeName => {
|
|
147
|
+
return previewTheme(themeName).then(result => {
|
|
148
|
+
if (!result.success && result.error && !isThemePreviewSuperseded(result)) {
|
|
149
|
+
this.ctx.showError(`Failed to preview theme: ${result.error}`);
|
|
150
|
+
}
|
|
151
|
+
this.#refreshThemeUi();
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
onThemePreviewCancel: themeName => {
|
|
155
|
+
return restoreThemePreview(themeName).then(result => {
|
|
156
|
+
if (!result.success && result.error && !isThemePreviewSuperseded(result)) {
|
|
157
|
+
this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
|
|
158
|
+
}
|
|
159
|
+
this.#refreshThemeUi();
|
|
160
|
+
});
|
|
143
161
|
},
|
|
144
162
|
onStatusLinePreview: previewSettings => {
|
|
145
163
|
// Update status line with preview settings
|
|
@@ -184,6 +202,48 @@ export class SelectorController {
|
|
|
184
202
|
});
|
|
185
203
|
}
|
|
186
204
|
|
|
205
|
+
#refreshThemeUi(): void {
|
|
206
|
+
this.ctx.statusLine.invalidate();
|
|
207
|
+
this.ctx.updateEditorTopBorder();
|
|
208
|
+
this.ctx.ui.requestRender();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
showThemeSelector(): void {
|
|
212
|
+
getAvailableThemes().then(availableThemes => {
|
|
213
|
+
const initialTheme = getCurrentThemeName() ?? "red-claw";
|
|
214
|
+
this.showSelector(done => {
|
|
215
|
+
const selector = new ThemeSelectorComponent(
|
|
216
|
+
initialTheme,
|
|
217
|
+
availableThemes,
|
|
218
|
+
themeName => {
|
|
219
|
+
const settingPath = getDetectedThemeSettingsPath();
|
|
220
|
+
settings.set(settingPath, themeName);
|
|
221
|
+
this.#refreshThemeUi();
|
|
222
|
+
done();
|
|
223
|
+
},
|
|
224
|
+
() => {
|
|
225
|
+
void restoreThemePreview(initialTheme).then(result => {
|
|
226
|
+
if (!result.success && result.error) {
|
|
227
|
+
this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
|
|
228
|
+
}
|
|
229
|
+
this.#refreshThemeUi();
|
|
230
|
+
});
|
|
231
|
+
done();
|
|
232
|
+
},
|
|
233
|
+
themeName => {
|
|
234
|
+
void previewTheme(themeName).then(result => {
|
|
235
|
+
if (!result.success && result.error) {
|
|
236
|
+
this.ctx.showError(`Failed to preview theme: ${result.error}`);
|
|
237
|
+
}
|
|
238
|
+
this.#refreshThemeUi();
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
return { component: selector, focus: selector };
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
187
247
|
showHistorySearch(): void {
|
|
188
248
|
const historyStorage = this.ctx.historyStorage;
|
|
189
249
|
if (!historyStorage) return;
|
|
@@ -1281,7 +1281,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1281
1281
|
reason?: "completed" | "paused" | "dropped";
|
|
1282
1282
|
}): Promise<void> {
|
|
1283
1283
|
const previousTools = this.#goalModePreviousTools;
|
|
1284
|
-
|
|
1284
|
+
// Drop keeps the `goal` tool callable so the agent can immediately create a new
|
|
1285
|
+
// goal in the same session without a leader-side cleanup. Complete (and pause)
|
|
1286
|
+
// exit goal mode and restore the pre-goal tool set even when the goal_updated
|
|
1287
|
+
// event has already cleared goalModeEnabled (see #completeGoalFromTool emitting
|
|
1288
|
+
// state.enabled = false before #exitGoalMode runs). Spec: deep-interview-ultragoal-goal-tool-wiring AC1+AC2.
|
|
1289
|
+
const shouldRestoreTools =
|
|
1290
|
+
previousTools &&
|
|
1291
|
+
options?.reason !== "dropped" &&
|
|
1292
|
+
(this.goalModeEnabled || options?.reason === "completed" || options?.paused === true);
|
|
1293
|
+
if (shouldRestoreTools) {
|
|
1285
1294
|
await this.session.setActiveToolsByName(previousTools);
|
|
1286
1295
|
}
|
|
1287
1296
|
const currentState = this.session.getGoalModeState();
|
|
@@ -2339,6 +2348,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2339
2348
|
this.#selectorController.showSettingsSelector();
|
|
2340
2349
|
}
|
|
2341
2350
|
|
|
2351
|
+
showThemeSelector(): void {
|
|
2352
|
+
this.#selectorController.showThemeSelector();
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2342
2355
|
showHistorySearch(): void {
|
|
2343
2356
|
this.#selectorController.showHistorySearch();
|
|
2344
2357
|
}
|