@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
|
@@ -201,6 +201,7 @@ export class EventController {
|
|
|
201
201
|
this.ctx.statusContainer.clear();
|
|
202
202
|
}
|
|
203
203
|
this.#cancelIdleCompaction();
|
|
204
|
+
this.ctx.updateEditorBorderColor();
|
|
204
205
|
this.ctx.ensureLoadingAnimation();
|
|
205
206
|
this.ctx.ui.requestRender();
|
|
206
207
|
}
|
|
@@ -633,6 +634,7 @@ export class EventController {
|
|
|
633
634
|
this.#readToolCallArgs.clear();
|
|
634
635
|
this.#readToolCallAssistantComponents.clear();
|
|
635
636
|
this.#lastAssistantComponent = undefined;
|
|
637
|
+
this.ctx.updateEditorBorderColor();
|
|
636
638
|
this.ctx.ui.requestRender();
|
|
637
639
|
this.#scheduleIdleCompaction();
|
|
638
640
|
this.sendCompletionNotification();
|
|
@@ -642,6 +644,7 @@ export class EventController {
|
|
|
642
644
|
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
643
645
|
): Promise<void> {
|
|
644
646
|
this.#cancelIdleCompaction();
|
|
647
|
+
this.ctx.updateEditorBorderColor();
|
|
645
648
|
this.ctx.autoCompactionEscapeHandler = this.ctx.editor.onEscape;
|
|
646
649
|
this.ctx.editor.onEscape = () => {
|
|
647
650
|
this.ctx.session.abortCompaction();
|
|
@@ -672,13 +675,20 @@ export class EventController {
|
|
|
672
675
|
this.ctx.autoCompactionLoader = undefined;
|
|
673
676
|
this.ctx.statusContainer.clear();
|
|
674
677
|
}
|
|
678
|
+
this.ctx.updateEditorBorderColor();
|
|
675
679
|
const isHandoffAction = event.action === "handoff";
|
|
680
|
+
const continuationDisabled = event.continuationSkipReason === "auto_continue_disabled_non_resumable_tail";
|
|
676
681
|
if (event.aborted) {
|
|
677
682
|
this.ctx.showStatus(isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled");
|
|
678
683
|
} else if (event.result) {
|
|
679
684
|
this.ctx.rebuildChatFromMessages();
|
|
680
685
|
this.ctx.statusLine.invalidate();
|
|
681
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
|
+
}
|
|
682
692
|
} else if (event.errorMessage) {
|
|
683
693
|
this.ctx.showWarning(event.errorMessage);
|
|
684
694
|
} else if (isHandoffAction) {
|
|
@@ -691,6 +701,11 @@ export class EventController {
|
|
|
691
701
|
} else if (event.skipped) {
|
|
692
702
|
// Benign skip: no model selected, no candidate models available, or nothing
|
|
693
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
|
+
}
|
|
694
709
|
} else {
|
|
695
710
|
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
696
711
|
}
|
|
@@ -26,8 +26,6 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
26
26
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
27
27
|
|
|
28
28
|
const MAX_WIDGET_LINES = 10;
|
|
29
|
-
const HOOK_SELECTOR_MOUSE_REPORTING_ENABLE = "\x1b[?1006h\x1b[?1000h";
|
|
30
|
-
const HOOK_SELECTOR_MOUSE_REPORTING_DISABLE = "\x1b[?1000l\x1b[?1006l";
|
|
31
29
|
const HOOK_SELECTOR_CHROME_ROWS = 7;
|
|
32
30
|
const HOOK_SELECTOR_OUTLINE_ROWS = 2;
|
|
33
31
|
const HOOK_SELECTOR_INLINE_INPUT_ROWS = 2;
|
|
@@ -36,7 +34,6 @@ export class ExtensionUiController {
|
|
|
36
34
|
#extensionTerminalInputUnsubscribers = new Set<() => void>();
|
|
37
35
|
#hookWidgetsAbove = new Map<string, ExtensionUiComponent>();
|
|
38
36
|
#hookWidgetsBelow = new Map<string, ExtensionUiComponent>();
|
|
39
|
-
#hookSelectorMouseReportingEnabled = false;
|
|
40
37
|
#activeHookCustomComponent?: Component & { dispose?(): void };
|
|
41
38
|
#activeHookCustomOverlay?: OverlayHandle;
|
|
42
39
|
|
|
@@ -625,9 +622,6 @@ export class ExtensionUiController {
|
|
|
625
622
|
this.ctx.ui.terminal.rows - scrollOptionRows - listChromeRows - inlineInputRows - HOOK_SELECTOR_CHROME_ROWS;
|
|
626
623
|
const scrollTitleRows =
|
|
627
624
|
requestedTitleRows === undefined ? undefined : Math.max(1, Math.min(requestedTitleRows, availableTitleRows));
|
|
628
|
-
if (scrollTitleRows !== undefined) {
|
|
629
|
-
this.#enableHookSelectorMouseReporting();
|
|
630
|
-
}
|
|
631
625
|
|
|
632
626
|
this.ctx.hookSelector = new HookSelectorComponent(
|
|
633
627
|
title,
|
|
@@ -692,31 +686,10 @@ export class ExtensionUiController {
|
|
|
692
686
|
return promise;
|
|
693
687
|
}
|
|
694
688
|
|
|
695
|
-
#enableHookSelectorMouseReporting(): void {
|
|
696
|
-
if (this.#hookSelectorMouseReportingEnabled) return;
|
|
697
|
-
this.#hookSelectorMouseReportingEnabled = true;
|
|
698
|
-
this.#writeTerminalControl(HOOK_SELECTOR_MOUSE_REPORTING_ENABLE);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
#disableHookSelectorMouseReporting(): void {
|
|
702
|
-
if (!this.#hookSelectorMouseReportingEnabled) return;
|
|
703
|
-
this.#hookSelectorMouseReportingEnabled = false;
|
|
704
|
-
this.#writeTerminalControl(HOOK_SELECTOR_MOUSE_REPORTING_DISABLE);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
#writeTerminalControl(sequence: string): void {
|
|
708
|
-
try {
|
|
709
|
-
this.ctx.ui.terminal.write(sequence);
|
|
710
|
-
} catch {
|
|
711
|
-
// Terminal teardown can race selector cleanup; normal shutdown restores modes.
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
689
|
/**
|
|
716
690
|
* Hide the hook selector.
|
|
717
691
|
*/
|
|
718
692
|
hideHookSelector(): void {
|
|
719
|
-
this.#disableHookSelectorMouseReporting();
|
|
720
693
|
this.ctx.hookSelector?.dispose();
|
|
721
694
|
this.ctx.editorContainer.clear();
|
|
722
695
|
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
@@ -4,8 +4,10 @@ import type { OAuthProvider } from "@sayknow-cli/ai/utils/oauth/types";
|
|
|
4
4
|
import type { Component, OverlayHandle } from "@sayknow-cli/tui";
|
|
5
5
|
import { Input, Loader, Spacer, Text } from "@sayknow-cli/tui";
|
|
6
6
|
import { getAgentDbPath, getProjectDir } from "@sayknow-cli/utils";
|
|
7
|
-
import { activateModelProfile } from "../../config/model-profile-activation";
|
|
7
|
+
import { activateModelProfile, materializeActiveModelProfileAssignment } from "../../config/model-profile-activation";
|
|
8
8
|
import { recommendModelProfileForProvider } from "../../config/model-profiles";
|
|
9
|
+
import { SKC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
10
|
+
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
9
11
|
import { settings } from "../../config/settings";
|
|
10
12
|
import { DebugSelectorComponent } from "../../debug";
|
|
11
13
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -667,7 +669,8 @@ export class SelectorController {
|
|
|
667
669
|
|
|
668
670
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
669
671
|
this.showSelector(done => {
|
|
670
|
-
|
|
672
|
+
let modelSelector: ModelSelectorComponent;
|
|
673
|
+
modelSelector = new ModelSelectorComponent(
|
|
671
674
|
this.ctx.ui,
|
|
672
675
|
this.ctx.session.model,
|
|
673
676
|
this.ctx.settings,
|
|
@@ -717,38 +720,73 @@ export class SelectorController {
|
|
|
717
720
|
this.ctx.ui.requestRender();
|
|
718
721
|
return;
|
|
719
722
|
}
|
|
720
|
-
const { model, role, thinkingLevel, selector } = selection;
|
|
723
|
+
const { model, role, thinkingLevel, selector: selectedSelector } = selection;
|
|
721
724
|
if (role === null) {
|
|
722
725
|
// Temporary: update agent state but don't persist to settings
|
|
723
726
|
await this.ctx.session.setModelTemporary(model, thinkingLevel);
|
|
724
727
|
this.ctx.statusLine.invalidate();
|
|
725
728
|
this.ctx.updateEditorBorderColor();
|
|
726
|
-
this.ctx.showStatus(`Temporary model: ${
|
|
729
|
+
this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
|
|
727
730
|
done();
|
|
728
731
|
this.ctx.ui.requestRender();
|
|
729
732
|
} else if (role === "default") {
|
|
730
733
|
// Default: update agent state and persist as the active default model.
|
|
731
734
|
await this.ctx.session.setModel(model, role, {
|
|
732
|
-
selector,
|
|
735
|
+
selector: selectedSelector,
|
|
733
736
|
thinkingLevel,
|
|
734
737
|
});
|
|
738
|
+
const value = formatModelSelectorValue(
|
|
739
|
+
selectedSelector ?? `${model.provider}/${model.id}`,
|
|
740
|
+
thinkingLevel,
|
|
741
|
+
);
|
|
742
|
+
materializeActiveModelProfileAssignment({
|
|
743
|
+
session: this.ctx.session,
|
|
744
|
+
settings: this.ctx.settings,
|
|
745
|
+
role,
|
|
746
|
+
selector: value,
|
|
747
|
+
});
|
|
735
748
|
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
736
749
|
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
737
750
|
}
|
|
751
|
+
modelSelector.refreshRoleAssignments({
|
|
752
|
+
currentModel: this.ctx.session.model,
|
|
753
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
754
|
+
activeModelProfile:
|
|
755
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
756
|
+
});
|
|
738
757
|
this.ctx.statusLine.invalidate();
|
|
739
758
|
this.ctx.updateEditorBorderColor();
|
|
740
|
-
this.ctx.showStatus(`Default model: ${
|
|
759
|
+
this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
|
|
741
760
|
done();
|
|
742
761
|
this.ctx.ui.requestRender();
|
|
743
762
|
} else {
|
|
744
|
-
// Role-agent assignments configure Task dispatch and must not switch the active chat model.
|
|
745
763
|
const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
|
|
746
764
|
if (!apiKey) {
|
|
747
765
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
748
766
|
}
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
767
|
+
const value =
|
|
768
|
+
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
769
|
+
const materializedProfile = materializeActiveModelProfileAssignment({
|
|
770
|
+
session: this.ctx.session,
|
|
771
|
+
settings: this.ctx.settings,
|
|
772
|
+
role,
|
|
773
|
+
selector: value,
|
|
774
|
+
});
|
|
775
|
+
if (!materializedProfile) {
|
|
776
|
+
const target = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
777
|
+
if (target.settingsPath === "modelRoles") {
|
|
778
|
+
this.ctx.settings.setModelRole(role, value);
|
|
779
|
+
} else {
|
|
780
|
+
const overrides = this.ctx.settings.get("task.agentModelOverrides");
|
|
781
|
+
this.ctx.settings.set("task.agentModelOverrides", { ...overrides, [role]: value });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
modelSelector.refreshRoleAssignments({
|
|
785
|
+
currentModel: this.ctx.session.model,
|
|
786
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
787
|
+
activeModelProfile:
|
|
788
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
789
|
+
});
|
|
752
790
|
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
753
791
|
this.ctx.showStatus(`${role} agent model: ${value}`);
|
|
754
792
|
done();
|
|
@@ -765,11 +803,15 @@ export class SelectorController {
|
|
|
765
803
|
{
|
|
766
804
|
...options,
|
|
767
805
|
sessionId: this.ctx.session.sessionId,
|
|
806
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
807
|
+
activeModelProfile:
|
|
808
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
768
809
|
isFastForProvider: provider => this.ctx.session.isFastForProvider(provider),
|
|
769
810
|
isFastForSubagentProvider: provider => this.ctx.session.isFastForSubagentProvider(provider),
|
|
811
|
+
isCurrentModelFastModeActive: () => this.ctx.session.isFastModeActive(),
|
|
770
812
|
},
|
|
771
813
|
);
|
|
772
|
-
return { component:
|
|
814
|
+
return { component: modelSelector, focus: modelSelector };
|
|
773
815
|
});
|
|
774
816
|
}
|
|
775
817
|
|
|
@@ -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",
|
|
@@ -138,11 +156,11 @@ function getShellInputPrefix(isNoContext: boolean): string {
|
|
|
138
156
|
|
|
139
157
|
function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
140
158
|
editor.setBorderVisible(true);
|
|
141
|
-
editor.setBorderStyle("
|
|
159
|
+
editor.setBorderStyle("round");
|
|
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
|
}
|
|
@@ -414,6 +432,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
414
432
|
this.#resizeHandler = () => {
|
|
415
433
|
this.#syncEditorMaxHeight();
|
|
416
434
|
this.updateEditorChrome();
|
|
435
|
+
this.editor.invalidate();
|
|
436
|
+
this.ui.requestRender(true, "resize");
|
|
417
437
|
};
|
|
418
438
|
process.stdout.on("resize", this.#resizeHandler);
|
|
419
439
|
try {
|
|
@@ -551,6 +571,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
551
571
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
552
572
|
this.ui.addChild(this.editorContainer);
|
|
553
573
|
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
574
|
+
this.ui.setBottomPinnedComponent(this.statusLine);
|
|
554
575
|
this.ui.setFocus(this.editor);
|
|
555
576
|
|
|
556
577
|
this.#inputController.setupKeyHandlers();
|
|
@@ -904,6 +925,31 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
904
925
|
this.editor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
905
926
|
}
|
|
906
927
|
|
|
928
|
+
#isPromptDeliveryBusy(): boolean {
|
|
929
|
+
return this.session.isStreaming || this.session.isCompacting;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
#getFirstKeyForAction(action: AppKeybinding): string | undefined {
|
|
933
|
+
return this.keybindings.getKeys(action)[0];
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
#getMessageQueueShortcut(): string | undefined {
|
|
937
|
+
const preferredAction: AppKeybinding =
|
|
938
|
+
process.platform === "darwin" ? "app.message.followUp" : "app.message.queue";
|
|
939
|
+
const fallbackAction: AppKeybinding =
|
|
940
|
+
process.platform === "darwin" ? "app.message.queue" : "app.message.followUp";
|
|
941
|
+
return this.#getFirstKeyForAction(preferredAction) ?? this.#getFirstKeyForAction(fallbackAction);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
#getComposerPlaceholder(): string {
|
|
945
|
+
if (!this.#isPromptDeliveryBusy()) return DEFAULT_COMPOSER_PLACEHOLDER;
|
|
946
|
+
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "Steering" : "Message Queueing";
|
|
947
|
+
const parts = [`Enter: ${enterAction}`];
|
|
948
|
+
const queueKey = this.#getMessageQueueShortcut();
|
|
949
|
+
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}: Message Queueing`);
|
|
950
|
+
return `${DEFAULT_COMPOSER_PLACEHOLDER} ${parts.join(" · ")}`;
|
|
951
|
+
}
|
|
952
|
+
|
|
907
953
|
updateEditorChrome(): void {
|
|
908
954
|
if (this.isBashMode) {
|
|
909
955
|
this.editor.borderColor = this.isBashNoContext
|
|
@@ -927,6 +973,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
927
973
|
if (!this.isBashMode) {
|
|
928
974
|
this.editor.setInputPrefix(getDefaultInputPrefix());
|
|
929
975
|
}
|
|
976
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
930
977
|
this.#setComposerTopBorder();
|
|
931
978
|
this.ui.requestRender();
|
|
932
979
|
}
|
|
@@ -73,7 +73,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
|
|
|
73
73
|
get_login_providers: "admin",
|
|
74
74
|
login: "admin",
|
|
75
75
|
negotiate_unattended: "control",
|
|
76
|
-
workflow_gate_response: "
|
|
76
|
+
workflow_gate_response: "control",
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
export const RPC_COMMAND_TYPES: readonly RpcCommandType[] = Object.keys(RPC_COMMAND_SCOPE_REGISTRY) as RpcCommandType[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/jaybeyond/Sayknow_CLI/main/packages/coding-agent/theme-schema.json",
|
|
3
|
+
"name": "gruvbox-dark",
|
|
4
|
+
"vars": {
|
|
5
|
+
"bgHard": "#1d2021",
|
|
6
|
+
"surface": "#32302f",
|
|
7
|
+
"surfaceBright": "#504945",
|
|
8
|
+
"borderNeutral": "#504945",
|
|
9
|
+
"borderSubtle": "#3c3836",
|
|
10
|
+
"fg": "#ebdbb2",
|
|
11
|
+
"muted": "#a89984",
|
|
12
|
+
"dim": "#7c6f64",
|
|
13
|
+
"gray": "#928374",
|
|
14
|
+
"red": "#fb4934",
|
|
15
|
+
"green": "#b8bb26",
|
|
16
|
+
"yellow": "#fabd2f",
|
|
17
|
+
"blue": "#83a598",
|
|
18
|
+
"purple": "#d3869b",
|
|
19
|
+
"aqua": "#8ec07c",
|
|
20
|
+
"orange": "#fe8019",
|
|
21
|
+
"diffRemovalRed": "#cc241d"
|
|
22
|
+
},
|
|
23
|
+
"colors": {
|
|
24
|
+
"accent": "orange",
|
|
25
|
+
"border": "borderNeutral",
|
|
26
|
+
"borderAccent": "orange",
|
|
27
|
+
"borderMuted": "borderSubtle",
|
|
28
|
+
"success": "green",
|
|
29
|
+
"error": "red",
|
|
30
|
+
"warning": "yellow",
|
|
31
|
+
"muted": "muted",
|
|
32
|
+
"dim": "dim",
|
|
33
|
+
"text": "fg",
|
|
34
|
+
"thinkingText": "muted",
|
|
35
|
+
"selectedBg": "surfaceBright",
|
|
36
|
+
"userMessageBg": "surface",
|
|
37
|
+
"userMessageText": "fg",
|
|
38
|
+
"customMessageBg": "surface",
|
|
39
|
+
"customMessageText": "fg",
|
|
40
|
+
"customMessageLabel": "orange",
|
|
41
|
+
"toolPendingBg": "surface",
|
|
42
|
+
"toolSuccessBg": "#283626",
|
|
43
|
+
"toolErrorBg": "#3c2323",
|
|
44
|
+
"toolTitle": "fg",
|
|
45
|
+
"toolOutput": "muted",
|
|
46
|
+
"mdHeading": "yellow",
|
|
47
|
+
"mdLink": "blue",
|
|
48
|
+
"mdLinkUrl": "muted",
|
|
49
|
+
"mdCode": "aqua",
|
|
50
|
+
"mdCodeBlock": "fg",
|
|
51
|
+
"mdCodeBlockBorder": "borderNeutral",
|
|
52
|
+
"mdQuote": "muted",
|
|
53
|
+
"mdQuoteBorder": "borderNeutral",
|
|
54
|
+
"mdHr": "dim",
|
|
55
|
+
"mdListBullet": "orange",
|
|
56
|
+
"toolDiffAdded": "green",
|
|
57
|
+
"toolDiffRemoved": "diffRemovalRed",
|
|
58
|
+
"toolDiffContext": "muted",
|
|
59
|
+
"syntaxComment": "gray",
|
|
60
|
+
"syntaxKeyword": "red",
|
|
61
|
+
"syntaxFunction": "green",
|
|
62
|
+
"syntaxVariable": "blue",
|
|
63
|
+
"syntaxString": "green",
|
|
64
|
+
"syntaxNumber": "purple",
|
|
65
|
+
"syntaxType": "yellow",
|
|
66
|
+
"syntaxOperator": "aqua",
|
|
67
|
+
"syntaxPunctuation": "muted",
|
|
68
|
+
"thinkingOff": "dim",
|
|
69
|
+
"thinkingMinimal": "muted",
|
|
70
|
+
"thinkingLow": "aqua",
|
|
71
|
+
"thinkingMedium": "yellow",
|
|
72
|
+
"thinkingHigh": "orange",
|
|
73
|
+
"thinkingXhigh": "red",
|
|
74
|
+
"bashMode": "green",
|
|
75
|
+
"pythonMode": "yellow",
|
|
76
|
+
"statusLineBg": "bgHard",
|
|
77
|
+
"statusLineSep": "dim",
|
|
78
|
+
"statusLineModel": "orange",
|
|
79
|
+
"statusLinePath": "blue",
|
|
80
|
+
"statusLineGitClean": "green",
|
|
81
|
+
"statusLineGitDirty": "yellow",
|
|
82
|
+
"statusLineContext": "aqua",
|
|
83
|
+
"statusLineSpend": "yellow",
|
|
84
|
+
"statusLineStaged": "green",
|
|
85
|
+
"statusLineDirty": "yellow",
|
|
86
|
+
"statusLineUntracked": "diffRemovalRed",
|
|
87
|
+
"statusLineOutput": "fg",
|
|
88
|
+
"statusLineCost": "orange",
|
|
89
|
+
"statusLineSubagents": "purple"
|
|
90
|
+
},
|
|
91
|
+
"export": {
|
|
92
|
+
"pageBg": "#1d2021",
|
|
93
|
+
"cardBg": "#282828",
|
|
94
|
+
"infoBg": "#32302f"
|
|
95
|
+
},
|
|
96
|
+
"symbols": {
|
|
97
|
+
"preset": "unicode"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import blue_octopus from "./blue-octopus.json" with { type: "json" };
|
|
2
2
|
import claude_code from "./claude-code.json" with { type: "json" };
|
|
3
3
|
import codex from "./codex.json" with { type: "json" };
|
|
4
|
+
import gruvbox_dark from "./gruvbox-dark.json" with { type: "json" };
|
|
4
5
|
import opencode from "./opencode.json" with { type: "json" };
|
|
5
6
|
import red_octopus from "./red-octopus.json" with { type: "json" };
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export const defaultThemes = {
|
|
|
8
9
|
"blue-octopus": blue_octopus,
|
|
9
10
|
"claude-code": claude_code,
|
|
10
11
|
codex,
|
|
12
|
+
"gruvbox-dark": gruvbox_dark,
|
|
11
13
|
opencode,
|
|
12
14
|
"red-octopus": red_octopus,
|
|
13
15
|
};
|
|
@@ -204,14 +204,14 @@ export function computeContextBreakdown(
|
|
|
204
204
|
if (contextWindow > 0) {
|
|
205
205
|
const compactionSettings = session.settings.getGroup("compaction") as CompactionSettings;
|
|
206
206
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
207
|
-
const threshold = resolveThresholdTokens(contextWindow, compactionSettings
|
|
207
|
+
const threshold = resolveThresholdTokens(contextWindow, compactionSettings);
|
|
208
208
|
autoCompactBufferTokens = Math.max(0, contextWindow - threshold);
|
|
209
209
|
} else {
|
|
210
210
|
autoCompactBufferTokens = 0;
|
|
211
211
|
}
|
|
212
212
|
// Even when fully disabled, fall back to a sensible reserve floor for display.
|
|
213
213
|
if (autoCompactBufferTokens === 0 && compactionSettings.enabled) {
|
|
214
|
-
autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings
|
|
214
|
+
autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow - usedTokens));
|
|
@@ -23,7 +23,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
23
23
|
"|-----|--------|",
|
|
24
24
|
"| `Enter` | Send / queue while busy |",
|
|
25
25
|
`| \`${appKey(bindings, "app.message.queue")}\` | Queue message for next turn |`,
|
|
26
|
-
"| `Shift+Enter` | New line |",
|
|
26
|
+
"| `Shift+Enter` / `Ctrl+J` | New line |",
|
|
27
27
|
"| `Ctrl+W` / `Option+Backspace` | Delete word backwards |",
|
|
28
28
|
"| `Ctrl+U` | Delete to start of line |",
|
|
29
29
|
"| `Ctrl+K` | Delete to end of line |",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide registry mapping a session id to a sink that delivers a local
|
|
3
|
+
* file to the session's connected Telegram chat as a document. Registered by
|
|
4
|
+
* the notifications extension; consumed by the telegram_send tool.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Delivers a local file to the session's Telegram chat. */
|
|
8
|
+
export type TelegramFileSink = (file: { path: string; caption?: string }) => Promise<{ ok: boolean; error?: string }>;
|
|
9
|
+
|
|
10
|
+
const sinks = new Map<string, TelegramFileSink>();
|
|
11
|
+
|
|
12
|
+
/** Register `sink` for `sessionId`. Returns a disposer that clears it. */
|
|
13
|
+
export function registerTelegramFileSink(sessionId: string, sink: TelegramFileSink): () => void {
|
|
14
|
+
sinks.set(sessionId, sink);
|
|
15
|
+
return () => {
|
|
16
|
+
if (sinks.get(sessionId) === sink) sinks.delete(sessionId);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The Telegram file sink for `sessionId`, if one is registered. */
|
|
21
|
+
export function getTelegramFileSink(sessionId: string): TelegramFileSink | undefined {
|
|
22
|
+
return sinks.get(sessionId);
|
|
23
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NotificationAdapterPayload,
|
|
3
|
+
NotificationEvent,
|
|
4
|
+
NotificationPresentationAdapter,
|
|
5
|
+
NotificationReplyRoute,
|
|
6
|
+
} from "./engine";
|
|
7
|
+
import { truncate } from "./helpers";
|
|
8
|
+
|
|
9
|
+
type AdapterKind = "discord" | "slack";
|
|
10
|
+
|
|
11
|
+
interface ChatAdapterOptions {
|
|
12
|
+
kind: AdapterKind;
|
|
13
|
+
channelId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface InboundShape {
|
|
17
|
+
sessionId?: unknown;
|
|
18
|
+
actionId?: unknown;
|
|
19
|
+
answer?: unknown;
|
|
20
|
+
text?: unknown;
|
|
21
|
+
value?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function text(value: unknown): string | undefined {
|
|
25
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function publicLine(label: string, value: unknown): string | undefined {
|
|
29
|
+
const v = text(value);
|
|
30
|
+
return v ? `${label}: ${truncate(v, 280)}` : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function actionText(event: Extract<NotificationEvent, { type: "action_needed" }>, format: "discord" | "slack"): string {
|
|
34
|
+
if (event.kind === "idle") {
|
|
35
|
+
const summary = text(event.summary);
|
|
36
|
+
return summary ? `Agent idle\n${truncate(summary, 1200)}` : "Agent idle";
|
|
37
|
+
}
|
|
38
|
+
const question = truncate(text(event.question) ?? "Question", 1200);
|
|
39
|
+
const options = Array.isArray(event.options) ? event.options.map(option => String(option)) : [];
|
|
40
|
+
const lines = [`Question: ${question}`];
|
|
41
|
+
if (options.length > 0) {
|
|
42
|
+
lines.push(
|
|
43
|
+
...options.map((option, index) => {
|
|
44
|
+
const label = truncate(option, 180);
|
|
45
|
+
return format === "slack" ? `${index + 1}. ${label}` : `**${index + 1}.** ${label}`;
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
lines.push("Reply with text.");
|
|
50
|
+
}
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function frameText(event: Extract<NotificationEvent, { type: "frame" }>): string | undefined {
|
|
55
|
+
const frame = event.frame;
|
|
56
|
+
const kind = text(frame.type);
|
|
57
|
+
if (!kind) return undefined;
|
|
58
|
+
const lines = [`SKC ${kind.replace(/_/g, " ")}`];
|
|
59
|
+
for (const line of [
|
|
60
|
+
publicLine("title", frame.title),
|
|
61
|
+
publicLine("repo", frame.repo),
|
|
62
|
+
publicLine("branch", frame.branch),
|
|
63
|
+
publicLine("task", frame.task),
|
|
64
|
+
publicLine("goal", frame.goal),
|
|
65
|
+
publicLine("model", frame.model),
|
|
66
|
+
]) {
|
|
67
|
+
if (line) lines.push(line);
|
|
68
|
+
}
|
|
69
|
+
const body = text(frame.text) ?? text(frame.lastMessage) ?? text(frame.caption);
|
|
70
|
+
if (body) lines.push(truncate(body, 1800));
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function routeFromInbound(input: unknown): NotificationReplyRoute | undefined {
|
|
75
|
+
const raw = input as InboundShape;
|
|
76
|
+
if (!raw || typeof raw !== "object") return undefined;
|
|
77
|
+
const sessionId = text(raw.sessionId);
|
|
78
|
+
const actionId = text(raw.actionId);
|
|
79
|
+
if (!sessionId || !actionId) return undefined;
|
|
80
|
+
const answer = raw.answer ?? raw.value ?? raw.text;
|
|
81
|
+
if (typeof answer !== "string" && typeof answer !== "number" && typeof answer !== "object") return undefined;
|
|
82
|
+
return { sessionId, actionId, answer: answer as NotificationReplyRoute["answer"] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class DiscordNotificationAdapter implements NotificationPresentationAdapter {
|
|
86
|
+
readonly kind = "discord" as const;
|
|
87
|
+
constructor(private readonly opts: ChatAdapterOptions) {}
|
|
88
|
+
|
|
89
|
+
render(event: NotificationEvent): NotificationAdapterPayload[] {
|
|
90
|
+
if (event.type === "action_resolved") return [];
|
|
91
|
+
const content = event.type === "action_needed" ? actionText(event, "discord") : frameText(event);
|
|
92
|
+
if (!content) return [];
|
|
93
|
+
const payload: Record<string, unknown> = {
|
|
94
|
+
content,
|
|
95
|
+
allowed_mentions: { parse: [] },
|
|
96
|
+
};
|
|
97
|
+
if (this.opts.channelId) payload.channel_id = this.opts.channelId;
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
adapter: this.kind,
|
|
101
|
+
channelKey: this.opts.channelId,
|
|
102
|
+
body: payload,
|
|
103
|
+
route: event.type === "action_needed" ? { sessionId: event.sessionId, actionId: event.id } : undefined,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mapInbound(input: unknown): NotificationReplyRoute | undefined {
|
|
109
|
+
return routeFromInbound(input);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class SlackNotificationAdapter implements NotificationPresentationAdapter {
|
|
114
|
+
readonly kind = "slack" as const;
|
|
115
|
+
constructor(private readonly opts: ChatAdapterOptions) {}
|
|
116
|
+
|
|
117
|
+
render(event: NotificationEvent): NotificationAdapterPayload[] {
|
|
118
|
+
if (event.type === "action_resolved") return [];
|
|
119
|
+
const textValue = event.type === "action_needed" ? actionText(event, "slack") : frameText(event);
|
|
120
|
+
if (!textValue) return [];
|
|
121
|
+
const payload: Record<string, unknown> = {
|
|
122
|
+
text: textValue,
|
|
123
|
+
mrkdwn: true,
|
|
124
|
+
};
|
|
125
|
+
if (this.opts.channelId) payload.channel = this.opts.channelId;
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
adapter: this.kind,
|
|
129
|
+
channelKey: this.opts.channelId,
|
|
130
|
+
body: payload,
|
|
131
|
+
route: event.type === "action_needed" ? { sessionId: event.sessionId, actionId: event.id } : undefined,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
mapInbound(input: unknown): NotificationReplyRoute | undefined {
|
|
137
|
+
return routeFromInbound(input);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createDiscordAdapter(opts: Omit<ChatAdapterOptions, "kind"> = {}): NotificationPresentationAdapter {
|
|
142
|
+
return new DiscordNotificationAdapter({ ...opts, kind: "discord" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function createSlackAdapter(opts: Omit<ChatAdapterOptions, "kind"> = {}): NotificationPresentationAdapter {
|
|
146
|
+
return new SlackNotificationAdapter({ ...opts, kind: "slack" });
|
|
147
|
+
}
|