@sayknow-cli/coding-agent 0.3.7 → 0.3.8

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 (248) hide show
  1. package/dist/types/append-only-mode.d.ts +31 -0
  2. package/dist/types/capability/rule.d.ts +8 -0
  3. package/dist/types/cli/completion-cli.d.ts +41 -0
  4. package/dist/types/cli/config-cli.d.ts +1 -0
  5. package/dist/types/cli/fixture-report.d.ts +20 -0
  6. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  7. package/dist/types/cli/nofile-limit.d.ts +11 -0
  8. package/dist/types/cli/notify-cli.d.ts +7 -0
  9. package/dist/types/cli/update-cli.d.ts +22 -0
  10. package/dist/types/cli.d.ts +124 -1
  11. package/dist/types/commands/completion.d.ts +29 -0
  12. package/dist/types/commands/config.d.ts +3 -0
  13. package/dist/types/commands/deep-interview.d.ts +3 -0
  14. package/dist/types/config/file-lock.d.ts +7 -6
  15. package/dist/types/config/keybindings.d.ts +2 -2
  16. package/dist/types/config/model-profile-activation.d.ts +6 -0
  17. package/dist/types/config/model-profiles.d.ts +1 -0
  18. package/dist/types/config/settings-schema.d.ts +105 -1
  19. package/dist/types/config/settings.d.ts +8 -0
  20. package/dist/types/discovery/index.d.ts +1 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/export/ttsr.d.ts +14 -3
  23. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  24. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  25. package/dist/types/goals/runtime.d.ts +1 -0
  26. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  27. package/dist/types/main.d.ts +4 -1
  28. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  29. package/dist/types/modes/components/model-selector.d.ts +4 -0
  30. package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
  31. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  32. package/dist/types/modes/components/status-line.d.ts +9 -0
  33. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  34. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/interactive-mode.d.ts +2 -0
  37. package/dist/types/modes/print-mode.d.ts +13 -1
  38. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
  39. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  40. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  41. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  42. package/dist/types/modes/types.d.ts +1 -0
  43. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  44. package/dist/types/notifications/config.d.ts +14 -1
  45. package/dist/types/notifications/daemon-paths.d.ts +1 -0
  46. package/dist/types/notifications/index.d.ts +6 -3
  47. package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
  48. package/dist/types/notifications/telegram-daemon.d.ts +23 -3
  49. package/dist/types/notifications/telegram-reference.d.ts +0 -5
  50. package/dist/types/notifications/threaded-render.d.ts +6 -0
  51. package/dist/types/runtime-api-key.d.ts +7 -0
  52. package/dist/types/sdk.d.ts +3 -0
  53. package/dist/types/session/agent-session.d.ts +61 -6
  54. package/dist/types/session/agent-storage.d.ts +1 -0
  55. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  56. package/dist/types/session/messages.d.ts +4 -0
  57. package/dist/types/session/session-manager.d.ts +17 -1
  58. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
  59. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
  60. package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
  61. package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
  62. package/dist/types/skc-runtime/state-writer.d.ts +3 -0
  63. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  64. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
  65. package/dist/types/skill-state/active-state.d.ts +18 -1
  66. package/dist/types/system-prompt.d.ts +18 -2
  67. package/dist/types/task/executor.d.ts +3 -0
  68. package/dist/types/task/fork-context-budget.d.ts +7 -0
  69. package/dist/types/task/token-log.d.ts +30 -0
  70. package/dist/types/task/token-log.test.d.ts +1 -0
  71. package/dist/types/tools/bisect.d.ts +81 -0
  72. package/dist/types/tools/computer.d.ts +2 -2
  73. package/dist/types/tools/index.d.ts +1 -0
  74. package/dist/types/tools/search.d.ts +2 -2
  75. package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
  76. package/dist/types/utils/file-mentions.d.ts +14 -0
  77. package/dist/types/utils/git.d.ts +32 -0
  78. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  79. package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
  80. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
  81. package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
  82. package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
  83. package/package.json +8 -8
  84. package/src/append-only-mode.ts +58 -0
  85. package/src/autoresearch/prompt.md +1 -1
  86. package/src/autoresearch/tools/log-experiment.ts +1 -1
  87. package/src/capability/rule.ts +9 -0
  88. package/src/cli/completion-cli.ts +234 -0
  89. package/src/cli/config-cli.ts +61 -9
  90. package/src/cli/fast-help.ts +1 -0
  91. package/src/cli/fixture-report.test.ts +230 -0
  92. package/src/cli/fixture-report.ts +171 -0
  93. package/src/cli/nofile-limit.ts +48 -0
  94. package/src/cli/notify-cli.ts +92 -8
  95. package/src/cli/telegram-cli.ts +1 -1
  96. package/src/cli/update-cli.ts +85 -9
  97. package/src/cli.ts +29 -1
  98. package/src/commands/completion.ts +84 -0
  99. package/src/commands/config.ts +2 -0
  100. package/src/commands/contribution-prep.ts +1 -1
  101. package/src/commands/deep-interview.ts +2 -1
  102. package/src/config/file-lock.ts +73 -34
  103. package/src/config/keybindings.ts +2 -2
  104. package/src/config/model-profile-activation.ts +65 -0
  105. package/src/config/model-profiles.ts +31 -2
  106. package/src/config/settings-schema.ts +68 -1
  107. package/src/config/settings.ts +22 -0
  108. package/src/defaults/skc/skills/team/SKILL.md +17 -14
  109. package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
  110. package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  111. package/src/defaults/skc-defaults.ts +9 -0
  112. package/src/discovery/helpers.ts +10 -0
  113. package/src/discovery/index.ts +1 -0
  114. package/src/export/html/template.generated.ts +1 -1
  115. package/src/export/html/template.js +60 -2
  116. package/src/export/ttsr.ts +96 -18
  117. package/src/extensibility/extensions/runner.ts +183 -219
  118. package/src/extensibility/extensions/types.ts +9 -0
  119. package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
  120. package/src/goals/runtime.ts +10 -2
  121. package/src/hindsight/backend.ts +1 -3
  122. package/src/hooks/native-skill-hook.ts +124 -4
  123. package/src/internal-urls/docs-index.generated.ts +14 -5
  124. package/src/main.ts +95 -43
  125. package/src/memories/index.ts +12 -1
  126. package/src/modes/components/assistant-message.ts +241 -82
  127. package/src/modes/components/custom-editor.ts +1 -0
  128. package/src/modes/components/model-selector.ts +54 -11
  129. package/src/modes/components/queued-message-selector.ts +92 -0
  130. package/src/modes/components/settings-selector.ts +4 -0
  131. package/src/modes/components/status-line.ts +167 -54
  132. package/src/modes/components/tool-execution.ts +44 -19
  133. package/src/modes/components/welcome.ts +181 -44
  134. package/src/modes/controllers/command-controller.ts +40 -4
  135. package/src/modes/controllers/event-controller.ts +66 -47
  136. package/src/modes/controllers/extension-ui-controller.ts +4 -10
  137. package/src/modes/controllers/input-controller.ts +180 -18
  138. package/src/modes/controllers/selector-controller.ts +127 -24
  139. package/src/modes/interactive-mode.ts +51 -16
  140. package/src/modes/print-mode.ts +43 -4
  141. package/src/modes/prompt-action-autocomplete.ts +21 -20
  142. package/src/modes/rpc/rpc-mode.ts +29 -3
  143. package/src/modes/rpc/rpc-types.ts +9 -0
  144. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  145. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  146. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  147. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  148. package/src/modes/types.ts +1 -0
  149. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  150. package/src/modes/utils/ui-helpers.ts +20 -7
  151. package/src/notifications/config.ts +25 -5
  152. package/src/notifications/daemon-paths.ts +2 -0
  153. package/src/notifications/index.ts +117 -14
  154. package/src/notifications/lifecycle-commands.ts +37 -7
  155. package/src/notifications/lifecycle-orchestrator.ts +19 -6
  156. package/src/notifications/telegram-daemon-control.ts +5 -3
  157. package/src/notifications/telegram-daemon.ts +302 -51
  158. package/src/notifications/telegram-reference.ts +0 -16
  159. package/src/notifications/threaded-render.ts +17 -1
  160. package/src/prompts/agents/architect.md +19 -10
  161. package/src/prompts/agents/critic.md +16 -18
  162. package/src/prompts/agents/executor.md +14 -11
  163. package/src/prompts/agents/planner.md +18 -13
  164. package/src/prompts/ci-green-request.md +3 -8
  165. package/src/prompts/goals/goal-continuation.md +0 -5
  166. package/src/prompts/goals/goal-mode-active.md +0 -5
  167. package/src/prompts/system/agent-creation-architect.md +2 -2
  168. package/src/prompts/system/plan-mode-active.md +1 -1
  169. package/src/prompts/system/plan-mode-subagent.md +3 -3
  170. package/src/prompts/system/project-prompt.md +0 -13
  171. package/src/prompts/system/subagent-system-prompt.md +3 -2
  172. package/src/prompts/system/system-prompt.md +29 -17
  173. package/src/prompts/system/volatile-project-context.md +10 -0
  174. package/src/prompts/system/web-search.md +4 -7
  175. package/src/prompts/tools/apply-patch.md +6 -0
  176. package/src/prompts/tools/ast-edit.md +1 -0
  177. package/src/prompts/tools/bash.md +1 -1
  178. package/src/prompts/tools/bisect.md +22 -0
  179. package/src/prompts/tools/browser.md +1 -1
  180. package/src/prompts/tools/computer.md +2 -2
  181. package/src/prompts/tools/cron.md +0 -2
  182. package/src/prompts/tools/eval.md +2 -2
  183. package/src/prompts/tools/find.md +1 -1
  184. package/src/prompts/tools/github.md +6 -6
  185. package/src/prompts/tools/goal.md +1 -3
  186. package/src/prompts/tools/image-gen.md +12 -5
  187. package/src/prompts/tools/irc.md +5 -4
  188. package/src/prompts/tools/job.md +2 -2
  189. package/src/prompts/tools/lsp.md +1 -1
  190. package/src/prompts/tools/monitor.md +2 -2
  191. package/src/prompts/tools/patch.md +2 -2
  192. package/src/prompts/tools/replace.md +2 -21
  193. package/src/prompts/tools/rewind.md +1 -1
  194. package/src/prompts/tools/search-tool-bm25.md +1 -11
  195. package/src/prompts/tools/search.md +1 -1
  196. package/src/prompts/tools/skill.md +3 -2
  197. package/src/prompts/tools/ssh.md +2 -1
  198. package/src/prompts/tools/subagent.md +2 -2
  199. package/src/prompts/tools/task.md +4 -5
  200. package/src/prompts/tools/todo-write.md +1 -1
  201. package/src/prompts/tools/vim.md +1 -0
  202. package/src/prompts/tools/web-search.md +2 -0
  203. package/src/prompts/tools/write.md +8 -2
  204. package/src/runtime-api-key.ts +16 -0
  205. package/src/sdk.ts +52 -28
  206. package/src/session/agent-session.ts +1011 -141
  207. package/src/session/agent-storage.ts +4 -0
  208. package/src/session/file-mention-pruning.ts +64 -0
  209. package/src/session/messages.ts +4 -0
  210. package/src/session/session-manager.ts +103 -4
  211. package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
  212. package/src/skc-runtime/deep-interview-recorder.ts +62 -8
  213. package/src/skc-runtime/deep-interview-state.ts +44 -2
  214. package/src/skc-runtime/session-state-sidecar.ts +263 -20
  215. package/src/skc-runtime/state-runtime.ts +12 -2
  216. package/src/skc-runtime/state-writer.ts +15 -0
  217. package/src/skc-runtime/ultragoal-guard.ts +55 -66
  218. package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
  219. package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
  220. package/src/skc-runtime/workflow-manifest.generated.json +113 -2
  221. package/src/skc-runtime/workflow-manifest.ts +74 -1
  222. package/src/skill-state/active-state.ts +141 -5
  223. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  224. package/src/slash-commands/builtin-registry.ts +218 -71
  225. package/src/system-prompt.ts +57 -16
  226. package/src/task/executor.ts +132 -24
  227. package/src/task/fork-context-advisory.ts +2 -7
  228. package/src/task/fork-context-budget.ts +9 -0
  229. package/src/task/index.ts +13 -6
  230. package/src/task/output-manager.ts +20 -8
  231. package/src/task/receipt.ts +26 -7
  232. package/src/task/token-log.test.ts +188 -0
  233. package/src/task/token-log.ts +179 -0
  234. package/src/tools/bisect.ts +367 -0
  235. package/src/tools/computer.ts +246 -43
  236. package/src/tools/index.ts +3 -0
  237. package/src/tools/output-meta.ts +116 -4
  238. package/src/tools/search.ts +8 -2
  239. package/src/tools/skill.ts +15 -0
  240. package/src/tools/telegram-send.ts +6 -0
  241. package/src/tools/ultragoal-ask-guard.ts +39 -5
  242. package/src/utils/file-mentions.ts +39 -7
  243. package/src/utils/git.ts +73 -0
  244. package/src/web/scrapers/youtube.ts +24 -12
  245. package/src/workflow/workflow-intent-diff.ts +29 -6
  246. package/src/workflow/workflow-intent-report-consumer.ts +221 -0
  247. package/src/workflow/workflow-intent-report-schema.ts +129 -0
  248. package/src/workflow/workflow-intent-report.ts +135 -0
