@gajae-code/coding-agent 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +28 -1
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +14 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +23 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +53 -1
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +7 -7
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +91 -7
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-registry.ts +14 -2
- package/src/config/settings-schema.ts +68 -1
- package/src/config/settings.ts +22 -0
- package/src/coordinator-mcp/server.ts +34 -8
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +50 -18
- package/src/defaults/gjc/skills/ralplan/SKILL.md +40 -99
- package/src/defaults/gjc/skills/team/SKILL.md +17 -14
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/gjc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
- package/src/gjc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +62 -8
- package/src/gjc-runtime/deep-interview-runtime.ts +210 -2
- package/src/gjc-runtime/deep-interview-state.ts +44 -2
- package/src/gjc-runtime/session-state-sidecar.ts +263 -20
- package/src/gjc-runtime/state-runtime.ts +12 -2
- package/src/gjc-runtime/state-writer.ts +15 -0
- package/src/gjc-runtime/team-runtime.ts +187 -15
- package/src/gjc-runtime/ultragoal-guard.ts +55 -66
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1663 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
- package/src/gjc-runtime/workflow-manifest.ts +74 -1
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +16 -7
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +160 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +180 -18
- package/src/modes/controllers/selector-controller.ts +122 -27
- package/src/modes/interactive-mode.ts +51 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +19 -6
- package/src/notifications/config.ts +25 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +117 -14
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-cli.ts +2 -2
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +302 -51
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -19
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +52 -28
- package/src/session/agent-session.ts +951 -132
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +218 -71
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -55,6 +55,15 @@ function elideRunawayThinkingRepetition(text: string): string {
|
|
|
55
55
|
return text;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
interface AssistantMessageUpdateOptions {
|
|
59
|
+
streaming?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type AssistantChildDescriptor = {
|
|
63
|
+
key: string;
|
|
64
|
+
component: Component;
|
|
65
|
+
};
|
|
66
|
+
|
|
58
67
|
/**
|
|
59
68
|
* Component that renders a complete assistant message
|
|
60
69
|
*/
|
|
@@ -67,6 +76,11 @@ export class AssistantMessageComponent extends Container {
|
|
|
67
76
|
#kittyConversionsInFlight = new Set<string>();
|
|
68
77
|
#responseHeader = new Text(theme.bold(theme.fg("statusLineModel", "gajae")), 1, 0);
|
|
69
78
|
#contentBlocksCache = new WeakMap<object, { source: string; component: Component }>();
|
|
79
|
+
#lastStreaming = false;
|
|
80
|
+
#childComponents = new Map<string, Component>();
|
|
81
|
+
#contentBlockKeys = new WeakMap<object, string>();
|
|
82
|
+
#nextContentBlockKey = 0;
|
|
83
|
+
#reusableChildren = new WeakSet<Component>();
|
|
70
84
|
|
|
71
85
|
constructor(
|
|
72
86
|
message?: AssistantMessage,
|
|
@@ -87,7 +101,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
87
101
|
override invalidate(): void {
|
|
88
102
|
super.invalidate();
|
|
89
103
|
if (this.#lastMessage) {
|
|
90
|
-
this.updateContent(this.#lastMessage);
|
|
104
|
+
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
@@ -115,7 +129,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
115
129
|
this.#convertToolImagesForKitty(toolCallId, validImages);
|
|
116
130
|
}
|
|
117
131
|
if (this.#lastMessage) {
|
|
118
|
-
this.updateContent(this.#lastMessage);
|
|
132
|
+
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
|
|
@@ -138,7 +152,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
138
152
|
mimeType: "image/png",
|
|
139
153
|
});
|
|
140
154
|
if (this.#lastMessage) {
|
|
141
|
-
this.updateContent(this.#lastMessage);
|
|
155
|
+
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
142
156
|
}
|
|
143
157
|
this.onImageUpdate?.();
|
|
144
158
|
})
|
|
@@ -151,153 +165,298 @@ export class AssistantMessageComponent extends Container {
|
|
|
151
165
|
setUsageInfo(usage: Usage): void {
|
|
152
166
|
this.#usageInfo = usage;
|
|
153
167
|
if (this.#lastMessage) {
|
|
154
|
-
this.updateContent(this.#lastMessage);
|
|
168
|
+
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#contentBlockKey(content: object): string {
|
|
173
|
+
let key = this.#contentBlockKeys.get(content);
|
|
174
|
+
if (!key) {
|
|
175
|
+
key = `block:${this.#nextContentBlockKey++}`;
|
|
176
|
+
this.#contentBlockKeys.set(content, key);
|
|
155
177
|
}
|
|
178
|
+
return key;
|
|
156
179
|
}
|
|
157
180
|
|
|
158
|
-
#
|
|
181
|
+
#cachedChild<T extends Component>(key: string, create: () => T): T {
|
|
182
|
+
const cached = this.#childComponents.get(key);
|
|
183
|
+
if (cached) return cached as T;
|
|
184
|
+
const component = create();
|
|
185
|
+
this.#childComponents.set(key, component);
|
|
186
|
+
this.#reusableChildren.add(component);
|
|
187
|
+
return component;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#renderTextBlock(content: { text: string }, streaming: boolean): Component {
|
|
159
191
|
const cached = this.#contentBlocksCache.get(content);
|
|
160
|
-
if (cached?.source === content.text)
|
|
192
|
+
if (cached?.source === content.text) {
|
|
193
|
+
if (cached.component instanceof Markdown) {
|
|
194
|
+
cached.component.setOnStaleThrottle(this.onImageUpdate);
|
|
195
|
+
cached.component.setStreaming(streaming);
|
|
196
|
+
}
|
|
197
|
+
return cached.component;
|
|
198
|
+
}
|
|
161
199
|
const trimmed = content.text.trim();
|
|
162
200
|
const deepInterview = renderDeepInterviewAssistantText(trimmed, theme);
|
|
163
201
|
// Reuse the same Markdown instance across streaming chunks (update text in place)
|
|
164
202
|
// instead of constructing a new one each chunk; combined with the markdown
|
|
165
203
|
// per-code-block highlight cache, appends no longer re-highlight the whole prefix.
|
|
166
204
|
if (!deepInterview && cached && cached.component instanceof Markdown) {
|
|
167
|
-
cached.component.
|
|
205
|
+
cached.component.setOnStaleThrottle(this.onImageUpdate);
|
|
206
|
+
cached.component.setText(trimmed, { streaming });
|
|
168
207
|
cached.source = content.text;
|
|
169
208
|
return cached.component;
|
|
170
209
|
}
|
|
171
210
|
const component = deepInterview ?? new Markdown(trimmed, 1, 0, getMarkdownTheme());
|
|
211
|
+
if (component instanceof Markdown) {
|
|
212
|
+
component.setOnStaleThrottle(this.onImageUpdate);
|
|
213
|
+
component.setStreaming(streaming);
|
|
214
|
+
}
|
|
172
215
|
this.#contentBlocksCache.set(content, { source: content.text, component });
|
|
216
|
+
this.#reusableChildren.add(component);
|
|
173
217
|
return component;
|
|
174
218
|
}
|
|
175
219
|
|
|
176
|
-
#renderThinkingBlock(content: { thinking: string }): Markdown {
|
|
220
|
+
#renderThinkingBlock(content: { thinking: string }, streaming: boolean): Markdown {
|
|
177
221
|
const cached = this.#contentBlocksCache.get(content);
|
|
178
|
-
if (cached?.source === content.thinking)
|
|
222
|
+
if (cached?.source === content.thinking) {
|
|
223
|
+
if (cached.component instanceof Markdown) {
|
|
224
|
+
cached.component.setOnStaleThrottle(this.onImageUpdate);
|
|
225
|
+
cached.component.setStreaming(streaming);
|
|
226
|
+
}
|
|
227
|
+
return cached.component as Markdown;
|
|
228
|
+
}
|
|
179
229
|
const trimmed = elideRunawayThinkingRepetition(content.thinking.trim());
|
|
230
|
+
if (cached?.component instanceof Markdown) {
|
|
231
|
+
cached.component.setOnStaleThrottle(this.onImageUpdate);
|
|
232
|
+
cached.component.setText(trimmed, { streaming });
|
|
233
|
+
cached.source = content.thinking;
|
|
234
|
+
return cached.component;
|
|
235
|
+
}
|
|
180
236
|
const component = new Markdown(trimmed, 1, 0, getMarkdownTheme(), {
|
|
181
237
|
color: (text: string) => theme.fg("thinkingText", text),
|
|
182
238
|
italic: true,
|
|
183
239
|
});
|
|
240
|
+
component.setOnStaleThrottle(this.onImageUpdate);
|
|
241
|
+
component.setStreaming(streaming);
|
|
184
242
|
this.#contentBlocksCache.set(content, { source: content.thinking, component });
|
|
243
|
+
this.#reusableChildren.add(component);
|
|
185
244
|
return component;
|
|
186
245
|
}
|
|
187
246
|
|
|
188
|
-
#
|
|
247
|
+
#toolImageDescriptors(): AssistantChildDescriptor[] {
|
|
189
248
|
const imageEntries = Array.from(this.#toolImagesByCallId.entries()).flatMap(([toolCallId, images]) =>
|
|
190
249
|
images.map((image, index) => ({ image, key: `${toolCallId}:${index}` })),
|
|
191
250
|
);
|
|
192
|
-
if (imageEntries.length === 0) return;
|
|
251
|
+
if (imageEntries.length === 0) return [];
|
|
193
252
|
|
|
194
|
-
|
|
253
|
+
const descriptors: AssistantChildDescriptor[] = [
|
|
254
|
+
{ key: "tool-images:spacer", component: this.#cachedChild("tool-images:spacer", () => new Spacer(1)) },
|
|
255
|
+
];
|
|
195
256
|
for (const { image, key } of imageEntries) {
|
|
196
257
|
const displayImage =
|
|
197
258
|
TERMINAL.imageProtocol === ImageProtocol.Kitty && image.mimeType !== "image/png"
|
|
198
259
|
? this.#convertedKittyImages.get(key)
|
|
199
260
|
: image;
|
|
200
261
|
if (TERMINAL.imageProtocol && displayImage) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
262
|
+
const imageKey = `tool-image:${key}:${displayImage.mimeType}:${displayImage.data}`;
|
|
263
|
+
descriptors.push({
|
|
264
|
+
key: imageKey,
|
|
265
|
+
component: this.#cachedChild(
|
|
266
|
+
imageKey,
|
|
267
|
+
() =>
|
|
268
|
+
new Image(
|
|
269
|
+
displayImage.data,
|
|
270
|
+
displayImage.mimeType,
|
|
271
|
+
{ fallbackColor: (text: string) => theme.fg("toolOutput", text) },
|
|
272
|
+
resolveImageOptions(),
|
|
273
|
+
),
|
|
207
274
|
),
|
|
208
|
-
);
|
|
275
|
+
});
|
|
209
276
|
continue;
|
|
210
277
|
}
|
|
211
|
-
|
|
278
|
+
const fallbackKey = `tool-image-fallback:${key}:${image.mimeType}`;
|
|
279
|
+
descriptors.push({
|
|
280
|
+
key: fallbackKey,
|
|
281
|
+
component: this.#cachedChild(
|
|
282
|
+
fallbackKey,
|
|
283
|
+
() => new Text(theme.fg("toolOutput", `[Image: ${image.mimeType}]`), 1, 0),
|
|
284
|
+
),
|
|
285
|
+
});
|
|
212
286
|
}
|
|
287
|
+
return descriptors;
|
|
213
288
|
}
|
|
214
289
|
|
|
215
|
-
|
|
216
|
-
|
|
290
|
+
#reconcileChildren(descriptors: AssistantChildDescriptor[]): void {
|
|
291
|
+
const nextChildren = descriptors.map(descriptor => descriptor.component);
|
|
292
|
+
if (
|
|
293
|
+
nextChildren.length === this.#contentContainer.children.length &&
|
|
294
|
+
nextChildren.every((child, index) => child === this.#contentContainer.children[index])
|
|
295
|
+
) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
217
298
|
|
|
218
|
-
|
|
219
|
-
this.#contentContainer.
|
|
299
|
+
const nextSet = new Set(nextChildren);
|
|
300
|
+
for (const child of this.#contentContainer.children) {
|
|
301
|
+
if (!nextSet.has(child) && !this.#reusableChildren.has(child)) {
|
|
302
|
+
child.dispose?.();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
this.#contentContainer.children = nextChildren;
|
|
306
|
+
}
|
|
220
307
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
308
|
+
updateContent(message: AssistantMessage, options?: AssistantMessageUpdateOptions): void {
|
|
309
|
+
this.#lastMessage = message;
|
|
310
|
+
this.#lastStreaming = options?.streaming ?? false;
|
|
224
311
|
|
|
312
|
+
const visibleContentAfter = new Array<boolean>(message.content.length).fill(false);
|
|
313
|
+
let hasVisibleContentAfter = false;
|
|
314
|
+
let hasVisibleContent = false;
|
|
315
|
+
let activeContentIndex = -1;
|
|
316
|
+
for (let i = message.content.length - 1; i >= 0; i--) {
|
|
317
|
+
visibleContentAfter[i] = hasVisibleContentAfter;
|
|
318
|
+
const content = message.content[i];
|
|
319
|
+
const isVisible =
|
|
320
|
+
(content.type === "text" && Boolean(content.text.trim())) ||
|
|
321
|
+
(content.type === "thinking" && Boolean(content.thinking.trim()));
|
|
322
|
+
if (isVisible) {
|
|
323
|
+
hasVisibleContent = true;
|
|
324
|
+
hasVisibleContentAfter = true;
|
|
325
|
+
if (this.#lastStreaming && activeContentIndex === -1) {
|
|
326
|
+
activeContentIndex = i;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const streaming = this.#lastStreaming;
|
|
332
|
+
const descriptors: AssistantChildDescriptor[] = [];
|
|
225
333
|
if (hasVisibleContent) {
|
|
226
|
-
|
|
227
|
-
|
|
334
|
+
descriptors.push({
|
|
335
|
+
key: "response-header:spacer",
|
|
336
|
+
component: this.#cachedChild("response-header:spacer", () => new Spacer(1)),
|
|
337
|
+
});
|
|
338
|
+
descriptors.push({ key: "response-header", component: this.#responseHeader });
|
|
228
339
|
}
|
|
229
340
|
|
|
230
341
|
// Render content in order
|
|
231
342
|
for (let i = 0; i < message.content.length; i++) {
|
|
232
343
|
const content = message.content[i];
|
|
344
|
+
const blockKey = this.#contentBlockKey(content);
|
|
233
345
|
if (content.type === "text" && content.text.trim()) {
|
|
234
|
-
|
|
346
|
+
descriptors.push({
|
|
347
|
+
key: `${blockKey}:text`,
|
|
348
|
+
component: this.#renderTextBlock(content, streaming && i === activeContentIndex),
|
|
349
|
+
});
|
|
235
350
|
} else if (content.type === "thinking" && content.thinking.trim()) {
|
|
236
351
|
// Add spacing only when another visible assistant content block follows.
|
|
237
352
|
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
|
|
238
|
-
const hasVisibleContentAfter = message.content
|
|
239
|
-
.slice(i + 1)
|
|
240
|
-
.some(c => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
|
|
241
|
-
|
|
242
353
|
if (this.hideThinkingBlock) {
|
|
243
354
|
// Show static "Thinking..." label when hidden
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.#
|
|
247
|
-
|
|
355
|
+
descriptors.push({
|
|
356
|
+
key: `${blockKey}:thinking-hidden`,
|
|
357
|
+
component: this.#cachedChild(
|
|
358
|
+
`${blockKey}:thinking-hidden`,
|
|
359
|
+
() => new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0),
|
|
360
|
+
),
|
|
361
|
+
});
|
|
248
362
|
} else {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.#
|
|
252
|
-
}
|
|
363
|
+
descriptors.push({
|
|
364
|
+
key: `${blockKey}:thinking`,
|
|
365
|
+
component: this.#renderThinkingBlock(content, streaming && i === activeContentIndex),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (visibleContentAfter[i]) {
|
|
369
|
+
descriptors.push({
|
|
370
|
+
key: `${blockKey}:thinking-spacer`,
|
|
371
|
+
component: this.#cachedChild(`${blockKey}:thinking-spacer`, () => new Spacer(1)),
|
|
372
|
+
});
|
|
253
373
|
}
|
|
254
374
|
}
|
|
255
375
|
}
|
|
256
376
|
|
|
257
|
-
this.#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
377
|
+
descriptors.push(...this.#toolImageDescriptors());
|
|
378
|
+
|
|
379
|
+
const isTerminal = !streaming || Boolean(message.stopReason);
|
|
380
|
+
if (isTerminal) {
|
|
381
|
+
// Check if aborted - show after partial content
|
|
382
|
+
// But only if there are no tool calls (tool execution components will show the error)
|
|
383
|
+
const hasToolCalls = message.content.some(c => c.type === "toolCall");
|
|
384
|
+
if (!hasToolCalls) {
|
|
385
|
+
if (message.stopReason === "aborted" && !isSilentAbort(message.errorMessage)) {
|
|
386
|
+
const abortMessage =
|
|
387
|
+
message.errorMessage && message.errorMessage !== "Request was aborted"
|
|
388
|
+
? message.errorMessage
|
|
389
|
+
: "Operation aborted";
|
|
390
|
+
descriptors.push({
|
|
391
|
+
key: "abort:spacer",
|
|
392
|
+
component: this.#cachedChild("abort:spacer", () => new Spacer(1)),
|
|
393
|
+
});
|
|
394
|
+
descriptors.push({
|
|
395
|
+
key: `abort:text:${abortMessage}`,
|
|
396
|
+
component: this.#cachedChild(
|
|
397
|
+
`abort:text:${abortMessage}`,
|
|
398
|
+
() => new Text(theme.fg("error", abortMessage), 1, 0),
|
|
399
|
+
),
|
|
400
|
+
});
|
|
401
|
+
} else if (message.stopReason === "error") {
|
|
402
|
+
const errorMsg = message.errorMessage || "Unknown error";
|
|
403
|
+
descriptors.push({
|
|
404
|
+
key: "error:spacer",
|
|
405
|
+
component: this.#cachedChild("error:spacer", () => new Spacer(1)),
|
|
406
|
+
});
|
|
407
|
+
descriptors.push({
|
|
408
|
+
key: `error:text:${errorMsg}`,
|
|
409
|
+
component: this.#cachedChild(
|
|
410
|
+
`error:text:${errorMsg}`,
|
|
411
|
+
() => new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0),
|
|
412
|
+
),
|
|
413
|
+
});
|
|
271
414
|
}
|
|
272
|
-
this.#contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
|
273
|
-
} else if (message.stopReason === "error") {
|
|
274
|
-
const errorMsg = message.errorMessage || "Unknown error";
|
|
275
|
-
this.#contentContainer.addChild(new Spacer(1));
|
|
276
|
-
this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
277
415
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
416
|
+
if (
|
|
417
|
+
message.errorMessage &&
|
|
418
|
+
!isSilentAbort(message.errorMessage) &&
|
|
419
|
+
message.stopReason !== "aborted" &&
|
|
420
|
+
message.stopReason !== "error"
|
|
421
|
+
) {
|
|
422
|
+
descriptors.push({
|
|
423
|
+
key: "message-error:spacer",
|
|
424
|
+
component: this.#cachedChild("message-error:spacer", () => new Spacer(1)),
|
|
425
|
+
});
|
|
426
|
+
descriptors.push({
|
|
427
|
+
key: `message-error:text:${message.errorMessage}`,
|
|
428
|
+
component: this.#cachedChild(
|
|
429
|
+
`message-error:text:${message.errorMessage}`,
|
|
430
|
+
() => new Text(theme.fg("error", `Error: ${message.errorMessage}`), 1, 0),
|
|
431
|
+
),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
288
434
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
435
|
+
// Token usage metadata
|
|
436
|
+
if (settings.get("display.showTokenUsage") && this.#usageInfo) {
|
|
437
|
+
const usage = this.#usageInfo;
|
|
438
|
+
const totalInput = usage.input + usage.cacheWrite;
|
|
439
|
+
const parts: string[] = [];
|
|
440
|
+
parts.push(`${theme.icon.input} ${formatNumber(totalInput)}`);
|
|
441
|
+
parts.push(`${theme.icon.output} ${formatNumber(usage.output)}`);
|
|
442
|
+
if (usage.cacheRead > 0) {
|
|
443
|
+
parts.push(`cache: ${formatNumber(usage.cacheRead)}`);
|
|
444
|
+
}
|
|
445
|
+
const usageText = parts.join(" ");
|
|
446
|
+
descriptors.push({
|
|
447
|
+
key: "usage:spacer",
|
|
448
|
+
component: this.#cachedChild("usage:spacer", () => new Spacer(1)),
|
|
449
|
+
});
|
|
450
|
+
descriptors.push({
|
|
451
|
+
key: `usage:text:${usageText}`,
|
|
452
|
+
component: this.#cachedChild(
|
|
453
|
+
`usage:text:${usageText}`,
|
|
454
|
+
() => new Text(theme.fg("dim", usageText), 1, 0),
|
|
455
|
+
),
|
|
456
|
+
});
|
|
298
457
|
}
|
|
299
|
-
this.#contentContainer.addChild(new Spacer(1));
|
|
300
|
-
this.#contentContainer.addChild(new Text(theme.fg("dim", parts.join(" ")), 1, 0));
|
|
301
458
|
}
|
|
459
|
+
|
|
460
|
+
this.#reconcileChildren(descriptors);
|
|
302
461
|
}
|
|
303
462
|
}
|
|
@@ -96,6 +96,7 @@ export type ModelSelectorSelection =
|
|
|
96
96
|
kind: "assignment";
|
|
97
97
|
model: Model;
|
|
98
98
|
role: GjcModelAssignmentTargetId | null;
|
|
99
|
+
roles?: readonly GjcModelAssignmentTargetId[];
|
|
99
100
|
thinkingLevel?: ThinkingLevel;
|
|
100
101
|
selector?: string;
|
|
101
102
|
}
|
|
@@ -1360,7 +1361,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1360
1361
|
for (let i = 0; i < actionCount; i++) {
|
|
1361
1362
|
const prefix = i === this.#selectedActionIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
1362
1363
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[i];
|
|
1363
|
-
const label =
|
|
1364
|
+
const label = role
|
|
1365
|
+
? `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name})`
|
|
1366
|
+
: i === GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1367
|
+
? "Set for all role agents"
|
|
1368
|
+
: "Set for all targets";
|
|
1364
1369
|
this.#listContainer.addChild(
|
|
1365
1370
|
new Text(`${prefix}${i === this.#selectedActionIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
1366
1371
|
);
|
|
@@ -1389,7 +1394,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1389
1394
|
return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
|
|
1390
1395
|
}
|
|
1391
1396
|
#getActionCount(_model: Model): number {
|
|
1392
|
-
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length;
|
|
1397
|
+
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length + 2;
|
|
1393
1398
|
}
|
|
1394
1399
|
|
|
1395
1400
|
#getSelectedItem(): ModelItem | CanonicalModelItem | undefined {
|
|
@@ -1660,7 +1665,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
1660
1665
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
1661
1666
|
this.#pendingActionItem = undefined;
|
|
1662
1667
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[this.#selectedActionIndex];
|
|
1663
|
-
if (role)
|
|
1668
|
+
if (role) {
|
|
1669
|
+
this.#handleSelect(item, role);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
const roles =
|
|
1673
|
+
this.#selectedActionIndex === GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1674
|
+
? (["executor", "architect", "planner", "critic"] as const)
|
|
1675
|
+
: GJC_MODEL_ASSIGNMENT_TARGET_IDS;
|
|
1676
|
+
this.#handleSelect(item, "default", undefined, roles);
|
|
1664
1677
|
return;
|
|
1665
1678
|
}
|
|
1666
1679
|
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
@@ -1699,17 +1712,34 @@ export class ModelSelectorComponent extends Container {
|
|
|
1699
1712
|
this.#updateList();
|
|
1700
1713
|
}
|
|
1701
1714
|
}
|
|
1715
|
+
#getInitialThinkingChoiceIndex(item: ModelItem | CanonicalModelItem, levels: ThinkingLevel[]): number {
|
|
1716
|
+
const preferred = this.#getPreferredThinkingLevel(item);
|
|
1717
|
+
if (preferred && preferred !== ThinkingLevel.Inherit) {
|
|
1718
|
+
const index = levels.indexOf(preferred);
|
|
1719
|
+
if (index !== -1) return index;
|
|
1720
|
+
}
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
#getPreferredThinkingLevel(item: ModelItem | CanonicalModelItem): ThinkingLevel | undefined {
|
|
1725
|
+
if (item.thinkingLevel && item.thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1726
|
+
return item.thinkingLevel;
|
|
1727
|
+
}
|
|
1728
|
+
return undefined;
|
|
1729
|
+
}
|
|
1702
1730
|
|
|
1703
1731
|
#handleSelect(
|
|
1704
1732
|
item: ModelItem | CanonicalModelItem,
|
|
1705
1733
|
role: GjcModelAssignmentTargetId | null,
|
|
1706
1734
|
thinkingLevel?: ThinkingLevel,
|
|
1735
|
+
roles?: readonly GjcModelAssignmentTargetId[],
|
|
1707
1736
|
): void {
|
|
1708
1737
|
const itemThinkingLevel = thinkingLevel ?? item.thinkingLevel;
|
|
1709
1738
|
const hasExplicitThinkingChoice = thinkingLevel !== undefined || item.explicitThinkingLevel === true;
|
|
1710
|
-
if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model)) {
|
|
1711
|
-
|
|
1712
|
-
this.#
|
|
1739
|
+
if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model, role)) {
|
|
1740
|
+
const levels = getSelectableThinkingLevels(item.model);
|
|
1741
|
+
this.#pendingThinkingChoice = { item, role, levels };
|
|
1742
|
+
this.#selectedThinkingIndex = this.#getInitialThinkingChoiceIndex(item, levels);
|
|
1713
1743
|
this.#updateList();
|
|
1714
1744
|
return;
|
|
1715
1745
|
}
|
|
@@ -1727,17 +1757,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
1727
1757
|
}
|
|
1728
1758
|
|
|
1729
1759
|
const selectedThinkingLevel = itemThinkingLevel ?? this.#getCurrentRoleThinkingLevel(role);
|
|
1730
|
-
const selectorValue =
|
|
1731
|
-
|
|
1760
|
+
const selectorValue = roles
|
|
1761
|
+
? formatModelSelectorValue(item.selector, selectedThinkingLevel)
|
|
1762
|
+
: role === "default"
|
|
1763
|
+
? item.selector
|
|
1764
|
+
: formatModelSelectorValue(item.selector, selectedThinkingLevel);
|
|
1732
1765
|
|
|
1733
1766
|
// Update local state for UI
|
|
1734
|
-
|
|
1767
|
+
for (const targetRole of roles ?? [role]) {
|
|
1768
|
+
this.#roles[targetRole] = { model: item.model, thinkingLevel: selectedThinkingLevel };
|
|
1769
|
+
}
|
|
1735
1770
|
|
|
1736
1771
|
// Notify caller (for updating agent state if needed)
|
|
1737
1772
|
this.#onSelectCallback({
|
|
1738
1773
|
kind: "assignment",
|
|
1739
1774
|
model: item.model,
|
|
1740
1775
|
role,
|
|
1776
|
+
roles,
|
|
1741
1777
|
thinkingLevel: selectedThinkingLevel,
|
|
1742
1778
|
selector: selectorValue,
|
|
1743
1779
|
});
|
|
@@ -1752,6 +1788,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1752
1788
|
async __testSelectProfile(profileName: string, setDefault: boolean): Promise<void> {
|
|
1753
1789
|
await this.#onSelectCallback({ kind: "profile", profileName, setDefault });
|
|
1754
1790
|
}
|
|
1791
|
+
async __testSelectAssignment(
|
|
1792
|
+
selection: Omit<Extract<ModelSelectorSelection, { kind: "assignment" }>, "kind">,
|
|
1793
|
+
): Promise<void> {
|
|
1794
|
+
await this.#onSelectCallback({ kind: "assignment", ...selection });
|
|
1795
|
+
}
|
|
1755
1796
|
async __testSelectPresetAction(profileName: string, action: "rename" | "delete"): Promise<void> {
|
|
1756
1797
|
await this.#onSelectCallback({
|
|
1757
1798
|
kind: action === "rename" ? "renameProfile" : "deleteProfile",
|
|
@@ -1764,8 +1805,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1764
1805
|
}
|
|
1765
1806
|
}
|
|
1766
1807
|
|
|
1767
|
-
function requiresExplicitThinkingChoice(model: Model): boolean {
|
|
1768
|
-
|
|
1808
|
+
function requiresExplicitThinkingChoice(model: Model, role: GjcModelAssignmentTargetId | null): boolean {
|
|
1809
|
+
if (model.reasoning !== true) return false;
|
|
1810
|
+
if (model.provider === "openai" || model.provider === "openai-codex") return true;
|
|
1811
|
+
return role !== null && GJC_MODEL_ASSIGNMENT_TARGETS[role].settingsPath === "task.agentModelOverrides";
|
|
1769
1812
|
}
|
|
1770
1813
|
|
|
1771
1814
|
function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Container, matchesKey, type SelectItem, SelectList, Spacer, Text } from "@gajae-code/tui";
|
|
2
|
+
import type { QueuedMessageEditEntry } from "../../session/agent-session";
|
|
3
|
+
import { getSelectListTheme, theme } from "../theme/theme";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
|
+
|
|
6
|
+
const MAX_VISIBLE_QUEUED_MESSAGES = 8;
|
|
7
|
+
const RAW_UP = "\x1b[A";
|
|
8
|
+
const RAW_DOWN = "\x1b[B";
|
|
9
|
+
type QueuedMessageMoveDirection = "up" | "down";
|
|
10
|
+
|
|
11
|
+
export type { QueuedMessageMoveDirection };
|
|
12
|
+
|
|
13
|
+
export class QueuedMessageSelectorComponent extends Container {
|
|
14
|
+
#selectList: SelectList;
|
|
15
|
+
#selectedEntry: QueuedMessageEditEntry | undefined;
|
|
16
|
+
#selectedIndex = 0;
|
|
17
|
+
#onDelete: (entry: QueuedMessageEditEntry, selectedIndex: number) => void;
|
|
18
|
+
#onMove: (entry: QueuedMessageEditEntry, selectedIndex: number, direction: QueuedMessageMoveDirection) => void;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
entries: QueuedMessageEditEntry[],
|
|
22
|
+
onSelect: (entry: QueuedMessageEditEntry) => void,
|
|
23
|
+
onDelete: (entry: QueuedMessageEditEntry, selectedIndex: number) => void,
|
|
24
|
+
onMove: (entry: QueuedMessageEditEntry, selectedIndex: number, direction: QueuedMessageMoveDirection) => void,
|
|
25
|
+
onCancel: () => void,
|
|
26
|
+
options?: { selectedIndex?: number },
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
this.#onDelete = onDelete;
|
|
31
|
+
this.#onMove = onMove;
|
|
32
|
+
const byId = new Map(entries.map(entry => [entry.id, entry]));
|
|
33
|
+
this.#selectedIndex = Math.max(0, Math.min(options?.selectedIndex ?? 0, entries.length - 1));
|
|
34
|
+
this.#selectedEntry = entries[this.#selectedIndex];
|
|
35
|
+
const items: SelectItem[] = entries.map((entry, index) => ({
|
|
36
|
+
value: entry.id,
|
|
37
|
+
label: `${entry.label} ${index + 1}`,
|
|
38
|
+
description: entry.text,
|
|
39
|
+
hint: "Enter edit · Del remove · Ctrl+↑/↓ move",
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
this.addChild(new Spacer(1));
|
|
43
|
+
this.addChild(new Text(theme.bold("Queued messages"), 1, 0));
|
|
44
|
+
this.addChild(new Text(theme.fg("muted", "Enter edit · Del remove · Ctrl+↑/↓ move · Esc cancel"), 1, 0));
|
|
45
|
+
this.addChild(new Spacer(1));
|
|
46
|
+
this.addChild(new DynamicBorder());
|
|
47
|
+
|
|
48
|
+
this.#selectList = new SelectList(items, MAX_VISIBLE_QUEUED_MESSAGES, getSelectListTheme());
|
|
49
|
+
this.#selectList.onSelect = item => {
|
|
50
|
+
const entry = byId.get(item.value);
|
|
51
|
+
if (entry) onSelect(entry);
|
|
52
|
+
};
|
|
53
|
+
this.#selectList.onSelectionChange = item => {
|
|
54
|
+
const index = entries.findIndex(entry => entry.id === item.value);
|
|
55
|
+
this.#selectedIndex = index === -1 ? this.#selectedIndex : index;
|
|
56
|
+
this.#selectedEntry = byId.get(item.value);
|
|
57
|
+
};
|
|
58
|
+
this.#selectList.setSelectedIndex(this.#selectedIndex);
|
|
59
|
+
this.#selectList.onCancel = onCancel;
|
|
60
|
+
|
|
61
|
+
this.addChild(this.#selectList);
|
|
62
|
+
this.addChild(new DynamicBorder());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
handleInput(keyData: string): void {
|
|
66
|
+
if (matchesKey(keyData, "alt+up")) {
|
|
67
|
+
this.#selectList.handleInput(RAW_UP);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (matchesKey(keyData, "alt+down")) {
|
|
71
|
+
this.#selectList.handleInput(RAW_DOWN);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (matchesKey(keyData, "ctrl+up") || matchesKey(keyData, "ctrl+shift+up")) {
|
|
75
|
+
if (this.#selectedEntry) this.#onMove(this.#selectedEntry, this.#selectedIndex, "up");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (matchesKey(keyData, "ctrl+down") || matchesKey(keyData, "ctrl+shift+down")) {
|
|
79
|
+
if (this.#selectedEntry) this.#onMove(this.#selectedEntry, this.#selectedIndex, "down");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (matchesKey(keyData, "delete")) {
|
|
83
|
+
if (this.#selectedEntry) this.#onDelete(this.#selectedEntry, this.#selectedIndex);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.#selectList.handleInput(keyData);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getSelectList(): SelectList {
|
|
90
|
+
return this.#selectList;
|
|
91
|
+
}
|
|
92
|
+
}
|