@sayknow-cli/coding-agent 0.2.7 → 0.3.1
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 +20 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +25 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +136 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -1
- 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-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +69 -0
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +295 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +77 -0
- package/dist/types/notifications/threaded-render.d.ts +71 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +41 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/state-writer.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +14 -0
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +19 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +22 -0
- 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 +10 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +421 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -3
- package/src/commands/daemon.ts +47 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +143 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/skc/rules/ponytail.md +68 -0
- package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/ultragoal/SKILL.md +16 -0
- package/src/edit/modes/replace.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/hashline/hash.ts +1 -1
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- 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 +30 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/settings-selector.ts +7 -2
- package/src/modes/controllers/event-controller.ts +35 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +52 -4
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/theme/theme.ts +4 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +128 -0
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +842 -0
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1591 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +136 -0
- package/src/notifications/threaded-render.ts +173 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/rlm/index.ts +19 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +195 -54
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skc-runtime/deep-interview-recorder.ts +2 -2
- package/src/skc-runtime/launch-tmux.ts +27 -5
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- package/src/skc-runtime/state-runtime.ts +18 -10
- package/src/skc-runtime/state-writer.ts +8 -8
- package/src/skc-runtime/tmux-common.ts +8 -0
- package/src/skc-runtime/tmux-sessions.ts +8 -1
- package/src/skc-runtime/ultragoal-guard.ts +57 -2
- package/src/skc-runtime/ultragoal-runtime.ts +105 -19
- package/src/skc-runtime/workflow-manifest.generated.json +56 -2
- package/src/skc-runtime/workflow-manifest.ts +18 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +77 -6
- package/src/tools/fetch.ts +78 -1
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +22 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/src/tools/telegram-send.ts +137 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +155 -0
- 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
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
import type { AgentSession } from "./session/agent-session";
|
|
48
48
|
import type { AuthStorage } from "./session/auth-storage";
|
|
49
49
|
import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
|
|
50
|
+
import { runStartupCredentialAutoImportIfNeeded } from "./setup/credential-auto-import";
|
|
50
51
|
import { formatModelOnboardingGuidance } from "./setup/model-onboarding-guidance";
|
|
51
52
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
52
53
|
import { resolvePromptInput } from "./system-prompt";
|
|
@@ -856,6 +857,14 @@ export async function runRootCommand(
|
|
|
856
857
|
settingsInstance.get("theme.light"),
|
|
857
858
|
);
|
|
858
859
|
|
|
860
|
+
const credentialAutoImportNotice = isInteractive
|
|
861
|
+
? await logger.time("credentialAutoImport", runStartupCredentialAutoImportIfNeeded, {
|
|
862
|
+
authStorage,
|
|
863
|
+
modelRegistry,
|
|
864
|
+
agentDir: settingsInstance.getAgentDir(),
|
|
865
|
+
})
|
|
866
|
+
: undefined;
|
|
867
|
+
|
|
859
868
|
let scopedModels: ScopedModel[] = [];
|
|
860
869
|
const modelPatterns = parsedArgs.models ?? settingsInstance.get("enabledModels");
|
|
861
870
|
const modelMatchPreferences = {
|
|
@@ -895,6 +904,24 @@ export async function runRootCommand(
|
|
|
895
904
|
sessionManager = await SessionManager.open(selectedPath);
|
|
896
905
|
}
|
|
897
906
|
|
|
907
|
+
// Restore the resumed session's working directory so the HUD branch, the
|
|
908
|
+
// project path, and the agent's tools all match where the session was
|
|
909
|
+
// created. A `--worktree` session lives in a linked worktree whose path
|
|
910
|
+
// differs from where `--continue`/`--resume` is invoked, which would
|
|
911
|
+
// otherwise leave the HUD pinned to the main checkout's branch.
|
|
912
|
+
if (sessionManager && !parsedArgs.cwd) {
|
|
913
|
+
const sessionCwd = sessionManager.getCwd();
|
|
914
|
+
if (sessionCwd && normalizePathForComparison(sessionCwd) !== normalizePathForComparison(getProjectDir())) {
|
|
915
|
+
try {
|
|
916
|
+
if ((await fs.stat(sessionCwd)).isDirectory()) {
|
|
917
|
+
setProjectDir(sessionCwd);
|
|
918
|
+
}
|
|
919
|
+
} catch {
|
|
920
|
+
// Session cwd no longer exists (e.g. worktree removed); keep current dir.
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
898
925
|
const { options: sessionOptions } = await logger.time(
|
|
899
926
|
"buildSessionOptions",
|
|
900
927
|
buildSessionOptions,
|
|
@@ -976,6 +1003,9 @@ export async function runRootCommand(
|
|
|
976
1003
|
if (modelRegistryError) {
|
|
977
1004
|
notifs.push({ kind: "error", message: modelRegistryError.message });
|
|
978
1005
|
}
|
|
1006
|
+
if (credentialAutoImportNotice) {
|
|
1007
|
+
notifs.push({ kind: "info", message: credentialAutoImportNotice });
|
|
1008
|
+
}
|
|
979
1009
|
|
|
980
1010
|
if (isInteractive && !session.model && !modelFallbackMessage) {
|
|
981
1011
|
notifs.push({
|
|
@@ -244,6 +244,7 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
244
244
|
case "retry_fallback_succeeded":
|
|
245
245
|
case "ttsr_triggered":
|
|
246
246
|
case "irc_message":
|
|
247
|
+
case "subagent_steer_message":
|
|
247
248
|
case "notice":
|
|
248
249
|
case "thinking_level_changed":
|
|
249
250
|
case "goal_updated":
|
|
@@ -17,6 +17,10 @@ export interface HookEditorOptions {
|
|
|
17
17
|
promptStyle?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function isWindowsRawLfNewlineInput(keyData: string): boolean {
|
|
21
|
+
return process.platform === "win32" && keyData === "\n";
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
export class HookEditorComponent extends Container {
|
|
21
25
|
#editor: Editor;
|
|
22
26
|
#onSubmitCallback: (value: string) => void;
|
|
@@ -92,8 +96,9 @@ export class HookEditorComponent extends Container {
|
|
|
92
96
|
return;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
// Submit on
|
|
96
|
-
|
|
99
|
+
// Submit on plain Enter encodings. On Windows, raw LF is reserved for terminal
|
|
100
|
+
// newline mappings (Shift+Enter/Ctrl+J/Ctrl+Enter); plain Enter reports CR.
|
|
101
|
+
if (!isWindowsRawLfNewlineInput(keyData) && (matchesKey(keyData, "enter") || matchesKey(keyData, "return"))) {
|
|
97
102
|
this.#onSubmitCallback(this.#editor.getText());
|
|
98
103
|
return;
|
|
99
104
|
}
|
|
@@ -4,6 +4,7 @@ import { Container, matchesKey, Spacer, TruncatedText } from "@sayknow-cli/tui";
|
|
|
4
4
|
import { theme } from "../../modes/theme/theme";
|
|
5
5
|
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
6
6
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
7
|
+
import type { ImportableCredential } from "../../setup/credential-import";
|
|
7
8
|
import { DynamicBorder } from "./dynamic-border";
|
|
8
9
|
|
|
9
10
|
const OAUTH_SELECTOR_MAX_VISIBLE = 10;
|
|
@@ -22,6 +23,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
22
23
|
#validateAuthCallback?: (providerId: string) => Promise<boolean>;
|
|
23
24
|
#requestRenderCallback?: () => void;
|
|
24
25
|
#authState: Map<string, "checking" | "valid" | "invalid"> = new Map();
|
|
26
|
+
#externalCredentialCandidates: ImportableCredential[] = [];
|
|
25
27
|
#spinnerFrame: number = 0;
|
|
26
28
|
#spinnerInterval?: NodeJS.Timeout;
|
|
27
29
|
#validationGeneration: number = 0;
|
|
@@ -33,6 +35,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
33
35
|
options?: {
|
|
34
36
|
validateAuth?: (providerId: string) => Promise<boolean>;
|
|
35
37
|
requestRender?: () => void;
|
|
38
|
+
externalCredentialCandidates?: ImportableCredential[];
|
|
36
39
|
},
|
|
37
40
|
) {
|
|
38
41
|
super();
|
|
@@ -42,6 +45,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
42
45
|
this.#onCancelCallback = onCancel;
|
|
43
46
|
this.#validateAuthCallback = options?.validateAuth;
|
|
44
47
|
this.#requestRenderCallback = options?.requestRender;
|
|
48
|
+
this.#externalCredentialCandidates = options?.externalCredentialCandidates ?? [];
|
|
45
49
|
// Load all OAuth providers
|
|
46
50
|
this.#loadProviders();
|
|
47
51
|
this.addChild(new DynamicBorder());
|
|
@@ -195,6 +199,21 @@ export class OAuthSelectorComponent extends Container {
|
|
|
195
199
|
this.#listContainer.addChild(new Spacer(1));
|
|
196
200
|
this.#listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.#statusMessage}`), 0, 0));
|
|
197
201
|
}
|
|
202
|
+
if (this.#mode === "login" && this.#externalCredentialCandidates.length > 0) {
|
|
203
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
204
|
+
for (const credential of this.#externalCredentialCandidates) {
|
|
205
|
+
this.#listContainer.addChild(
|
|
206
|
+
new TruncatedText(
|
|
207
|
+
theme.fg(
|
|
208
|
+
"success",
|
|
209
|
+
` ${theme.status.success} Imported ${credential.provider} from ${credential.source}`,
|
|
210
|
+
),
|
|
211
|
+
0,
|
|
212
|
+
0,
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
198
217
|
}
|
|
199
218
|
handleInput(keyData: string): void {
|
|
200
219
|
// Up arrow
|
|
@@ -62,6 +62,7 @@ export interface SubmenuSettingDef extends BaseSettingDef {
|
|
|
62
62
|
|
|
63
63
|
export interface TextInputSettingDef extends BaseSettingDef {
|
|
64
64
|
type: "text";
|
|
65
|
+
secret?: boolean;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef | TextInputSettingDef;
|
|
@@ -128,7 +129,7 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
128
129
|
if (options) {
|
|
129
130
|
return { ...base, type: "submenu", options };
|
|
130
131
|
}
|
|
131
|
-
return { ...base, type: "text" };
|
|
132
|
+
return { ...base, type: "text", secret: (ui as { secret?: boolean }).secret };
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
return null;
|
|
@@ -14,6 +14,7 @@ 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";
|
|
17
18
|
import type {
|
|
18
19
|
SettingTab,
|
|
19
20
|
StatusLinePreset,
|
|
@@ -352,7 +353,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
352
353
|
id: def.path,
|
|
353
354
|
label,
|
|
354
355
|
description,
|
|
355
|
-
currentValue:
|
|
356
|
+
currentValue: def.secret
|
|
357
|
+
? maskToken(currentValue as string | undefined)
|
|
358
|
+
: ((currentValue as string) ?? ""),
|
|
356
359
|
submenu: (cv, done) => this.#createTextInput(def, cv, done),
|
|
357
360
|
};
|
|
358
361
|
}
|
|
@@ -480,10 +483,12 @@ export class SettingsSelectorComponent extends Container {
|
|
|
480
483
|
this.#textInputActive = false;
|
|
481
484
|
done(value);
|
|
482
485
|
};
|
|
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;
|
|
483
488
|
return new TextInputSubmenu(
|
|
484
489
|
tSettingLabel(def),
|
|
485
490
|
tSettingDesc(def),
|
|
486
|
-
|
|
491
|
+
prefillValue,
|
|
487
492
|
value => {
|
|
488
493
|
// Empty string clears the setting; undefined-typed string settings
|
|
489
494
|
// store "" which the browser.ts expandPath ignores (no-op fallback).
|
|
@@ -83,6 +83,7 @@ export class EventController {
|
|
|
83
83
|
todo_reminder: e => this.#handleTodoReminder(e),
|
|
84
84
|
todo_auto_clear: e => this.#handleTodoAutoClear(e),
|
|
85
85
|
irc_message: e => this.#handleIrcMessage(e),
|
|
86
|
+
subagent_steer_message: e => this.#handleSubagentSteerMessage(e),
|
|
86
87
|
notice: e => this.#handleNotice(e),
|
|
87
88
|
thinking_level_changed: async () => {},
|
|
88
89
|
goal_updated: async () => {},
|
|
@@ -200,6 +201,7 @@ export class EventController {
|
|
|
200
201
|
this.ctx.statusContainer.clear();
|
|
201
202
|
}
|
|
202
203
|
this.#cancelIdleCompaction();
|
|
204
|
+
this.ctx.updateEditorBorderColor();
|
|
203
205
|
this.ctx.ensureLoadingAnimation();
|
|
204
206
|
this.ctx.ui.requestRender();
|
|
205
207
|
}
|
|
@@ -284,6 +286,25 @@ export class EventController {
|
|
|
284
286
|
this.ctx.ui.requestRender();
|
|
285
287
|
}
|
|
286
288
|
|
|
289
|
+
async #handleSubagentSteerMessage(
|
|
290
|
+
event: Extract<AgentSessionEvent, { type: "subagent_steer_message" }>,
|
|
291
|
+
): Promise<void> {
|
|
292
|
+
const details = event.message.details as
|
|
293
|
+
| { observationId?: string; from?: string; to?: string; body?: string; state?: string }
|
|
294
|
+
| undefined;
|
|
295
|
+
const obsId = details?.observationId;
|
|
296
|
+
const signature = obsId
|
|
297
|
+
? `steer:${obsId}`
|
|
298
|
+
: `${event.message.role}:${event.message.customType}:${event.message.timestamp}:${details?.from}:${details?.to}:${details?.state}:${details?.body}`;
|
|
299
|
+
if (this.#renderedCustomMessages.has(signature)) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.#renderedCustomMessages.add(signature);
|
|
303
|
+
this.#resetReadGroup();
|
|
304
|
+
this.ctx.addMessageToChat(event.message);
|
|
305
|
+
this.ctx.ui.requestRender();
|
|
306
|
+
}
|
|
307
|
+
|
|
287
308
|
#scheduleIrcExpiry(signature: string, components: Component[]): void {
|
|
288
309
|
if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
|
|
289
310
|
const timer = setTimeout(() => {
|
|
@@ -613,6 +634,7 @@ export class EventController {
|
|
|
613
634
|
this.#readToolCallArgs.clear();
|
|
614
635
|
this.#readToolCallAssistantComponents.clear();
|
|
615
636
|
this.#lastAssistantComponent = undefined;
|
|
637
|
+
this.ctx.updateEditorBorderColor();
|
|
616
638
|
this.ctx.ui.requestRender();
|
|
617
639
|
this.#scheduleIdleCompaction();
|
|
618
640
|
this.sendCompletionNotification();
|
|
@@ -622,6 +644,7 @@ export class EventController {
|
|
|
622
644
|
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
623
645
|
): Promise<void> {
|
|
624
646
|
this.#cancelIdleCompaction();
|
|
647
|
+
this.ctx.updateEditorBorderColor();
|
|
625
648
|
this.ctx.autoCompactionEscapeHandler = this.ctx.editor.onEscape;
|
|
626
649
|
this.ctx.editor.onEscape = () => {
|
|
627
650
|
this.ctx.session.abortCompaction();
|
|
@@ -652,13 +675,20 @@ export class EventController {
|
|
|
652
675
|
this.ctx.autoCompactionLoader = undefined;
|
|
653
676
|
this.ctx.statusContainer.clear();
|
|
654
677
|
}
|
|
678
|
+
this.ctx.updateEditorBorderColor();
|
|
655
679
|
const isHandoffAction = event.action === "handoff";
|
|
680
|
+
const continuationDisabled = event.continuationSkipReason === "auto_continue_disabled_non_resumable_tail";
|
|
656
681
|
if (event.aborted) {
|
|
657
682
|
this.ctx.showStatus(isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled");
|
|
658
683
|
} else if (event.result) {
|
|
659
684
|
this.ctx.rebuildChatFromMessages();
|
|
660
685
|
this.ctx.statusLine.invalidate();
|
|
661
686
|
this.ctx.updateEditorTopBorder();
|
|
687
|
+
if (continuationDisabled && !isHandoffAction) {
|
|
688
|
+
this.ctx.showStatus("Context overflow recovery skipped: auto_continue_disabled_non_resumable_tail");
|
|
689
|
+
} else if (event.willRetry && !isHandoffAction) {
|
|
690
|
+
this.ctx.showStatus("Context overflow maintenance completed");
|
|
691
|
+
}
|
|
662
692
|
} else if (event.errorMessage) {
|
|
663
693
|
this.ctx.showWarning(event.errorMessage);
|
|
664
694
|
} else if (isHandoffAction) {
|
|
@@ -671,6 +701,11 @@ export class EventController {
|
|
|
671
701
|
} else if (event.skipped) {
|
|
672
702
|
// Benign skip: no model selected, no candidate models available, or nothing
|
|
673
703
|
// to compact yet. Not a failure — suppress the warning.
|
|
704
|
+
if (continuationDisabled) {
|
|
705
|
+
this.ctx.showStatus("Context overflow recovery skipped: auto_continue_disabled_non_resumable_tail");
|
|
706
|
+
} else if (event.willRetry && !isHandoffAction) {
|
|
707
|
+
this.ctx.showStatus("Context overflow maintenance skipped");
|
|
708
|
+
}
|
|
674
709
|
} else {
|
|
675
710
|
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
676
711
|
}
|
|
@@ -29,10 +29,14 @@ import {
|
|
|
29
29
|
setTheme,
|
|
30
30
|
theme,
|
|
31
31
|
} from "../../modes/theme/theme";
|
|
32
|
-
import type { InteractiveModeContext } from "../../modes/types";
|
|
32
|
+
import type { InteractiveModeContext, OAuthSelectorOptions } from "../../modes/types";
|
|
33
33
|
import { type SessionInfo, SessionManager } from "../../session/session-manager";
|
|
34
34
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
CREDENTIAL_AUTO_IMPORT_ROTATION_WARNING,
|
|
37
|
+
runExternalCredentialAutoImport,
|
|
38
|
+
} from "../../setup/credential-auto-import";
|
|
39
|
+
import { filterAutoImportOAuthCredentials, formatDiscoverySummary } from "../../setup/credential-import";
|
|
36
40
|
import {
|
|
37
41
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
38
42
|
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
@@ -160,10 +164,22 @@ export class SelectorController {
|
|
|
160
164
|
|
|
161
165
|
async #handleCredentialImport(): Promise<void> {
|
|
162
166
|
this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI credentials…");
|
|
163
|
-
const
|
|
164
|
-
|
|
167
|
+
const preview = await runExternalCredentialAutoImport({
|
|
168
|
+
authStorage: {
|
|
169
|
+
importCredentialIfAbsent: async () => ({
|
|
170
|
+
inserted: false,
|
|
171
|
+
reason: "skipped-existing",
|
|
172
|
+
provider: "",
|
|
173
|
+
entries: [],
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
trigger: "bare-login",
|
|
177
|
+
});
|
|
178
|
+
const result = preview.discovery ?? { importable: [], skipped: [], environment: [] };
|
|
179
|
+
const candidates = filterAutoImportOAuthCredentials(result.importable);
|
|
180
|
+
const summaryLines = formatDiscoverySummary({ ...result, importable: candidates });
|
|
165
181
|
|
|
166
|
-
if (
|
|
182
|
+
if (candidates.length === 0) {
|
|
167
183
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
168
184
|
for (const line of summaryLines) {
|
|
169
185
|
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", line), 1, 0));
|
|
@@ -172,7 +188,7 @@ export class SelectorController {
|
|
|
172
188
|
new Text(
|
|
173
189
|
theme.fg(
|
|
174
190
|
"warning",
|
|
175
|
-
"No importable Claude/Codex credentials found. Use /login or add a custom provider.",
|
|
191
|
+
"No importable Claude/Codex OAuth credentials found. Use /login or add a custom provider.",
|
|
176
192
|
),
|
|
177
193
|
1,
|
|
178
194
|
0,
|
|
@@ -183,7 +199,7 @@ export class SelectorController {
|
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
const confirmed = await this.ctx.showHookConfirm(
|
|
186
|
-
`Import ${
|
|
202
|
+
`Import ${candidates.length} credential(s)?`,
|
|
187
203
|
summaryLines.join("\n"),
|
|
188
204
|
);
|
|
189
205
|
if (!confirmed) {
|
|
@@ -191,9 +207,10 @@ export class SelectorController {
|
|
|
191
207
|
return;
|
|
192
208
|
}
|
|
193
209
|
|
|
194
|
-
const summary = await
|
|
195
|
-
this.ctx.session.modelRegistry.authStorage
|
|
196
|
-
|
|
210
|
+
const summary = await runExternalCredentialAutoImport({
|
|
211
|
+
authStorage: this.ctx.session.modelRegistry.authStorage,
|
|
212
|
+
trigger: "bare-login",
|
|
213
|
+
});
|
|
197
214
|
await this.ctx.session.modelRegistry.refresh();
|
|
198
215
|
|
|
199
216
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -206,13 +223,15 @@ export class SelectorController {
|
|
|
206
223
|
),
|
|
207
224
|
);
|
|
208
225
|
}
|
|
209
|
-
for (const
|
|
226
|
+
for (const skip of summary.skipped) {
|
|
210
227
|
this.ctx.chatContainer.addChild(
|
|
211
|
-
new Text(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
new Text(theme.fg("dim", `${theme.status.info} Skipped ${skip.credential.provider}: ${skip.reason}`), 1, 0),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
for (const failure of summary.failures) {
|
|
232
|
+
const provider = failure.credential?.provider ?? failure.origin ?? "credential discovery";
|
|
233
|
+
this.ctx.chatContainer.addChild(
|
|
234
|
+
new Text(theme.fg("error", `${theme.status.error} Failed ${provider}: ${failure.failureClass}`), 1, 0),
|
|
216
235
|
);
|
|
217
236
|
}
|
|
218
237
|
if (summary.imported.length > 0) {
|
|
@@ -1280,7 +1299,11 @@ export class SelectorController {
|
|
|
1280
1299
|
}
|
|
1281
1300
|
}
|
|
1282
1301
|
|
|
1283
|
-
async showOAuthSelector(
|
|
1302
|
+
async showOAuthSelector(
|
|
1303
|
+
mode: "login" | "logout",
|
|
1304
|
+
providerId?: string,
|
|
1305
|
+
options?: OAuthSelectorOptions,
|
|
1306
|
+
): Promise<void> {
|
|
1284
1307
|
if (providerId) {
|
|
1285
1308
|
const oauthProvider = getOAuthProviders().find(provider => provider.id === providerId);
|
|
1286
1309
|
if (!oauthProvider && !this.ctx.session.modelRegistry.getModelProfiles().has(providerId)) {
|
|
@@ -1307,6 +1330,45 @@ export class SelectorController {
|
|
|
1307
1330
|
}
|
|
1308
1331
|
}
|
|
1309
1332
|
|
|
1333
|
+
let externalCredentialCandidates: ReturnType<typeof filterAutoImportOAuthCredentials> = [];
|
|
1334
|
+
if (
|
|
1335
|
+
mode === "login" &&
|
|
1336
|
+
providerId === undefined &&
|
|
1337
|
+
options?.allowExternalCredentialDiscovery === true &&
|
|
1338
|
+
options.trigger === "bare-login"
|
|
1339
|
+
) {
|
|
1340
|
+
const preview = await runExternalCredentialAutoImport({
|
|
1341
|
+
authStorage: {
|
|
1342
|
+
importCredentialIfAbsent: async () => ({
|
|
1343
|
+
inserted: false,
|
|
1344
|
+
reason: "skipped-existing",
|
|
1345
|
+
provider: "",
|
|
1346
|
+
entries: [],
|
|
1347
|
+
}),
|
|
1348
|
+
},
|
|
1349
|
+
trigger: "bare-login",
|
|
1350
|
+
discover: options.externalCredentialDiscover,
|
|
1351
|
+
});
|
|
1352
|
+
const result = preview.discovery ?? { importable: [], skipped: [], environment: [] };
|
|
1353
|
+
const candidates = filterAutoImportOAuthCredentials(result.importable);
|
|
1354
|
+
if (candidates.length > 0) {
|
|
1355
|
+
const confirmed = await this.ctx.showHookConfirm(
|
|
1356
|
+
`Import ${candidates.length} external credential(s)?`,
|
|
1357
|
+
`${formatDiscoverySummary({ ...result, importable: candidates }).join("\n")}\n\n${CREDENTIAL_AUTO_IMPORT_ROTATION_WARNING}`,
|
|
1358
|
+
);
|
|
1359
|
+
if (confirmed) {
|
|
1360
|
+
const summary = await runExternalCredentialAutoImport({
|
|
1361
|
+
authStorage: this.ctx.session.modelRegistry.authStorage,
|
|
1362
|
+
trigger: "bare-login",
|
|
1363
|
+
discover: options.externalCredentialDiscover,
|
|
1364
|
+
});
|
|
1365
|
+
externalCredentialCandidates = summary.imported;
|
|
1366
|
+
if (externalCredentialCandidates.length > 0) {
|
|
1367
|
+
await this.ctx.session.modelRegistry.refresh("offline");
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1310
1372
|
this.showSelector(done => {
|
|
1311
1373
|
let selector: OAuthSelectorComponent;
|
|
1312
1374
|
selector = new OAuthSelectorComponent(
|
|
@@ -1337,6 +1399,7 @@ export class SelectorController {
|
|
|
1337
1399
|
requestRender: () => {
|
|
1338
1400
|
this.ctx.ui.requestRender();
|
|
1339
1401
|
},
|
|
1402
|
+
externalCredentialCandidates,
|
|
1340
1403
|
},
|
|
1341
1404
|
);
|
|
1342
1405
|
return { component: selector, focus: selector };
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { APP_NAME, adjustHsv, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@sayknow-cli/utils";
|
|
30
30
|
import chalk from "chalk";
|
|
31
31
|
import { AsyncJobManager } from "../async";
|
|
32
|
-
import { KeybindingsManager } from "../config/keybindings";
|
|
32
|
+
import { type AppKeybinding, KeybindingsManager } from "../config/keybindings";
|
|
33
33
|
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
34
34
|
import { DEFAULT_SKC_DEFINITION_NAMES } from "../defaults/skc-defaults";
|
|
35
35
|
import type {
|
|
@@ -118,6 +118,24 @@ import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInpu
|
|
|
118
118
|
import { UiHelpers } from "./utils/ui-helpers";
|
|
119
119
|
|
|
120
120
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
121
|
+
const DEFAULT_COMPOSER_PLACEHOLDER = "Type your message...";
|
|
122
|
+
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
123
|
+
alt: "Alt",
|
|
124
|
+
cmd: "Command",
|
|
125
|
+
command: "Command",
|
|
126
|
+
ctrl: "Control",
|
|
127
|
+
enter: "Enter",
|
|
128
|
+
meta: process.platform === "darwin" ? "Command" : "Meta",
|
|
129
|
+
option: "Option",
|
|
130
|
+
shift: "Shift",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function formatShortcutForPlaceholder(key: string): string {
|
|
134
|
+
return key
|
|
135
|
+
.split("+")
|
|
136
|
+
.map(part => FRIENDLY_KEY_PARTS[part.toLowerCase()] ?? part)
|
|
137
|
+
.join("+");
|
|
138
|
+
}
|
|
121
139
|
|
|
122
140
|
const HINT_SHIMMER_PALETTE: ShimmerPalette = {
|
|
123
141
|
low: "dim",
|
|
@@ -142,7 +160,7 @@ function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
|
142
160
|
editor.setClosedBorderBox(true);
|
|
143
161
|
editor.setPromptGutter(undefined);
|
|
144
162
|
editor.setInputPrefix(getDefaultInputPrefix());
|
|
145
|
-
editor.setPlaceholder(
|
|
163
|
+
editor.setPlaceholder(DEFAULT_COMPOSER_PLACEHOLDER);
|
|
146
164
|
editor.setPaddingX(1);
|
|
147
165
|
editor.setTopBorder(undefined);
|
|
148
166
|
}
|
|
@@ -904,6 +922,31 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
904
922
|
this.editor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
905
923
|
}
|
|
906
924
|
|
|
925
|
+
#isPromptDeliveryBusy(): boolean {
|
|
926
|
+
return this.session.isStreaming || this.session.isCompacting;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#getFirstKeyForAction(action: AppKeybinding): string | undefined {
|
|
930
|
+
return this.keybindings.getKeys(action)[0];
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
#getMessageQueueShortcut(): string | undefined {
|
|
934
|
+
const preferredAction: AppKeybinding =
|
|
935
|
+
process.platform === "darwin" ? "app.message.followUp" : "app.message.queue";
|
|
936
|
+
const fallbackAction: AppKeybinding =
|
|
937
|
+
process.platform === "darwin" ? "app.message.queue" : "app.message.followUp";
|
|
938
|
+
return this.#getFirstKeyForAction(preferredAction) ?? this.#getFirstKeyForAction(fallbackAction);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
#getComposerPlaceholder(): string {
|
|
942
|
+
if (!this.#isPromptDeliveryBusy()) return DEFAULT_COMPOSER_PLACEHOLDER;
|
|
943
|
+
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "Steering" : "Message Queueing";
|
|
944
|
+
const parts = [`Enter: ${enterAction}`];
|
|
945
|
+
const queueKey = this.#getMessageQueueShortcut();
|
|
946
|
+
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}: Message Queueing`);
|
|
947
|
+
return `${DEFAULT_COMPOSER_PLACEHOLDER} ${parts.join(" · ")}`;
|
|
948
|
+
}
|
|
949
|
+
|
|
907
950
|
updateEditorChrome(): void {
|
|
908
951
|
if (this.isBashMode) {
|
|
909
952
|
this.editor.borderColor = this.isBashNoContext
|
|
@@ -927,6 +970,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
927
970
|
if (!this.isBashMode) {
|
|
928
971
|
this.editor.setInputPrefix(getDefaultInputPrefix());
|
|
929
972
|
}
|
|
973
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
930
974
|
this.#setComposerTopBorder();
|
|
931
975
|
this.ui.requestRender();
|
|
932
976
|
}
|
|
@@ -2503,8 +2547,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2503
2547
|
return this.#selectorController.handleSessionDeleteCommand();
|
|
2504
2548
|
}
|
|
2505
2549
|
|
|
2506
|
-
showOAuthSelector(
|
|
2507
|
-
|
|
2550
|
+
showOAuthSelector(
|
|
2551
|
+
mode: "login" | "logout",
|
|
2552
|
+
providerId?: string,
|
|
2553
|
+
options?: import("./types").OAuthSelectorOptions,
|
|
2554
|
+
): Promise<void> {
|
|
2555
|
+
return this.#selectorController.showOAuthSelector(mode, providerId, options);
|
|
2508
2556
|
}
|
|
2509
2557
|
|
|
2510
2558
|
showHookConfirm(title: string, message: string): Promise<boolean> {
|
|
@@ -78,6 +78,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
78
78
|
shutdown,
|
|
79
79
|
getContextUsage: () => session.getContextUsage(),
|
|
80
80
|
getSystemPrompt: () => session.systemPrompt,
|
|
81
|
+
getWorkflowGate: () => session.getWorkflowGateEmitter(),
|
|
81
82
|
compact: instructionsOrOptions => runExtensionCompact(session, instructionsOrOptions),
|
|
82
83
|
},
|
|
83
84
|
// ExtensionCommandContextActions — commands invokable via prompt("/command")
|
|
@@ -50,6 +50,7 @@ export function agentSessionEventType(event: AgentSessionEvent): AgentWireEventT
|
|
|
50
50
|
case "todo_reminder":
|
|
51
51
|
case "todo_auto_clear":
|
|
52
52
|
case "irc_message":
|
|
53
|
+
case "subagent_steer_message":
|
|
53
54
|
case "notice":
|
|
54
55
|
case "thinking_level_changed":
|
|
55
56
|
case "goal_updated":
|
|
@@ -235,6 +235,22 @@ export function observeAgentSessionEvent(event: AgentSessionEvent): AgentWireOwn
|
|
|
235
235
|
semantic: false,
|
|
236
236
|
coalesceKey: null,
|
|
237
237
|
});
|
|
238
|
+
case "subagent_steer_message": {
|
|
239
|
+
const details = recordObject(event.message.details);
|
|
240
|
+
return obs(event, {
|
|
241
|
+
kind: "rpc_subagent_steer",
|
|
242
|
+
signal: null,
|
|
243
|
+
evidence: {
|
|
244
|
+
from: str(details?.from) ?? null,
|
|
245
|
+
to: str(details?.to) ?? null,
|
|
246
|
+
state: str(details?.state) ?? null,
|
|
247
|
+
observationId: str(details?.observationId) ?? null,
|
|
248
|
+
},
|
|
249
|
+
severity: "info",
|
|
250
|
+
semantic: false,
|
|
251
|
+
coalesceKey: null,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
238
254
|
case "notice": {
|
|
239
255
|
const level = event.level;
|
|
240
256
|
return obs(event, {
|