@sayknow-cli/coding-agent 0.3.0 → 0.3.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/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +2 -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/settings-schema.d.ts +39 -2
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/defaults/skc-defaults.d.ts +2 -13
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- 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/lsp/types.d.ts +2 -0
- 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/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +9 -1
- package/dist/types/notifications/engine.d.ts +59 -0
- 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/managed-daemon.d.ts +48 -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 +133 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/threaded-inbound.d.ts +19 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -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 +16 -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/ralplan-runtime.d.ts +1 -1
- 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/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/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +25 -0
- 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/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +14 -10
- 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/scripts/verify-insane-vendor.ts +132 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/fast-help.ts +30 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/notify-cli.ts +152 -5
- package/src/cli/plugin-cli.ts +66 -3
- 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/commands/team.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +55 -7
- package/src/config/settings-schema.ts +30 -1
- 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/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/defaults/skc-defaults.ts +2 -27
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/shared-events.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/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- 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 +56 -11
- 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/event-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +53 -11
- package/src/modes/interactive-mode.ts +50 -3
- 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/utils/context-usage.ts +2 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config.ts +23 -2
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +417 -45
- 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/managed-daemon.ts +163 -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 +984 -242
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/threaded-inbound.ts +60 -4
- package/src/notifications/threaded-render.ts +20 -2
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +248 -125
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +85 -22
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- 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 +25 -8
- package/src/skc-runtime/ultragoal-runtime.ts +75 -15
- package/src/skc-runtime/workflow-manifest.generated.json +29 -0
- package/src/skc-runtime/workflow-manifest.ts +7 -2
- 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/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +94 -1
- package/src/tools/index.ts +6 -1
- package/src/tools/telegram-send.ts +137 -0
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +159 -0
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
package/src/lsp/config.ts
CHANGED
|
@@ -88,6 +88,7 @@ function normalizeServerConfig(name: string, config: RawServerConfig): ServerCon
|
|
|
88
88
|
: isRecord(config.initializationOptions)
|
|
89
89
|
? config.initializationOptions
|
|
90
90
|
: undefined;
|
|
91
|
+
const supersedes = normalizeStringArray(config.supersedes);
|
|
91
92
|
|
|
92
93
|
return {
|
|
93
94
|
...config,
|
|
@@ -96,6 +97,7 @@ function normalizeServerConfig(name: string, config: RawServerConfig): ServerCon
|
|
|
96
97
|
fileTypes,
|
|
97
98
|
rootMarkers,
|
|
98
99
|
...(initOptions ? { initOptions } : {}),
|
|
100
|
+
...(supersedes ? { supersedes } : {}),
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -144,6 +146,18 @@ function mergeServers(
|
|
|
144
146
|
return merged;
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
function applyServerPrecedence(servers: Record<string, ServerConfig>): Record<string, ServerConfig> {
|
|
150
|
+
const suppressed = new Set<string>();
|
|
151
|
+
for (const config of Object.values(servers)) {
|
|
152
|
+
for (const serverName of config.supersedes ?? []) {
|
|
153
|
+
suppressed.add(serverName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (suppressed.size === 0) return servers;
|
|
157
|
+
|
|
158
|
+
return Object.fromEntries(Object.entries(servers).filter(([name]) => !suppressed.has(name)));
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
function applyRuntimeDefaults(servers: Record<string, ServerConfig>): Record<string, ServerConfig> {
|
|
148
162
|
const updated: Record<string, ServerConfig> = { ...servers };
|
|
149
163
|
|
|
@@ -422,9 +436,8 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
422
436
|
detected[name] = { ...config, resolvedCommand: resolved };
|
|
423
437
|
}
|
|
424
438
|
|
|
425
|
-
return { servers: detected, idleTimeoutMs };
|
|
439
|
+
return { servers: applyServerPrecedence(detected), idleTimeoutMs };
|
|
426
440
|
}
|
|
427
|
-
|
|
428
441
|
// Merge overrides with defaults and filter to available servers
|
|
429
442
|
const mergedWithRuntime = applyRuntimeDefaults(mergedServers);
|
|
430
443
|
const available: Record<string, ServerConfig> = {};
|
|
@@ -437,7 +450,7 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
437
450
|
available[name] = { ...config, resolvedCommand: resolved };
|
|
438
451
|
}
|
|
439
452
|
|
|
440
|
-
return { servers: available, idleTimeoutMs };
|
|
453
|
+
return { servers: applyServerPrecedence(available), idleTimeoutMs };
|
|
441
454
|
}
|
|
442
455
|
|
|
443
456
|
// =============================================================================
|
package/src/lsp/defaults.json
CHANGED
|
@@ -342,6 +342,13 @@
|
|
|
342
342
|
"fileTypes": [".php"],
|
|
343
343
|
"rootMarkers": ["composer.json", ".phpactor.json", ".phpactor.yml"]
|
|
344
344
|
},
|
|
345
|
+
"csharp-ls": {
|
|
346
|
+
"command": "csharp-ls",
|
|
347
|
+
"args": [],
|
|
348
|
+
"fileTypes": [".cs", ".csx"],
|
|
349
|
+
"rootMarkers": ["*.sln", "*.slnx", "*.csproj", "global.json"],
|
|
350
|
+
"supersedes": ["omnisharp"]
|
|
351
|
+
},
|
|
345
352
|
"omnisharp": {
|
|
346
353
|
"command": "omnisharp",
|
|
347
354
|
"args": ["-z", "--hostPID", "$PID", "--encoding", "utf-8", "--languageserver"],
|
package/src/lsp/types.ts
CHANGED
|
@@ -358,6 +358,8 @@ export interface ServerConfig {
|
|
|
358
358
|
/** Per-server warmup timeout in milliseconds. Overrides the global WARMUP_TIMEOUT_MS for this server during startup. */
|
|
359
359
|
warmupTimeoutMs?: number;
|
|
360
360
|
capabilities?: ServerCapabilities;
|
|
361
|
+
/** Names of lower-precedence servers to drop when this server is available. */
|
|
362
|
+
supersedes?: string[];
|
|
361
363
|
/** If true, this is a linter/formatter server (e.g., Biome) - used only for diagnostics/actions, not type intelligence */
|
|
362
364
|
isLinter?: boolean;
|
|
363
365
|
/** Resolved absolute path to the command binary (set during config loading) */
|
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
|
|
|
@@ -228,8 +228,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
228
228
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
229
229
|
#temporaryOnly: boolean;
|
|
230
230
|
#currentModel?: Model;
|
|
231
|
+
#currentThinkingLevel?: ThinkingLevel;
|
|
232
|
+
#activeModelProfile?: string;
|
|
231
233
|
#isFastForProvider: (provider?: string) => boolean = () => false;
|
|
232
234
|
#isFastForSubagentProvider: (provider?: string) => boolean = () => false;
|
|
235
|
+
#isCurrentModelFastModeActive: () => boolean = () => false;
|
|
233
236
|
#pendingActionItem?: ModelItem | CanonicalModelItem;
|
|
234
237
|
#selectedActionIndex: number = 0;
|
|
235
238
|
#pendingThinkingChoice?: PendingThinkingChoice;
|
|
@@ -265,6 +268,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
265
268
|
sessionId?: string;
|
|
266
269
|
isFastForProvider?: (provider?: string) => boolean;
|
|
267
270
|
isFastForSubagentProvider?: (provider?: string) => boolean;
|
|
271
|
+
isCurrentModelFastModeActive?: () => boolean;
|
|
272
|
+
currentThinkingLevel?: ThinkingLevel;
|
|
273
|
+
activeModelProfile?: string;
|
|
268
274
|
},
|
|
269
275
|
) {
|
|
270
276
|
super();
|
|
@@ -278,8 +284,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
278
284
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
279
285
|
this.#authSessionId = options?.sessionId;
|
|
280
286
|
this.#currentModel = _currentModel;
|
|
287
|
+
this.#currentThinkingLevel = options?.currentThinkingLevel;
|
|
288
|
+
this.#activeModelProfile = options?.activeModelProfile;
|
|
281
289
|
this.#isFastForProvider = options?.isFastForProvider ?? (() => false);
|
|
282
290
|
this.#isFastForSubagentProvider = options?.isFastForSubagentProvider ?? (() => false);
|
|
291
|
+
// Current-model EFFECTIVE fast state. Defaults to intent for the current
|
|
292
|
+
// model so existing callers/tests keep prior behavior; production wires the
|
|
293
|
+
// session's effective predicate so an auto-disabled provider shows no glyph.
|
|
294
|
+
this.#isCurrentModelFastModeActive =
|
|
295
|
+
options?.isCurrentModelFastModeActive ??
|
|
296
|
+
(() => (this.#currentModel ? this.#isFastForProvider(this.#currentModel.provider) : false));
|
|
283
297
|
const initialSearchInput = options?.initialSearchInput;
|
|
284
298
|
this.#viewMode = this.#temporaryOnly || initialSearchInput || scopedModels.length > 0 ? "models" : "presets";
|
|
285
299
|
|
|
@@ -373,6 +387,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
373
387
|
};
|
|
374
388
|
}
|
|
375
389
|
}
|
|
390
|
+
if (this.#activeModelProfile && this.#currentModel) {
|
|
391
|
+
this.#roles.default = {
|
|
392
|
+
model: this.#currentModel,
|
|
393
|
+
thinkingLevel: this.#currentThinkingLevel ?? ThinkingLevel.Inherit,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
refreshRoleAssignments(
|
|
399
|
+
options: { currentModel?: Model; currentThinkingLevel?: ThinkingLevel; activeModelProfile?: string } = {},
|
|
400
|
+
): void {
|
|
401
|
+
if ("currentModel" in options) this.#currentModel = options.currentModel;
|
|
402
|
+
if ("currentThinkingLevel" in options) this.#currentThinkingLevel = options.currentThinkingLevel;
|
|
403
|
+
if ("activeModelProfile" in options) this.#activeModelProfile = options.activeModelProfile;
|
|
404
|
+
this.#roles = {};
|
|
405
|
+
this.#loadRoleModels();
|
|
406
|
+
this.#applyTabFilter();
|
|
376
407
|
}
|
|
377
408
|
|
|
378
409
|
#sortModels(models: ModelItem[]): void {
|
|
@@ -980,32 +1011,46 @@ export class ModelSelectorComponent extends Container {
|
|
|
980
1011
|
|
|
981
1012
|
// Build role badges (inverted: color as background, black text)
|
|
982
1013
|
const roleBadgeTokens: string[] = [];
|
|
983
|
-
|
|
1014
|
+
// Whether a non-subagent (modelRoles) badge on the CURRENT model row already
|
|
1015
|
+
// rendered the current-model EFFECTIVE glyph. Only that case should suppress
|
|
1016
|
+
// the standalone current glyph below — a subagent-only match must NOT, since
|
|
1017
|
+
// subagent badges reflect the subagent tier, not the current model.
|
|
1018
|
+
let currentModelEffectiveGlyphRendered = false;
|
|
984
1019
|
for (const role of SKC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
985
1020
|
const roleInfo = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
986
1021
|
const assigned = this.#roles[role];
|
|
987
1022
|
if (roleInfo.tag && assigned && modelsAreEqual(assigned.model, item.model)) {
|
|
988
|
-
roleMatched = true;
|
|
989
1023
|
const badge = makeInvertedBadge(roleInfo.tag, roleInfo.color ?? "muted");
|
|
990
1024
|
const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label;
|
|
991
|
-
|
|
992
|
-
//
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1025
|
+
|
|
1026
|
+
// Subagent roles (task.agentModelOverrides) run under task.serviceTier,
|
|
1027
|
+
// so their ⚡ uses the effective subagent tier. A non-subagent
|
|
1028
|
+
// (modelRoles) badge on the CURRENT model row uses the current-model
|
|
1029
|
+
// effective predicate so a provider auto-disable hides the glyph;
|
|
1030
|
+
// other modelRoles rows show pure intent.
|
|
1031
|
+
const isSubagentRole = roleInfo.settingsPath === "task.agentModelOverrides";
|
|
1032
|
+
const isCurrentRow = this.#currentModel !== undefined && modelsAreEqual(this.#currentModel, item.model);
|
|
1033
|
+
const roleFast = isSubagentRole
|
|
1034
|
+
? this.#isFastForSubagentProvider(assigned.model.provider)
|
|
1035
|
+
: isCurrentRow
|
|
1036
|
+
? this.#isCurrentModelFastModeActive()
|
|
996
1037
|
: this.#isFastForProvider(assigned.model.provider);
|
|
1038
|
+
if (roleFast && isCurrentRow && !isSubagentRole) {
|
|
1039
|
+
currentModelEffectiveGlyphRendered = true;
|
|
1040
|
+
}
|
|
997
1041
|
const fastSuffix = roleFast ? ` ${theme.icon.fast}` : "";
|
|
998
1042
|
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}${fastSuffix}`);
|
|
999
1043
|
}
|
|
1000
1044
|
}
|
|
1001
1045
|
// Active/current non-role row: show the fast glyph on the session's current
|
|
1002
|
-
// model row
|
|
1003
|
-
//
|
|
1046
|
+
// model row. Suppress only when a non-subagent current-row badge already
|
|
1047
|
+
// rendered the current-model effective glyph (duplicate-glyph guard) — a
|
|
1048
|
+
// subagent-only match must not hide the current model's own indicator.
|
|
1004
1049
|
if (
|
|
1005
|
-
!
|
|
1050
|
+
!currentModelEffectiveGlyphRendered &&
|
|
1006
1051
|
this.#currentModel !== undefined &&
|
|
1007
1052
|
modelsAreEqual(this.#currentModel, item.model) &&
|
|
1008
|
-
this.#
|
|
1053
|
+
this.#isCurrentModelFastModeActive()
|
|
1009
1054
|
) {
|
|
1010
1055
|
roleBadgeTokens.push(theme.icon.fast);
|
|
1011
1056
|
}
|
|
@@ -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
|
}
|