@gajae-code/coding-agent 0.8.0 → 0.8.2
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 +48 -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/notify-cli.d.ts +7 -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.d.ts +8 -0
- 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 +12 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/main.d.ts +4 -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/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/notifications/config.d.ts +8 -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 +18 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/skill.d.ts +1 -1
- 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/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +37 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/notify-cli.ts +91 -7
- package/src/cli.ts +3 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- 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.ts +22 -0
- package/src/coordinator-mcp/server.ts +67 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +38 -5
- package/src/defaults/gjc/skills/ralplan/SKILL.md +15 -11
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skills.ts +30 -2
- 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 +13 -0
- package/src/gjc-runtime/session-state-sidecar.ts +59 -11
- package/src/gjc-runtime/state-runtime.ts +12 -2
- package/src/gjc-runtime/team-runtime.ts +23 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +73 -19
- package/src/hooks/native-skill-hook.ts +29 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +52 -41
- package/src/modes/acp/acp-agent.ts +7 -6
- 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/welcome.ts +160 -44
- package/src/modes/controllers/command-controller.ts +4 -4
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +186 -25
- package/src/modes/controllers/selector-controller.ts +122 -27
- package/src/modes/interactive-mode.ts +43 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +24 -13
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/notifications/config.ts +9 -1
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +39 -9
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +3 -1
- package/src/notifications/telegram-daemon.ts +231 -43
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/computer.md +2 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +20 -6
- package/src/session/agent-session.ts +225 -31
- package/src/session/agent-storage.ts +4 -0
- package/src/session/session-manager.ts +44 -2
- package/src/slash-commands/builtin-registry.ts +153 -71
- package/src/task/executor.ts +7 -4
- package/src/task/index.ts +9 -3
- package/src/task/output-manager.ts +16 -6
- package/src/task/receipt.ts +26 -7
- package/src/tools/computer.ts +246 -43
- package/src/tools/skill.ts +1 -1
- package/src/tools/telegram-send.ts +6 -0
- 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
|
@@ -21,6 +21,25 @@ export interface WelcomeComponentOptions {
|
|
|
21
21
|
collapseChangelog?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS = 9;
|
|
25
|
+
const DEFAULT_WHATS_NEW_ROWS = 3;
|
|
26
|
+
const MAX_WHATS_NEW_ROWS = 12;
|
|
27
|
+
const NEWLINE_FLOW_KEY =
|
|
28
|
+
process.platform === "win32" ? { key: "alt+enter", label: "newline" } : { key: "ctrl+j", label: "newline" };
|
|
29
|
+
|
|
30
|
+
const FLOW_KEY_ITEMS: ReadonlyArray<{ key: string; label: string }> = [
|
|
31
|
+
{ key: "/", label: "commands" },
|
|
32
|
+
{ key: "#", label: "actions" },
|
|
33
|
+
{ key: "!", label: "shell" },
|
|
34
|
+
{ key: "$", label: "python" },
|
|
35
|
+
{ key: "?", label: "keymap" },
|
|
36
|
+
{ key: "ctrl+l", label: "model" },
|
|
37
|
+
{ key: "shift+tab", label: "reasoning" },
|
|
38
|
+
{ key: "tab", label: "complete" },
|
|
39
|
+
NEWLINE_FLOW_KEY,
|
|
40
|
+
{ key: "ctrl+c", label: "clear" },
|
|
41
|
+
];
|
|
42
|
+
|
|
24
43
|
/**
|
|
25
44
|
* GJC-native launch surface with compact command affordances, project
|
|
26
45
|
* signals, and a claw/talon mark without copying another agent shell.
|
|
@@ -97,7 +116,6 @@ export class WelcomeComponent implements Component {
|
|
|
97
116
|
}
|
|
98
117
|
const targetContentRows = targetRows === undefined ? undefined : Math.max(0, targetRows - 2);
|
|
99
118
|
const dualContentWidth = boxWidth - 3; // 3 = │ + │ + │
|
|
100
|
-
const preferredLeftCol = 40;
|
|
101
119
|
const minLeftCol = 20; // logo mark plus GJC identity labels
|
|
102
120
|
const minRightCol = 24;
|
|
103
121
|
const modelPill = this.#pill(theme.icon.model || "model", this.modelName, "statusLineModel");
|
|
@@ -112,10 +130,12 @@ export class WelcomeComponent implements Component {
|
|
|
112
130
|
visibleWidth(modelPill),
|
|
113
131
|
visibleWidth(providerPill),
|
|
114
132
|
);
|
|
115
|
-
const
|
|
133
|
+
const evenLeftCol = Math.floor(dualContentWidth / 2);
|
|
134
|
+
const maxLeftColWithRightMinimum = Math.max(1, dualContentWidth - minRightCol);
|
|
135
|
+
const desiredLeftCol = Math.max(leftMinContentWidth, evenLeftCol);
|
|
116
136
|
const dualLeftCol =
|
|
117
137
|
dualContentWidth >= minRightCol + 1
|
|
118
|
-
? Math.min(desiredLeftCol,
|
|
138
|
+
? Math.min(desiredLeftCol, maxLeftColWithRightMinimum)
|
|
119
139
|
: Math.max(1, dualContentWidth - 1);
|
|
120
140
|
const dualRightCol = Math.max(1, dualContentWidth - dualLeftCol);
|
|
121
141
|
const showRightColumn = dualLeftCol >= leftMinContentWidth && dualRightCol >= minRightCol;
|
|
@@ -142,26 +162,6 @@ export class WelcomeComponent implements Component {
|
|
|
142
162
|
|
|
143
163
|
const rightColumnWidth = showRightColumn ? rightCol : leftCol;
|
|
144
164
|
const separator = buildSeparator(rightColumnWidth);
|
|
145
|
-
const changelogBudget = Math.max(1, Math.min(6, Math.floor((targetContentRows ?? 18) * 0.3)));
|
|
146
|
-
|
|
147
|
-
const sessionLines: string[] = [];
|
|
148
|
-
if (this.recentSessions.length === 0) {
|
|
149
|
-
sessionLines.push(` ${theme.fg("dim", "No saved trails")}`);
|
|
150
|
-
} else {
|
|
151
|
-
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
152
|
-
const prefixWidth = visibleWidth(bulletPrefix);
|
|
153
|
-
for (const session of this.recentSessions.slice(0, 3)) {
|
|
154
|
-
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
155
|
-
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
156
|
-
const nameBudget = Math.max(1, rightColumnWidth - prefixWidth - timeWidth);
|
|
157
|
-
const nameVis = visibleWidth(session.name);
|
|
158
|
-
const name = nameVis > nameBudget ? truncateToWidth(session.name, nameBudget) : session.name;
|
|
159
|
-
sessionLines.push(
|
|
160
|
-
`${theme.fg("dim", bulletPrefix)}${theme.fg("muted", name)}${theme.fg("dim", timeSuffixRaw)}`,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
165
|
const lspLines: string[] = [];
|
|
166
166
|
if (this.lspServers.length === 0) {
|
|
167
167
|
lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
|
|
@@ -178,25 +178,31 @@ export class WelcomeComponent implements Component {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
const flowPreferredRows = this.#flowKeyRows(rightColumnWidth).length;
|
|
182
|
+
const changelogRowLimit = this.#whatsNewRowLimit(targetContentRows, lspLines.length, flowPreferredRows);
|
|
183
|
+
const changelogLines = this.#whatsNewLines(rightColumnWidth, changelogRowLimit);
|
|
184
|
+
const flowRowLimit = this.#flowKeyRowLimit(
|
|
185
|
+
targetContentRows,
|
|
186
|
+
changelogLines.length,
|
|
187
|
+
lspLines.length,
|
|
188
|
+
rightColumnWidth,
|
|
189
|
+
);
|
|
190
|
+
const flowLines = this.#flowKeyLines(rightColumnWidth, flowRowLimit);
|
|
191
|
+
const sessionLimit = this.#sessionTrailLimit(
|
|
192
|
+
targetContentRows,
|
|
193
|
+
changelogLines.length,
|
|
194
|
+
lspLines.length,
|
|
195
|
+
flowLines.length,
|
|
196
|
+
);
|
|
197
|
+
const sessionLines = this.#sessionTrailLines(rightColumnWidth, sessionLimit);
|
|
198
|
+
|
|
181
199
|
const rightLines = [
|
|
182
200
|
"",
|
|
183
201
|
` ${theme.bold(theme.fg("accent", "What's New"))}`,
|
|
184
|
-
...
|
|
202
|
+
...changelogLines,
|
|
185
203
|
separator,
|
|
186
204
|
` ${theme.bold(theme.fg("accent", "Flow keys"))}`,
|
|
187
|
-
|
|
188
|
-
"dim",
|
|
189
|
-
"#",
|
|
190
|
-
)}${theme.fg("muted", " actions")}`,
|
|
191
|
-
` ${theme.fg("dim", "!")}${theme.fg("muted", " shell")} ${theme.fg("dim", "·")} ${theme.fg("dim", "$")}${theme.fg(
|
|
192
|
-
"muted",
|
|
193
|
-
" python",
|
|
194
|
-
)}`,
|
|
195
|
-
` ${theme.fg("dim", "?")}${theme.fg("muted", " keymap")} ${theme.fg("dim", "·")} ${theme.fg(
|
|
196
|
-
"dim",
|
|
197
|
-
"ctrl+l",
|
|
198
|
-
)}${theme.fg("muted", " model")}`,
|
|
199
|
-
` ${theme.fg("dim", "shift+tab")}${theme.fg("muted", " reasoning")}`,
|
|
205
|
+
...flowLines,
|
|
200
206
|
separator,
|
|
201
207
|
` ${theme.bold(theme.fg("accent", "Project pulse"))}`,
|
|
202
208
|
...lspLines,
|
|
@@ -315,7 +321,118 @@ export class WelcomeComponent implements Component {
|
|
|
315
321
|
return [...Array.from({ length: topPad }, () => ""), ...clipped, ...Array.from({ length: bottomPad }, () => "")];
|
|
316
322
|
}
|
|
317
323
|
|
|
318
|
-
#
|
|
324
|
+
#flowKeyItemText(item: { key: string; label: string }): string {
|
|
325
|
+
return `${theme.fg("dim", item.key)}${theme.fg("muted", ` ${item.label}`)}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#flowKeyRows(width: number): string[] {
|
|
329
|
+
const contentWidth = Math.max(1, width - 1);
|
|
330
|
+
const separator = ` ${theme.fg("dim", "·")} `;
|
|
331
|
+
const rows: string[] = [];
|
|
332
|
+
let current = "";
|
|
333
|
+
for (const item of FLOW_KEY_ITEMS) {
|
|
334
|
+
const segment = this.#flowKeyItemText(item);
|
|
335
|
+
const next = current ? `${current}${separator}${segment}` : segment;
|
|
336
|
+
if (current && visibleWidth(next) > contentWidth) {
|
|
337
|
+
rows.push(` ${current}`);
|
|
338
|
+
current = segment;
|
|
339
|
+
} else {
|
|
340
|
+
current = next;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (current) rows.push(` ${current}`);
|
|
344
|
+
return rows.length > 0 ? rows : [` ${theme.fg("dim", "No flow keys")}`];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#flowKeyLines(width: number, maxRows: number): string[] {
|
|
348
|
+
const rows = this.#flowKeyRows(width);
|
|
349
|
+
const rowLimit = Math.max(1, Math.floor(maxRows));
|
|
350
|
+
if (rows.length <= rowLimit) return rows;
|
|
351
|
+
if (rowLimit === 1) {
|
|
352
|
+
const firstItem = FLOW_KEY_ITEMS[0];
|
|
353
|
+
const firstSegment = firstItem ? this.#flowKeyItemText(firstItem) : theme.fg("dim", "keys");
|
|
354
|
+
return [this.#fitToWidth(` ${firstSegment} ${theme.fg("dim", "· … ")}${theme.bold("/help")}`, width)];
|
|
355
|
+
}
|
|
356
|
+
return [...rows.slice(0, rowLimit - 1), ` ${theme.fg("dim", `… ${theme.bold("/help")} for more`)}`];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#flowKeyRowLimit(
|
|
360
|
+
targetContentRows: number | undefined,
|
|
361
|
+
changelogLineCount: number,
|
|
362
|
+
lspLineCount: number,
|
|
363
|
+
rightColumnWidth: number,
|
|
364
|
+
): number {
|
|
365
|
+
const preferredRows = this.#flowKeyRows(rightColumnWidth).length;
|
|
366
|
+
if (targetContentRows === undefined) return preferredRows;
|
|
367
|
+
|
|
368
|
+
const sessionBaselineRows = this.recentSessions.length === 0 ? 1 : Math.min(3, this.recentSessions.length);
|
|
369
|
+
const availableRows =
|
|
370
|
+
targetContentRows -
|
|
371
|
+
WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS -
|
|
372
|
+
changelogLineCount -
|
|
373
|
+
lspLineCount -
|
|
374
|
+
sessionBaselineRows;
|
|
375
|
+
return Math.max(1, Math.min(preferredRows, availableRows));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#whatsNewRowLimit(targetContentRows: number | undefined, lspLineCount: number, flowLineCount: number): number {
|
|
379
|
+
if (targetContentRows === undefined) return 5;
|
|
380
|
+
|
|
381
|
+
const sessionBaselineRows = this.recentSessions.length === 0 ? 1 : Math.min(3, this.recentSessions.length);
|
|
382
|
+
const dynamicRows = Math.max(
|
|
383
|
+
1,
|
|
384
|
+
targetContentRows - WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS - flowLineCount - lspLineCount,
|
|
385
|
+
);
|
|
386
|
+
const rowsAfterBaselineSessions = Math.max(1, dynamicRows - sessionBaselineRows);
|
|
387
|
+
const spareRows = Math.max(0, rowsAfterBaselineSessions - DEFAULT_WHATS_NEW_ROWS);
|
|
388
|
+
return Math.max(
|
|
389
|
+
1,
|
|
390
|
+
Math.min(MAX_WHATS_NEW_ROWS, rowsAfterBaselineSessions, DEFAULT_WHATS_NEW_ROWS + Math.floor(spareRows / 2)),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#sessionTrailLimit(
|
|
395
|
+
targetContentRows: number | undefined,
|
|
396
|
+
changelogLineCount: number,
|
|
397
|
+
lspLineCount: number,
|
|
398
|
+
flowLineCount: number,
|
|
399
|
+
): number {
|
|
400
|
+
if (this.recentSessions.length === 0) return 0;
|
|
401
|
+
|
|
402
|
+
const defaultLimit = Math.min(3, this.recentSessions.length);
|
|
403
|
+
if (targetContentRows === undefined) return defaultLimit;
|
|
404
|
+
|
|
405
|
+
const rowsWithDefaultTrail =
|
|
406
|
+
WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS +
|
|
407
|
+
changelogLineCount +
|
|
408
|
+
flowLineCount +
|
|
409
|
+
lspLineCount +
|
|
410
|
+
defaultLimit;
|
|
411
|
+
const extraRows = Math.max(0, targetContentRows - rowsWithDefaultTrail);
|
|
412
|
+
return Math.min(this.recentSessions.length, defaultLimit + extraRows);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#sessionTrailLines(rightColumnWidth: number, limit: number): string[] {
|
|
416
|
+
if (this.recentSessions.length === 0) {
|
|
417
|
+
return [` ${theme.fg("dim", "No saved trails")}`];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
421
|
+
const prefixWidth = visibleWidth(bulletPrefix);
|
|
422
|
+
const lines: string[] = [];
|
|
423
|
+
for (const session of this.recentSessions.slice(0, limit)) {
|
|
424
|
+
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
425
|
+
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
426
|
+
const nameBudget = Math.max(1, rightColumnWidth - prefixWidth - timeWidth);
|
|
427
|
+
const nameVis = visibleWidth(session.name);
|
|
428
|
+
const name = nameVis > nameBudget ? truncateToWidth(session.name, nameBudget) : session.name;
|
|
429
|
+
lines.push(`${theme.fg("dim", bulletPrefix)}${theme.fg("muted", name)}${theme.fg("dim", timeSuffixRaw)}`);
|
|
430
|
+
}
|
|
431
|
+
return lines;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
#whatsNewLines(width: number, maxRows: number): string[] {
|
|
435
|
+
const rowLimit = Math.max(1, Math.floor(maxRows));
|
|
319
436
|
const changelog = this.options.changelogMarkdown?.trim();
|
|
320
437
|
if (!changelog) {
|
|
321
438
|
return [` ${theme.fg("dim", "Ready for your next prompt")}`];
|
|
@@ -326,25 +443,25 @@ export class WelcomeComponent implements Component {
|
|
|
326
443
|
return [
|
|
327
444
|
` ${theme.fg("muted", `Updated to v${version}`)}`,
|
|
328
445
|
` ${theme.fg("dim", `Use ${theme.bold("/changelog")} for details`)}`,
|
|
329
|
-
];
|
|
446
|
+
].slice(0, rowLimit);
|
|
330
447
|
}
|
|
331
448
|
|
|
332
|
-
const itemLimit = Math.max(1, Math.floor(maxItems));
|
|
333
449
|
const items = this.#changelogItems(changelog);
|
|
334
450
|
if (items.length === 0) {
|
|
335
451
|
return [
|
|
336
452
|
` ${theme.fg("muted", `Updated to v${version}`)}`,
|
|
337
453
|
` ${theme.fg("dim", `Use ${theme.bold("/changelog")} for details`)}`,
|
|
338
|
-
];
|
|
454
|
+
].slice(0, rowLimit);
|
|
339
455
|
}
|
|
340
456
|
|
|
341
457
|
const prefix = ` ${theme.md.bullet} `;
|
|
342
458
|
const textWidth = Math.max(1, width - visibleWidth(prefix));
|
|
343
|
-
const
|
|
459
|
+
const visibleItemCount = items.length > rowLimit ? Math.max(1, rowLimit - 1) : rowLimit;
|
|
460
|
+
const visibleItems = items.slice(0, visibleItemCount).map(item => {
|
|
344
461
|
const text = visibleWidth(item) > textWidth ? truncateToWidth(item, textWidth) : item;
|
|
345
462
|
return `${theme.fg("dim", prefix)}${theme.fg("muted", text)}`;
|
|
346
463
|
});
|
|
347
|
-
if (items.length > visibleItems.length) {
|
|
464
|
+
if (items.length > visibleItems.length && visibleItems.length < rowLimit) {
|
|
348
465
|
visibleItems.push(` ${theme.fg("dim", `… ${theme.bold("/changelog")} for full notes`)}`);
|
|
349
466
|
}
|
|
350
467
|
return visibleItems;
|
|
@@ -472,7 +589,6 @@ interface ShineConfig {
|
|
|
472
589
|
/** Center of the shine band along the diagonal, in [0, 1]. */
|
|
473
590
|
pos: number;
|
|
474
591
|
}
|
|
475
|
-
|
|
476
592
|
/**
|
|
477
593
|
* Apply a multi-stop diagonal gradient (bottom-left → top-right) plus an
|
|
478
594
|
* optional sliding shine band across multi-line art. `phase` (0..1) shifts the
|
|
@@ -926,8 +926,7 @@ export class CommandController {
|
|
|
926
926
|
this.ctx.streamingMessage = undefined;
|
|
927
927
|
this.ctx.pendingTools.clear();
|
|
928
928
|
|
|
929
|
-
this.ctx.chatContainer.addChild(new
|
|
930
|
-
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
|
|
929
|
+
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 0));
|
|
931
930
|
await this.ctx.reloadTodos();
|
|
932
931
|
this.ctx.ui.requestRender();
|
|
933
932
|
}
|
|
@@ -1276,17 +1275,18 @@ export class CommandController {
|
|
|
1276
1275
|
async handleContributionPrepCommand(customInstructions?: string): Promise<void> {
|
|
1277
1276
|
this.ctx.editor.setText("");
|
|
1278
1277
|
try {
|
|
1279
|
-
const result = await this.ctx.session.prepareContributionPrep({ customInstructions, spawnWorker:
|
|
1278
|
+
const result = await this.ctx.session.prepareContributionPrep({ customInstructions, spawnWorker: false });
|
|
1280
1279
|
this.ctx.showStatus(
|
|
1281
1280
|
[
|
|
1282
1281
|
"Contribution prep artifacts written.",
|
|
1283
1282
|
`Manifest: ${result.manifestPath}`,
|
|
1284
1283
|
`Worker prompt: ${result.workerPromptPath}`,
|
|
1284
|
+
"Open the worker prompt from a separate terminal to keep this TUI session stable.",
|
|
1285
1285
|
].join("\n"),
|
|
1286
1286
|
);
|
|
1287
1287
|
this.ctx.chatContainer.addChild(
|
|
1288
1288
|
new Text(
|
|
1289
|
-
`${theme.fg("accent", `${theme.status.success} Contribution prep ready`)}\
|
|
1289
|
+
`${theme.fg("accent", `${theme.status.success} Contribution prep ready`)}\nWorker prompt: ${result.workerPromptPath}`,
|
|
1290
1290
|
1,
|
|
1291
1291
|
1,
|
|
1292
1292
|
),
|
|
@@ -191,9 +191,8 @@ export class ExtensionUiController {
|
|
|
191
191
|
this.ctx.streamingMessage = undefined;
|
|
192
192
|
this.ctx.pendingTools.clear();
|
|
193
193
|
|
|
194
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
195
194
|
this.ctx.chatContainer.addChild(
|
|
196
|
-
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1,
|
|
195
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
197
196
|
);
|
|
198
197
|
await this.ctx.reloadTodos();
|
|
199
198
|
this.ctx.ui.requestRender();
|
|
@@ -299,15 +298,14 @@ export class ExtensionUiController {
|
|
|
299
298
|
}
|
|
300
299
|
|
|
301
300
|
#rebuildHookWidgets(): void {
|
|
302
|
-
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true
|
|
303
|
-
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false
|
|
301
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true);
|
|
302
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false);
|
|
304
303
|
this.ctx.ui.requestRender();
|
|
305
304
|
}
|
|
306
305
|
|
|
307
306
|
#renderHookWidgetContainer(
|
|
308
307
|
container: Container,
|
|
309
308
|
widgets: Map<string, ExtensionUiComponent>,
|
|
310
|
-
spacerWhenEmpty: boolean,
|
|
311
309
|
leadingSpacer: boolean,
|
|
312
310
|
): void {
|
|
313
311
|
// Detach (not dispose): hook widgets are persistent instances owned by the
|
|
@@ -317,9 +315,6 @@ export class ExtensionUiController {
|
|
|
317
315
|
container.detachAll();
|
|
318
316
|
|
|
319
317
|
if (widgets.size === 0) {
|
|
320
|
-
if (spacerWhenEmpty) {
|
|
321
|
-
container.addChild(new Spacer(1));
|
|
322
|
-
}
|
|
323
318
|
return;
|
|
324
319
|
}
|
|
325
320
|
|
|
@@ -434,9 +429,8 @@ export class ExtensionUiController {
|
|
|
434
429
|
this.ctx.streamingMessage = undefined;
|
|
435
430
|
this.ctx.pendingTools.clear();
|
|
436
431
|
|
|
437
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
438
432
|
this.ctx.chatContainer.addChild(
|
|
439
|
-
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1,
|
|
433
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
440
434
|
);
|
|
441
435
|
await this.ctx.reloadTodos();
|
|
442
436
|
this.ctx.ui.requestRender();
|
|
@@ -11,7 +11,7 @@ import { createPromptActionAutocompleteProvider } from "../../modes/prompt-actio
|
|
|
11
11
|
import { theme } from "../../modes/theme/theme";
|
|
12
12
|
import { scrollTmuxToPreviousUserInput as scrollTmuxPaneToPreviousUserInput } from "../../modes/tmux-scroll";
|
|
13
13
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
14
|
-
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
14
|
+
import type { AgentSessionEvent, QueuedMessageEditEntry } from "../../session/agent-session";
|
|
15
15
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
16
16
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
17
17
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
@@ -19,6 +19,7 @@ import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
|
19
19
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
20
20
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
21
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
22
|
+
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
22
23
|
|
|
23
24
|
interface Expandable {
|
|
24
25
|
setExpanded(expanded: boolean): void;
|
|
@@ -313,14 +314,7 @@ export class InputController {
|
|
|
313
314
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
314
315
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
315
316
|
this.ctx.editor.onQueue = () => void this.handleQueueSubmit();
|
|
316
|
-
|
|
317
|
-
if (!this.ctx.session.isStreaming && !this.ctx.session.isCompacting) return false;
|
|
318
|
-
void this.handleQueueSubmit();
|
|
319
|
-
return true;
|
|
320
|
-
};
|
|
321
|
-
this.ctx.editor.onTabDeclined = () => {
|
|
322
|
-
if (this.ctx.session.isStreaming || this.ctx.session.isCompacting) void this.handleQueueSubmit();
|
|
323
|
-
};
|
|
317
|
+
|
|
324
318
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
325
319
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
326
320
|
this.ctx.ui.followLiveViewport();
|
|
@@ -614,12 +608,182 @@ export class InputController {
|
|
|
614
608
|
}
|
|
615
609
|
|
|
616
610
|
handleDequeue(): void {
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
611
|
+
const entries = this.#getEditableQueuedMessages();
|
|
612
|
+
if (entries.length === 0) {
|
|
613
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
619
614
|
this.ctx.showStatus("No queued messages to restore");
|
|
620
|
-
|
|
621
|
-
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (entries.length === 1) {
|
|
618
|
+
const restored = this.#restoreQueuedMessageToEditor(entries[0]);
|
|
619
|
+
this.ctx.showStatus(
|
|
620
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
621
|
+
);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
this.#showQueuedMessageSelector(entries, this.#newestQueuedMessageIndex(entries));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#compactionQueuedMessageId(index: number): string {
|
|
628
|
+
return `compaction:${index}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
#getEditableQueuedMessages(): QueuedMessageEditEntry[] {
|
|
632
|
+
const compactionEntries = this.ctx.compactionQueuedMessages.map((entry, index): QueuedMessageEditEntry => {
|
|
633
|
+
const label = entry.mode === "steer" ? "Steer" : "Queued";
|
|
634
|
+
return {
|
|
635
|
+
id: this.#compactionQueuedMessageId(index),
|
|
636
|
+
text: entry.text,
|
|
637
|
+
mode: entry.mode,
|
|
638
|
+
label,
|
|
639
|
+
};
|
|
640
|
+
});
|
|
641
|
+
return [...compactionEntries, ...this.ctx.session.getQueuedMessageEntries()];
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#queuedMessageStableSequence(entry: QueuedMessageEditEntry): number | undefined {
|
|
645
|
+
const [mode, sequenceText] = entry.id.split(":");
|
|
646
|
+
if ((mode !== "steer" && mode !== "followUp") || sequenceText === undefined) return undefined;
|
|
647
|
+
const sequence = Number(sequenceText);
|
|
648
|
+
return Number.isInteger(sequence) ? sequence : undefined;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#newestQueuedMessageIndex(entries: QueuedMessageEditEntry[]): number {
|
|
652
|
+
let selectedIndex = entries.length - 1;
|
|
653
|
+
let newestSequence = Number.NEGATIVE_INFINITY;
|
|
654
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
655
|
+
const sequence = this.#queuedMessageStableSequence(entries[index]);
|
|
656
|
+
if (sequence !== undefined && sequence > newestSequence) {
|
|
657
|
+
newestSequence = sequence;
|
|
658
|
+
selectedIndex = index;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return Math.max(0, selectedIndex);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
#restoreEditorFocus(): void {
|
|
665
|
+
this.ctx.editorContainer.clear();
|
|
666
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
667
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#showQueuedMessageSelector(entries: QueuedMessageEditEntry[], selectedIndex = 0): void {
|
|
671
|
+
const selector = new QueuedMessageSelectorComponent(
|
|
672
|
+
entries,
|
|
673
|
+
entry => {
|
|
674
|
+
const restored = this.#restoreQueuedMessageToEditor(entry);
|
|
675
|
+
this.#restoreEditorFocus();
|
|
676
|
+
this.ctx.showStatus(
|
|
677
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
678
|
+
);
|
|
679
|
+
this.ctx.ui.requestRender();
|
|
680
|
+
},
|
|
681
|
+
(entry, index) => {
|
|
682
|
+
const deleted = this.#deleteQueuedMessage(entry);
|
|
683
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
684
|
+
if (nextEntries.length === 0) {
|
|
685
|
+
this.#restoreEditorFocus();
|
|
686
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
687
|
+
this.ctx.ui.requestRender();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
691
|
+
this.#showQueuedMessageSelector(nextEntries, Math.min(index, nextEntries.length - 1));
|
|
692
|
+
},
|
|
693
|
+
(entry, index, direction) => {
|
|
694
|
+
const moved = this.#moveQueuedMessage(entry, direction);
|
|
695
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
696
|
+
if (nextEntries.length === 0) {
|
|
697
|
+
this.#restoreEditorFocus();
|
|
698
|
+
this.ctx.showStatus("Queued message is no longer available");
|
|
699
|
+
this.ctx.ui.requestRender();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const nextIndex = direction === "up" ? index - 1 : index + 1;
|
|
703
|
+
const selectedNextIndex = moved ? nextIndex : index;
|
|
704
|
+
this.ctx.showStatus(moved ? "Moved queued message" : "Queued message cannot move further");
|
|
705
|
+
this.#showQueuedMessageSelector(
|
|
706
|
+
nextEntries,
|
|
707
|
+
Math.max(0, Math.min(selectedNextIndex, nextEntries.length - 1)),
|
|
708
|
+
);
|
|
709
|
+
},
|
|
710
|
+
() => {
|
|
711
|
+
this.#restoreEditorFocus();
|
|
712
|
+
this.ctx.ui.requestRender();
|
|
713
|
+
},
|
|
714
|
+
{ selectedIndex },
|
|
715
|
+
);
|
|
716
|
+
this.ctx.editorContainer.clear();
|
|
717
|
+
this.ctx.editorContainer.addChild(selector);
|
|
718
|
+
this.ctx.ui.setFocus(selector);
|
|
719
|
+
this.ctx.ui.requestRender();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
#removeQueuedMessageForEditing(id: string): string | undefined {
|
|
723
|
+
const compactionPrefix = "compaction:";
|
|
724
|
+
if (id.startsWith(compactionPrefix)) {
|
|
725
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
726
|
+
if (!Number.isInteger(index)) return undefined;
|
|
727
|
+
const [entry] = this.ctx.compactionQueuedMessages.splice(index, 1);
|
|
728
|
+
return entry?.text;
|
|
729
|
+
}
|
|
730
|
+
return this.ctx.session.removeQueuedMessageForEditing(id);
|
|
731
|
+
}
|
|
732
|
+
#parseCompactionQueuedMessageId(id: string): number | undefined {
|
|
733
|
+
const compactionPrefix = "compaction:";
|
|
734
|
+
if (!id.startsWith(compactionPrefix)) return undefined;
|
|
735
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
736
|
+
return Number.isInteger(index) ? index : undefined;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
#moveCompactionQueuedMessage(index: number, direction: QueuedMessageMoveDirection): boolean {
|
|
740
|
+
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
741
|
+
if (index < 0 || index >= this.ctx.compactionQueuedMessages.length) return false;
|
|
742
|
+
if (targetIndex < 0 || targetIndex >= this.ctx.compactionQueuedMessages.length) return false;
|
|
743
|
+
const current = this.ctx.compactionQueuedMessages[index];
|
|
744
|
+
const target = this.ctx.compactionQueuedMessages[targetIndex];
|
|
745
|
+
if (!current || !target) return false;
|
|
746
|
+
this.ctx.compactionQueuedMessages[index] = target;
|
|
747
|
+
this.ctx.compactionQueuedMessages[targetIndex] = current;
|
|
748
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
#moveQueuedMessage(entry: QueuedMessageEditEntry, direction: QueuedMessageMoveDirection): boolean {
|
|
753
|
+
const compactionIndex = this.#parseCompactionQueuedMessageId(entry.id);
|
|
754
|
+
if (compactionIndex !== undefined) {
|
|
755
|
+
return this.#moveCompactionQueuedMessage(compactionIndex, direction);
|
|
756
|
+
}
|
|
757
|
+
const moved = this.ctx.session.moveQueuedMessageForEditing(entry.id, direction);
|
|
758
|
+
if (moved) {
|
|
759
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
622
760
|
}
|
|
761
|
+
return moved;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
#deleteQueuedMessage(entry: QueuedMessageEditEntry): boolean {
|
|
765
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
766
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
767
|
+
if (!queuedText) return false;
|
|
768
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#restoreQueuedMessageToEditor(entry: QueuedMessageEditEntry | undefined): number {
|
|
773
|
+
if (!entry) {
|
|
774
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
778
|
+
if (!queuedText) {
|
|
779
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
784
|
+
this.ctx.editor.setText(queuedText);
|
|
785
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
786
|
+
return 1;
|
|
623
787
|
}
|
|
624
788
|
|
|
625
789
|
/**
|
|
@@ -644,19 +808,18 @@ export class InputController {
|
|
|
644
808
|
|
|
645
809
|
/**
|
|
646
810
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
647
|
-
* using the supplied `streamingBehavior`. Returns true if the text
|
|
648
|
-
* recognised skill command chain and was dispatched. A
|
|
649
|
-
* file is surfaced via `showError` but still returns
|
|
650
|
-
* already cleared on the success path, so falling
|
|
651
|
-
* handling at that point would double-submit. Returns
|
|
652
|
-
*
|
|
653
|
-
*
|
|
811
|
+
* using the supplied `streamingBehavior`. Returns true if the text contains a
|
|
812
|
+
* recognised canonical skill command or command chain and was dispatched. A
|
|
813
|
+
* failure to load a skill file is surfaced via `showError` but still returns
|
|
814
|
+
* true — the editor was already cleared on the success path, so falling
|
|
815
|
+
* through to plain-text handling at that point would double-submit. Returns
|
|
816
|
+
* false when the text has no registered canonical skill invocation, so the
|
|
817
|
+
* caller can fall through to plain-text handling (this branch
|
|
654
818
|
* leaves the editor state untouched). `streamingBehavior` is only consulted
|
|
655
819
|
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
656
820
|
* ignores it.
|
|
657
821
|
*/
|
|
658
822
|
async #invokeSkillCommand(text: string, streamingBehavior: "steer" | "followUp"): Promise<boolean> {
|
|
659
|
-
if (!text.startsWith("/")) return false;
|
|
660
823
|
const invocations = parseSkillInvocations(text, this.ctx.skillCommands ?? new Map());
|
|
661
824
|
if (invocations.length === 0) return false;
|
|
662
825
|
this.ctx.editor.addToHistory(text);
|
|
@@ -767,7 +930,7 @@ export class InputController {
|
|
|
767
930
|
return this.handleFollowUp();
|
|
768
931
|
}
|
|
769
932
|
|
|
770
|
-
restoreLatestQueuedMessageToEditor(
|
|
933
|
+
restoreLatestQueuedMessageToEditor(): number {
|
|
771
934
|
const compactionQueued = this.ctx.compactionQueuedMessages.pop();
|
|
772
935
|
const queuedText = compactionQueued?.text ?? this.ctx.session.popLastQueuedMessage();
|
|
773
936
|
if (!queuedText) {
|
|
@@ -776,9 +939,7 @@ export class InputController {
|
|
|
776
939
|
}
|
|
777
940
|
|
|
778
941
|
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
779
|
-
|
|
780
|
-
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
781
|
-
this.ctx.editor.setText(combinedText);
|
|
942
|
+
this.ctx.editor.setText(queuedText);
|
|
782
943
|
this.ctx.updatePendingMessagesDisplay();
|
|
783
944
|
return 1;
|
|
784
945
|
}
|