@oh-my-pi/pi-coding-agent 15.10.1 → 15.10.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 +113 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +7 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/edit/index.d.ts +0 -1
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +0 -5
- package/dist/types/main.d.ts +14 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +0 -9
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/late-diagnostics-message.d.ts +20 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-selector.d.ts +16 -7
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +0 -18
- package/dist/types/modes/controllers/event-controller.d.ts +17 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +11 -8
- package/dist/types/session/session-manager.d.ts +5 -2
- package/dist/types/session/yield-queue.d.ts +10 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/tools/eval-render.d.ts +0 -1
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/index.d.ts +31 -0
- package/dist/types/tools/path-utils.d.ts +13 -1
- package/dist/types/tools/read.d.ts +2 -1
- package/dist/types/tools/render-utils.d.ts +3 -1
- package/dist/types/tools/renderers.d.ts +0 -15
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/write.d.ts +0 -2
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/code-cell.d.ts +0 -2
- package/dist/types/tui/hyperlink.d.ts +5 -7
- package/dist/types/tui/output-block.d.ts +0 -18
- package/package.json +9 -9
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +2 -4
- package/src/cli/gallery-cli.ts +4 -0
- package/src/cli/gallery-fixtures/codeintel.ts +0 -1
- package/src/cli/gallery-fixtures/fs.ts +68 -1
- package/src/cli/gallery-fixtures/types.ts +8 -1
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/agentic/agent.ts +1 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +4 -22
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings.ts +86 -41
- package/src/debug/index.ts +8 -0
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/diff.ts +86 -0
- package/src/edit/hashline/execute.ts +14 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/index.ts +31 -17
- package/src/edit/renderer.ts +116 -31
- package/src/eval/__tests__/llm-bridge.test.ts +20 -0
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/js/shared/prelude.txt +26 -10
- package/src/eval/llm-bridge.ts +14 -3
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +189 -61
- package/src/main.ts +144 -78
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +2 -2
- package/src/modes/components/assistant-message.ts +3 -15
- package/src/modes/components/custom-editor.ts +143 -111
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/plan-review-overlay.ts +26 -5
- package/src/modes/components/read-tool-group.ts +415 -35
- package/src/modes/components/session-selector.ts +89 -35
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/tool-execution.ts +7 -49
- package/src/modes/components/transcript-container.ts +108 -32
- package/src/modes/components/user-message.ts +1 -1
- package/src/modes/controllers/event-controller.ts +32 -1
- package/src/modes/controllers/input-controller.ts +56 -9
- package/src/modes/interactive-mode.ts +107 -20
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/types.ts +7 -0
- package/src/modes/utils/ui-helpers.ts +26 -5
- package/src/modes/workflow.ts +10 -10
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/plan-mode-active.md +56 -72
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +85 -10
- package/src/session/agent-session.ts +42 -15
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +21 -14
- package/src/session/session-manager.ts +98 -25
- package/src/session/yield-queue.ts +20 -2
- package/src/task/executor.ts +72 -36
- package/src/task/render.ts +3 -4
- package/src/tiny/title-client.ts +6 -1
- package/src/tools/bash.ts +7 -7
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/eval-render.ts +4 -23
- package/src/tools/eval.ts +13 -2
- package/src/tools/find.ts +148 -99
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/index.ts +32 -0
- package/src/tools/inspect-image.ts +2 -2
- package/src/tools/path-utils.ts +47 -24
- package/src/tools/plan-mode-guard.ts +52 -7
- package/src/tools/read.ts +41 -20
- package/src/tools/render-utils.ts +3 -1
- package/src/tools/renderers.ts +0 -15
- package/src/tools/search.ts +38 -3
- package/src/tools/ssh.ts +0 -1
- package/src/tools/todo.ts +1 -0
- package/src/tools/write.ts +5 -14
- package/src/tools/yield.ts +10 -1
- package/src/tui/code-cell.ts +1 -6
- package/src/tui/hyperlink.ts +13 -23
- package/src/tui/output-block.ts +2 -97
- package/src/utils/commit-message-generator.ts +2 -2
- package/src/utils/enhanced-paste.ts +30 -2
- package/src/web/search/providers/codex.ts +37 -8
|
@@ -10,6 +10,7 @@ import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
|
10
10
|
import { materializeImageReferenceLinks } from "../../modes/image-references";
|
|
11
11
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
12
12
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
13
|
+
import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
|
|
13
14
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails, USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
14
15
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
15
16
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
@@ -94,6 +95,7 @@ export class InputController {
|
|
|
94
95
|
if (this.ctx.loopModeEnabled) {
|
|
95
96
|
this.ctx.pauseLoop();
|
|
96
97
|
if (this.ctx.session.isStreaming) {
|
|
98
|
+
this.ctx.notifyInterrupting();
|
|
97
99
|
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
98
100
|
} else {
|
|
99
101
|
this.ctx.cancelPendingSubmission();
|
|
@@ -124,6 +126,7 @@ export class InputController {
|
|
|
124
126
|
this.ctx.isPythonMode = false;
|
|
125
127
|
this.ctx.updateEditorBorderColor();
|
|
126
128
|
} else if (this.ctx.session.isStreaming) {
|
|
129
|
+
this.ctx.notifyInterrupting();
|
|
127
130
|
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
128
131
|
} else if (!this.ctx.editor.getText().trim()) {
|
|
129
132
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
@@ -284,14 +287,21 @@ export class InputController {
|
|
|
284
287
|
|
|
285
288
|
if (!text) return;
|
|
286
289
|
|
|
287
|
-
// Continue shortcuts: "." or "c"
|
|
290
|
+
// Continue shortcuts: "." or "c" resume the agent with a hidden agent-authored
|
|
291
|
+
// developer directive (no visible user message) instead of an empty turn, so the
|
|
292
|
+
// model continues the prior intent rather than second-guessing the interrupt.
|
|
288
293
|
if (text === "." || text === "c") {
|
|
289
294
|
if (this.ctx.onInputCallback) {
|
|
290
295
|
this.ctx.editor.setText("");
|
|
291
296
|
this.ctx.pendingImages = [];
|
|
292
297
|
this.ctx.pendingImageLinks = [];
|
|
293
298
|
this.ctx.editor.imageLinks = undefined;
|
|
294
|
-
this.ctx.onInputCallback({
|
|
299
|
+
this.ctx.onInputCallback({
|
|
300
|
+
text: manualContinuePrompt,
|
|
301
|
+
cancelled: false,
|
|
302
|
+
started: true,
|
|
303
|
+
synthetic: true,
|
|
304
|
+
});
|
|
295
305
|
}
|
|
296
306
|
return;
|
|
297
307
|
}
|
|
@@ -499,17 +509,44 @@ export class InputController {
|
|
|
499
509
|
}
|
|
500
510
|
|
|
501
511
|
handleCtrlZ(): void {
|
|
502
|
-
//
|
|
503
|
-
process.
|
|
512
|
+
// SIGTSTP is POSIX job-control: Windows has no equivalent and
|
|
513
|
+
// `process.kill(_, "SIGTSTP")` throws `TypeError: Unknown signal:
|
|
514
|
+
// SIGTSTP` there, taking the whole agent down via an uncaught
|
|
515
|
+
// exception (issue #2036). No-op on platforms that cannot suspend.
|
|
516
|
+
if (process.platform === "win32") {
|
|
517
|
+
this.ctx.showStatus("Suspend (Ctrl+Z) is not supported on this platform");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Capture the listener so we can detach it if the signal never
|
|
522
|
+
// fires; otherwise a failed suspend would leave a stale SIGCONT
|
|
523
|
+
// handler that fires on the next unrelated continue and tries to
|
|
524
|
+
// re-`start()` an already-running TUI.
|
|
525
|
+
const onResume = (): void => {
|
|
504
526
|
this.ctx.ui.start();
|
|
505
527
|
this.ctx.ui.requestRender(true);
|
|
506
|
-
}
|
|
528
|
+
};
|
|
529
|
+
process.once("SIGCONT", onResume);
|
|
507
530
|
|
|
508
|
-
// Stop the TUI (restore terminal to normal mode)
|
|
531
|
+
// Stop the TUI (restore terminal to normal mode) before sending the
|
|
532
|
+
// signal so the parent shell sees a sane terminal state.
|
|
509
533
|
this.ctx.ui.stop();
|
|
510
534
|
|
|
511
|
-
|
|
512
|
-
|
|
535
|
+
try {
|
|
536
|
+
// pid=0 → entire foreground process group; the shell receives
|
|
537
|
+
// SIGTSTP and parks the job.
|
|
538
|
+
process.kill(0, "SIGTSTP");
|
|
539
|
+
} catch (err) {
|
|
540
|
+
// Either the runtime refused the signal or the kernel rejected
|
|
541
|
+
// it (some sandboxes block sending to pid=0). Tear the resume
|
|
542
|
+
// hook down and bring the TUI back so the user is not stranded
|
|
543
|
+
// on a frozen prompt.
|
|
544
|
+
process.removeListener("SIGCONT", onResume);
|
|
545
|
+
this.ctx.ui.start();
|
|
546
|
+
this.ctx.ui.requestRender(true);
|
|
547
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
548
|
+
this.ctx.showError(`Failed to suspend: ${reason}`);
|
|
549
|
+
}
|
|
513
550
|
}
|
|
514
551
|
|
|
515
552
|
handleDequeue(): void {
|
|
@@ -589,7 +626,7 @@ export class InputController {
|
|
|
589
626
|
|
|
590
627
|
/** Send editor text as a follow-up message (queued behind current stream). */
|
|
591
628
|
async handleFollowUp(): Promise<void> {
|
|
592
|
-
|
|
629
|
+
let text = this.ctx.editor.getText().trim();
|
|
593
630
|
if (!text) return;
|
|
594
631
|
|
|
595
632
|
// Compaction first: while compacting, free text gets queued via
|
|
@@ -603,6 +640,16 @@ export class InputController {
|
|
|
603
640
|
return;
|
|
604
641
|
}
|
|
605
642
|
|
|
643
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
644
|
+
ctx: this.ctx,
|
|
645
|
+
});
|
|
646
|
+
if (slashResult === true) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (typeof slashResult === "string") {
|
|
650
|
+
text = slashResult;
|
|
651
|
+
}
|
|
652
|
+
|
|
606
653
|
// Skill commands invoke through the custom-message path regardless of
|
|
607
654
|
// which keybinding submitted them. Enter routes them as `steer`;
|
|
608
655
|
// Ctrl+Enter (this handler) routes them as `followUp`.
|
|
@@ -50,7 +50,7 @@ import chalk from "chalk";
|
|
|
50
50
|
import { reset as resetCapabilities } from "../capability";
|
|
51
51
|
import { KeybindingsManager } from "../config/keybindings";
|
|
52
52
|
import { MODEL_ROLES, type ModelRole } from "../config/model-registry";
|
|
53
|
-
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
53
|
+
import { isSettingsInitialized, onStatusLineSessionAccentChanged, Settings, settings } from "../config/settings";
|
|
54
54
|
import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
55
55
|
import type {
|
|
56
56
|
ContextUsage,
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
126
126
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
127
127
|
import { interruptHint } from "./shared";
|
|
128
|
-
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
128
|
+
import { type ShimmerPalette, shimmerEnabled, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
129
129
|
import type { Theme } from "./theme/theme";
|
|
130
130
|
import {
|
|
131
131
|
getEditorTheme,
|
|
@@ -157,6 +157,12 @@ interface WorkingMessageAccent {
|
|
|
157
157
|
dim: string;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
interface WorkingMessageAccentCacheKey {
|
|
161
|
+
sessionName: string | undefined;
|
|
162
|
+
accentSurfaceLuminance: number | undefined;
|
|
163
|
+
sessionAccentEnabled: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
160
166
|
function renderWorkingMessage(message: string, accent?: WorkingMessageAccent): string {
|
|
161
167
|
const palette = accent
|
|
162
168
|
? ({
|
|
@@ -301,6 +307,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
301
307
|
autoCompactionLoader: Loader | undefined = undefined;
|
|
302
308
|
retryLoader: Loader | undefined = undefined;
|
|
303
309
|
#pendingWorkingMessage: string | undefined;
|
|
310
|
+
#workingMessageAccentCacheKey?: WorkingMessageAccentCacheKey;
|
|
311
|
+
#workingMessageAccentCacheValue?: WorkingMessageAccent;
|
|
312
|
+
#workingMessageAccentCacheHasValue = false;
|
|
304
313
|
get #defaultWorkingMessage(): string {
|
|
305
314
|
return `Working…${interruptHint()}`;
|
|
306
315
|
}
|
|
@@ -638,9 +647,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
638
647
|
this.session.subscribe(event => {
|
|
639
648
|
void this.#handleGoalSessionEvent(event);
|
|
640
649
|
}),
|
|
650
|
+
this.sessionManager.onSessionNameChanged(() => {
|
|
651
|
+
this.#handleSessionAccentInputsChanged();
|
|
652
|
+
}),
|
|
653
|
+
onStatusLineSessionAccentChanged(() => {
|
|
654
|
+
this.#syncStatusLineSettings();
|
|
655
|
+
this.#handleSessionAccentInputsChanged();
|
|
656
|
+
}),
|
|
641
657
|
);
|
|
642
658
|
// Set up theme file watcher
|
|
643
659
|
onThemeChange(() => {
|
|
660
|
+
this.#clearWorkingMessageAccentCache();
|
|
644
661
|
clearRenderCache();
|
|
645
662
|
this.ui.invalidate();
|
|
646
663
|
this.updateEditorBorderColor();
|
|
@@ -965,9 +982,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
965
982
|
this.#goalContinuationTurnInFlight = false;
|
|
966
983
|
}
|
|
967
984
|
if (this.loadingAnimation) {
|
|
968
|
-
this
|
|
969
|
-
this.loadingAnimation = undefined;
|
|
970
|
-
this.statusContainer.clear();
|
|
985
|
+
this.#stopLoadingAnimation(true);
|
|
971
986
|
}
|
|
972
987
|
if (!submission.customType) {
|
|
973
988
|
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
@@ -1005,9 +1020,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1005
1020
|
pendingSubmissionDispose?.();
|
|
1006
1021
|
this.#pendingWorkingMessage = undefined;
|
|
1007
1022
|
if (this.loadingAnimation) {
|
|
1008
|
-
this
|
|
1009
|
-
this.loadingAnimation = undefined;
|
|
1010
|
-
this.statusContainer.clear();
|
|
1023
|
+
this.#stopLoadingAnimation(true);
|
|
1011
1024
|
}
|
|
1012
1025
|
}
|
|
1013
1026
|
}
|
|
@@ -1023,6 +1036,24 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1023
1036
|
this.editor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
1024
1037
|
}
|
|
1025
1038
|
|
|
1039
|
+
#syncStatusLineSettings(): void {
|
|
1040
|
+
this.statusLine.updateSettings({
|
|
1041
|
+
preset: settings.get("statusLine.preset"),
|
|
1042
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
1043
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
1044
|
+
separator: settings.get("statusLine.separator"),
|
|
1045
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
1046
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1047
|
+
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
#handleSessionAccentInputsChanged(): void {
|
|
1052
|
+
this.#clearWorkingMessageAccentCache();
|
|
1053
|
+
this.statusLine.invalidate();
|
|
1054
|
+
this.updateEditorBorderColor();
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1026
1057
|
updateEditorBorderColor(): void {
|
|
1027
1058
|
if (this.isBashMode) {
|
|
1028
1059
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
@@ -2416,8 +2447,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2416
2447
|
|
|
2417
2448
|
stop(): void {
|
|
2418
2449
|
if (this.loadingAnimation) {
|
|
2419
|
-
this
|
|
2420
|
-
this.loadingAnimation = undefined;
|
|
2450
|
+
this.#stopLoadingAnimation(false);
|
|
2421
2451
|
}
|
|
2422
2452
|
this.#cleanupMicAnimation();
|
|
2423
2453
|
this.#cancelTodoAutoClearTimer();
|
|
@@ -2581,9 +2611,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2581
2611
|
this.#pendingSubmissionDispose = undefined;
|
|
2582
2612
|
this.#pendingWorkingMessage = undefined;
|
|
2583
2613
|
if (this.loadingAnimation) {
|
|
2584
|
-
this
|
|
2585
|
-
this.loadingAnimation = undefined;
|
|
2586
|
-
this.statusContainer.clear();
|
|
2614
|
+
this.#stopLoadingAnimation(true);
|
|
2587
2615
|
}
|
|
2588
2616
|
this.#uiHelpers.showError(message);
|
|
2589
2617
|
}
|
|
@@ -2646,24 +2674,69 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2646
2674
|
this.ui.requestRender();
|
|
2647
2675
|
}
|
|
2648
2676
|
|
|
2677
|
+
#clearWorkingMessageAccentCache(): void {
|
|
2678
|
+
this.#workingMessageAccentCacheKey = undefined;
|
|
2679
|
+
this.#workingMessageAccentCacheValue = undefined;
|
|
2680
|
+
this.#workingMessageAccentCacheHasValue = false;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
#buildWorkingMessageAccentCacheKey(): WorkingMessageAccentCacheKey {
|
|
2684
|
+
const sessionAccentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
|
|
2685
|
+
return {
|
|
2686
|
+
sessionAccentEnabled,
|
|
2687
|
+
sessionName: sessionAccentEnabled ? this.sessionManager.getSessionName() : undefined,
|
|
2688
|
+
accentSurfaceLuminance: theme.accentSurfaceLuminance,
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
#workingMessageAccentCacheKeyEquals(a: WorkingMessageAccentCacheKey, b: WorkingMessageAccentCacheKey): boolean {
|
|
2693
|
+
return (
|
|
2694
|
+
a.sessionName === b.sessionName &&
|
|
2695
|
+
a.accentSurfaceLuminance === b.accentSurfaceLuminance &&
|
|
2696
|
+
a.sessionAccentEnabled === b.sessionAccentEnabled
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
#cacheWorkingMessageAccent(
|
|
2701
|
+
key: WorkingMessageAccentCacheKey,
|
|
2702
|
+
value: WorkingMessageAccent | undefined,
|
|
2703
|
+
): WorkingMessageAccent | undefined {
|
|
2704
|
+
this.#workingMessageAccentCacheKey = key;
|
|
2705
|
+
this.#workingMessageAccentCacheValue = value;
|
|
2706
|
+
this.#workingMessageAccentCacheHasValue = true;
|
|
2707
|
+
return value;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2649
2710
|
#getWorkingMessageAccent(): WorkingMessageAccent | undefined {
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2711
|
+
const key = this.#buildWorkingMessageAccentCacheKey();
|
|
2712
|
+
if (
|
|
2713
|
+
this.#workingMessageAccentCacheHasValue &&
|
|
2714
|
+
this.#workingMessageAccentCacheKey &&
|
|
2715
|
+
this.#workingMessageAccentCacheKeyEquals(key, this.#workingMessageAccentCacheKey)
|
|
2716
|
+
) {
|
|
2717
|
+
return this.#workingMessageAccentCacheValue;
|
|
2718
|
+
}
|
|
2719
|
+
if (!key.sessionAccentEnabled || !key.sessionName) {
|
|
2720
|
+
return this.#cacheWorkingMessageAccent(key, undefined);
|
|
2721
|
+
}
|
|
2722
|
+
const hex = getSessionAccentHex(key.sessionName, key.accentSurfaceLuminance);
|
|
2654
2723
|
const main = getSessionAccentAnsi(hex);
|
|
2655
2724
|
const dim = getSessionAccentAnsi(adjustHsv(hex, { s: 0.55, v: 0.65 }));
|
|
2656
|
-
return main && dim ? { main, dim } : undefined;
|
|
2725
|
+
return this.#cacheWorkingMessageAccent(key, main && dim ? { main, dim } : undefined);
|
|
2657
2726
|
}
|
|
2658
2727
|
|
|
2659
2728
|
ensureLoadingAnimation(): void {
|
|
2660
2729
|
if (!this.loadingAnimation) {
|
|
2730
|
+
this.#clearWorkingMessageAccentCache();
|
|
2661
2731
|
this.statusContainer.clear();
|
|
2662
2732
|
const messageColorFn = ((message: string) =>
|
|
2663
2733
|
renderWorkingMessage(message, this.#getWorkingMessageAccent())) as LoaderMessageColorFn & {
|
|
2664
|
-
animated
|
|
2734
|
+
animated?: true;
|
|
2665
2735
|
};
|
|
2666
|
-
|
|
2736
|
+
// Shimmer drives the 30fps redraw; when it is disabled the working
|
|
2737
|
+
// message is static, so leave `animated` unset and let the loader use
|
|
2738
|
+
// the spinner-only ~12.5fps cadence instead of repainting a frozen line.
|
|
2739
|
+
if (shimmerEnabled()) messageColorFn.animated = true;
|
|
2667
2740
|
this.loadingAnimation = new Loader(
|
|
2668
2741
|
this.ui,
|
|
2669
2742
|
spinner => {
|
|
@@ -2680,6 +2753,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2680
2753
|
this.applyPendingWorkingMessage();
|
|
2681
2754
|
}
|
|
2682
2755
|
|
|
2756
|
+
#stopLoadingAnimation(clearStatusContainer: boolean): void {
|
|
2757
|
+
if (!this.loadingAnimation) return;
|
|
2758
|
+
this.loadingAnimation.stop();
|
|
2759
|
+
this.loadingAnimation = undefined;
|
|
2760
|
+
this.#clearWorkingMessageAccentCache();
|
|
2761
|
+
if (clearStatusContainer) {
|
|
2762
|
+
this.statusContainer.clear();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2683
2766
|
setWorkingMessage(message?: string): void {
|
|
2684
2767
|
if (message === undefined) {
|
|
2685
2768
|
this.#pendingWorkingMessage = undefined;
|
|
@@ -2707,6 +2790,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2707
2790
|
this.setWorkingMessage(message);
|
|
2708
2791
|
}
|
|
2709
2792
|
|
|
2793
|
+
notifyInterrupting(): void {
|
|
2794
|
+
this.#eventController.notifyInterrupting();
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2710
2797
|
showNewVersionNotification(newVersion: string): void {
|
|
2711
2798
|
this.#uiHelpers.showNewVersionNotification(newVersion);
|
|
2712
2799
|
}
|
|
@@ -4,7 +4,7 @@ import { highlightWorkflow } from "./workflow";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
|
|
7
|
-
* "
|
|
7
|
+
* "workflowz") that appears as standalone prose, skipping any occurrence inside a
|
|
8
8
|
* code block, inline code span, or XML/HTML section. Each highlighter paints its
|
|
9
9
|
* own keyword with its own gradient, so chaining is order-independent — the
|
|
10
10
|
* earlier passes only inject zero-width SGR escapes (no backticks or angle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Markdown structure awareness for the magic-keyword affordances
|
|
3
|
-
* ("ultrathink"/"orchestrate"/"
|
|
3
|
+
* ("ultrathink"/"orchestrate"/"workflowz").
|
|
4
4
|
*
|
|
5
5
|
* Keyword detection and editor/transcript highlighting must fire only on prose
|
|
6
6
|
* the user is actually addressing to the model — never on a word that happens to
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
2
2
|
import type { Theme, ThemeColor } from "./theme";
|
|
3
3
|
|
|
4
|
+
// ─── Animation velocity ──────────────────────────────────────────────────────
|
|
5
|
+
// Band/head travel speed in border cells per second. Driving position by a fixed
|
|
6
|
+
// velocity — instead of dividing a fixed sweep duration by the (length-derived)
|
|
7
|
+
// period — makes smoothness independent of message length: at the loader's
|
|
8
|
+
// default 30fps redraw cadence the band advances ≤1 cell per frame for any
|
|
9
|
+
// string, so it never visibly steps. Sweep/round-trip durations now scale with
|
|
10
|
+
// length. Keep ≤ the animated redraw fps (loader RENDER_INTERVAL_MS = 1000/30).
|
|
11
|
+
const SHIMMER_SPEED_CELLS_PER_S = 30;
|
|
12
|
+
|
|
4
13
|
// ─── Classic sweep tunables ──────────────────────────────────────────────────
|
|
5
14
|
const CLASSIC_PADDING = 10;
|
|
6
|
-
const CLASSIC_SWEEP_MS = 1400;
|
|
7
15
|
const CLASSIC_BAND_HALF_WIDTH = 6;
|
|
8
16
|
|
|
9
17
|
// ─── KITT scanner tunables ───────────────────────────────────────────────────
|
|
10
|
-
// 1.5s round trip ≈ classic 1982 K.I.T.T. scanner cadence (~0.75s per direction).
|
|
11
|
-
const KITT_CYCLE_MS = 1500;
|
|
12
18
|
const KITT_HEAD_HALF = 0.6;
|
|
13
19
|
const KITT_TRAIL_LEN = 7;
|
|
14
20
|
|
|
@@ -103,9 +109,10 @@ function compile(theme: ShimmerTheme, palette: ShimmerPalette): CompiledPalette
|
|
|
103
109
|
/** Smooth cosine bump sweeping left → right with edge padding. */
|
|
104
110
|
function classicIntensity(time: number, index: number, length: number): number {
|
|
105
111
|
const period = length + CLASSIC_PADDING * 2;
|
|
106
|
-
//
|
|
107
|
-
// frame
|
|
108
|
-
|
|
112
|
+
// Fixed-velocity, un-floored band position: advancing at a constant
|
|
113
|
+
// cells/second (not period / fixed-sweep) keeps the per-frame step ≤1 cell at
|
|
114
|
+
// the default cadence for any length, so long messages are no steppier.
|
|
115
|
+
const pos = ((time / 1000) * SHIMMER_SPEED_CELLS_PER_S) % period;
|
|
109
116
|
const dist = Math.abs(index + CLASSIC_PADDING - pos);
|
|
110
117
|
if (dist >= CLASSIC_BAND_HALF_WIDTH) return 0;
|
|
111
118
|
return 0.5 * (1 + Math.cos((Math.PI * dist) / CLASSIC_BAND_HALF_WIDTH));
|
|
@@ -119,9 +126,13 @@ function classicIntensity(time: number, index: number, length: number): number {
|
|
|
119
126
|
function kittIntensity(time: number, index: number, length: number): number {
|
|
120
127
|
const range = length - 1;
|
|
121
128
|
if (range <= 0) return 1;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
// Fixed head velocity: a triangle ping-pong over a 2*range round trip at a
|
|
130
|
+
// constant cells/second, so the bright head advances ≤1 cell per frame at the
|
|
131
|
+
// default cadence regardless of bar length. Round-trip duration scales with length.
|
|
132
|
+
const cycleCells = 2 * range;
|
|
133
|
+
const sweep = ((time / 1000) * SHIMMER_SPEED_CELLS_PER_S) % cycleCells;
|
|
134
|
+
const goingRight = sweep < range;
|
|
135
|
+
const head = goingRight ? sweep : cycleCells - sweep;
|
|
125
136
|
const delta = index - head;
|
|
126
137
|
const abs = delta < 0 ? -delta : delta;
|
|
127
138
|
if (abs <= KITT_HEAD_HALF) return 1;
|
package/src/modes/types.ts
CHANGED
|
@@ -42,6 +42,10 @@ export type SubmittedUserInput = {
|
|
|
42
42
|
images?: ImageContent[];
|
|
43
43
|
imageLinks?: (string | undefined)[];
|
|
44
44
|
customType?: string;
|
|
45
|
+
/** Route through `session.prompt(text, { synthetic: true })` so the text lands
|
|
46
|
+
* as a hidden agent-authored `developer` message rather than a visible user
|
|
47
|
+
* turn. Used by the `c`/`.` continue shortcut. */
|
|
48
|
+
synthetic?: boolean;
|
|
45
49
|
display?: boolean;
|
|
46
50
|
cancelled: boolean;
|
|
47
51
|
started: boolean;
|
|
@@ -182,6 +186,9 @@ export interface InteractiveModeContext {
|
|
|
182
186
|
flushPendingModelSwitch(): Promise<void>;
|
|
183
187
|
setWorkingMessage(message?: string): void;
|
|
184
188
|
applyPendingWorkingMessage(): void;
|
|
189
|
+
/** Acknowledge a user interrupt (Esc) by switching the loader to an
|
|
190
|
+
* "Interrupting…" label until the agent turn unwinds. */
|
|
191
|
+
notifyInterrupting(): void;
|
|
185
192
|
ensureLoadingAnimation(): void;
|
|
186
193
|
startPendingSubmission(input: {
|
|
187
194
|
text: string;
|
|
@@ -10,6 +10,10 @@ import { CompactionSummaryMessageComponent } from "../../modes/components/compac
|
|
|
10
10
|
import { CustomMessageComponent } from "../../modes/components/custom-message";
|
|
11
11
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
12
12
|
import { EvalExecutionComponent } from "../../modes/components/eval-execution";
|
|
13
|
+
import {
|
|
14
|
+
type LateDiagnosticsFile,
|
|
15
|
+
LateDiagnosticsMessageComponent,
|
|
16
|
+
} from "../../modes/components/late-diagnostics-message";
|
|
13
17
|
import {
|
|
14
18
|
ReadToolGroupComponent,
|
|
15
19
|
readArgsHaveTarget,
|
|
@@ -25,6 +29,7 @@ import type { CompactionQueuedMessage, InteractiveModeContext } from "../../mode
|
|
|
25
29
|
import {
|
|
26
30
|
type CustomMessage,
|
|
27
31
|
isSilentAbort,
|
|
32
|
+
LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
28
33
|
resolveAbortLabel,
|
|
29
34
|
SKILL_PROMPT_MESSAGE_TYPE,
|
|
30
35
|
type SkillPromptDetails,
|
|
@@ -168,6 +173,17 @@ export class UiHelpers {
|
|
|
168
173
|
this.ctx.chatContainer.addChild(block);
|
|
169
174
|
break;
|
|
170
175
|
}
|
|
176
|
+
if (message.customType === LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE) {
|
|
177
|
+
const details = (
|
|
178
|
+
message as CustomMessage<{
|
|
179
|
+
files?: LateDiagnosticsFile[];
|
|
180
|
+
}>
|
|
181
|
+
).details;
|
|
182
|
+
const component = new LateDiagnosticsMessageComponent(details?.files ?? []);
|
|
183
|
+
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
184
|
+
this.ctx.chatContainer.addChild(component);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
171
187
|
if (message.customType === SKILL_PROMPT_MESSAGE_TYPE) {
|
|
172
188
|
const component = new SkillMessageComponent(message as CustomMessage<SkillPromptDetails>);
|
|
173
189
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
@@ -342,7 +358,11 @@ export class UiHelpers {
|
|
|
342
358
|
(content.type === "thinking" && content.thinking.trim().length > 0),
|
|
343
359
|
);
|
|
344
360
|
if (hasVisibleAssistantContent) {
|
|
345
|
-
|
|
361
|
+
// Rebuild reconstructs immutable history; seal (not finalize) so the
|
|
362
|
+
// group freezes even if a read's result was never persisted —
|
|
363
|
+
// finalize alone keeps a pending entry live and would stop the whole
|
|
364
|
+
// transcript below it from committing to native scrollback.
|
|
365
|
+
readGroup?.seal();
|
|
346
366
|
readGroup = null;
|
|
347
367
|
}
|
|
348
368
|
const isAbortedSilently = message.stopReason === "aborted" && isSilentAbort(message.errorMessage);
|
|
@@ -392,7 +412,7 @@ export class UiHelpers {
|
|
|
392
412
|
continue;
|
|
393
413
|
}
|
|
394
414
|
|
|
395
|
-
readGroup?.
|
|
415
|
+
readGroup?.seal();
|
|
396
416
|
readGroup = null;
|
|
397
417
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
398
418
|
const renderArgs =
|
|
@@ -480,9 +500,10 @@ export class UiHelpers {
|
|
|
480
500
|
}
|
|
481
501
|
}
|
|
482
502
|
|
|
483
|
-
// The trailing read run has no following break to close it;
|
|
484
|
-
// rebuilt group
|
|
485
|
-
|
|
503
|
+
// The trailing read run has no following break to close it; seal so the
|
|
504
|
+
// rebuilt group freezes (even with a never-persisted result) and commits to
|
|
505
|
+
// native scrollback like every other historical block.
|
|
506
|
+
readGroup?.seal();
|
|
486
507
|
|
|
487
508
|
// Render deferred messages (compaction summaries) at the bottom so they're visible
|
|
488
509
|
for (const message of deferredMessages) {
|
package/src/modes/workflow.ts
CHANGED
|
@@ -3,25 +3,25 @@ import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-h
|
|
|
3
3
|
import { keywordInProse } from "./markdown-prose";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* "
|
|
6
|
+
* "workflowz" keyword support.
|
|
7
7
|
*
|
|
8
8
|
* Typing the standalone word in the input editor paints it with a warm
|
|
9
9
|
* amber→green gradient ({@link highlightWorkflow}); submitting a message that
|
|
10
10
|
* mentions it appends a hidden {@link WORKFLOW_NOTICE} that steers the model to
|
|
11
11
|
* author a deterministic multi-subagent workflow in eval cells (agent/parallel/
|
|
12
12
|
* pipeline). Matching is whitespace-delimited and case-sensitive (lowercase
|
|
13
|
-
* only) — "
|
|
14
|
-
* "
|
|
13
|
+
* only) — "workflowz" triggers, but "workflowzed", "Workflowz", and
|
|
14
|
+
* "workflowz.ts" never do.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
// Detection: lowercase keyword
|
|
18
|
-
const WORKFLOW_WORD = /(?<!\S)
|
|
17
|
+
// Detection: lowercase keyword flanked by whitespace or a string edge. Non-global so `.test` stays stateless.
|
|
18
|
+
const WORKFLOW_WORD = /(?<!\S)workflowz(?!\S)/;
|
|
19
19
|
|
|
20
|
-
/** Hidden system notice appended after a user message that mentions "
|
|
20
|
+
/** Hidden system notice appended after a user message that mentions "workflowz". */
|
|
21
21
|
export const WORKFLOW_NOTICE: string = workflowNotice.trim();
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Whether `text` contains the standalone keyword "
|
|
24
|
+
* Whether `text` contains the standalone keyword "workflowz"
|
|
25
25
|
* (lowercase, whitespace-delimited) in prose — never inside a code block, inline
|
|
26
26
|
* code span, or XML/HTML section.
|
|
27
27
|
*/
|
|
@@ -30,13 +30,13 @@ export function containsWorkflow(text: string): boolean {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Highlight every standalone "
|
|
33
|
+
* Highlight every standalone "workflowz" in `text` for editor display
|
|
34
34
|
* with a warm amber→green gradient (hue 30..150), visually distinct from
|
|
35
35
|
* ultrathink's rainbow and orchestrate's teal→violet.
|
|
36
36
|
*/
|
|
37
37
|
export const highlightWorkflow: KeywordHighlighter = createGradientHighlighter({
|
|
38
|
-
probe: /
|
|
39
|
-
highlight: /(?<!\S)
|
|
38
|
+
probe: /workflowz/,
|
|
39
|
+
highlight: /(?<!\S)workflowz(?!\S)/g,
|
|
40
40
|
stops: 14,
|
|
41
41
|
hue: t => 30 + t * 120,
|
|
42
42
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<system-notice>
|
|
2
|
+
Continue. Keep going from where you left off.
|
|
3
|
+
|
|
4
|
+
- You MUST resume the most recent intent and carry the unfinished work to completion.
|
|
5
|
+
- Interrupted mid-step? Pick it back up from where it stopped.
|
|
6
|
+
- You NEVER pause to summarize progress, re-confirm the plan, or ask whether to proceed — just continue.
|
|
7
|
+
</system-notice>
|