@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- 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.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -29,6 +29,9 @@ interface PromptActionAutocompleteOptions {
|
|
|
29
29
|
copyCurrentLine: () => void;
|
|
30
30
|
copyPrompt: () => void;
|
|
31
31
|
pasteImage: () => void;
|
|
32
|
+
newSession: () => void;
|
|
33
|
+
showHelp: () => void;
|
|
34
|
+
scrollTmuxToPreviousUserInput: () => void;
|
|
32
35
|
undo: (prefix: string) => void;
|
|
33
36
|
moveCursorToMessageEnd: () => void;
|
|
34
37
|
moveCursorToMessageStart: () => void;
|
|
@@ -100,6 +103,12 @@ function mergeAutocompleteSuggestions(
|
|
|
100
103
|
|
|
101
104
|
return { items, prefix: primary.prefix };
|
|
102
105
|
}
|
|
106
|
+
const ADVANCED_SLASH_COMMAND_PRIORITIES = new Map<string, number>([["grok-build-usage", -100]]);
|
|
107
|
+
|
|
108
|
+
function getSlashCommandPriority(command: SlashCommand | undefined, item: AutocompleteItem): number {
|
|
109
|
+
if (command?.priority !== undefined) return command.priority;
|
|
110
|
+
return ADVANCED_SLASH_COMMAND_PRIORITIES.get(item.value) ?? 0;
|
|
111
|
+
}
|
|
103
112
|
|
|
104
113
|
function sortSlashCommandSuggestions(
|
|
105
114
|
suggestions: { items: AutocompleteItem[]; prefix: string } | null,
|
|
@@ -122,7 +131,7 @@ function sortSlashCommandSuggestions(
|
|
|
122
131
|
index,
|
|
123
132
|
commandIndex,
|
|
124
133
|
matchRank: getSlashCommandMatchRank(query, lowerName),
|
|
125
|
-
priority: command
|
|
134
|
+
priority: getSlashCommandPriority(command, item),
|
|
126
135
|
score: Math.max(nameScore, descScore),
|
|
127
136
|
};
|
|
128
137
|
})
|
|
@@ -168,6 +177,9 @@ function getSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
|
168
177
|
return token;
|
|
169
178
|
}
|
|
170
179
|
|
|
180
|
+
function isLeadingSlashToken(textBeforeCursor: string, slashPrefix: string): boolean {
|
|
181
|
+
return textBeforeCursor.trimStart() === slashPrefix;
|
|
182
|
+
}
|
|
171
183
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
172
184
|
#baseProvider: CombinedAutocompleteProvider;
|
|
173
185
|
#actions: PromptActionDefinition[];
|
|
@@ -214,10 +226,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
214
226
|
|
|
215
227
|
const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
|
|
216
228
|
if (slashPrefix) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
const isLeading = isLeadingSlashToken(textBeforeCursor, slashPrefix);
|
|
230
|
+
const baseSuggestions = isLeading
|
|
231
|
+
? withoutSkillCommandSuggestions(await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol))
|
|
232
|
+
: null;
|
|
233
|
+
const skillCommandSuggestions =
|
|
234
|
+
isLeading || slashPrefix.startsWith("/skill")
|
|
235
|
+
? this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: isLeading })
|
|
236
|
+
: null;
|
|
221
237
|
return sortSlashCommandSuggestions(
|
|
222
238
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
223
239
|
this.#commands,
|
|
@@ -291,7 +307,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
291
307
|
const baseSuggestions = withoutSkillCommandSuggestions(
|
|
292
308
|
this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null,
|
|
293
309
|
);
|
|
294
|
-
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
|
|
310
|
+
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: false });
|
|
295
311
|
return sortSlashCommandSuggestions(
|
|
296
312
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
297
313
|
this.#commands,
|
|
@@ -302,11 +318,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
302
318
|
return tryEmojiInlineReplace(textBeforeCursor);
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
#getSkillCommandSuggestions(
|
|
321
|
+
#getSkillCommandSuggestions(
|
|
322
|
+
textBeforeCursor: string,
|
|
323
|
+
options: { includeEmpty: boolean },
|
|
324
|
+
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
306
325
|
const prefix = getSlashTokenPrefix(textBeforeCursor);
|
|
307
326
|
if (!prefix) return null;
|
|
308
327
|
const query = prefix.slice(1).toLowerCase();
|
|
309
|
-
if (query.length === 0) return null;
|
|
328
|
+
if (query.length === 0 && !options.includeEmpty) return null;
|
|
310
329
|
const normalizedQuery = query.startsWith("skill-") ? `skill:${query.slice("skill-".length)}` : query;
|
|
311
330
|
const exactNonSkillCommand = this.#commands.some(
|
|
312
331
|
command => command.name === query && !command.name.startsWith("skill:"),
|
|
@@ -357,6 +376,20 @@ export function createPromptActionAutocompleteProvider(
|
|
|
357
376
|
): PromptActionAutocompleteProvider {
|
|
358
377
|
const editorKeybindings = getKeybindings();
|
|
359
378
|
const actions: PromptActionDefinition[] = [
|
|
379
|
+
{
|
|
380
|
+
id: "new-session",
|
|
381
|
+
label: "Start new session",
|
|
382
|
+
description: formatKeyHints(options.keybindings.getKeys("app.session.new")) || "/new",
|
|
383
|
+
keywords: ["new", "session", "fresh", "clear", "start", "conversation"],
|
|
384
|
+
execute: options.newSession,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: "help",
|
|
388
|
+
label: "Open command help",
|
|
389
|
+
description: "/help",
|
|
390
|
+
keywords: ["help", "commands", "command", "palette", "shortcuts", "beginner"],
|
|
391
|
+
execute: options.showHelp,
|
|
392
|
+
},
|
|
360
393
|
{
|
|
361
394
|
id: "copy-line",
|
|
362
395
|
label: "Copy current line",
|
|
@@ -378,6 +411,13 @@ export function createPromptActionAutocompleteProvider(
|
|
|
378
411
|
keywords: ["paste", "image", "clipboard", "screenshot", "attach", "vision"],
|
|
379
412
|
execute: options.pasteImage,
|
|
380
413
|
},
|
|
414
|
+
{
|
|
415
|
+
id: "tmux-previous-user-input",
|
|
416
|
+
label: "Scroll to previous user input",
|
|
417
|
+
description: "tmux copy-mode",
|
|
418
|
+
keywords: ["scroll", "tmux", "previous", "user", "input", "prompt", "history"],
|
|
419
|
+
execute: options.scrollTmuxToPreviousUserInput,
|
|
420
|
+
},
|
|
381
421
|
{
|
|
382
422
|
id: "undo",
|
|
383
423
|
label: "Undo",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* the interactive select/editor UI) when an unattended controller + gate broker
|
|
12
12
|
* are attached is wired with the transport in #321 and exercised by #323.
|
|
13
13
|
*/
|
|
14
|
-
import type { RpcJsonSchema } from "../../rpc/rpc-types";
|
|
14
|
+
import type { RpcJsonSchema, RpcWorkflowGateKind, RpcWorkflowStage } from "../../rpc/rpc-types";
|
|
15
15
|
import type { OpenGateInput } from "./workflow-gate-broker";
|
|
16
16
|
|
|
17
17
|
/** "Other (type your own)" sentinel, mirroring the interactive ask tool. */
|
|
@@ -26,6 +26,11 @@ export interface AskGateDeepInterviewState {
|
|
|
26
26
|
ambiguity: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export interface AskGateWorkflowGateMeta {
|
|
30
|
+
stage: RpcWorkflowStage;
|
|
31
|
+
kind: RpcWorkflowGateKind;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export interface AskGateQuestion {
|
|
30
35
|
id: string;
|
|
31
36
|
question: string;
|
|
@@ -37,6 +42,8 @@ export interface AskGateQuestion {
|
|
|
37
42
|
* `stage_state`; when absent, the question text is regex-parsed as a fallback.
|
|
38
43
|
*/
|
|
39
44
|
deepInterview?: AskGateDeepInterviewState;
|
|
45
|
+
/** Override the emitted workflow gate address for non-deep-interview ask prompts. */
|
|
46
|
+
workflowGate?: AskGateWorkflowGateMeta;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export interface AskGateResult {
|
|
@@ -46,6 +53,7 @@ export interface AskGateResult {
|
|
|
46
53
|
multi: boolean;
|
|
47
54
|
selectedOptions: string[];
|
|
48
55
|
customInput?: string;
|
|
56
|
+
clarificationQuestion?: string;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/**
|
|
@@ -54,12 +62,15 @@ export interface AskGateResult {
|
|
|
54
62
|
* `selected` are picked option labels; free text is conveyed by `other: true`
|
|
55
63
|
* plus `custom`, encoded separately from `selected` so a real option whose label
|
|
56
64
|
* happens to equal the display sentinel can never collide with the free-text path.
|
|
65
|
+
*
|
|
66
|
+
* `action: "clarify"` is a non-answer. It lets a Deep Interview user ask about
|
|
67
|
+
* the presented choices without advancing the round, scoring ambiguity, or
|
|
68
|
+
* recording selection state.
|
|
57
69
|
*/
|
|
58
|
-
export
|
|
59
|
-
selected: string[];
|
|
60
|
-
other?:
|
|
61
|
-
|
|
62
|
-
}
|
|
70
|
+
export type DeepInterviewGateAnswer =
|
|
71
|
+
| { selected: string[]; other?: false; custom?: undefined; action?: "answer" }
|
|
72
|
+
| { selected: string[]; other: true; custom: string; action?: "answer" }
|
|
73
|
+
| { action: "clarify"; question: string };
|
|
63
74
|
|
|
64
75
|
export class DeepInterviewGateError extends Error {
|
|
65
76
|
constructor(
|
|
@@ -68,6 +79,7 @@ export class DeepInterviewGateError extends Error {
|
|
|
68
79
|
| "unknown_option"
|
|
69
80
|
| "multi_not_allowed"
|
|
70
81
|
| "missing_custom"
|
|
82
|
+
| "missing_clarification"
|
|
71
83
|
| "empty_selection"
|
|
72
84
|
| "duplicate_selection",
|
|
73
85
|
message: string,
|
|
@@ -119,14 +131,29 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
119
131
|
properties: {
|
|
120
132
|
selected: selectedBase,
|
|
121
133
|
other: { type: "boolean", description: "set true to provide a free-text answer in `custom`" },
|
|
122
|
-
custom: {
|
|
134
|
+
custom: {
|
|
135
|
+
type: "string",
|
|
136
|
+
minLength: 1,
|
|
137
|
+
pattern: "\\S",
|
|
138
|
+
description: "free-text answer; required when `other` is true",
|
|
139
|
+
},
|
|
140
|
+
action: {
|
|
141
|
+
type: "string",
|
|
142
|
+
enum: ["answer", "clarify"],
|
|
143
|
+
description: "set to `clarify` to ask about the choices without answering the round",
|
|
144
|
+
},
|
|
145
|
+
question: {
|
|
146
|
+
type: "string",
|
|
147
|
+
minLength: 1,
|
|
148
|
+
pattern: "\\S",
|
|
149
|
+
description: "clarification question; required when action is `clarify`",
|
|
150
|
+
},
|
|
123
151
|
},
|
|
124
|
-
required: ["selected"],
|
|
125
152
|
additionalProperties: false,
|
|
126
153
|
anyOf: [
|
|
127
154
|
{
|
|
128
155
|
type: "object",
|
|
129
|
-
properties: { selected: selectedOnly, other: { const: false } },
|
|
156
|
+
properties: { selected: selectedOnly, other: { const: false }, action: { const: "answer" } },
|
|
130
157
|
required: ["selected"],
|
|
131
158
|
additionalProperties: false,
|
|
132
159
|
},
|
|
@@ -135,11 +162,21 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
135
162
|
properties: {
|
|
136
163
|
selected: selectedWithOther,
|
|
137
164
|
other: { const: true },
|
|
138
|
-
custom: { type: "string", minLength: 1 },
|
|
165
|
+
custom: { type: "string", minLength: 1, pattern: "\\S" },
|
|
166
|
+
action: { const: "answer" },
|
|
139
167
|
},
|
|
140
168
|
required: ["selected", "other", "custom"],
|
|
141
169
|
additionalProperties: false,
|
|
142
170
|
},
|
|
171
|
+
{
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
action: { const: "clarify" },
|
|
175
|
+
question: { type: "string", minLength: 1, pattern: "\\S" },
|
|
176
|
+
},
|
|
177
|
+
required: ["action", "question"],
|
|
178
|
+
additionalProperties: false,
|
|
179
|
+
},
|
|
143
180
|
],
|
|
144
181
|
};
|
|
145
182
|
}
|
|
@@ -157,13 +194,13 @@ function structuredDeepInterviewState(meta: AskGateDeepInterviewState): Record<s
|
|
|
157
194
|
return state;
|
|
158
195
|
}
|
|
159
196
|
|
|
160
|
-
/** Build the `workflow_gate` open-input for one
|
|
197
|
+
/** Build the `workflow_gate` open-input for one ask question. */
|
|
161
198
|
export function questionToGate(question: AskGateQuestion): OpenGateInput {
|
|
162
199
|
const labels = question.options.map(o => o.label);
|
|
163
200
|
const schema = questionAnswerSchema(question, labels);
|
|
164
201
|
return {
|
|
165
|
-
stage: "deep-interview",
|
|
166
|
-
kind: "question",
|
|
202
|
+
stage: question.workflowGate?.stage ?? "deep-interview",
|
|
203
|
+
kind: question.workflowGate?.kind ?? "question",
|
|
167
204
|
schema,
|
|
168
205
|
options: question.options.map((o, i) => ({
|
|
169
206
|
value: o.label,
|
|
@@ -178,6 +215,7 @@ export function questionToGate(question: AskGateQuestion): OpenGateInput {
|
|
|
178
215
|
multi: question.multi ?? false,
|
|
179
216
|
options: labels,
|
|
180
217
|
other_option: GATE_OTHER_OPTION,
|
|
218
|
+
clarification_action: "clarify",
|
|
181
219
|
...(question.deepInterview
|
|
182
220
|
? structuredDeepInterviewState(question.deepInterview)
|
|
183
221
|
: deepInterviewQuestionState(question.question)),
|
|
@@ -188,8 +226,14 @@ export function questionToGate(question: AskGateQuestion): OpenGateInput {
|
|
|
188
226
|
|
|
189
227
|
function isAnswer(value: unknown): value is DeepInterviewGateAnswer {
|
|
190
228
|
if (typeof value !== "object" || value === null) return false;
|
|
191
|
-
const v = value as
|
|
229
|
+
const v = value as Record<string, unknown>;
|
|
230
|
+
if (v.action === "clarify") {
|
|
231
|
+
return (
|
|
232
|
+
typeof v.question === "string" && v.selected === undefined && v.other === undefined && v.custom === undefined
|
|
233
|
+
);
|
|
234
|
+
}
|
|
192
235
|
return (
|
|
236
|
+
(v.action === undefined || v.action === "answer") &&
|
|
193
237
|
Array.isArray(v.selected) &&
|
|
194
238
|
v.selected.every(s => typeof s === "string") &&
|
|
195
239
|
(v.other === undefined || typeof v.other === "boolean") &&
|
|
@@ -207,11 +251,24 @@ export function gateAnswerToResult(question: AskGateQuestion, answer: unknown):
|
|
|
207
251
|
if (!isAnswer(answer)) {
|
|
208
252
|
throw new DeepInterviewGateError(
|
|
209
253
|
"invalid_answer_shape",
|
|
210
|
-
|
|
254
|
+
'answer must be an answer ({ selected: string[]; other?: boolean; custom?: string }) or clarification ({ action: "clarify"; question: string })',
|
|
211
255
|
);
|
|
212
256
|
}
|
|
213
257
|
const labels = question.options.map(o => o.label);
|
|
214
258
|
const multi = question.multi ?? false;
|
|
259
|
+
if (answer.action === "clarify") {
|
|
260
|
+
if (answer.question.trim() === "") {
|
|
261
|
+
throw new DeepInterviewGateError("missing_clarification", "clarification question is required");
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
id: question.id,
|
|
265
|
+
question: question.question,
|
|
266
|
+
options: labels,
|
|
267
|
+
multi,
|
|
268
|
+
selectedOptions: [],
|
|
269
|
+
clarificationQuestion: answer.question,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
215
272
|
const valid = new Set(labels);
|
|
216
273
|
for (const sel of answer.selected) {
|
|
217
274
|
if (!valid.has(sel)) throw new DeepInterviewGateError("unknown_option", `unknown option: ${sel}`);
|
|
@@ -25,6 +25,7 @@ const SUPPORTED_KEYWORDS = new Set<keyof RpcJsonSchema>([
|
|
|
25
25
|
"items",
|
|
26
26
|
"minLength",
|
|
27
27
|
"maxLength",
|
|
28
|
+
"pattern",
|
|
28
29
|
"minItems",
|
|
29
30
|
"maxItems",
|
|
30
31
|
"uniqueItems",
|
|
@@ -107,6 +108,17 @@ function walkSchema(schema: RpcJsonSchema, depth: number, path: string): void {
|
|
|
107
108
|
throw new WorkflowGateSchemaError(`${meta} at ${path} must be a string`);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
111
|
+
if (schema.pattern !== undefined) {
|
|
112
|
+
if (typeof schema.pattern !== "string") {
|
|
113
|
+
throw new WorkflowGateSchemaError(`pattern at ${path} must be a string`);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
new RegExp(schema.pattern);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
119
|
+
throw new WorkflowGateSchemaError(`pattern at ${path} must be a valid regular expression: ${message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
110
122
|
for (const limit of ["minLength", "maxLength", "minItems", "maxItems"] as const) {
|
|
111
123
|
const v = schema[limit];
|
|
112
124
|
if (v !== undefined && (typeof v !== "number" || !Number.isInteger(v) || v < 0)) {
|
|
@@ -242,6 +254,14 @@ function validateValue(schema: RpcJsonSchema, value: unknown, path: string, erro
|
|
|
242
254
|
expected: schema.maxLength,
|
|
243
255
|
});
|
|
244
256
|
}
|
|
257
|
+
if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
|
|
258
|
+
errors.push({
|
|
259
|
+
path,
|
|
260
|
+
keyword: "pattern",
|
|
261
|
+
message: `does not match pattern ${schema.pattern}`,
|
|
262
|
+
expected: schema.pattern,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
245
265
|
}
|
|
246
266
|
if (typeof value === "number") {
|
|
247
267
|
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const BEGINNER_HIDDEN_EXTENSION_COMMANDS = new Set(["grok-build-usage"]);
|
|
2
|
+
|
|
3
|
+
export function shouldShowExtensionCommand(name: string, activeProvider: string | undefined): boolean {
|
|
4
|
+
if (!BEGINNER_HIDDEN_EXTENSION_COMMANDS.has(name)) return true;
|
|
5
|
+
if (name === "grok-build-usage") {
|
|
6
|
+
return activeProvider === "grok-build" || Boolean(process.env.GROK_CLI_OAUTH_TOKEN);
|
|
7
|
+
}
|
|
8
|
+
return true;
|
|
9
|
+
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -188,7 +188,8 @@ export type SymbolKey =
|
|
|
188
188
|
| "tab.tools"
|
|
189
189
|
| "tab.memory"
|
|
190
190
|
| "tab.tasks"
|
|
191
|
-
| "tab.providers"
|
|
191
|
+
| "tab.providers"
|
|
192
|
+
| "tab.integrations";
|
|
192
193
|
|
|
193
194
|
type SymbolMap = Record<SymbolKey, string>;
|
|
194
195
|
|
|
@@ -353,6 +354,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
353
354
|
"tab.memory": "🧠",
|
|
354
355
|
"tab.tasks": "📦",
|
|
355
356
|
"tab.providers": "🌐",
|
|
357
|
+
"tab.integrations": "🔌",
|
|
356
358
|
};
|
|
357
359
|
|
|
358
360
|
const NERD_SYMBOLS: SymbolMap = {
|
|
@@ -612,6 +614,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
612
614
|
"tab.memory": "",
|
|
613
615
|
"tab.tasks": "",
|
|
614
616
|
"tab.providers": "",
|
|
617
|
+
"tab.integrations": "",
|
|
615
618
|
};
|
|
616
619
|
|
|
617
620
|
const ASCII_SYMBOLS: SymbolMap = {
|
|
@@ -774,6 +777,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
774
777
|
"tab.memory": "[Y]",
|
|
775
778
|
"tab.tasks": "[K]",
|
|
776
779
|
"tab.providers": "[P]",
|
|
780
|
+
"tab.integrations": "[+]",
|
|
777
781
|
};
|
|
778
782
|
|
|
779
783
|
const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { resolveSkcTmuxCommand } from "../skc-runtime/tmux-common";
|
|
2
|
+
|
|
3
|
+
// tmux copy-mode pads lines to the pane width, so allow trailing spaces before
|
|
4
|
+
// the line end while keeping the match scoped to the standalone `user` label.
|
|
5
|
+
export const TMUX_PREVIOUS_USER_INPUT_SEARCH_PATTERN = "^ *user *$";
|
|
6
|
+
|
|
7
|
+
type TmuxScrollFailureReason =
|
|
8
|
+
| "not_inside_tmux"
|
|
9
|
+
| "tmux_command_failed"
|
|
10
|
+
| "copy_mode_failed"
|
|
11
|
+
| "history_bottom_failed"
|
|
12
|
+
| "search_failed";
|
|
13
|
+
|
|
14
|
+
export type TmuxScrollToPreviousUserInputResult =
|
|
15
|
+
| { ok: true }
|
|
16
|
+
| { ok: false; reason: TmuxScrollFailureReason; error?: string };
|
|
17
|
+
|
|
18
|
+
interface TmuxSpawnResult {
|
|
19
|
+
exitCode: number | null;
|
|
20
|
+
stderr?: { toString(): string };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type TmuxSpawnSync = (command: string, args: string[], env: NodeJS.ProcessEnv) => TmuxSpawnResult;
|
|
24
|
+
|
|
25
|
+
function defaultSpawnSync(command: string, args: string[], env: NodeJS.ProcessEnv): TmuxSpawnResult {
|
|
26
|
+
return Bun.spawnSync([command, ...args], {
|
|
27
|
+
env,
|
|
28
|
+
stdout: "ignore",
|
|
29
|
+
stderr: "pipe",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getErrorText(result: TmuxSpawnResult): string | undefined {
|
|
34
|
+
const text = result.stderr?.toString().trim();
|
|
35
|
+
return text ? text : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runTmuxStep(
|
|
39
|
+
command: string,
|
|
40
|
+
args: string[],
|
|
41
|
+
env: NodeJS.ProcessEnv,
|
|
42
|
+
spawnSync: TmuxSpawnSync,
|
|
43
|
+
reason: TmuxScrollFailureReason,
|
|
44
|
+
): TmuxScrollToPreviousUserInputResult {
|
|
45
|
+
try {
|
|
46
|
+
const result = spawnSync(command, args, env);
|
|
47
|
+
if (result.exitCode === 0) return { ok: true };
|
|
48
|
+
return { ok: false, reason, error: getErrorText(result) };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { ok: false, reason, error: error instanceof Error ? error.message : String(error) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function scrollTmuxToPreviousUserInput(
|
|
55
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
56
|
+
spawnSync: TmuxSpawnSync = defaultSpawnSync,
|
|
57
|
+
): TmuxScrollToPreviousUserInputResult {
|
|
58
|
+
if (!env.TMUX) return { ok: false, reason: "not_inside_tmux" };
|
|
59
|
+
|
|
60
|
+
let tmuxCommand: string;
|
|
61
|
+
try {
|
|
62
|
+
tmuxCommand = resolveSkcTmuxCommand(env);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
reason: "tmux_command_failed",
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const targetPane = env.TMUX_PANE?.trim();
|
|
72
|
+
const targetArgs = targetPane ? ["-t", targetPane] : [];
|
|
73
|
+
const copyModeResult = runTmuxStep(tmuxCommand, ["copy-mode", ...targetArgs], env, spawnSync, "copy_mode_failed");
|
|
74
|
+
if (!copyModeResult.ok) return copyModeResult;
|
|
75
|
+
|
|
76
|
+
const historyBottomResult = runTmuxStep(
|
|
77
|
+
tmuxCommand,
|
|
78
|
+
["send-keys", ...targetArgs, "-X", "history-bottom"],
|
|
79
|
+
env,
|
|
80
|
+
spawnSync,
|
|
81
|
+
"history_bottom_failed",
|
|
82
|
+
);
|
|
83
|
+
if (!historyBottomResult.ok) return historyBottomResult;
|
|
84
|
+
|
|
85
|
+
return runTmuxStep(
|
|
86
|
+
tmuxCommand,
|
|
87
|
+
["send-keys", ...targetArgs, "-X", "search-backward", TMUX_PREVIOUS_USER_INPUT_SEARCH_PATTERN],
|
|
88
|
+
env,
|
|
89
|
+
spawnSync,
|
|
90
|
+
"search_failed",
|
|
91
|
+
);
|
|
92
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -110,6 +110,7 @@ export interface InteractiveModeContext {
|
|
|
110
110
|
retryLoader: Loader | undefined;
|
|
111
111
|
autoCompactionEscapeHandler?: () => void;
|
|
112
112
|
retryEscapeHandler?: () => void;
|
|
113
|
+
retryEscapePrimed: boolean;
|
|
113
114
|
retryCountdownTimer?: NodeJS.Timeout;
|
|
114
115
|
unsubscribe?: () => void;
|
|
115
116
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
@@ -209,6 +210,7 @@ export interface InteractiveModeContext {
|
|
|
209
210
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
210
211
|
handleChangelogCommand(showFull?: boolean): Promise<void>;
|
|
211
212
|
handleHotkeysCommand(): void;
|
|
213
|
+
handleHelpCommand(): void;
|
|
212
214
|
handleToolsCommand(): void;
|
|
213
215
|
handleContextCommand(): void;
|
|
214
216
|
handleDumpCommand(): void;
|
|
@@ -290,7 +292,12 @@ export interface InteractiveModeContext {
|
|
|
290
292
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
291
293
|
): Promise<string | undefined>;
|
|
292
294
|
hideHookSelector(): void;
|
|
293
|
-
showHookInput(
|
|
295
|
+
showHookInput(
|
|
296
|
+
title: string,
|
|
297
|
+
placeholder?: string,
|
|
298
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
299
|
+
inputOptions?: { readonly initialValue?: string },
|
|
300
|
+
): Promise<string | undefined>;
|
|
294
301
|
hideHookInput(): void;
|
|
295
302
|
showHookEditor(
|
|
296
303
|
title: string,
|
|
@@ -38,6 +38,8 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
38
38
|
`| \`${appKey(bindings, "app.clear")}\` | Clear editor (first) / exit (second) |`,
|
|
39
39
|
`| \`${appKey(bindings, "app.exit")}\` | Exit (when editor is empty) |`,
|
|
40
40
|
`| \`${appKey(bindings, "app.suspend")}\` | Suspend to background |`,
|
|
41
|
+
`| \`${appKey(bindings, "app.commandPalette.open")}\` | Open command palette |`,
|
|
42
|
+
`| \`${appKey(bindings, "app.session.new")}\` | Start a fresh session |`,
|
|
41
43
|
`| \`${appKey(bindings, "app.thinking.cycle")}\` | Cycle thinking level |`,
|
|
42
44
|
`| \`${appKey(bindings, "app.model.cycleForward")}\` | Cycle configured model roles |`,
|
|
43
45
|
`| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle configured model roles temporarily |`,
|
|
@@ -51,8 +53,9 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
51
53
|
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
|
52
54
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
|
|
53
55
|
`| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
|
|
54
|
-
"|
|
|
55
|
-
"|
|
|
56
|
+
"| `?` | Show help / shortcuts |",
|
|
57
|
+
"| `#` | Prompt actions (command-palette style actions) |",
|
|
58
|
+
"| `/` | Slash commands (try `/help` or `/new`) |",
|
|
56
59
|
"| `!` | Run bash command |",
|
|
57
60
|
"| `!!` | Run bash command (excluded from context) |",
|
|
58
61
|
"| `$` | Run Python in shared kernel |",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { settings } from "../../config/settings";
|
|
2
|
+
|
|
3
|
+
export type TerminalBellEvent = "complete" | "approval" | "ask";
|
|
4
|
+
|
|
5
|
+
const BEL = "\x07";
|
|
6
|
+
|
|
7
|
+
function getBellSetting(key: Parameters<typeof settings.get>[0]): boolean {
|
|
8
|
+
try {
|
|
9
|
+
return Boolean(settings.get(key));
|
|
10
|
+
} catch {
|
|
11
|
+
// Some component-level tests exercise UI helpers before Settings.init().
|
|
12
|
+
// Terminal bells are purely best-effort, so an unavailable settings store
|
|
13
|
+
// must not make the UI path throw.
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function enabledForEvent(event: TerminalBellEvent): boolean {
|
|
19
|
+
if (!getBellSetting("notifications.terminalBell")) return false;
|
|
20
|
+
switch (event) {
|
|
21
|
+
case "complete":
|
|
22
|
+
return getBellSetting("notifications.bellOnComplete");
|
|
23
|
+
case "approval":
|
|
24
|
+
return getBellSetting("notifications.bellOnApproval");
|
|
25
|
+
case "ask":
|
|
26
|
+
return getBellSetting("notifications.bellOnAsk");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ringTerminalBell(
|
|
31
|
+
event: TerminalBellEvent,
|
|
32
|
+
output: Pick<NodeJS.WriteStream, "write"> = process.stdout,
|
|
33
|
+
): void {
|
|
34
|
+
if (!enabledForEvent(event)) return;
|
|
35
|
+
try {
|
|
36
|
+
output.write(BEL);
|
|
37
|
+
} catch {
|
|
38
|
+
// Best-effort local notification only.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function classifyHookSelectorBellEvent(title: string): TerminalBellEvent {
|
|
43
|
+
const normalized = title.toLowerCase();
|
|
44
|
+
if (normalized.includes("approval") || normalized.includes("approve") || normalized.includes("plan ready")) {
|
|
45
|
+
return "approval";
|
|
46
|
+
}
|
|
47
|
+
return "ask";
|
|
48
|
+
}
|