@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.
Files changed (238) hide show
  1. package/README.md +76 -0
  2. package/dist/types/capability/mcp.d.ts +2 -0
  3. package/dist/types/cli/local-provider-smoke.d.ts +59 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -1
  5. package/dist/types/cli/telegram-cli.d.ts +13 -0
  6. package/dist/types/cli/update-cli.d.ts +24 -1
  7. package/dist/types/commands/local-provider.d.ts +34 -0
  8. package/dist/types/commands/telegram.d.ts +22 -0
  9. package/dist/types/config/keybindings.d.ts +12 -6
  10. package/dist/types/config/model-registry.d.ts +5 -0
  11. package/dist/types/config/models-config-schema.d.ts +5 -0
  12. package/dist/types/config/settings-schema.d.ts +284 -4
  13. package/dist/types/config/settings.d.ts +7 -0
  14. package/dist/types/config/telegram-autostart.d.ts +14 -0
  15. package/dist/types/config/telegram-env-bridge.d.ts +18 -0
  16. package/dist/types/coordinator-mcp/server.d.ts +8 -0
  17. package/dist/types/defaults/skc-defaults.d.ts +7 -0
  18. package/dist/types/discovery/cursor.d.ts +4 -1
  19. package/dist/types/discovery/index.d.ts +0 -1
  20. package/dist/types/discovery/windsurf.d.ts +3 -1
  21. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  22. package/dist/types/i18n/messages/en.d.ts +1 -0
  23. package/dist/types/modes/components/custom-editor.d.ts +6 -1
  24. package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
  25. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  26. package/dist/types/modes/components/model-selector.d.ts +2 -0
  27. package/dist/types/modes/components/settings-selector.d.ts +5 -1
  28. package/dist/types/modes/components/skill-message.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +5 -7
  30. package/dist/types/modes/components/status-line.d.ts +15 -3
  31. package/dist/types/modes/components/two-column-body.d.ts +9 -0
  32. package/dist/types/modes/components/welcome.d.ts +9 -1
  33. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  34. package/dist/types/modes/controllers/input-controller.d.ts +6 -1
  35. package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
  36. package/dist/types/modes/interactive-mode.d.ts +2 -0
  37. package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
  38. package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
  39. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
  40. package/dist/types/modes/slash-command-visibility.d.ts +1 -0
  41. package/dist/types/modes/theme/theme.d.ts +1 -1
  42. package/dist/types/modes/tmux-scroll.d.ts +18 -0
  43. package/dist/types/modes/types.d.ts +5 -1
  44. package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
  45. package/dist/types/notifications/daemon-paths.d.ts +9 -0
  46. package/dist/types/notifications/html-format.d.ts +2 -0
  47. package/dist/types/notifications/recent-activity.d.ts +5 -1
  48. package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
  49. package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
  50. package/dist/types/notifications/telegram-daemon.d.ts +4 -10
  51. package/dist/types/notifications/telegram-reference.d.ts +4 -0
  52. package/dist/types/notifications/threaded-render.d.ts +1 -0
  53. package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
  54. package/dist/types/runtime-mcp/config.d.ts +2 -0
  55. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  56. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  57. package/dist/types/runtime-mcp/types.d.ts +6 -0
  58. package/dist/types/sdk.d.ts +1 -1
  59. package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
  60. package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
  61. package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
  62. package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
  63. package/dist/types/skc-runtime/state-writer.d.ts +1 -0
  64. package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
  65. package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
  66. package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
  67. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
  68. package/dist/types/slash-commands/types.d.ts +2 -0
  69. package/dist/types/system-prompt.d.ts +7 -0
  70. package/dist/types/tools/skill.d.ts +1 -1
  71. package/dist/types/utils/cmux-workspace.d.ts +52 -0
  72. package/dist/types/web/search/index.d.ts +4 -2
  73. package/dist/types/web/search/provider.d.ts +14 -0
  74. package/dist/types/web/search/providers/utils.d.ts +42 -13
  75. package/dist/types/web/search/render.d.ts +1 -0
  76. package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
  77. package/package.json +8 -8
  78. package/scripts/build-binary.ts +3 -0
  79. package/scripts/verify-insane-vendor.ts +0 -5
  80. package/src/capability/mcp.ts +2 -0
  81. package/src/cli/local-provider-smoke.ts +632 -0
  82. package/src/cli/notify-cli.ts +4 -2
  83. package/src/cli/telegram-cli.ts +156 -0
  84. package/src/cli/update-cli.ts +179 -13
  85. package/src/cli/web-search-cli.ts +5 -7
  86. package/src/cli.ts +20 -1
  87. package/src/commands/launch.ts +47 -2
  88. package/src/commands/local-provider.ts +62 -0
  89. package/src/commands/notify.ts +1 -1
  90. package/src/commands/ralplan.ts +1 -0
  91. package/src/commands/telegram.ts +47 -0
  92. package/src/config/keybindings.ts +15 -6
  93. package/src/config/mcp-schema.json +4 -0
  94. package/src/config/model-registry.ts +55 -1
  95. package/src/config/models-config-schema.ts +9 -0
  96. package/src/config/settings-schema.ts +256 -4
  97. package/src/config/settings.ts +11 -0
  98. package/src/config/telegram-autostart.ts +107 -0
  99. package/src/config/telegram-env-bridge.ts +92 -0
  100. package/src/coordinator-mcp/server.ts +276 -34
  101. package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
  102. package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
  103. package/src/defaults/skc-defaults.ts +16 -0
  104. package/src/discovery/builtin.ts +12 -0
  105. package/src/discovery/cursor.ts +5 -84
  106. package/src/discovery/gemini.ts +3 -89
  107. package/src/discovery/index.ts +4 -1
  108. package/src/discovery/mcp-json.ts +11 -0
  109. package/src/discovery/opencode.ts +5 -122
  110. package/src/discovery/windsurf.ts +4 -82
  111. package/src/edit/read-file.ts +9 -1
  112. package/src/extensibility/extensions/runner.ts +2 -1
  113. package/src/extensibility/extensions/types.ts +11 -0
  114. package/src/extensibility/skills.ts +30 -2
  115. package/src/hooks/skill-state.ts +33 -73
  116. package/src/i18n/messages/de.ts +1 -0
  117. package/src/i18n/messages/en.ts +1 -0
  118. package/src/i18n/messages/es.ts +1 -0
  119. package/src/i18n/messages/fr.ts +1 -0
  120. package/src/i18n/messages/ja.settings.ts +28 -0
  121. package/src/i18n/messages/ja.ts +1 -0
  122. package/src/i18n/messages/ko.settings.ts +28 -0
  123. package/src/i18n/messages/ko.ts +1 -0
  124. package/src/i18n/messages/zh.settings.ts +28 -0
  125. package/src/i18n/messages/zh.ts +1 -0
  126. package/src/internal-urls/docs-index.generated.ts +12 -7
  127. package/src/main.ts +5 -2
  128. package/src/migrate/executor.ts +23 -6
  129. package/src/modes/acp/acp-agent.ts +7 -6
  130. package/src/modes/components/agent-dashboard.ts +3 -35
  131. package/src/modes/components/custom-editor.ts +26 -0
  132. package/src/modes/components/custom-model-preset-wizard.ts +30 -189
  133. package/src/modes/components/extensions/extension-dashboard.ts +2 -48
  134. package/src/modes/components/hook-selector.ts +21 -3
  135. package/src/modes/components/model-selector.ts +246 -11
  136. package/src/modes/components/session-observer-overlay.ts +3 -3
  137. package/src/modes/components/session-selector.ts +19 -9
  138. package/src/modes/components/settings-selector.ts +551 -11
  139. package/src/modes/components/skill-message.ts +18 -10
  140. package/src/modes/components/status-line/presets.ts +11 -0
  141. package/src/modes/components/status-line/segments.ts +16 -19
  142. package/src/modes/components/status-line/types.ts +6 -2
  143. package/src/modes/components/status-line.ts +281 -117
  144. package/src/modes/components/tree-selector.ts +28 -8
  145. package/src/modes/components/two-column-body.ts +36 -0
  146. package/src/modes/components/welcome.ts +165 -30
  147. package/src/modes/controllers/command-controller.ts +26 -2
  148. package/src/modes/controllers/event-controller.ts +102 -4
  149. package/src/modes/controllers/extension-ui-controller.ts +21 -2
  150. package/src/modes/controllers/input-controller.ts +214 -47
  151. package/src/modes/controllers/selector-controller.ts +35 -33
  152. package/src/modes/interactive-mode.ts +36 -30
  153. package/src/modes/prompt-action-autocomplete.ts +48 -8
  154. package/src/modes/rpc/rpc-types.ts +1 -0
  155. package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
  156. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
  157. package/src/modes/slash-command-visibility.ts +9 -0
  158. package/src/modes/theme/theme.ts +5 -1
  159. package/src/modes/tmux-scroll.ts +92 -0
  160. package/src/modes/types.ts +8 -1
  161. package/src/modes/utils/hotkeys-markdown.ts +5 -2
  162. package/src/modes/utils/terminal-bell.ts +48 -0
  163. package/src/modes/utils/ui-helpers.ts +76 -3
  164. package/src/notifications/daemon-paths.ts +22 -0
  165. package/src/notifications/html-format.ts +75 -1
  166. package/src/notifications/index.ts +32 -2
  167. package/src/notifications/lifecycle-control-runtime.ts +12 -3
  168. package/src/notifications/recent-activity.ts +32 -8
  169. package/src/notifications/telegram-daemon-cli.ts +125 -13
  170. package/src/notifications/telegram-daemon-control.ts +9 -0
  171. package/src/notifications/telegram-daemon.ts +88 -46
  172. package/src/notifications/telegram-reference.ts +36 -14
  173. package/src/notifications/threaded-render.ts +14 -6
  174. package/src/prompts/agents/architect.md +7 -4
  175. package/src/prompts/agents/critic.md +15 -6
  176. package/src/prompts/agents/planner.md +8 -3
  177. package/src/prompts/system/system-prompt.md +20 -2
  178. package/src/prompts/tools/bash.md +1 -1
  179. package/src/runtime-mcp/config-writer.ts +28 -0
  180. package/src/runtime-mcp/config.ts +7 -0
  181. package/src/runtime-mcp/loader.ts +3 -0
  182. package/src/runtime-mcp/manager.ts +3 -0
  183. package/src/runtime-mcp/types.ts +6 -0
  184. package/src/sdk.ts +10 -11
  185. package/src/session/agent-session.ts +103 -13
  186. package/src/session/session-manager.ts +58 -18
  187. package/src/skc-runtime/launch-tmux.ts +34 -8
  188. package/src/skc-runtime/launch-worktree.ts +16 -2
  189. package/src/skc-runtime/ralplan-runtime.ts +22 -6
  190. package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
  191. package/src/skc-runtime/session-state-sidecar.ts +167 -24
  192. package/src/skc-runtime/state-runtime.ts +8 -5
  193. package/src/skc-runtime/state-writer.ts +5 -5
  194. package/src/skc-runtime/team-runtime.ts +21 -6
  195. package/src/skc-runtime/tmux-common.ts +16 -0
  196. package/src/skc-runtime/tmux-sessions.ts +13 -9
  197. package/src/skc-runtime/ultragoal-guard.ts +150 -24
  198. package/src/skc-runtime/ultragoal-runtime.ts +16 -124
  199. package/src/skc-runtime/workflow-manifest.generated.json +0 -8
  200. package/src/skc-runtime/workflow-manifest.ts +0 -1
  201. package/src/slash-commands/builtin-registry.ts +136 -23
  202. package/src/slash-commands/types.ts +2 -0
  203. package/src/system-prompt.ts +8 -0
  204. package/src/task/executor.ts +5 -0
  205. package/src/tools/ast-grep.ts +10 -7
  206. package/src/tools/bash.ts +39 -14
  207. package/src/tools/index.ts +8 -1
  208. package/src/tools/skill.ts +1 -1
  209. package/src/utils/cmux-workspace.ts +233 -0
  210. package/src/utils/title-generator.ts +2 -0
  211. package/src/web/kagi.ts +1 -1
  212. package/src/web/parallel.ts +2 -2
  213. package/src/web/search/index.ts +137 -52
  214. package/src/web/search/provider.ts +80 -0
  215. package/src/web/search/providers/anthropic.ts +1 -1
  216. package/src/web/search/providers/brave.ts +1 -1
  217. package/src/web/search/providers/codex.ts +1 -1
  218. package/src/web/search/providers/duckduckgo.ts +1 -1
  219. package/src/web/search/providers/exa.ts +1 -1
  220. package/src/web/search/providers/gemini.ts +7 -3
  221. package/src/web/search/providers/jina.ts +1 -1
  222. package/src/web/search/providers/kimi.ts +6 -1
  223. package/src/web/search/providers/openai-compatible.ts +1 -1
  224. package/src/web/search/providers/parallel.ts +1 -1
  225. package/src/web/search/providers/perplexity.ts +2 -2
  226. package/src/web/search/providers/searxng.ts +1 -1
  227. package/src/web/search/providers/synthetic.ts +1 -1
  228. package/src/web/search/providers/tavily.ts +1 -1
  229. package/src/web/search/providers/utils.ts +67 -17
  230. package/src/web/search/providers/xai.ts +1 -1
  231. package/src/web/search/providers/zai.ts +1 -1
  232. package/src/web/search/render.ts +3 -0
  233. package/src/workflow/workflow-intent-diff.ts +199 -0
  234. package/vendor/insane-search/MANIFEST.json +1 -3
  235. package/vendor/insane-search/engine/tests/test_u1.py +0 -28
  236. package/vendor/insane-search/engine/transport.py +2 -3
  237. package/vendor/insane-search/engine/validators.py +0 -14
  238. 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?.priority ?? 0,
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 baseSuggestions = withoutSkillCommandSuggestions(
218
- await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol),
219
- );
220
- const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
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(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null {
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",
@@ -435,6 +435,7 @@ export interface RpcJsonSchema {
435
435
  items?: RpcJsonSchema;
436
436
  minLength?: number;
437
437
  maxLength?: number;
438
+ pattern?: string;
438
439
  minItems?: number;
439
440
  maxItems?: number;
440
441
  uniqueItems?: boolean;
@@ -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 interface DeepInterviewGateAnswer {
59
- selected: string[];
60
- other?: boolean;
61
- custom?: string;
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: { type: "string", minLength: 1, description: "free-text answer; required when `other` is true" },
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 deep-interview question. */
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 DeepInterviewGateAnswer;
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
- "answer must be { selected: string[]; other?: boolean; custom?: string }",
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
+ }
@@ -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
+ }
@@ -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(title: string, placeholder?: string): Promise<string | undefined>;
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
- "| `#` | Open prompt actions |",
55
- "| `/` | Slash commands |",
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
+ }