@gajae-code/coding-agent 0.2.1 → 0.2.3
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 +59 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +2 -2
- package/dist/types/config/models-config-schema.d.ts +17 -9
- package/dist/types/config/settings-schema.d.ts +37 -24
- 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 +33 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- 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 +0 -2
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- 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 +47 -1
- package/dist/types/session/contribution-prep.d.ts +47 -0
- 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 +30 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -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/args.ts +3 -2
- package/src/cli/setup-cli.ts +26 -12
- package/src/cli.ts +7 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +30 -23
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +15 -4
- package/src/commands/team.ts +23 -3
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +120 -102
- package/src/config/settings-schema.ts +42 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +32 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +22 -2
- package/src/defaults/gjc/skills/team/SKILL.md +39 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -25
- package/src/discovery/helpers.ts +24 -1
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +546 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +731 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +39 -18
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/internal-urls/memory-protocol.ts +3 -2
- package/src/main.ts +2 -3
- package/src/memories/index.ts +2 -1
- 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 +5 -12
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +27 -4
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- 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/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/subagent-system-prompt.md +6 -0
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +51 -11
- package/src/session/agent-session.ts +222 -21
- package/src/session/contribution-prep.ts +320 -0
- 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 +188 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +72 -21
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/builtin-registry.ts +75 -25
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- package/src/task/commands.ts +1 -5
- 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/gh.ts +212 -2
- package/src/tools/index.ts +25 -6
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- package/src/prompts/tools/update-goal.md +0 -3
|
@@ -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
|
@@ -149,10 +149,11 @@ export function startMemoryStartupTask(options: {
|
|
|
149
149
|
export async function buildMemoryToolDeveloperInstructions(
|
|
150
150
|
agentDir: string,
|
|
151
151
|
settings: Settings,
|
|
152
|
+
session?: AgentSession,
|
|
152
153
|
): Promise<string | undefined> {
|
|
153
154
|
const cfg = loadMemoryConfig(settings);
|
|
154
155
|
if (!cfg.enabled) return undefined;
|
|
155
|
-
const memoryRoot = getMemoryRoot(agentDir, settings.getCwd());
|
|
156
|
+
const memoryRoot = getMemoryRoot(agentDir, session?.sessionManager.getCwd() ?? settings.getCwd());
|
|
156
157
|
const summaryPath = path.join(memoryRoot, "memory_summary.md");
|
|
157
158
|
|
|
158
159
|
let text: string;
|
|
@@ -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,
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
StatusLineSeparatorStyle,
|
|
22
22
|
} from "../../config/settings-schema";
|
|
23
23
|
import { SETTING_TABS, TAB_METADATA } from "../../config/settings-schema";
|
|
24
|
-
import {
|
|
24
|
+
import { getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
25
25
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
26
26
|
import { getTabBarTheme } from "../shared";
|
|
27
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -200,8 +200,6 @@ 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 */
|
|
204
|
-
onThemePreview?: (theme: string) => void | Promise<void>;
|
|
205
203
|
/** Called for status line preview while configuring */
|
|
206
204
|
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
207
205
|
/** Get current rendered status line for inline preview */
|
|
@@ -376,15 +374,10 @@ export class SettingsSelectorComponent extends Container {
|
|
|
376
374
|
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
377
375
|
let onPreviewCancel: (() => void) | undefined;
|
|
378
376
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
};
|
|
384
|
-
onPreviewCancel = () => {
|
|
385
|
-
this.callbacks.onThemePreview?.(activeThemeBeforePreview);
|
|
386
|
-
};
|
|
387
|
-
} else if (def.path === "statusLine.preset") {
|
|
377
|
+
// Theme selection is confirm-only: moving through the list must not mutate
|
|
378
|
+
// the rendered theme while the displayed/persisted setting still names
|
|
379
|
+
// the previous value. Confirmation persists through Settings hooks.
|
|
380
|
+
if (def.path === "statusLine.preset") {
|
|
388
381
|
onPreview = value => {
|
|
389
382
|
const presetDef = getPreset(
|
|
390
383
|
value as "default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "custom",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SkillActiveEntry, WorkflowHudChip } from "../../../skill-state/active-state";
|
|
2
|
+
import { workflowReceiptStatus } from "../../../skill-state/workflow-state-contract";
|
|
2
3
|
|
|
3
4
|
const ANSI_RESET_FG = "\x1b[39m";
|
|
4
5
|
const ANSI_RESET_BOLD = "\x1b[22m";
|
|
@@ -60,6 +61,9 @@ function formatEntry(entry: SkillActiveEntry): string {
|
|
|
60
61
|
.map(formatChip)
|
|
61
62
|
.filter((chip): chip is string => Boolean(chip));
|
|
62
63
|
if (entry.stale === true) chips.unshift("warn:stale");
|
|
64
|
+
const receiptStatus = workflowReceiptStatus(entry.receipt);
|
|
65
|
+
if (receiptStatus === "stale") chips.unshift("warn:receipt=stale");
|
|
66
|
+
if (receiptStatus === "fresh") chips.push("receipt=fresh");
|
|
63
67
|
const summary = sanitizeHudPart(entry.hud?.summary);
|
|
64
68
|
return [base, summary, ...chips].filter(Boolean).join(" ");
|
|
65
69
|
}
|
|
@@ -110,10 +110,9 @@ const modelSegment: StatusLineSegment = {
|
|
|
110
110
|
},
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
function
|
|
113
|
+
function formatGoalUsage(current: number): string {
|
|
114
114
|
const used = formatNumber(current);
|
|
115
|
-
|
|
116
|
-
return `${used}/${formatNumber(budget)}`;
|
|
115
|
+
return used;
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
function renderGoalMode(ctx: SegmentContext, mode: { enabled: boolean; paused: boolean }): RenderedSegment {
|
|
@@ -131,10 +130,6 @@ function renderGoalMode(ctx: SegmentContext, mode: { enabled: boolean; paused: b
|
|
|
131
130
|
icon = theme.symbol("status.success");
|
|
132
131
|
color = "success";
|
|
133
132
|
break;
|
|
134
|
-
case "budget-limited":
|
|
135
|
-
icon = theme.symbol("status.warning");
|
|
136
|
-
color = "warning";
|
|
137
|
-
break;
|
|
138
133
|
case "dropped":
|
|
139
134
|
icon = theme.symbol("status.aborted");
|
|
140
135
|
color = "dim";
|
|
@@ -144,9 +139,9 @@ function renderGoalMode(ctx: SegmentContext, mode: { enabled: boolean; paused: b
|
|
|
144
139
|
}
|
|
145
140
|
|
|
146
141
|
const parts: string[] = [withIcon(icon, "Goal")];
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
parts.push(
|
|
142
|
+
const showUsage = ctx.session.settings.get("goal.statusInFooter") === true;
|
|
143
|
+
if (showUsage && goal) {
|
|
144
|
+
parts.push(formatGoalUsage(goal.tokensUsed));
|
|
150
145
|
}
|
|
151
146
|
return { content: theme.fg(color, parts.join(" ")), visible: true };
|
|
152
147
|
}
|
|
@@ -169,12 +164,6 @@ const modeSegment: StatusLineSegment = {
|
|
|
169
164
|
return renderGoalMode(ctx, goal);
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
const loop = ctx.loopMode;
|
|
173
|
-
if (loop?.enabled) {
|
|
174
|
-
const content = withIcon(theme.icon.loop, "Loop");
|
|
175
|
-
return { content: theme.fg("customMessageLabel", content), visible: true };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
167
|
return { content: "", visible: false };
|
|
179
168
|
},
|
|
180
169
|
};
|
|
@@ -155,7 +155,6 @@ export class StatusLineComponent implements Component {
|
|
|
155
155
|
#subagentCount: number = 0;
|
|
156
156
|
#sessionStartTime: number = Date.now();
|
|
157
157
|
#planModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
158
|
-
#loopModeStatus: { enabled: boolean } | null = null;
|
|
159
158
|
#goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
160
159
|
#skillHudEntries: SkillActiveEntry[] = [];
|
|
161
160
|
#skillHudLastFetch = 0;
|
|
@@ -229,10 +228,6 @@ export class StatusLineComponent implements Component {
|
|
|
229
228
|
this.#planModeStatus = status ?? null;
|
|
230
229
|
}
|
|
231
230
|
|
|
232
|
-
setLoopModeStatus(status: { enabled: boolean } | undefined): void {
|
|
233
|
-
this.#loopModeStatus = status ?? null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
231
|
setGoalModeStatus(status: { enabled: boolean; paused: boolean } | undefined): void {
|
|
237
232
|
this.#goalModeStatus = status ?? null;
|
|
238
233
|
}
|
|
@@ -611,7 +606,6 @@ export class StatusLineComponent implements Component {
|
|
|
611
606
|
width,
|
|
612
607
|
options: this.#resolveSettings().segmentOptions ?? {},
|
|
613
608
|
planMode: this.#planModeStatus,
|
|
614
|
-
loopMode: this.#loopModeStatus,
|
|
615
609
|
goalMode: this.#goalModeStatus,
|
|
616
610
|
usageStats,
|
|
617
611
|
contextPercent,
|
|
@@ -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);
|
|
@@ -1248,7 +1247,31 @@ export class CommandController {
|
|
|
1248
1247
|
this.ctx.statusContainer.clear();
|
|
1249
1248
|
this.ctx.editor.onEscape = originalOnEscape;
|
|
1250
1249
|
}
|
|
1251
|
-
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
async handleContributionPrepCommand(customInstructions?: string): Promise<void> {
|
|
1253
|
+
this.ctx.editor.setText("");
|
|
1254
|
+
try {
|
|
1255
|
+
const result = await this.ctx.session.prepareContributionPrep({ customInstructions, spawnWorker: true });
|
|
1256
|
+
this.ctx.showStatus(
|
|
1257
|
+
[
|
|
1258
|
+
"Contribution prep artifacts written.",
|
|
1259
|
+
`Manifest: ${result.manifestPath}`,
|
|
1260
|
+
`Worker prompt: ${result.workerPromptPath}`,
|
|
1261
|
+
].join("\n"),
|
|
1262
|
+
);
|
|
1263
|
+
this.ctx.chatContainer.addChild(
|
|
1264
|
+
new Text(
|
|
1265
|
+
`${theme.fg("accent", `${theme.status.success} Contribution prep ready`)}\nManifest: ${result.manifestPath}`,
|
|
1266
|
+
1,
|
|
1267
|
+
1,
|
|
1268
|
+
),
|
|
1269
|
+
);
|
|
1270
|
+
this.ctx.ui.requestRender();
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1273
|
+
this.ctx.showError(`Contribution prep failed: ${message}`);
|
|
1274
|
+
}
|
|
1252
1275
|
}
|
|
1253
1276
|
}
|
|
1254
1277
|
|
|
@@ -51,15 +51,6 @@ export class InputController {
|
|
|
51
51
|
this.ctx.retryEscapeHandler,
|
|
52
52
|
);
|
|
53
53
|
this.ctx.editor.onEscape = () => {
|
|
54
|
-
if (this.ctx.loopModeEnabled) {
|
|
55
|
-
this.ctx.pauseLoop();
|
|
56
|
-
if (this.ctx.session.isStreaming) {
|
|
57
|
-
void this.#abortInteractive();
|
|
58
|
-
} else {
|
|
59
|
-
this.ctx.cancelPendingSubmission();
|
|
60
|
-
}
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
54
|
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
64
55
|
return;
|
|
65
56
|
}
|
|
@@ -292,12 +283,6 @@ export class InputController {
|
|
|
292
283
|
}
|
|
293
284
|
}
|
|
294
285
|
|
|
295
|
-
// While loop mode is on, every user-typed prompt becomes the new loop
|
|
296
|
-
// prompt that auto-resubmits after each yield.
|
|
297
|
-
if (this.ctx.loopModeEnabled) {
|
|
298
|
-
this.ctx.loopPrompt = text;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
286
|
// Queue input during compaction
|
|
302
287
|
if (this.ctx.session.isCompacting) {
|
|
303
288
|
if (this.ctx.pendingImages.length > 0) {
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
getAvailableThemes,
|
|
20
20
|
getSymbolTheme,
|
|
21
|
-
previewTheme,
|
|
22
21
|
setColorBlindMode,
|
|
23
22
|
setSymbolPreset,
|
|
24
23
|
setTheme,
|
|
@@ -29,6 +28,7 @@ import { type SessionInfo, SessionManager } from "../../session/session-manager"
|
|
|
29
28
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
30
29
|
import {
|
|
31
30
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
31
|
+
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
32
32
|
MODEL_ONBOARDING_SETUP_COMMAND,
|
|
33
33
|
} from "../../setup/model-onboarding-guidance";
|
|
34
34
|
import { isSearchProviderPreference, setPreferredImageProvider, setPreferredSearchProvider } from "../../tools";
|
|
@@ -64,7 +64,9 @@ const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect UR
|
|
|
64
64
|
|
|
65
65
|
function formatProviderOnboardingCommandGuide(): string {
|
|
66
66
|
return [
|
|
67
|
-
"
|
|
67
|
+
"Provider preset setup:",
|
|
68
|
+
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
69
|
+
"Custom API-compatible provider setup:",
|
|
68
70
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
69
71
|
MODEL_ONBOARDING_SETUP_COMMAND,
|
|
70
72
|
].join("\n");
|
|
@@ -132,15 +134,6 @@ export class SelectorController {
|
|
|
132
134
|
},
|
|
133
135
|
{
|
|
134
136
|
onChange: (id, value) => this.handleSettingChange(id, value),
|
|
135
|
-
onThemePreview: async themeName => {
|
|
136
|
-
const result = await previewTheme(themeName);
|
|
137
|
-
if (result.success) {
|
|
138
|
-
this.ctx.statusLine.invalidate();
|
|
139
|
-
this.ctx.updateEditorTopBorder();
|
|
140
|
-
this.ctx.ui.invalidate();
|
|
141
|
-
this.ctx.ui.requestRender();
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
137
|
onStatusLinePreview: previewSettings => {
|
|
145
138
|
// Update status line with preview settings
|
|
146
139
|
this.ctx.statusLine.updateSettings({
|