@@ -1,5 +1,12 @@
1
- import type { AutocompleteItem, AutocompleteProvider, SlashCommand } from "@sayknow-cli/tui";
2
- import { CombinedAutocompleteProvider, getKeybindings, getSlashCommandMatchRank } from "@sayknow-cli/tui";
1
+ import {
2
+ type AutocompleteItem,
3
+ type AutocompleteProvider,
4
+ CombinedAutocompleteProvider,
5
+ extractSlashCommandTokenPrefix,
6
+ getKeybindings,
7
+ getSlashCommandMatchRank,
8
+ type SlashCommand,
9
+ } from "@sayknow-cli/tui";
3
10
  import type { KeybindingsManager } from "../config/keybindings";
4
11
  import { formatKeyHints } from "../config/keybindings";
5
12
  import { isSettingsInitialized, settings } from "../config/settings";
@@ -147,6 +154,10 @@ function sortSlashCommandSuggestions(
147
154
  return { ...suggestions, items };
148
155
  }
149
156
 
157
+ function isRootPathSuggestionResult(suggestions: { items: AutocompleteItem[]; prefix: string } | null): boolean {
158
+ return suggestions?.prefix.startsWith("/") === true && suggestions.items.some(item => item.value.startsWith("/"));
159
+ }
160
+
150
161
  function withoutSkillCommandSuggestions(
151
162
  suggestions: { items: AutocompleteItem[]; prefix: string } | null,
152
163
  ): { items: AutocompleteItem[]; prefix: string } | null {
@@ -168,18 +179,9 @@ function getPromptActionPrefix(textBeforeCursor: string): string | null {
168
179
  }
169
180
 
170
181
  function getSlashTokenPrefix(textBeforeCursor: string): string | null {
171
- const slashIndex = textBeforeCursor.lastIndexOf("/");
172
- if (slashIndex === -1) return null;
173
- const beforeSlash = textBeforeCursor.slice(0, slashIndex);
174
- if (beforeSlash && !/\s$/.test(beforeSlash)) return null;
175
- const token = textBeforeCursor.slice(slashIndex);
176
- if (/[\s]/.test(token)) return null;
177
- return token;
182
+ return extractSlashCommandTokenPrefix(textBeforeCursor);
178
183
  }
179
184
 
180
- function isLeadingSlashToken(textBeforeCursor: string, slashPrefix: string): boolean {
181
- return textBeforeCursor.trimStart() === slashPrefix;
182
- }
183
185
  export class PromptActionAutocompleteProvider implements AutocompleteProvider {
184
186
  #baseProvider: CombinedAutocompleteProvider;
185
187
  #actions: PromptActionDefinition[];
@@ -226,14 +228,13 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
226
228
 
227
229
  const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
228
230
  if (slashPrefix) {
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;
231
+ const baseSuggestions = withoutSkillCommandSuggestions(
232
+ await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol),
233
+ );
234
+ if (isRootPathSuggestionResult(baseSuggestions)) return baseSuggestions;
235
+ const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, {
236
+ includeEmpty: false,
237
+ });
237
238
  return sortSlashCommandSuggestions(
238
239
  mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
239
240
  this.#commands,
@@ -22,8 +22,13 @@ import type { AgentSession } from "../../session/agent-session";
22
22
  import { workflowGatePath } from "../../skc-runtime/session-layout";
23
23
  import { initializeExtensions } from "../runtime-init";
24
24
  import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
25
- import { AgentWireFrameSequencer, toAgentWireEventFrame } from "../shared/agent-wire/event-envelope";
26
- import { rpcError as error } from "../shared/agent-wire/responses";
25
+ import {
26
+ AgentWireCompactEventEncoder,
27
+ AgentWireFrameSequencer,
28
+ toAgentWireCompactEventFrame,
29
+ toAgentWireEventFrame,
30
+ } from "../shared/agent-wire/event-envelope";
31
+ import { rpcError as error, rpcSuccess } from "../shared/agent-wire/responses";
27
32
  import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
28
33
  import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
29
34
  import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
@@ -297,6 +302,7 @@ export async function runRpcMode(
297
302
  output({ type: "ready" });
298
303
  }
299
304
  const emitRpcTitles = shouldEmitRpcTitles();
305
+ const rpcCapabilities = { compactMessageUpdate: false };
300
306
  const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
301
307
 
302
308
  const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
@@ -636,8 +642,13 @@ export async function runRpcMode(
636
642
  // Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
637
643
  // { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
638
644
  const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
645
+ const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
639
646
  session.subscribe(event => {
640
- output(toAgentWireEventFrame(event, eventSequencer));
647
+ output(
648
+ rpcCapabilities.compactMessageUpdate
649
+ ? toAgentWireCompactEventFrame(event, compactEventEncoder)
650
+ : toAgentWireEventFrame(event, eventSequencer),
651
+ );
641
652
  });
642
653
 
643
654
  // Handle a single command through the shared agent-wire dispatcher so RPC
@@ -710,6 +721,21 @@ export async function runRpcMode(
710
721
  hostUriBridge.handleResult(parsed);
711
722
  return;
712
723
  }
724
+ if ((parsed as RpcCommand).type === "set_capabilities") {
725
+ // RPC has no startup handshake; clients negotiate optional event-shape capabilities
726
+ // with this explicit command. Defaults remain legacy/full-frame until accepted here.
727
+ const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
728
+ const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
729
+ const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
730
+ rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
731
+ output(
732
+ rpcSuccess(command.id, "set_capabilities", {
733
+ acceptedCapabilities,
734
+ unsupported: requested.filter(capability => capability !== "compact_message_update"),
735
+ }),
736
+ );
737
+ return;
738
+ }
713
739
  // Ordered commands run through a serial chain to preserve causal order; the
714
740
  // reader never blocks, so cancellation commands stay responsive even while a
715
741
  // long command is in flight (issue 13).
@@ -13,6 +13,7 @@ import type { SessionStats } from "../../session/agent-session";
13
13
  import type { TodoPhase } from "../../tools/todo-write";
14
14
 
15
15
  export type RpcGetStateInclude = "tools" | "dumpTools" | "systemPrompt";
16
+ export type RpcCapability = "compact_message_update";
16
17
 
17
18
  // ============================================================================
18
19
  // RPC Commands (stdin)
@@ -33,6 +34,7 @@ export type RpcCommand =
33
34
  | { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
34
35
  | { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
35
36
  | { id?: string; type: "get_pending_workflow_gates" }
37
+ | { id?: string; type: "set_capabilities"; capabilities: RpcCapability[] }
36
38
 
37
39
  // Model
38
40
  | { id?: string; type: "set_model"; provider: string; modelId: string }
@@ -140,6 +142,13 @@ export type RpcResponse =
140
142
  success: true;
141
143
  data: { gates: RpcWorkflowGate[] };
142
144
  }
145
+ | {
146
+ id?: string;
147
+ type: "response";
148
+ command: "set_capabilities";
149
+ success: true;
150
+ data: { acceptedCapabilities: RpcCapability[]; unsupported: string[] };
151
+ }
143
152
 
144
153
  // Model
145
154
  | {
@@ -130,6 +130,10 @@ export function isRpcCommand(value: unknown): value is RpcCommand {
130
130
  return Array.isArray(value.tools) && value.tools.every(hostToolDefinition);
131
131
  case "set_host_uri_schemes":
132
132
  return Array.isArray(value.schemes) && value.schemes.every(hostUriScheme);
133
+ case "set_capabilities":
134
+ return (
135
+ Array.isArray(value.capabilities) && value.capabilities.every(capability => typeof capability === "string")
136
+ );
133
137
  case "set_model":
134
138
  return stringField(value, "provider") && stringField(value, "modelId");
135
139
  case "set_thinking_level":
@@ -13,6 +13,8 @@
13
13
  * an `AgentSessionEvent` variant fails typecheck until it is registered, and so
14
14
  * conformance tests can assert fixture coverage equals the registry exactly.
15
15
  */
16
+
17
+ import type { AssistantMessageEvent } from "@sayknow-cli/ai";
16
18
  import type { AgentSessionEvent } from "../../../session/agent-session";
17
19
 
18
20
  /** Wire protocol version. Bump on breaking envelope/semantic changes. */
@@ -68,6 +70,25 @@ export interface AgentWireEventPayload {
68
70
  event_type: AgentWireEventType;
69
71
  event: AgentSessionEvent;
70
72
  }
73
+ export type AgentWireCompactAssistantMessageEvent = AssistantMessageEvent extends infer TEvent
74
+ ? TEvent extends { type: string }
75
+ ? Omit<TEvent, "partial" | "message" | "error">
76
+ : never
77
+ : never;
78
+
79
+ export interface AgentWireCompactMessageUpdatePayload {
80
+ event_type: "message_update";
81
+ /** Compact RPC opt-in payload: delta-only update plus minimal routing metadata. */
82
+ compact: true;
83
+ message_id: string | null;
84
+ content_index: number | null;
85
+ assistantMessageEvent: AgentWireCompactAssistantMessageEvent;
86
+ /** Full message checkpoint, snapshotted synchronously when this frame is enqueued. */
87
+ checkpoint_message?: Extract<AgentSessionEvent, { type: "message_update" }>["message"];
88
+ checkpoint_reason?: "periodic";
89
+ }
90
+
91
+ export type AgentWireEventFramePayload = AgentWireEventPayload | AgentWireCompactMessageUpdatePayload;
71
92
 
72
93
  /**
73
94
  * Bounded observed-signal vocabulary surfaced to owner control planes. Mirrors
@@ -146,3 +167,4 @@ export interface AgentWireFrameEnvelope<TType extends AgentWireFrameType = Agent
146
167
 
147
168
  /** An `AgentSessionEvent` serialized into a versioned wire frame. */
148
169
  export type AgentWireEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventPayload>;
170
+ export type AgentWireCompactEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventFramePayload>;
@@ -12,6 +12,9 @@ import { randomUUID } from "node:crypto";
12
12
  import type { AgentSessionEvent } from "../../../session/agent-session";
13
13
  import {
14
14
  AGENT_WIRE_PROTOCOL_VERSION,
15
+ type AgentWireCompactAssistantMessageEvent,
16
+ type AgentWireCompactEventFrame,
17
+ type AgentWireCompactMessageUpdatePayload,
15
18
  type AgentWireEventFrame,
16
19
  type AgentWireEventPayload,
17
20
  type AgentWireEventType,
@@ -19,6 +22,26 @@ import {
19
22
  type AgentWireFrameType,
20
23
  } from "./event-contract";
21
24
 
25
+ export const AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL = 32;
26
+
27
+ function recordObject(value: unknown): Record<string, unknown> | undefined {
28
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
29
+ }
30
+
31
+ function messageIdOf(message: unknown): string | null {
32
+ const id = recordObject(message)?.id;
33
+ return typeof id === "string" ? id : null;
34
+ }
35
+
36
+ function contentIndexOf(assistantMessageEvent: unknown): number | null {
37
+ const contentIndex = recordObject(assistantMessageEvent)?.contentIndex;
38
+ return typeof contentIndex === "number" && Number.isInteger(contentIndex) ? contentIndex : null;
39
+ }
40
+
41
+ function snapshotMessage<T>(message: T): T {
42
+ return structuredClone(message);
43
+ }
44
+
22
45
  function assertNever(value: never): never {
23
46
  throw new Error(`Unhandled AgentSessionEvent variant: ${JSON.stringify(value)}`);
24
47
  }
@@ -123,6 +146,65 @@ export function toAgentWireEventFrame(
123
146
  export function toAgentWireEventPayload(event: AgentSessionEvent): AgentWireEventPayload {
124
147
  return { event_type: agentSessionEventType(event), event };
125
148
  }
149
+ export function sanitizeAssistantMessageEventForCompact(
150
+ assistantMessageEvent: Extract<AgentSessionEvent, { type: "message_update" }>["assistantMessageEvent"],
151
+ ): AgentWireCompactAssistantMessageEvent {
152
+ const {
153
+ partial: _partial,
154
+ message: _message,
155
+ error: _error,
156
+ ...deltaOnly
157
+ } = assistantMessageEvent as Record<string, unknown>;
158
+ return deltaOnly as AgentWireCompactAssistantMessageEvent;
159
+ }
160
+
161
+ export interface AgentWireCompactMessageUpdateOptions {
162
+ checkpointInterval?: number;
163
+ }
164
+
165
+ /**
166
+ * Stateful compact event serializer for opt-in RPC clients.
167
+ *
168
+ * Only `message_update` changes shape: it carries the immutable provider delta
169
+ * plus message/content identifiers, with periodic full-message checkpoints that
170
+ * are cloned synchronously before the caller returns to the event loop. All other
171
+ * event variants remain canonical full event frames so legacy bridge/RPC behavior
172
+ * and terminal `message_end` reconstruction are unchanged.
173
+ */
174
+ export class AgentWireCompactEventEncoder {
175
+ readonly #sequencer: AgentWireFrameSequencer;
176
+ readonly #checkpointInterval: number;
177
+ #messageUpdateCount = 0;
178
+
179
+ constructor(sequencer: AgentWireFrameSequencer, options: AgentWireCompactMessageUpdateOptions = {}) {
180
+ this.#sequencer = sequencer;
181
+ this.#checkpointInterval = options.checkpointInterval ?? AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL;
182
+ }
183
+
184
+ frame(event: AgentSessionEvent): AgentWireCompactEventFrame {
185
+ if (event.type !== "message_update") return toAgentWireEventFrame(event, this.#sequencer);
186
+ this.#messageUpdateCount += 1;
187
+ const payload: AgentWireCompactMessageUpdatePayload = {
188
+ event_type: "message_update",
189
+ compact: true,
190
+ message_id: messageIdOf(event.message),
191
+ content_index: contentIndexOf(event.assistantMessageEvent),
192
+ assistantMessageEvent: sanitizeAssistantMessageEventForCompact(event.assistantMessageEvent),
193
+ };
194
+ if (this.#checkpointInterval > 0 && this.#messageUpdateCount % this.#checkpointInterval === 0) {
195
+ payload.checkpoint_message = snapshotMessage(event.message);
196
+ payload.checkpoint_reason = "periodic";
197
+ }
198
+ return this.#sequencer.next("event", payload);
199
+ }
200
+ }
201
+
202
+ export function toAgentWireCompactEventFrame(
203
+ event: AgentSessionEvent,
204
+ encoder: AgentWireCompactEventEncoder,
205
+ ): AgentWireCompactEventFrame {
206
+ return encoder.frame(event);
207
+ }
126
208
 
127
209
  /** Back-compat alias for {@link toAgentWireEventFrame}. */
128
210
  export const toBridgeEventFrame = toAgentWireEventFrame;
@@ -47,6 +47,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
47
47
  set_host_tools: "host_tools",
48
48
  set_host_uri_schemes: "host_uri",
49
49
  get_pending_workflow_gates: "message:read",
50
+ set_capabilities: "control",
50
51
  set_model: "model",
51
52
  cycle_model: "model",
52
53
  get_available_models: "model",
@@ -216,6 +216,7 @@ export interface InteractiveModeContext {
216
216
  handleDumpCommand(): void;
217
217
  handleDebugTranscriptCommand(): Promise<void>;
218
218
  handleClearCommand(): Promise<void>;
219
+ handleContextClearCommand(): Promise<void>;
219
220
  handleDropCommand(): Promise<void>;
220
221
  handleForkCommand(): Promise<void>;
221
222
  handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
@@ -23,6 +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
+ `| \`${appKey(bindings, "app.message.dequeue")}\` | Select queued message to edit |`,
26
27
  "| `Shift+Enter` / `Ctrl+J` | New line |",
27
28
  "| `Ctrl+W` / `Option+Backspace` | Delete word backwards |",
28
29
  "| `Ctrl+U` | Delete to start of line |",
@@ -2,9 +2,9 @@ import type { AgentMessage } from "@sayknow-cli/agent-core";
2
2
  import type { AssistantMessage, ImageContent, Message } from "@sayknow-cli/ai";
3
3
  import { type Component, Spacer, Text, TruncatedText } from "@sayknow-cli/tui";
4
4
  import { settings } from "../../config/settings";
5
- import { tMessage } from "../../i18n";
6
5
  import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/skc-plugins";
7
6
  import { buildSkillPromptMessage, parseSkillInvocations } from "../../extensibility/skills";
7
+ import { tMessage } from "../../i18n";
8
8
  import { AssistantMessageComponent } from "../../modes/components/assistant-message";
9
9
  import { BashExecutionComponent } from "../../modes/components/bash-execution";
10
10
  import { BranchSummaryMessageComponent } from "../../modes/components/branch-summary-message";
@@ -37,6 +37,19 @@ interface RenderInitialMessagesOptions {
37
37
  preserveExistingChat?: boolean;
38
38
  }
39
39
 
40
+ export function argsWithPartialJson(args: unknown, partialJson: unknown): unknown {
41
+ if (typeof partialJson !== "string" || !args || typeof args !== "object" || Array.isArray(args)) return args;
42
+ // Non-enumerable so the transient streaming buffer reaches renderers via direct
43
+ // property read but never serializes into persisted/wire message content.
44
+ Object.defineProperty(args, "__partialJson", {
45
+ value: partialJson,
46
+ enumerable: false,
47
+ configurable: true,
48
+ writable: true,
49
+ });
50
+ return args;
51
+ }
52
+
40
53
  type QueuedMessages = {
41
54
  steering: string[];
42
55
  followUp: string[];
@@ -394,10 +407,10 @@ export class UiHelpers {
394
407
 
395
408
  readGroup = null;
396
409
  const tool = this.ctx.session.getToolByName(content.name);
397
- const renderArgs =
398
- "partialJson" in content
399
- ? { ...content.arguments, __partialJson: content.partialJson }
400
- : content.arguments;
410
+ const renderArgs = argsWithPartialJson(
411
+ content.arguments,
412
+ "partialJson" in content ? content.partialJson : undefined,
413
+ );
401
414
  const component = new ToolExecutionComponent(
402
415
  content.name,
403
416
  renderArgs,
@@ -605,8 +618,8 @@ export class UiHelpers {
605
618
  const queuedText = theme.fg("dim", `${entry.label}: ${entry.message}`);
606
619
  this.ctx.pendingMessagesContainer.addChild(new TruncatedText(queuedText, 1, 0));
607
620
  }
608
- const dequeueKey = this.ctx.keybindings.getDisplayString("app.message.dequeue") || "Alt+Up";
609
- const hintText = theme.fg("dim", `${theme.tree.hook} ${dequeueKey} to edit`);
621
+ const dequeueKey = this.ctx.keybindings.getDisplayString("app.message.dequeue") || "Alt+Up/Alt+Down";
622
+ const hintText = theme.fg("dim", `${theme.tree.hook} ${dequeueKey} to select/edit/reorder`);
610
623
  this.ctx.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
611
624
  }
612
625
  }
@@ -38,13 +38,24 @@ export function getNotificationConfig(settings: Settings): NotificationConfig {
38
38
  };
39
39
  }
40
40
 
41
+ export function hasNonBlankValue(value: string | undefined): boolean {
42
+ return typeof value === "string" && value.trim().length > 0;
43
+ }
44
+
45
+ /** Is Telegram configured with usable non-blank boundary credentials? */
46
+ export function isTelegramConfigured(
47
+ cfg: NotificationConfig,
48
+ ): cfg is NotificationConfig & { botToken: string; chatId: string } {
49
+ return cfg.enabled && hasNonBlankValue(cfg.botToken) && hasNonBlankValue(cfg.chatId);
50
+ }
51
+
41
52
  /** Is global config sufficient for auto-on (enabled + at least one configured adapter)? */
42
53
  export function isGloballyConfigured(cfg: NotificationConfig): boolean {
43
54
  return (
44
55
  cfg.enabled &&
45
- ((Boolean(cfg.botToken) && Boolean(cfg.chatId)) ||
46
- (Boolean(cfg.discord.botToken) && Boolean(cfg.discord.channelId)) ||
47
- (Boolean(cfg.slack.botToken) && Boolean(cfg.slack.channelId)))
56
+ (isTelegramConfigured(cfg) ||
57
+ (hasNonBlankValue(cfg.discord.botToken) && hasNonBlankValue(cfg.discord.channelId)) ||
58
+ (hasNonBlankValue(cfg.slack.botToken) && hasNonBlankValue(cfg.slack.channelId)))
48
59
  );
49
60
  }
50
61
 
@@ -52,7 +63,14 @@ export function isGloballyConfigured(cfg: NotificationConfig): boolean {
52
63
  export function shouldRegisterNotificationsExtension(input: {
53
64
  env: NodeJS.ProcessEnv;
54
65
  cfg?: NotificationConfig;
66
+ /** Task recursion depth; helper/subagent sessions must not spawn remote surfaces. */
67
+ taskDepth?: number;
68
+ /** Parent subagent id/prefix; present for helper/subagent sessions even when depth is omitted. */
69
+ parentTaskPrefix?: string;
70
+ /** Role-agent type/name; present for task sessions even if depth metadata is lost. */
71
+ currentAgentType?: string;
55
72
  }): boolean {
73
+ if ((input.taskDepth ?? 0) > 0 || input.parentTaskPrefix || input.currentAgentType) return false;
56
74
  if (input.env.SKC_NOTIFICATIONS === "0") return false;
57
75
  if (input.env.SKC_NOTIFICATIONS === "1" || input.env.SKC_NOTIFICATIONS_TOKEN) return true;
58
76
  return input.cfg ? isGloballyConfigured(input.cfg) : false;
@@ -81,6 +99,7 @@ export function isSessionNotificationsEnabled(input: {
81
99
  /** Mask a bot token for display: first 4 chars + "…" + "(len N)"; "(unset)" when undefined/empty. Never reveal full token. */
82
100
  export function maskToken(token: string | undefined): string {
83
101
  if (!token) return "(unset)";
102
+ if (token.length <= 4) return `…(len ${token.length})`;
84
103
  return `${token.slice(0, 4)}…(len ${token.length})`;
85
104
  }
86
105
 
@@ -112,7 +131,8 @@ export interface RedactableAction {
112
131
  * When redact is false, return the action unchanged.
113
132
  *
114
133
  * Redaction still applies to streamed content frames (turn_stream, context_update,
115
- * image_attachment) which are suppressed at their emit sites, not here.
134
+ * image_attachment) which are suppressed at their emit sites, not here. Explicit
135
+ * `telegram_send` file attachments are rejected before the file is read or forwarded.
116
136
  */
117
137
  export function buildRedactedAction(
118
138
  action: RedactableAction,
@@ -123,6 +143,6 @@ export function buildRedactedAction(
123
143
  // Asks stay fully readable/answerable even under redaction.
124
144
  if (action.kind === "ask") return action;
125
145
 
126
- const { summary: _summary, question: _question, ...base } = action;
146
+ const { summary: _summary, question: _question, options: _options, ...base } = action;
127
147
  return base;
128
148
  }
@@ -7,6 +7,7 @@ export interface DaemonPaths {
7
7
  roots: string;
8
8
  steal: string;
9
9
  aliases: string;
10
+ seenUpdates: string;
10
11
  }
11
12
 
12
13
  export function daemonPaths(agentDir: string): DaemonPaths {
@@ -18,5 +19,6 @@ export function daemonPaths(agentDir: string): DaemonPaths {
18
19
  roots: path.join(dir, "telegram-daemon.roots.json"),
19
20
  steal: path.join(dir, "telegram-daemon.steal"),
20
21
  aliases: path.join(dir, "telegram-callback-aliases.json"),
22
+ seenUpdates: path.join(dir, "telegram-seen-updates.json"),
21
23
  };
22
24
  }