@sayknow-cli/coding-agent 0.3.1 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/config/model-profiles.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +0 -5
- package/dist/types/config/settings-schema.d.ts +79 -68
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
- package/dist/types/i18n/messages/en.d.ts +0 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +0 -1
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/state-renderer.d.ts +5 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
- package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +37 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +80 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +31 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/computer-gc.d.ts +23 -0
- package/dist/types/tools/cron.d.ts +36 -61
- package/dist/types/tools/index.d.ts +3 -2
- package/dist/types/tools/resource-gc.d.ts +54 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +11 -4
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +180 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/args.ts +0 -1
- package/src/cli/fast-help.ts +29 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli/web-search-cli.ts +5 -0
- package/src/cli.ts +30 -12
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +62 -8
- package/src/config/model-profiles.ts +3 -4
- package/src/config/model-registry.ts +3 -6
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +88 -103
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -12
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/skc-plugins/compiler.ts +351 -0
- package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/skc-plugins/index.ts +9 -0
- package/src/extensibility/skc-plugins/injection.ts +109 -0
- package/src/extensibility/skc-plugins/installer.ts +434 -0
- package/src/extensibility/skc-plugins/loader.ts +3 -1
- package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/skc-plugins/observability.ts +84 -0
- package/src/extensibility/skc-plugins/paths.ts +1 -1
- package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/skc-plugins/registry.ts +180 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/skc-plugins/schema.ts +250 -20
- package/src/extensibility/skc-plugins/session-validation.ts +147 -0
- package/src/extensibility/skc-plugins/types.ts +199 -3
- package/src/extensibility/skc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/goals/tools/goal-tool.ts +14 -1
- package/src/hooks/skill-state.ts +57 -0
- package/src/i18n/messages/de.ts +0 -1
- package/src/i18n/messages/en.ts +0 -1
- package/src/i18n/messages/es.ts +0 -1
- package/src/i18n/messages/fr.ts +0 -1
- package/src/i18n/messages/ja.ts +0 -1
- package/src/i18n/messages/ko.ts +0 -1
- package/src/i18n/messages/zh.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -11
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/assistant-message.ts +49 -1
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +65 -12
- package/src/modes/components/settings-defs.ts +1 -2
- package/src/modes/components/settings-selector.ts +2 -7
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +59 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +0 -4
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +238 -0
- package/src/notifications/lifecycle-control-runtime.ts +405 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/prompts/system/system-prompt.md +2 -2
- package/src/prompts/tools/cron.md +5 -3
- package/src/prompts/tools/read.md +1 -1
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +162 -10
- package/src/session/agent-session.ts +210 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +68 -19
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/state-renderer.ts +13 -0
- package/src/skc-runtime/team-runtime.ts +56 -23
- package/src/skc-runtime/tmux-common.ts +88 -4
- package/src/skc-runtime/tmux-sessions.ts +111 -9
- package/src/skc-runtime/ultragoal-guard.ts +192 -8
- package/src/skc-runtime/ultragoal-runtime.ts +296 -16
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/browser/tab-supervisor.ts +86 -2
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/computer-gc.ts +66 -0
- package/src/tools/computer.ts +2 -0
- package/src/tools/cron.ts +75 -112
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +5 -9
- package/src/tools/read.ts +25 -55
- package/src/tools/renderers.ts +0 -2
- package/src/tools/resource-gc.ts +291 -0
- package/src/tools/ultragoal-ask-guard.ts +7 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/index.ts +1 -0
- package/src/web/search/providers/utils.ts +22 -5
- package/vendor/insane-search/MANIFEST.json +3 -1
- package/vendor/insane-search/engine/__init__.py +14 -0
- package/vendor/insane-search/engine/content_safety.py +151 -0
- package/vendor/insane-search/engine/fetch_chain.py +32 -0
- package/vendor/insane-search/engine/tests/test_u8.py +216 -0
- package/dist/types/tools/inspect-image-renderer.d.ts +0 -26
- package/dist/types/tools/inspect-image.d.ts +0 -31
- package/src/prompts/tools/inspect-image-system.md +0 -20
- package/src/prompts/tools/inspect-image.md +0 -32
- package/src/tools/inspect-image-renderer.ts +0 -103
- package/src/tools/inspect-image.ts +0 -172
package/src/main.ts
CHANGED
|
@@ -52,7 +52,7 @@ import { formatModelOnboardingGuidance } from "./setup/model-onboarding-guidance
|
|
|
52
52
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
53
53
|
import { resolvePromptInput } from "./system-prompt";
|
|
54
54
|
import type { LspStartupServerInfo } from "./tools";
|
|
55
|
-
import { getDisplayChangelogEntries, getNewEntries } from "./utils/changelog";
|
|
55
|
+
import { getDisplayChangelogEntries, getInstalledVersionChangelogEntry, getNewEntries } from "./utils/changelog";
|
|
56
56
|
import type { EventBus } from "./utils/event-bus";
|
|
57
57
|
|
|
58
58
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
@@ -407,7 +407,7 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
|
|
|
407
407
|
if (entries.length > 0) {
|
|
408
408
|
settings.set("lastChangelogVersion", VERSION);
|
|
409
409
|
await flushChangelogVersion();
|
|
410
|
-
return entries
|
|
410
|
+
return getInstalledVersionChangelogEntry(entries, VERSION)?.content;
|
|
411
411
|
}
|
|
412
412
|
} else {
|
|
413
413
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
@@ -429,7 +429,7 @@ async function flushChangelogVersion(): Promise<void> {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
async function createSessionManager(
|
|
432
|
+
export async function createSessionManager(
|
|
433
433
|
parsed: Args,
|
|
434
434
|
cwd: string,
|
|
435
435
|
activeSettings: Settings = settings,
|
|
@@ -482,6 +482,17 @@ async function createSessionManager(
|
|
|
482
482
|
if (parsed.sessionDir) {
|
|
483
483
|
return SessionManager.create(cwd, parsed.sessionDir);
|
|
484
484
|
}
|
|
485
|
+
// A lifecycle `/session_create` child must start a FRESH session that adopts
|
|
486
|
+
// the pre-allocated id (SKC_SESSION_ID), never auto-resume existing history in
|
|
487
|
+
// the target cwd — otherwise the daemon/tmux id and the session header id
|
|
488
|
+
// diverge and close/resume-by-create-id break. Resume children are launched
|
|
489
|
+
// with `--resume <id>` (handled above) and carry no SKC_LIFECYCLE_REQUEST_ID.
|
|
490
|
+
if (
|
|
491
|
+
process.env.SKC_LIFECYCLE_REQUEST_ID &&
|
|
492
|
+
/^[A-Za-z0-9._-]{1,128}$/.test(process.env.SKC_SESSION_ID?.trim() ?? "")
|
|
493
|
+
) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
485
496
|
// Auto-resume: behave like --continue if the setting is enabled and a prior
|
|
486
497
|
// session exists. When a prior session is resumed, mark parsed.continue so
|
|
487
498
|
// buildSessionOptions restores the session's model/thinking instead of
|
|
@@ -153,6 +153,14 @@ function jsonResponse(status: number, body: unknown): Response {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function isBridgeControllerOwner(options: BridgeFetchHandlerOptions, ownerToken: string): boolean {
|
|
157
|
+
if (!ownerToken) return false;
|
|
158
|
+
const ownerTokens = [options.permissionBroker?.ownerToken, options.uiBroker?.ownerToken].filter(
|
|
159
|
+
(token): token is string => typeof token === "string" && token.length > 0,
|
|
160
|
+
);
|
|
161
|
+
return ownerTokens.length > 0 && ownerTokens.every(token => token === ownerToken);
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
function parseBridgeScopes(value: string | undefined): readonly BridgeCommandScope[] {
|
|
157
165
|
if (!value?.trim()) return DEFAULT_BRIDGE_SCOPES;
|
|
158
166
|
const allowed = new Set(BRIDGE_COMMAND_SCOPES);
|
|
@@ -425,6 +433,9 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
|
|
|
425
433
|
"answer" in payload &&
|
|
426
434
|
(correlationId === (payload as RpcWorkflowGateResponse).gate_id || correlationId.startsWith("wg_"))
|
|
427
435
|
) {
|
|
436
|
+
if (!isBridgeControllerOwner(options, ownerToken)) {
|
|
437
|
+
return jsonResponse(403, { status: "rejected", code: "not_controller" });
|
|
438
|
+
}
|
|
428
439
|
try {
|
|
429
440
|
const resolution = await options.unattendedControlPlane?.resolveGate({
|
|
430
441
|
gate_id: (payload as RpcWorkflowGateResponse).gate_id,
|
|
@@ -7,6 +7,54 @@ import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
|
7
7
|
import { isSilentAbort } from "../../session/messages";
|
|
8
8
|
import { resolveImageOptions } from "../../tools/render-utils";
|
|
9
9
|
|
|
10
|
+
const THINKING_REPETITION_ELIDE_MIN_RUN = 24;
|
|
11
|
+
const THINKING_REPETITION_VISIBLE_TOKENS = 3;
|
|
12
|
+
const THINKING_REPETITION_TOKEN_PATTERN = /[\p{L}\p{N}_'-]{1,32}/gu;
|
|
13
|
+
|
|
14
|
+
interface ThinkingRepetitionToken {
|
|
15
|
+
text: string;
|
|
16
|
+
normalized: string;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function elideRunawayThinkingRepetition(text: string): string {
|
|
22
|
+
const tokens: ThinkingRepetitionToken[] = [];
|
|
23
|
+
for (const match of text.matchAll(THINKING_REPETITION_TOKEN_PATTERN)) {
|
|
24
|
+
if (match.index === undefined) continue;
|
|
25
|
+
tokens.push({
|
|
26
|
+
text: match[0],
|
|
27
|
+
normalized: match[0].toLocaleLowerCase("en-US"),
|
|
28
|
+
start: match.index,
|
|
29
|
+
end: match.index + match[0].length,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let runStart = 0;
|
|
34
|
+
for (let i = 1; i <= tokens.length; i++) {
|
|
35
|
+
const current = tokens[i];
|
|
36
|
+
const previous = tokens[i - 1];
|
|
37
|
+
if (current && previous && current.normalized === previous.normalized) continue;
|
|
38
|
+
|
|
39
|
+
const runLength = i - runStart;
|
|
40
|
+
if (runLength >= THINKING_REPETITION_ELIDE_MIN_RUN) {
|
|
41
|
+
const first = tokens[runStart];
|
|
42
|
+
const last = tokens[i - 1];
|
|
43
|
+
if (!first || !last) return text;
|
|
44
|
+
|
|
45
|
+
const visibleCount = Math.min(THINKING_REPETITION_VISIBLE_TOKENS, runLength);
|
|
46
|
+
const visible = Array.from({ length: visibleCount }, () => first.text).join(" ");
|
|
47
|
+
const omitted = runLength - visibleCount;
|
|
48
|
+
const marker = `${visible} … [thinking loop elided: "${first.text}" repeated ${omitted} more times]`;
|
|
49
|
+
return `${text.slice(0, first.start)}${marker}${text.slice(last.end)}`.trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
runStart = i;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
|
|
10
58
|
/**
|
|
11
59
|
* Component that renders a complete assistant message
|
|
12
60
|
*/
|
|
@@ -128,7 +176,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
128
176
|
#renderThinkingBlock(content: { thinking: string }): Markdown {
|
|
129
177
|
const cached = this.#contentBlocksCache.get(content);
|
|
130
178
|
if (cached?.source === content.thinking) return cached.component as Markdown;
|
|
131
|
-
const trimmed = content.thinking.trim();
|
|
179
|
+
const trimmed = elideRunawayThinkingRepetition(content.thinking.trim());
|
|
132
180
|
const component = new Markdown(trimmed, 1, 0, getMarkdownTheme(), {
|
|
133
181
|
color: (text: string) => theme.fg("thinkingText", text),
|
|
134
182
|
italic: true,
|
|
@@ -18,6 +18,7 @@ type ConfigurableEditorAction = Extract<
|
|
|
18
18
|
| "app.editor.external"
|
|
19
19
|
| "app.history.search"
|
|
20
20
|
| "app.message.dequeue"
|
|
21
|
+
| "app.message.followUp"
|
|
21
22
|
| "app.message.queue"
|
|
22
23
|
| "app.clipboard.pasteImage"
|
|
23
24
|
| "app.clipboard.copyPrompt"
|
|
@@ -40,6 +41,7 @@ const CONFIGURABLE_EDITOR_ACTIONS = [
|
|
|
40
41
|
"app.thinking.toggle",
|
|
41
42
|
"app.editor.external",
|
|
42
43
|
"app.history.search",
|
|
44
|
+
"app.message.followUp",
|
|
43
45
|
"app.message.queue",
|
|
44
46
|
"app.message.dequeue",
|
|
45
47
|
"app.clipboard.pasteImage",
|
|
@@ -8,6 +8,7 @@ import { shortenPath } from "../../tools/render-utils";
|
|
|
8
8
|
import * as git from "../../utils/git";
|
|
9
9
|
import { sanitizeStatusText } from "../shared";
|
|
10
10
|
import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
|
|
11
|
+
import { resolveCurrentBranch } from "./status-line/git-utils";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Footer component that shows pwd, token stats, and context usage
|
|
@@ -103,9 +104,7 @@ export class FooterComponent implements Component {
|
|
|
103
104
|
return this.#cachedBranch;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
this.#cachedBranch =
|
|
108
|
-
headState === null ? null : headState.kind === "ref" ? (headState.branchName ?? headState.ref) : "detached";
|
|
107
|
+
this.#cachedBranch = resolveCurrentBranch(getProjectDir()).branch;
|
|
109
108
|
return this.#cachedBranch;
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -66,7 +66,7 @@ export class HookEditorComponent extends Container {
|
|
|
66
66
|
|
|
67
67
|
// Hint
|
|
68
68
|
const hint = this.#promptStyle
|
|
69
|
-
? "enter submit esc cancel ctrl+g external editor"
|
|
69
|
+
? "enter submit shift+enter/ctrl+j newline esc cancel ctrl+g external editor"
|
|
70
70
|
: "ctrl+enter submit esc cancel ctrl+g external editor";
|
|
71
71
|
this.addChild(new Text(theme.fg("dim", hint), 1, 0));
|
|
72
72
|
|
|
@@ -29,22 +29,6 @@ import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
|
29
29
|
import { CountdownTimer } from "./countdown-timer";
|
|
30
30
|
import { DynamicBorder } from "./dynamic-border";
|
|
31
31
|
|
|
32
|
-
const SGR_MOUSE_PRESS_PATTERN = /^\x1b\[<(\d+);\d+;\d+M$/;
|
|
33
|
-
const MOUSE_WHEEL_TITLE_SCROLL_ROWS = 3;
|
|
34
|
-
|
|
35
|
-
function getMouseWheelTitleScrollRows(keyData: string): number {
|
|
36
|
-
const match = SGR_MOUSE_PRESS_PATTERN.exec(keyData);
|
|
37
|
-
if (!match) return 0;
|
|
38
|
-
|
|
39
|
-
const button = Number.parseInt(match[1] ?? "", 10);
|
|
40
|
-
if (!Number.isFinite(button) || (button & 64) === 0) return 0;
|
|
41
|
-
|
|
42
|
-
const wheelDirection = button & 3;
|
|
43
|
-
if (wheelDirection === 0) return -MOUSE_WHEEL_TITLE_SCROLL_ROWS;
|
|
44
|
-
if (wheelDirection === 1) return MOUSE_WHEEL_TITLE_SCROLL_ROWS;
|
|
45
|
-
return 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
32
|
export interface HookSelectorOptions {
|
|
49
33
|
tui?: TUI;
|
|
50
34
|
timeout?: number;
|
|
@@ -133,26 +117,58 @@ class ScrollableTitle extends Container {
|
|
|
133
117
|
|
|
134
118
|
render(width: number): string[] {
|
|
135
119
|
const lines = this.#markdown.render(width);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
if (lines.length <= this.#maxRows) {
|
|
121
|
+
this.#lastMaxScrollOffset = 0;
|
|
122
|
+
this.#scrollOffset = 0;
|
|
123
|
+
return lines;
|
|
124
|
+
}
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
126
|
+
if (this.#maxRows < 3) {
|
|
127
|
+
const maxScrollOffset = Math.max(0, lines.length - this.#maxRows);
|
|
128
|
+
this.#lastMaxScrollOffset = maxScrollOffset;
|
|
129
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#scrollOffset, maxScrollOffset));
|
|
130
|
+
|
|
131
|
+
const visibleLines = lines.slice(this.#scrollOffset, this.#scrollOffset + this.#maxRows);
|
|
132
|
+
const indicator =
|
|
133
|
+
this.#scrollOffset === 0
|
|
134
|
+
? theme.fg("dim", " PgDn↓")
|
|
135
|
+
: this.#scrollOffset >= maxScrollOffset
|
|
136
|
+
? theme.fg("dim", " PgUp↑")
|
|
137
|
+
: theme.fg("dim", " PgUp/PgDn↕");
|
|
138
|
+
const lastIndex = visibleLines.length - 1;
|
|
139
|
+
const availableWidth = Math.max(1, width - visibleWidth(indicator));
|
|
140
|
+
const fittedLine = truncateToWidth(visibleLines[lastIndex] ?? "", availableWidth);
|
|
141
|
+
visibleLines[lastIndex] = `${fittedLine}${indicator}`;
|
|
142
142
|
return visibleLines;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
let showTopIndicator = this.#scrollOffset > 0;
|
|
146
|
+
let showBottomIndicator = true;
|
|
147
|
+
let contentRows = 1;
|
|
148
|
+
let maxScrollOffset = 0;
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < 4; i++) {
|
|
151
|
+
contentRows = Math.max(1, this.#maxRows - (showTopIndicator ? 1 : 0) - (showBottomIndicator ? 1 : 0));
|
|
152
|
+
maxScrollOffset = Math.max(0, lines.length - contentRows);
|
|
153
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#scrollOffset, maxScrollOffset));
|
|
154
|
+
|
|
155
|
+
const nextShowTopIndicator = this.#scrollOffset > 0;
|
|
156
|
+
const nextShowBottomIndicator = this.#scrollOffset + contentRows < lines.length;
|
|
157
|
+
if (nextShowTopIndicator === showTopIndicator && nextShowBottomIndicator === showBottomIndicator) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
showTopIndicator = nextShowTopIndicator;
|
|
161
|
+
showBottomIndicator = nextShowBottomIndicator;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.#lastMaxScrollOffset = maxScrollOffset;
|
|
165
|
+
|
|
166
|
+
const visibleLines = lines.slice(this.#scrollOffset, this.#scrollOffset + contentRows);
|
|
167
|
+
const result: string[] = [];
|
|
168
|
+
if (showTopIndicator) result.push(theme.fg("dim", truncateToWidth("▲ more", width)));
|
|
169
|
+
result.push(...visibleLines);
|
|
170
|
+
if (showBottomIndicator) result.push(theme.fg("dim", truncateToWidth("▼ more", width)));
|
|
171
|
+
return result.slice(0, this.#maxRows);
|
|
156
172
|
}
|
|
157
173
|
}
|
|
158
174
|
|
|
@@ -455,13 +471,6 @@ export class HookSelectorComponent extends Container {
|
|
|
455
471
|
// Reset countdown on any interaction
|
|
456
472
|
this.#countdown?.reset();
|
|
457
473
|
|
|
458
|
-
if (this.#scrollTitleRows !== undefined) {
|
|
459
|
-
const wheelRows = getMouseWheelTitleScrollRows(keyData);
|
|
460
|
-
if (wheelRows !== 0) {
|
|
461
|
-
this.#scrollableTitle?.scrollBy(wheelRows);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
474
|
if (this.#scrollTitleRows !== undefined && matchesKey(keyData, "pageUp")) {
|
|
466
475
|
this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
|
|
467
476
|
return;
|
|
@@ -470,6 +479,14 @@ export class HookSelectorComponent extends Container {
|
|
|
470
479
|
this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
|
|
471
480
|
return;
|
|
472
481
|
}
|
|
482
|
+
if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+u")) {
|
|
483
|
+
this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+d")) {
|
|
487
|
+
this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
473
490
|
if (this.#inlineEditor) {
|
|
474
491
|
this.#handleInputModeKey(keyData, this.#inlineEditor);
|
|
475
492
|
return;
|
|
@@ -538,16 +555,23 @@ export class HookSelectorComponent extends Container {
|
|
|
538
555
|
editor.setBorderVisible(false);
|
|
539
556
|
editor.setPromptGutter("> ");
|
|
540
557
|
editor.disableSubmit = true;
|
|
558
|
+
// Mark the inline editor focused only when mirroring the app's hardware-cursor
|
|
559
|
+
// mode, so it emits CURSOR_MARKER at the input caret for IME preedit anchoring
|
|
560
|
+
// without changing legacy non-hardware-cursor layout.
|
|
561
|
+
const useTerminalCursor = this.#tui?.getShowHardwareCursor() ?? false;
|
|
562
|
+
editor.focused = useTerminalCursor;
|
|
563
|
+
editor.setUseTerminalCursor(useTerminalCursor);
|
|
541
564
|
if (this.#autocompleteProvider) {
|
|
542
565
|
editor.setAutocompleteProvider(this.#autocompleteProvider);
|
|
543
566
|
}
|
|
544
567
|
this.#inlineEditor = editor;
|
|
545
568
|
this.#inputArea.addChild(new Spacer(1));
|
|
546
569
|
this.#inputArea.addChild(editor);
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
570
|
+
const helpText =
|
|
571
|
+
this.#scrollTitleRows === undefined
|
|
572
|
+
? "enter submit esc back to options ctrl+g external editor"
|
|
573
|
+
: "enter submit esc back to options PgUp/PgDn: question · Wheel: transcript";
|
|
574
|
+
this.#helpTextComponent.setText(theme.fg("dim", helpText));
|
|
551
575
|
this.invalidate();
|
|
552
576
|
}
|
|
553
577
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import type { ModelRegistry, SkcModelAssignmentTargetId } from "../../config/model-registry";
|
|
21
21
|
import {
|
|
22
22
|
isAuthenticated,
|
|
23
|
+
kNoAuth,
|
|
23
24
|
SKC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
24
25
|
SKC_MODEL_ASSIGNMENT_TARGETS,
|
|
25
26
|
} from "../../config/model-registry";
|
|
@@ -228,8 +229,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
228
229
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
229
230
|
#temporaryOnly: boolean;
|
|
230
231
|
#currentModel?: Model;
|
|
232
|
+
#currentThinkingLevel?: ThinkingLevel;
|
|
233
|
+
#activeModelProfile?: string;
|
|
231
234
|
#isFastForProvider: (provider?: string) => boolean = () => false;
|
|
232
235
|
#isFastForSubagentProvider: (provider?: string) => boolean = () => false;
|
|
236
|
+
#isCurrentModelFastModeActive: () => boolean = () => false;
|
|
233
237
|
#pendingActionItem?: ModelItem | CanonicalModelItem;
|
|
234
238
|
#selectedActionIndex: number = 0;
|
|
235
239
|
#pendingThinkingChoice?: PendingThinkingChoice;
|
|
@@ -265,6 +269,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
265
269
|
sessionId?: string;
|
|
266
270
|
isFastForProvider?: (provider?: string) => boolean;
|
|
267
271
|
isFastForSubagentProvider?: (provider?: string) => boolean;
|
|
272
|
+
isCurrentModelFastModeActive?: () => boolean;
|
|
273
|
+
currentThinkingLevel?: ThinkingLevel;
|
|
274
|
+
activeModelProfile?: string;
|
|
268
275
|
},
|
|
269
276
|
) {
|
|
270
277
|
super();
|
|
@@ -278,8 +285,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
278
285
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
279
286
|
this.#authSessionId = options?.sessionId;
|
|
280
287
|
this.#currentModel = _currentModel;
|
|
288
|
+
this.#currentThinkingLevel = options?.currentThinkingLevel;
|
|
289
|
+
this.#activeModelProfile = options?.activeModelProfile;
|
|
281
290
|
this.#isFastForProvider = options?.isFastForProvider ?? (() => false);
|
|
282
291
|
this.#isFastForSubagentProvider = options?.isFastForSubagentProvider ?? (() => false);
|
|
292
|
+
// Current-model EFFECTIVE fast state. Defaults to intent for the current
|
|
293
|
+
// model so existing callers/tests keep prior behavior; production wires the
|
|
294
|
+
// session's effective predicate so an auto-disabled provider shows no glyph.
|
|
295
|
+
this.#isCurrentModelFastModeActive =
|
|
296
|
+
options?.isCurrentModelFastModeActive ??
|
|
297
|
+
(() => (this.#currentModel ? this.#isFastForProvider(this.#currentModel.provider) : false));
|
|
283
298
|
const initialSearchInput = options?.initialSearchInput;
|
|
284
299
|
this.#viewMode = this.#temporaryOnly || initialSearchInput || scopedModels.length > 0 ? "models" : "presets";
|
|
285
300
|
|
|
@@ -373,6 +388,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
373
388
|
};
|
|
374
389
|
}
|
|
375
390
|
}
|
|
391
|
+
if (this.#activeModelProfile && this.#currentModel) {
|
|
392
|
+
this.#roles.default = {
|
|
393
|
+
model: this.#currentModel,
|
|
394
|
+
thinkingLevel: this.#currentThinkingLevel ?? ThinkingLevel.Inherit,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
refreshRoleAssignments(
|
|
400
|
+
options: { currentModel?: Model; currentThinkingLevel?: ThinkingLevel; activeModelProfile?: string } = {},
|
|
401
|
+
): void {
|
|
402
|
+
if ("currentModel" in options) this.#currentModel = options.currentModel;
|
|
403
|
+
if ("currentThinkingLevel" in options) this.#currentThinkingLevel = options.currentThinkingLevel;
|
|
404
|
+
if ("activeModelProfile" in options) this.#activeModelProfile = options.activeModelProfile;
|
|
405
|
+
this.#roles = {};
|
|
406
|
+
this.#loadRoleModels();
|
|
407
|
+
this.#applyTabFilter();
|
|
376
408
|
}
|
|
377
409
|
|
|
378
410
|
#sortModels(models: ModelItem[]): void {
|
|
@@ -773,7 +805,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
773
805
|
const entries = await Promise.all(
|
|
774
806
|
[...providers].map(async provider => {
|
|
775
807
|
const apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.#authSessionId);
|
|
776
|
-
|
|
808
|
+
// "Usable" — not "has a real API key". getApiKeyForProvider returns the
|
|
809
|
+
// kNoAuth ("N/A") sentinel for keyless/no-auth providers (local LLMs,
|
|
810
|
+
// `--auth none` custom providers), which are usable WITHOUT a key. But
|
|
811
|
+
// isAuthenticated() deliberately rejects kNoAuth, so using it alone would
|
|
812
|
+
// flag those providers unauthenticated and silently bail their presets
|
|
813
|
+
// into a login flow instead of applying. Treat kNoAuth as usable here,
|
|
814
|
+
// matching setModel()/getApiKey() which already accept it.
|
|
815
|
+
return [provider, isAuthenticated(apiKey) || apiKey === kNoAuth] as const;
|
|
777
816
|
}),
|
|
778
817
|
);
|
|
779
818
|
this.#providerAuthById = new Map(entries);
|
|
@@ -980,32 +1019,46 @@ export class ModelSelectorComponent extends Container {
|
|
|
980
1019
|
|
|
981
1020
|
// Build role badges (inverted: color as background, black text)
|
|
982
1021
|
const roleBadgeTokens: string[] = [];
|
|
983
|
-
|
|
1022
|
+
// Whether a non-subagent (modelRoles) badge on the CURRENT model row already
|
|
1023
|
+
// rendered the current-model EFFECTIVE glyph. Only that case should suppress
|
|
1024
|
+
// the standalone current glyph below — a subagent-only match must NOT, since
|
|
1025
|
+
// subagent badges reflect the subagent tier, not the current model.
|
|
1026
|
+
let currentModelEffectiveGlyphRendered = false;
|
|
984
1027
|
for (const role of SKC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
985
1028
|
const roleInfo = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
986
1029
|
const assigned = this.#roles[role];
|
|
987
1030
|
if (roleInfo.tag && assigned && modelsAreEqual(assigned.model, item.model)) {
|
|
988
|
-
roleMatched = true;
|
|
989
1031
|
const badge = makeInvertedBadge(roleInfo.tag, roleInfo.color ?? "muted");
|
|
990
1032
|
const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label;
|
|
991
|
-
|
|
992
|
-
//
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1033
|
+
|
|
1034
|
+
// Subagent roles (task.agentModelOverrides) run under task.serviceTier,
|
|
1035
|
+
// so their ⚡ uses the effective subagent tier. A non-subagent
|
|
1036
|
+
// (modelRoles) badge on the CURRENT model row uses the current-model
|
|
1037
|
+
// effective predicate so a provider auto-disable hides the glyph;
|
|
1038
|
+
// other modelRoles rows show pure intent.
|
|
1039
|
+
const isSubagentRole = roleInfo.settingsPath === "task.agentModelOverrides";
|
|
1040
|
+
const isCurrentRow = this.#currentModel !== undefined && modelsAreEqual(this.#currentModel, item.model);
|
|
1041
|
+
const roleFast = isSubagentRole
|
|
1042
|
+
? this.#isFastForSubagentProvider(assigned.model.provider)
|
|
1043
|
+
: isCurrentRow
|
|
1044
|
+
? this.#isCurrentModelFastModeActive()
|
|
996
1045
|
: this.#isFastForProvider(assigned.model.provider);
|
|
1046
|
+
if (roleFast && isCurrentRow && !isSubagentRole) {
|
|
1047
|
+
currentModelEffectiveGlyphRendered = true;
|
|
1048
|
+
}
|
|
997
1049
|
const fastSuffix = roleFast ? ` ${theme.icon.fast}` : "";
|
|
998
1050
|
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}${fastSuffix}`);
|
|
999
1051
|
}
|
|
1000
1052
|
}
|
|
1001
1053
|
// Active/current non-role row: show the fast glyph on the session's current
|
|
1002
|
-
// model row
|
|
1003
|
-
//
|
|
1054
|
+
// model row. Suppress only when a non-subagent current-row badge already
|
|
1055
|
+
// rendered the current-model effective glyph (duplicate-glyph guard) — a
|
|
1056
|
+
// subagent-only match must not hide the current model's own indicator.
|
|
1004
1057
|
if (
|
|
1005
|
-
!
|
|
1058
|
+
!currentModelEffectiveGlyphRendered &&
|
|
1006
1059
|
this.#currentModel !== undefined &&
|
|
1007
1060
|
modelsAreEqual(this.#currentModel, item.model) &&
|
|
1008
|
-
this.#
|
|
1061
|
+
this.#isCurrentModelFastModeActive()
|
|
1009
1062
|
) {
|
|
1010
1063
|
roleBadgeTokens.push(theme.icon.fast);
|
|
1011
1064
|
}
|
|
@@ -62,7 +62,6 @@ export interface SubmenuSettingDef extends BaseSettingDef {
|
|
|
62
62
|
|
|
63
63
|
export interface TextInputSettingDef extends BaseSettingDef {
|
|
64
64
|
type: "text";
|
|
65
|
-
secret?: boolean;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef | TextInputSettingDef;
|
|
@@ -129,7 +128,7 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
129
128
|
if (options) {
|
|
130
129
|
return { ...base, type: "submenu", options };
|
|
131
130
|
}
|
|
132
|
-
return { ...base, type: "text"
|
|
131
|
+
return { ...base, type: "text" };
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
return null;
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
Text,
|
|
15
15
|
} from "@sayknow-cli/tui";
|
|
16
16
|
import { type SettingPath, settings } from "../../config/settings";
|
|
17
|
-
import { maskToken } from "../../notifications/config";
|
|
18
17
|
import type {
|
|
19
18
|
SettingTab,
|
|
20
19
|
StatusLinePreset,
|
|
@@ -353,9 +352,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
353
352
|
id: def.path,
|
|
354
353
|
label,
|
|
355
354
|
description,
|
|
356
|
-
currentValue:
|
|
357
|
-
? maskToken(currentValue as string | undefined)
|
|
358
|
-
: ((currentValue as string) ?? ""),
|
|
355
|
+
currentValue: (currentValue as string) ?? "",
|
|
359
356
|
submenu: (cv, done) => this.#createTextInput(def, cv, done),
|
|
360
357
|
};
|
|
361
358
|
}
|
|
@@ -483,12 +480,10 @@ export class SettingsSelectorComponent extends Container {
|
|
|
483
480
|
this.#textInputActive = false;
|
|
484
481
|
done(value);
|
|
485
482
|
};
|
|
486
|
-
// For secret fields, pre-fill with the real stored value rather than the masked display value.
|
|
487
|
-
const prefillValue = def.secret ? ((settings.get(def.path) as string) ?? "") : currentValue;
|
|
488
483
|
return new TextInputSubmenu(
|
|
489
484
|
tSettingLabel(def),
|
|
490
485
|
tSettingDesc(def),
|
|
491
|
-
|
|
486
|
+
currentValue,
|
|
492
487
|
value => {
|
|
493
488
|
// Empty string clears the setting; undefined-typed string settings
|
|
494
489
|
// store "" which the browser.ts expandPath ignores (no-op fallback).
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { GitHeadState } from "../../../utils/git";
|
|
2
|
+
import * as git from "../../../utils/git";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Extract "owner/repo" from a GitHub remote URL.
|
|
3
6
|
* Handles HTTPS, SSH (scp-style), and git:// protocols.
|
|
@@ -40,3 +43,25 @@ export function canReuseCachedPr(
|
|
|
40
43
|
): boolean {
|
|
41
44
|
return cachedPr !== undefined && currentContext !== null && isSamePrCacheContext(cachedContext, currentContext);
|
|
42
45
|
}
|
|
46
|
+
|
|
47
|
+
export interface CurrentBranchState {
|
|
48
|
+
readonly branch: string | null;
|
|
49
|
+
readonly repoId: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveCurrentBranch(
|
|
53
|
+
cwd: string,
|
|
54
|
+
resolveHead: (cwd: string) => GitHeadState | null = git.head.resolveSync,
|
|
55
|
+
): CurrentBranchState {
|
|
56
|
+
try {
|
|
57
|
+
const head = resolveHead(cwd);
|
|
58
|
+
if (!head) return { branch: null, repoId: null };
|
|
59
|
+
return {
|
|
60
|
+
branch: head.kind === "ref" ? (head.branchName ?? head.ref) : "detached",
|
|
61
|
+
repoId: head.headPath,
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof Error) return { branch: null, repoId: null };
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createPrCacheContext,
|
|
21
21
|
isSamePrCacheContext,
|
|
22
22
|
type PrCacheContext,
|
|
23
|
+
resolveCurrentBranch,
|
|
23
24
|
} from "./status-line/git-utils";
|
|
24
25
|
import { getPreset } from "./status-line/presets";
|
|
25
26
|
import { renderSegment, type SegmentContext } from "./status-line/segments";
|
|
@@ -303,19 +304,13 @@ export class StatusLineComponent implements Component {
|
|
|
303
304
|
this.#cachedPrContext = undefined;
|
|
304
305
|
}
|
|
305
306
|
#getCurrentBranch(): string | null {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === gitHeadPath) {
|
|
307
|
+
const current = resolveCurrentBranch(getProjectDir());
|
|
308
|
+
if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === current.repoId) {
|
|
309
309
|
return this.#cachedBranch;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
this.#cachedBranchRepoId =
|
|
313
|
-
|
|
314
|
-
this.#cachedBranch = null;
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
this.#cachedBranch = head.kind === "ref" ? (head.branchName ?? head.ref) : "detached";
|
|
312
|
+
this.#cachedBranchRepoId = current.repoId;
|
|
313
|
+
this.#cachedBranch = current.branch;
|
|
319
314
|
|
|
320
315
|
return this.#cachedBranch ?? null;
|
|
321
316
|
}
|
|
@@ -680,7 +675,11 @@ export class StatusLineComponent implements Component {
|
|
|
680
675
|
const effectiveSettings = this.#resolveSettings();
|
|
681
676
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
682
677
|
|
|
683
|
-
|
|
678
|
+
// Use the subtle surface tone (the same elevated background as user-message
|
|
679
|
+
// bubbles) instead of the heavy `statusLineBg` block, so the rail layers
|
|
680
|
+
// just above the base background as a quiet zone rather than a solid bar.
|
|
681
|
+
// Resolving through a semantic slot keeps it correct across every theme.
|
|
682
|
+
const bgAnsi = theme.getBgAnsi("userMessageBg");
|
|
684
683
|
const fgAnsi = theme.getFgAnsi("text");
|
|
685
684
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
686
685
|
|
|
@@ -76,9 +76,8 @@ export class WelcomeComponent implements Component {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
render(termWidth: number): string[] {
|
|
79
|
-
// Box dimensions
|
|
80
|
-
const
|
|
81
|
-
const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
|
|
79
|
+
// Box dimensions track the live viewport so wide terminals feel intentionally full-screen.
|
|
80
|
+
const boxWidth = Math.max(0, termWidth - 2);
|
|
82
81
|
if (boxWidth < 4) {
|
|
83
82
|
return [];
|
|
84
83
|
}
|