@sayknow-cli/coding-agent 0.3.12 → 0.3.15

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/autoresearch/tools/init-experiment.d.ts +1 -1
  2. package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
  3. package/dist/types/cli/classify-install-target.d.ts +0 -10
  4. package/dist/types/cli/daemon-cli.d.ts +2 -0
  5. package/dist/types/cli/initial-message.d.ts +1 -1
  6. package/dist/types/cli/malloc-env-guard.d.ts +65 -0
  7. package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
  8. package/dist/types/cli/notify-cli.d.ts +3 -1
  9. package/dist/types/cli/session-picker.d.ts +4 -3
  10. package/dist/types/cli/update-cli.d.ts +22 -3
  11. package/dist/types/cli.d.ts +5 -0
  12. package/dist/types/commands/daemon.d.ts +6 -2
  13. package/dist/types/commands/launch.d.ts +1 -0
  14. package/dist/types/commands/notify.d.ts +6 -0
  15. package/dist/types/commands/web-search.d.ts +1 -1
  16. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
  17. package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
  18. package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
  19. package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
  20. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  21. package/dist/types/commit/analysis/summary.d.ts +1 -1
  22. package/dist/types/commit/changelog/generate.d.ts +3 -3
  23. package/dist/types/commit/changelog/index.d.ts +2 -2
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  26. package/dist/types/commit/shared-llm.d.ts +9 -9
  27. package/dist/types/config/keybindings.d.ts +134 -129
  28. package/dist/types/config/model-profile-activation.d.ts +1 -0
  29. package/dist/types/config/model-registry.d.ts +35 -35
  30. package/dist/types/config/models-config-schema.d.ts +111 -111
  31. package/dist/types/config/settings-schema.d.ts +56 -16
  32. package/dist/types/config/settings.d.ts +2 -0
  33. package/dist/types/coordinator/contract.d.ts +1 -1
  34. package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
  35. package/dist/types/coordinator-mcp/policy.d.ts +3 -0
  36. package/dist/types/coordinator-mcp/server.d.ts +79 -6
  37. package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
  38. package/dist/types/daemon/control-types.d.ts +10 -0
  39. package/dist/types/daemon/operator-contract.d.ts +47 -0
  40. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
  41. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
  42. package/dist/types/discovery/helpers.d.ts +1 -1
  43. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  44. package/dist/types/edit/modes/patch.d.ts +2 -2
  45. package/dist/types/extensibility/extensions/types.d.ts +3 -1
  46. package/dist/types/goals/tools/goal-tool.d.ts +6 -6
  47. package/dist/types/i18n/messages/en.d.ts +8 -8
  48. package/dist/types/internal-urls/local-protocol.d.ts +13 -9
  49. package/dist/types/lsp/index.d.ts +9 -9
  50. package/dist/types/lsp/types.d.ts +9 -9
  51. package/dist/types/main.d.ts +66 -2
  52. package/dist/types/modes/acp/acp-agent.d.ts +19 -2
  53. package/dist/types/modes/acp/acp-mode.d.ts +12 -0
  54. package/dist/types/modes/components/assistant-message.d.ts +1 -1
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  56. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  57. package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
  58. package/dist/types/modes/components/pet-selector.d.ts +11 -0
  59. package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
  60. package/dist/types/modes/components/session-selector.d.ts +22 -9
  61. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  62. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  63. package/dist/types/modes/components/status-line.d.ts +0 -10
  64. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  65. package/dist/types/modes/components/user-message.d.ts +2 -1
  66. package/dist/types/modes/controllers/event-controller.d.ts +4 -0
  67. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  68. package/dist/types/modes/interactive-mode.d.ts +24 -2
  69. package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
  70. package/dist/types/modes/print-mode.d.ts +3 -4
  71. package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
  72. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
  73. package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
  74. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
  75. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
  76. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
  77. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
  78. package/dist/types/modes/theme/defaults/index.d.ts +568 -568
  79. package/dist/types/modes/types.d.ts +28 -2
  80. package/dist/types/modes/utils/abort-message.d.ts +1 -1
  81. package/dist/types/modes/utils/context-usage.d.ts +4 -19
  82. package/dist/types/modes/utils/irc-message.d.ts +38 -0
  83. package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
  84. package/dist/types/notifications/config.d.ts +34 -0
  85. package/dist/types/notifications/daemon-paths.d.ts +7 -0
  86. package/dist/types/notifications/index.d.ts +27 -0
  87. package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
  88. package/dist/types/notifications/notification-service.d.ts +144 -0
  89. package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
  90. package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
  91. package/dist/types/notifications/telegram-daemon.d.ts +130 -3
  92. package/dist/types/notifications/telegram-reference.d.ts +19 -2
  93. package/dist/types/notifications/topic-registry.d.ts +24 -4
  94. package/dist/types/registry/agent-registry.d.ts +2 -0
  95. package/dist/types/rlm/index.d.ts +1 -1
  96. package/dist/types/sdk.d.ts +2 -0
  97. package/dist/types/secrets/obfuscator.d.ts +1 -1
  98. package/dist/types/session/agent-session.d.ts +40 -3
  99. package/dist/types/session/cache-economics.d.ts +79 -0
  100. package/dist/types/session/context-estimation.d.ts +31 -0
  101. package/dist/types/session/provider-safety-stop.d.ts +5 -0
  102. package/dist/types/session/session-manager.d.ts +124 -4
  103. package/dist/types/session/session-storage.d.ts +155 -13
  104. package/dist/types/setup/credential-auto-import.d.ts +2 -2
  105. package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
  106. package/dist/types/skc-runtime/state-schema.d.ts +19 -19
  107. package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
  108. package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
  109. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
  110. package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
  111. package/dist/types/task/agents.d.ts +2 -2
  112. package/dist/types/task/executor.d.ts +3 -0
  113. package/dist/types/task/receipt.d.ts +3 -1
  114. package/dist/types/task/types.d.ts +35 -27
  115. package/dist/types/tools/ask-answer-registry.d.ts +5 -0
  116. package/dist/types/tools/bash.d.ts +1 -1
  117. package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
  118. package/dist/types/tools/browser.d.ts +28 -28
  119. package/dist/types/tools/computer.d.ts +20 -20
  120. package/dist/types/tools/cron.d.ts +2 -2
  121. package/dist/types/tools/debug.d.ts +40 -40
  122. package/dist/types/tools/gh.d.ts +10 -10
  123. package/dist/types/tools/image-gen.d.ts +2 -2
  124. package/dist/types/tools/index.d.ts +58 -6
  125. package/dist/types/tools/monitor.d.ts +6 -6
  126. package/dist/types/tools/skill-discovery.d.ts +4 -4
  127. package/dist/types/tools/subagent.d.ts +12 -12
  128. package/dist/types/tools/telegram-send.d.ts +7 -0
  129. package/dist/types/tools/todo-write.d.ts +8 -8
  130. package/dist/types/utils/external-editor.d.ts +2 -0
  131. package/dist/types/web/insane/bridge.d.ts +9 -9
  132. package/dist/types/web/search/index.d.ts +4 -4
  133. package/package.json +8 -8
  134. package/scripts/g004-tmux-smoke.ts +196 -36
  135. package/src/cli/daemon-cli.ts +19 -29
  136. package/src/cli/malloc-env-guard.test.ts +104 -0
  137. package/src/cli/malloc-env-guard.ts +149 -0
  138. package/src/cli/notify-cli.ts +78 -14
  139. package/src/cli/session-picker.ts +23 -40
  140. package/src/cli/update-cli.ts +56 -37
  141. package/src/cli.ts +51 -10
  142. package/src/commands/daemon.ts +19 -5
  143. package/src/commands/harness.ts +515 -47
  144. package/src/commands/launch.ts +11 -3
  145. package/src/commands/notify.ts +5 -1
  146. package/src/commands/session.ts +6 -1
  147. package/src/config/keybindings.ts +5 -0
  148. package/src/config/model-profile-activation.ts +1 -1
  149. package/src/config/model-profiles.ts +14 -14
  150. package/src/config/settings-schema.ts +41 -1
  151. package/src/config/settings.ts +96 -45
  152. package/src/coordinator/contract.ts +1 -0
  153. package/src/coordinator-mcp/model-preset.ts +100 -0
  154. package/src/coordinator-mcp/policy.ts +30 -0
  155. package/src/coordinator-mcp/server.ts +2958 -430
  156. package/src/coordinator-mcp/session-reaper.ts +129 -0
  157. package/src/daemon/control-types.ts +11 -0
  158. package/src/daemon/operator-contract.ts +121 -0
  159. package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  160. package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  161. package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  162. package/src/extensibility/extensions/runner.ts +1 -1
  163. package/src/extensibility/extensions/types.ts +3 -1
  164. package/src/internal-urls/docs-index.generated.ts +10 -9
  165. package/src/internal-urls/local-protocol.ts +29 -10
  166. package/src/main.ts +301 -80
  167. package/src/modes/acp/acp-agent.ts +730 -100
  168. package/src/modes/acp/acp-mode.ts +29 -2
  169. package/src/modes/components/assistant-message.ts +32 -3
  170. package/src/modes/components/bash-execution.ts +43 -13
  171. package/src/modes/components/custom-editor.ts +5 -0
  172. package/src/modes/components/footer.ts +3 -3
  173. package/src/modes/components/hook-selector.ts +12 -0
  174. package/src/modes/components/irc-sidebar.ts +206 -0
  175. package/src/modes/components/pet-selector.ts +55 -0
  176. package/src/modes/components/sayknow-pet-widget.ts +360 -0
  177. package/src/modes/components/session-selector.ts +222 -221
  178. package/src/modes/components/settings-selector.ts +10 -0
  179. package/src/modes/components/status-line/segments.ts +14 -7
  180. package/src/modes/components/status-line/types.ts +1 -1
  181. package/src/modes/components/status-line.ts +4 -171
  182. package/src/modes/components/tool-execution.ts +47 -6
  183. package/src/modes/components/user-message.ts +26 -9
  184. package/src/modes/controllers/command-controller.ts +28 -2
  185. package/src/modes/controllers/event-controller.ts +159 -36
  186. package/src/modes/controllers/extension-ui-controller.ts +37 -18
  187. package/src/modes/controllers/input-controller.ts +8 -2
  188. package/src/modes/controllers/selector-controller.ts +62 -15
  189. package/src/modes/interactive-mode.ts +199 -7
  190. package/src/modes/irc-observation-ledger.ts +127 -0
  191. package/src/modes/print-mode.ts +228 -92
  192. package/src/modes/rpc/rpc-client.ts +58 -1
  193. package/src/modes/rpc/rpc-mode.ts +286 -85
  194. package/src/modes/rpc/rpc-types.ts +15 -1
  195. package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
  196. package/src/modes/shared/agent-wire/command-validation.ts +10 -0
  197. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  198. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  199. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  200. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  201. package/src/modes/types.ts +34 -2
  202. package/src/modes/utils/context-usage.ts +56 -92
  203. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  204. package/src/modes/utils/irc-message.ts +138 -0
  205. package/src/modes/utils/ui-helpers.ts +216 -50
  206. package/src/notifications/config.ts +44 -0
  207. package/src/notifications/daemon-paths.ts +8 -0
  208. package/src/notifications/index.ts +535 -75
  209. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  210. package/src/notifications/notification-service.ts +710 -0
  211. package/src/notifications/rate-limit-pool.ts +46 -12
  212. package/src/notifications/telegram-daemon-control.ts +27 -2
  213. package/src/notifications/telegram-daemon.ts +635 -94
  214. package/src/notifications/telegram-reference.ts +73 -13
  215. package/src/notifications/topic-registry.ts +86 -9
  216. package/src/prompts/system/irc-peer-roster.md +1 -0
  217. package/src/prompts/system/subagent-system-prompt.md +1 -0
  218. package/src/prompts/system/system-prompt.md +1 -0
  219. package/src/prompts/tools/irc.md +1 -1
  220. package/src/registry/agent-registry.ts +3 -0
  221. package/src/sdk.ts +52 -1
  222. package/src/secrets/obfuscator.ts +4 -2
  223. package/src/session/agent-session.ts +670 -217
  224. package/src/session/cache-economics.ts +275 -0
  225. package/src/session/context-estimation.ts +91 -0
  226. package/src/session/provider-safety-stop.ts +9 -0
  227. package/src/session/session-dump-format.ts +6 -0
  228. package/src/session/session-manager.ts +809 -39
  229. package/src/session/session-storage.ts +582 -38
  230. package/src/skc-runtime/session-state-sidecar.ts +756 -135
  231. package/src/skc-runtime/tmux-common.ts +26 -7
  232. package/src/skc-runtime/tmux-gc.ts +52 -9
  233. package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
  234. package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
  235. package/src/skc-runtime/tmux-sessions.ts +871 -68
  236. package/src/slash-commands/builtin-registry.ts +153 -9
  237. package/src/slash-commands/helpers/context-report.ts +26 -4
  238. package/src/task/executor.ts +12 -3
  239. package/src/task/index.ts +8 -0
  240. package/src/task/receipt.ts +7 -2
  241. package/src/task/types.ts +26 -0
  242. package/src/tools/ask-answer-registry.ts +25 -0
  243. package/src/tools/browser/tab-supervisor.ts +63 -6
  244. package/src/tools/browser.ts +8 -3
  245. package/src/tools/index.ts +81 -6
  246. package/src/tools/sqlite-reader.ts +8 -4
  247. package/src/tui/output-block.ts +18 -6
  248. package/src/utils/external-editor.ts +6 -1
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import type { Settings } from "../config/settings";
3
3
  import type { DaemonRuntimeInfo } from "../daemon/control-types";
4
+ import { HEARTBEAT_TTL_MS } from "./daemon-paths";
4
5
  import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
5
6
  import { NotificationOperatorRuntime, OperatorBackoffPolicy } from "./operator-runtime";
6
7
  import { type AliasTable, type CallbackRoute } from "./telegram-reference";
@@ -14,6 +15,14 @@ export interface DaemonState {
14
15
  heartbeatAt: number;
15
16
  roots: string[];
16
17
  version: 1;
18
+ /**
19
+ * Operational daemon generation of the process that owns the lock, distinct
20
+ * from the persisted state-schema {@link DaemonState.version}. It records the
21
+ * wire generation ({@link DAEMON_GENERATION}) the owning daemon speaks so a
22
+ * freshly-upgraded host can detect — and reload — a still-live pre-upgrade
23
+ * daemon whose schema version is unchanged. Absent on pre-generation state.
24
+ */
25
+ generation?: number;
17
26
  stoppedAt?: number;
18
27
  }
19
28
  export interface TelegramDaemonFs {
@@ -43,14 +52,35 @@ export interface TelegramDaemonDeps {
43
52
  }) => SpawnResult;
44
53
  execPath?: string;
45
54
  randomId?: () => string;
55
+ /**
56
+ * Signal delivery + poll timing for the stale-generation reload handoff in
57
+ * {@link ensureTelegramDaemonRunning}. Defaults use real signals/timers; tests
58
+ * inject them to drive the handoff deterministically.
59
+ */
60
+ sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
61
+ sleep?: (ms: number) => Promise<void>;
62
+ waitStepMs?: number;
46
63
  }
47
64
  export declare const HEARTBEAT_INTERVAL_MS = 5000;
48
- export declare const HEARTBEAT_TTL_MS = 20000;
65
+ export { HEARTBEAT_TTL_MS };
49
66
  export declare const DAEMON_VERSION = 1;
50
67
  /** Capability token advertised when the server supports app-level ping/pong. */
51
68
  export declare const CLIENT_PING_PONG_CAPABILITY = "client_ping_pong";
52
69
  /** Protocol version the daemon advertises in its ClientHello. */
53
- export declare const NOTIFICATION_PROTOCOL_VERSION = 2;
70
+ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
71
+ /** Capability required for typed controls and semantic Selected acknowledgement frames. */
72
+ export declare const ASK_SELECTED_ACK_CAPABILITY = "ask_selected_ack_v1";
73
+ export declare const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
74
+ /**
75
+ * Operational generation the current daemon build speaks, persisted into
76
+ * {@link DaemonState.generation} on ownership acquisition. It is tied to the
77
+ * wire {@link NOTIFICATION_PROTOCOL_VERSION} so any protocol bump (which is what
78
+ * gates capabilities like {@link ASK_SELECTED_ACK_CAPABILITY}) also bumps the
79
+ * generation, letting a freshly-upgraded host recognise an older, still-live
80
+ * daemon and reload it instead of silently attaching to it. Distinct from the
81
+ * persisted schema {@link DAEMON_VERSION}, which did not change across #1999.
82
+ */
83
+ export declare const DAEMON_GENERATION = 3;
54
84
  export { type DaemonPaths, daemonPaths } from "./daemon-paths";
55
85
  /**
56
86
  * Attach session-lifecycle control (create/close/resume) to the running daemon.
@@ -97,6 +127,7 @@ export declare function acquireDaemonOwnership(input: {
97
127
  attached?: boolean;
98
128
  blocked?: boolean;
99
129
  reason?: "identity_mismatch";
130
+ reloadRequired?: boolean;
100
131
  }>;
101
132
  export declare function renewDaemonHeartbeat(input: {
102
133
  settings: Settings;
@@ -126,6 +157,12 @@ export interface TelegramSpawnOwnerResult {
126
157
  ownerId?: string;
127
158
  runtime: DaemonRuntimeInfo;
128
159
  warnings: string[];
160
+ /**
161
+ * Set when ownership was NOT acquired because a still-live owner is running an
162
+ * older daemon generation. The caller must hand off via a reload rather than
163
+ * attach; see {@link ensureTelegramDaemonRunning}.
164
+ */
165
+ reloadRequired?: boolean;
129
166
  }
130
167
  /**
131
168
  * Build the detached spawn command/args for the daemon-internal entrypoint.
@@ -156,6 +193,7 @@ export declare function ensureTelegramDaemonRunning(input: {
156
193
  export interface BotApi {
157
194
  call(method: string, body: unknown, opts?: {
158
195
  signal?: AbortSignal;
196
+ noRetry?: boolean;
159
197
  }): Promise<unknown>;
160
198
  }
161
199
  export interface TelegramTransportOptions {
@@ -170,13 +208,15 @@ export declare class TelegramBotTransport implements BotApi {
170
208
  constructor(opts: TelegramTransportOptions);
171
209
  call(method: string, body: unknown, opts?: {
172
210
  signal?: AbortSignal;
211
+ noRetry?: boolean;
173
212
  }): Promise<unknown>;
174
213
  }
214
+ export type TelegramUpdateOutcome = "consumed" | "retry";
175
215
  export interface TelegramUpdatePollerOptions {
176
216
  botApi: BotApi;
177
217
  runtime: NotificationOperatorRuntime;
178
218
  backoff: OperatorBackoffPolicy;
179
- processUpdate: (update: unknown) => Promise<void>;
219
+ processUpdate: (update: unknown) => Promise<TelegramUpdateOutcome>;
180
220
  }
181
221
  /** Owns getUpdates offset, conflict backoff, and per-update error isolation. */
182
222
  export declare class TelegramUpdatePoller {
@@ -239,6 +279,14 @@ export interface TelegramDaemonOptions {
239
279
  richDraft?: {
240
280
  enabled: boolean;
241
281
  };
282
+ /**
283
+ * Per-session Telegram forum-topic naming. `nameTemplate` supports the
284
+ * `{repo}`, `{branch}`, and `{title}` placeholders; unset preserves the
285
+ * built-in `{repo}/{branch} - {title}` composition and its fallbacks.
286
+ */
287
+ topics?: {
288
+ nameTemplate?: string;
289
+ };
242
290
  }
243
291
  interface SessionSocket {
244
292
  sessionId: string;
@@ -273,6 +321,11 @@ export declare class TelegramNotificationDaemon {
273
321
  private readonly fsImpl;
274
322
  private readonly botApi;
275
323
  private readonly topics;
324
+ /** Serializes registry snapshots so an older atomic write cannot overwrite newer rename state. */
325
+ private topicsPersistQueue;
326
+ /** Daemon edit attempts that can race an accepted user service message. */
327
+ private readonly daemonRenameAttempts;
328
+ private readonly selectedAckPending;
276
329
  private readonly pool;
277
330
  private readonly poller;
278
331
  private readonly dispatchState;
@@ -314,12 +367,24 @@ export declare class TelegramNotificationDaemon {
314
367
  private readonly pendingLifecycle;
315
368
  /** Monotonic counter for unique lifecycle request ids. */
316
369
  private lifecycleSeq;
370
+ /** Attempt tombstones live for the daemon lifetime so a commit key can never send twice. */
371
+ private readonly selectedAckCache;
372
+ private cacheSelectedAck;
373
+ private getCachedSelectedAck;
374
+ private finishSelectedAck;
317
375
  /**
318
376
  * Cooperatively stop the daemon: set the stop flag and abort the in-flight
319
377
  * long poll so the run loop wakes immediately instead of waiting out the
320
378
  * ~25s getUpdates timeout. Safe to call from a signal handler.
321
379
  */
322
380
  requestStop(_reason?: "reload" | "stop" | "signal"): void;
381
+ /**
382
+ * Start the owner-bound lifecycle control server and wire it to the
383
+ * orchestrator. Called from {@link run} ONLY after ownership is confirmed, so
384
+ * exactly one owner ever starts exactly one control server (no second poller
385
+ * / 409). A control-server failure degrades gracefully: the daemon keeps
386
+ * serving notifications without lifecycle control. Returns true when started.
387
+ */
323
388
  private startLifecycleControl;
324
389
  /** Stop the lifecycle control server (idempotent); called from run()'s finally. */
325
390
  private stopLifecycleControl;
@@ -334,6 +399,11 @@ export declare class TelegramNotificationDaemon {
334
399
  private nextLifecycleRequestId;
335
400
  /** Build an authenticated lifecycle frame from a parsed command + identity. */
336
401
  private buildLifecycleFrame;
402
+ /**
403
+ * Handle a paired-chat /session_* command: validate (shared validator),
404
+ * route to the control endpoint, and reply with the outcome. Returns true
405
+ * when the message was a lifecycle command (so the caller stops processing).
406
+ */
337
407
  private handleLifecycleCommand;
338
408
  private refreshBotIdentity;
339
409
  /** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
@@ -366,20 +436,40 @@ export declare class TelegramNotificationDaemon {
366
436
  private deleteMessageRoutes;
367
437
  private static readonly THREADED_FRAMES;
368
438
  private topicNameFor;
439
+ /**
440
+ * Render the operator-configured topic name template, or `undefined` when no
441
+ * usable template applies so the caller uses the built-in composition. The
442
+ * template is honored only if it is non-blank AND every placeholder it
443
+ * references (`{repo}`, `{branch}`, `{title}`) has a value for this session,
444
+ * which preserves the default title/repo/branch fallbacks and prevents
445
+ * half-filled names with dangling separators. Unknown placeholders are left
446
+ * verbatim.
447
+ */
448
+ private renderTopicNameTemplate;
369
449
  private topicIdentityKey;
370
450
  private topicIdentityBase;
371
451
  private topicOwnerForIdentity;
372
452
  private sessionCanClaimIdentity;
373
453
  private submitThreadedFrame;
374
454
  private existingTopicForPrivateChat;
455
+ /** Best-effort re-assertion for a durable user-owned topic name. */
456
+ private reconcileUserTopicName;
375
457
  private rememberPendingThreadedFrame;
376
458
  private flushPendingThreadedFrames;
459
+ /**
460
+ * Resolve (creating once via `createForumTopic`) the forum topic for a
461
+ * session. On capability failure (e.g. Threaded Mode off) this returns
462
+ * `undefined`; callers then flat-deliver to a private paired chat (with a
463
+ * one-time nudge) or drop fail-closed for a non-private chat.
464
+ */
377
465
  private ensureTopic;
378
466
  private topicPastOrphanGrace;
379
467
  private deleteOrphanedTopic;
468
+ /** Best-effort delete of a session topic once its local notification endpoint shuts down. */
380
469
  private deleteTopic;
381
470
  private persistTopics;
382
471
  loadTopics(): Promise<void>;
472
+ /** Download a Telegram file by its file_path (from getFile) into memory. */
383
473
  private downloadTelegramFile;
384
474
  /**
385
475
  * Per-session private temp directories (mode 0700) holding inbound non-image
@@ -387,8 +477,19 @@ export declare class TelegramNotificationDaemon {
387
477
  * removed when the daemon stops (see {@link cleanupAllAttachmentDirs}).
388
478
  */
389
479
  private readonly attachmentDirs;
480
+ /** Lazily create a private, unguessable 0700 temp dir for `sessionId`. */
390
481
  private ensureAttachmentDir;
482
+ /** Remove all per-session attachment directories. Called on daemon shutdown. */
391
483
  private cleanupAllAttachmentDirs;
484
+ /**
485
+ * Resolve an inbound attachment to inline image bytes (forwarded as images) or
486
+ * a securely-saved file path note (non-images). Non-image bytes are written
487
+ * into a private per-session temp dir (0700) under an unguessable name via an
488
+ * exclusive 0600 create (`wx`), so the files are not world-readable and the
489
+ * write never follows a pre-existing symlink. The directory is removed when the
490
+ * daemon stops. Returns base64 images to inline plus human-readable file notes
491
+ * to append to the injected text.
492
+ */
392
493
  private resolveInboundAttachment;
393
494
  /**
394
495
  * Serialize all pool flushes. Every caller (`submitThreadedFrame`, the flat
@@ -400,6 +501,7 @@ export declare class TelegramNotificationDaemon {
400
501
  */
401
502
  private flushChain;
402
503
  private flushPool;
504
+ /** Drain the shared rate-limit pool and deliver each granted send to its topic. */
403
505
  private flushPoolInner;
404
506
  /**
405
507
  * Track the Telegram message id backing a streamed `(sessionId, coalesceKey)`
@@ -407,25 +509,50 @@ export declare class TelegramNotificationDaemon {
407
509
  * same-category entries (e.g. prior turns) so the map stays bounded.
408
510
  */
409
511
  private recordLiveMessage;
512
+ /**
513
+ * Threaded Mode is unavailable (the bot owner has not enabled forum topics in
514
+ * @BotFather, so `createForumTopic` fails). Deliver the rendered frame flat to
515
+ * the paired chat instead of dropping it, and nudge the user once. Flat delivery
516
+ * is gated on the paired chat being a private chat: for a group/supergroup/channel
517
+ * (e.g. a legacy or hand-edited `chatId`) we keep dropping fail-closed so session
518
+ * content never lands in a shared chat. Identity headers are sent at most once per
519
+ * session in flat mode.
520
+ */
410
521
  private deliverFlatFallback;
522
+ /**
523
+ * Resolve (and cache definitive resolution of) whether the paired `chatId` is
524
+ * a private chat. Topic and flat delivery are only safe in a private DM; an
525
+ * indeterminate `getChat` result fails closed for this attempt and is retried
526
+ * later.
527
+ */
528
+ private resolvePairedChatPrivacy;
529
+ /** Keep existing outbound callers fail-closed for indeterminate privacy. */
411
530
  private pairedChatIsPrivate;
531
+ /** Tell the user once (per daemon run) how to enable Threaded Mode. */
412
532
  private notifyThreadedFallback;
413
533
  private startFlushTimer;
414
534
  private stopFlushTimer;
535
+ /** Run a root scan, guarding against overlapping scans from the timer + loop. */
415
536
  private runScan;
416
537
  private startScanTimer;
417
538
  private stopScanTimer;
539
+ /** Send a single `typing` chat action into a busy session's topic (best-effort). */
418
540
  private sendTyping;
541
+ /** Set a native reaction on an inbound thread message (best-effort). */
419
542
  private setReaction;
420
543
  private startTypingTimer;
421
544
  private stopTypingTimer;
422
545
  handleSessionMessage(session: SessionSocket, msg: any): Promise<void>;
423
546
  private answerCallbackQueryBestEffort;
424
547
  private sendStaleGuidance;
548
+ /** Consume Telegram forum-topic rename service messages before text routing. */
549
+ private handleForumTopicEdited;
550
+ private processTelegramUpdate;
425
551
  handleTelegramUpdate(update: unknown): Promise<void>;
426
552
  pollOnce(signal?: AbortSignal): Promise<number>;
427
553
  /** Sync the bot's Telegram command menu to what the daemon actually handles. */
428
554
  registerBotCommands(): Promise<void>;
429
555
  run(): Promise<void>;
556
+ /** True when a signal-driven stop or an owner-scoped control request asks the loop to exit. */
430
557
  private controlStopRequested;
431
558
  }
@@ -18,6 +18,16 @@ export interface InlineButton {
18
18
  text: string;
19
19
  callback_data: string;
20
20
  }
21
+ /** Typed action controls are protocol data, never inferred from option labels. */
22
+ export interface TelegramActionControl {
23
+ id: "navigation_forward";
24
+ kind: "navigation";
25
+ label: "Next" | "Done";
26
+ enabled: boolean;
27
+ }
28
+ export type TelegramCallbackAnswer = number | string | {
29
+ controlId: TelegramActionControl["id"];
30
+ };
21
31
  /** A rendered Telegram message for an `action_needed`. */
22
32
  export interface RenderedMessage {
23
33
  text: string;
@@ -31,10 +41,16 @@ export declare function decodeCallbackData(data: string): {
31
41
  id: string;
32
42
  index: number;
33
43
  } | null;
44
+ /** Encode a typed control independently from option labels. */
45
+ export declare function encodeControlCallbackData(actionId: string, controlId: TelegramActionControl["id"]): string;
46
+ export declare function decodeControlCallbackData(data: string): {
47
+ id: string;
48
+ controlId: TelegramActionControl["id"];
49
+ } | null;
34
50
  export interface CallbackRoute {
35
51
  sessionId: string;
36
52
  actionId: string;
37
- answer: number | string;
53
+ answer: TelegramCallbackAnswer;
38
54
  }
39
55
  export interface SerializedAliasTable {
40
56
  version: 1;
@@ -57,6 +73,7 @@ export declare function buildActionMessage(action: {
57
73
  id: string;
58
74
  question?: string;
59
75
  options?: string[];
76
+ controls?: readonly TelegramActionControl[];
60
77
  summary?: string;
61
78
  }): RenderedMessage;
62
79
  /** Render an `action_needed` body as raw markdown (rich-message source; the HTML fallback stays on buildActionMessage). */
@@ -72,7 +89,7 @@ export declare function sendTelegramHtmlChunks(send: TelegramSend, chatId: strin
72
89
  export interface ReplyFrame {
73
90
  type: "reply";
74
91
  id: string;
75
- answer: number | string;
92
+ answer: TelegramCallbackAnswer;
76
93
  token: string;
77
94
  }
78
95
  /**
@@ -20,8 +20,16 @@ export interface TopicRecord {
20
20
  identitySent: boolean;
21
21
  /** Creation timestamp (ms epoch). */
22
22
  createdAt: number;
23
- /** Last applied topic title (for rename detection). */
23
+ /** Last applied or observed Telegram topic title. */
24
24
  name?: string;
25
+ /** Naming authority. Missing values are legacy daemon-owned records. */
26
+ nameOwner?: "user";
27
+ /** Whether a user-owned name still needs a best-effort Telegram re-assert. */
28
+ nameReconcilePending?: boolean;
29
+ /** Last accepted Telegram update id for a user-owned name. */
30
+ userNameUpdateId?: number;
31
+ /** Stable repo/branch identity used when topic names are user-owned or customized. */
32
+ identityKey?: string;
25
33
  }
26
34
  /** Serialisable shape persisted to disk. */
27
35
  export interface TopicRegistryState {
@@ -41,7 +49,7 @@ export declare class TopicRegistry {
41
49
  /** In-flight create promises, keyed by session, to dedupe concurrent creates. */
42
50
  private readonly inflight;
43
51
  constructor(state?: TopicRegistryState);
44
- /** Merge a serialized state into this registry, preserving all persisted fields. */
52
+ /** Merge serialized state and normalize authority fields from older releases. */
45
53
  load(state: TopicRegistryState): void;
46
54
  /** Resolve the owning session for a topic id (for fail-closed inbound routing). */
47
55
  sessionForTopic(topicId: string): string | undefined;
@@ -58,9 +66,21 @@ export declare class TopicRegistry {
58
66
  markIdentitySent(sessionId: string): void;
59
67
  /** Whether the identity header still needs sending for this session. */
60
68
  needsIdentity(sessionId: string): boolean;
61
- /** Whether a known session topic's applied title differs from `name`. */
69
+ /** Remember stable repo/branch identity independently of the displayed name. */
70
+ markIdentityKey(sessionId: string, identityKey: string): boolean;
71
+ /** Whether daemon identity reconciliation should apply `name`. */
62
72
  needsRename(sessionId: string, name: string): boolean;
63
- /** Commit a successfully-applied Telegram topic title. */
73
+ /** The user-owned name that must be preserved, when one exists. */
74
+ userOwnedName(sessionId: string): string | undefined;
75
+ /** A user-owned name whose Telegram reconciliation is still pending. */
76
+ userNameToReconcile(sessionId: string): string | undefined;
77
+ /** Record an explicit Telegram-side user rename, rejecting stale update ids. */
78
+ markUserName(sessionId: string, name: string, updateId: number): "updated" | "duplicate" | "stale";
79
+ /** Mark the matching preserved user name as reconciled with Telegram. */
80
+ markUserNameReconciled(sessionId: string, name: string): boolean;
81
+ /** Restore retryable reconciliation after a failed pending-clear persistence. */
82
+ markUserNamePending(sessionId: string, name: string): boolean;
83
+ /** Commit a successfully-applied daemon topic title. */
64
84
  markNameApplied(sessionId: string, name: string): void;
65
85
  /** Remove a session topic record after Telegram deletes the topic. */
66
86
  delete(sessionId: string): boolean;
@@ -12,6 +12,7 @@ export type AgentKind = "main" | "sub";
12
12
  export interface AgentRef {
13
13
  id: string;
14
14
  displayName: string;
15
+ rosterLabel?: string;
15
16
  kind: AgentKind;
16
17
  parentId?: string;
17
18
  status: AgentStatus;
@@ -34,6 +35,7 @@ type RegistryListener = (event: RegistryEvent) => void;
34
35
  export interface RegisterInput {
35
36
  id: string;
36
37
  displayName: string;
38
+ rosterLabel?: string;
37
39
  kind: AgentKind;
38
40
  parentId?: string;
39
41
  session: AgentSession | null;
@@ -24,7 +24,7 @@ export declare function extractDataFlag(argv: string[]): {
24
24
  dataPath: string | undefined;
25
25
  rest: string[];
26
26
  };
27
- export declare function createRlmPreset({ dataContext, pythonTool, completeResearchTool, objective, resumeContext, onSessionReady }: RlmPresetOptions): RlmPreset;
27
+ export declare function createRlmPreset({ dataContext, pythonTool, completeResearchTool, objective, resumeContext, onSessionReady, }: RlmPresetOptions): RlmPreset;
28
28
  export declare function buildRlmGoalObjective(input: {
29
29
  messages: readonly string[];
30
30
  dataContext: RlmDataContext | null;
@@ -122,6 +122,8 @@ export interface CreateAgentSessionOptions {
122
122
  agentId?: string;
123
123
  /** Display name for the agent in IRC. Default: "main" or "sub". */
124
124
  agentDisplayName?: string;
125
+ /** Compact task label for hidden IRC roster reminders. */
126
+ agentRosterLabel?: string;
125
127
  /** Optional restricted bash command prefixes for read-only role agents. */
126
128
  bashAllowedPrefixes?: string[];
127
129
  /** Restriction policy paired with bashAllowedPrefixes. */
@@ -1,5 +1,5 @@
1
1
  import type { Message } from "@sayknow-cli/ai";
2
- import type { SessionContext } from "../session/session-manager";
2
+ import { type SessionContext } from "../session/session-manager";
3
3
  export interface SecretEntry {
4
4
  type: "plain" | "regex";
5
5
  content: string;
@@ -12,9 +12,9 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type StablePrefixSnapshot, ThinkingLevel } from "@sayknow-cli/agent-core";
15
+ import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type ResolvedThinkingLevel, type StablePrefixSnapshot, ThinkingLevel } from "@sayknow-cli/agent-core";
16
16
  import { type CompactionResult, type EmergencyCompactionSample } from "@sayknow-cli/agent-core/compaction";
17
- import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolCall, ToolChoice, UsageReport } from "@sayknow-cli/ai";
17
+ import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolCall, ToolChoice, Usage, UsageReport } from "@sayknow-cli/ai";
18
18
  export interface ForkContextSeedMetadata {
19
19
  sourceSessionId: string;
20
20
  parentMessageCount: number;
@@ -79,7 +79,7 @@ import { type WorkspaceTree } from "../workspace-tree";
79
79
  import type { ClientBridge } from "./client-bridge";
80
80
  import { type ContributionPrepOptions, type ContributionPrepResult } from "./contribution-prep";
81
81
  import { type CustomMessage } from "./messages";
82
- import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionContext, SessionManager } from "./session-manager";
82
+ import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionContext, SessionManager, SessionManagerCloseOutcome } from "./session-manager";
83
83
  import { ToolChoiceQueue } from "./tool-choice-queue";
84
84
  import { YieldQueue } from "./yield-queue";
85
85
  /** Session-specific events that extend the core AgentEvent */
@@ -309,6 +309,11 @@ export interface RoleModelCycleResult {
309
309
  thinkingLevel: ThinkingLevel | undefined;
310
310
  role: string;
311
311
  }
312
+ export interface DefaultModelSelectionResult {
313
+ readonly provider: string;
314
+ readonly modelId: string;
315
+ readonly thinkingLevel: ResolvedThinkingLevel;
316
+ }
312
317
  /** Session statistics for /session command */
313
318
  export interface SessionStats {
314
319
  sessionFile: string | undefined;
@@ -327,6 +332,7 @@ export interface SessionStats {
327
332
  };
328
333
  premiumRequests: number;
329
334
  cost: number;
335
+ costBreakdown?: Usage["cost"];
330
336
  }
331
337
  export type QueuedMessageEditMode = "steer" | "followUp";
332
338
  export interface QueuedMessageEditEntry {
@@ -483,6 +489,16 @@ export declare class AgentSession {
483
489
  * Call this when completely done with the session.
484
490
  */
485
491
  dispose(): Promise<void>;
492
+ /**
493
+ * Strict writer close for ACP session delete. On the first attempt it flushes
494
+ * pending writes, then returns the certainty-aware close outcome so the caller
495
+ * can block destructive mutation on a non-`closed` result. When the manager
496
+ * retains a retryable writer (a prior `close_failed_retryable`), the flush is
497
+ * NOT repeated: the underlying writer rejects flushes while in the retryable
498
+ * state, and `SessionManager.closeStrict()` owns the flush/close sequencing so
499
+ * a second call can return `closed` once the OS close lands.
500
+ */
501
+ closeWriterStrict(): Promise<SessionManagerCloseOutcome>;
486
502
  /**
487
503
  * Bounded, best-effort teardown of the subprocess-spawning resources this session
488
504
  * owns: the browser tool's headless/spawned Chrome and the Python eval kernel + JS VM
@@ -509,6 +525,17 @@ export declare class AgentSession {
509
525
  drainAsyncJobDeliveriesForAcp(options?: {
510
526
  timeoutMs?: number;
511
527
  }): Promise<boolean>;
528
+ /**
529
+ * Owner-scoped async-delivery snapshot used by the strict ACP delete
530
+ * quiescence barrier to PROVE quiescence after a best-effort drain. Unlike
531
+ * {@link drainAsyncJobDeliveriesForAcp}'s boolean return (which conflates
532
+ * "nothing to drain" with "timed out"), this reads the live state directly so
533
+ * any remaining queued/delivering work is observable and can block mutation.
534
+ */
535
+ getAsyncDeliveryStateForAcp(): {
536
+ queued: number;
537
+ delivering: boolean;
538
+ };
512
539
  /** Most recent assistant message in agent state. */
513
540
  getLastAssistantMessage(): AssistantMessage | undefined;
514
541
  /** Current effective system prompt blocks (includes any per-turn extension modifications) */
@@ -607,6 +634,8 @@ export declare class AgentSession {
607
634
  get hasPostPromptWork(): boolean;
608
635
  /** All messages including custom types like BashExecutionMessage */
609
636
  get messages(): AgentMessage[];
637
+ /** Main startup calls this exactly once, after a strict open returned `kind: "opened"`. */
638
+ continuePersistedHistory(): Promise<void>;
610
639
  buildDisplaySessionContext(): SessionContext;
611
640
  /** Convert session messages using the same pre-LLM pipeline as the active session. */
612
641
  convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]>;
@@ -830,6 +859,7 @@ export declare class AgentSession {
830
859
  setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel, options?: {
831
860
  persistAsSessionDefault?: boolean;
832
861
  }): Promise<void>;
862
+ setDefaultModelSelection(model: Model, thinkingLevel: ThinkingLevel | undefined): Promise<DefaultModelSelectionResult>;
833
863
  /**
834
864
  * Cycle to next/previous model.
835
865
  * Uses scoped models (from --models flag) if available, otherwise all available models.
@@ -1084,9 +1114,13 @@ export declare class AgentSession {
1084
1114
  promptText: string;
1085
1115
  onTextDelta?: (delta: string) => void;
1086
1116
  signal?: AbortSignal;
1117
+ /** Defer the successful roster-claim commit until the caller accepts its exchange. */
1118
+ deferRosterCommit?: boolean;
1087
1119
  }): Promise<{
1088
1120
  replyText: string;
1089
1121
  assistantMessage: AssistantMessage;
1122
+ commitRosterClaim?: () => void;
1123
+ releaseRosterClaim?: () => void;
1090
1124
  }>;
1091
1125
  /**
1092
1126
  * Reload the current session from disk.
@@ -1152,6 +1186,9 @@ export declare class AgentSession {
1152
1186
  * otherwise estimates tokens for all messages.
1153
1187
  */
1154
1188
  getContextUsage(): ContextUsage | undefined;
1189
+ getContextUsageObservabilityForTests(): {
1190
+ estimateCount: number;
1191
+ };
1155
1192
  fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null>;
1156
1193
  /**
1157
1194
  * Export session to HTML.
@@ -0,0 +1,79 @@
1
+ import type { Usage } from "@sayknow-cli/ai";
2
+ export interface CacheEconomicsPricing {
3
+ input: number;
4
+ cacheRead: number;
5
+ cacheWrite: number;
6
+ }
7
+ export interface CacheEconomicsModelCost extends CacheEconomicsPricing {
8
+ output: number;
9
+ }
10
+ export type CacheEconomicsBasis = {
11
+ kind: "persisted-aggregate";
12
+ costBreakdown: Usage["cost"];
13
+ } | {
14
+ kind: "current-model-estimate";
15
+ pricing: CacheEconomicsPricing;
16
+ };
17
+ export interface CacheEconomicsUsage {
18
+ input: number;
19
+ output?: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ total?: number;
23
+ cost?: {
24
+ input?: number;
25
+ output?: number;
26
+ cacheRead?: number;
27
+ cacheWrite?: number;
28
+ total?: number;
29
+ };
30
+ }
31
+ export interface CacheMissCostSummary {
32
+ inputTokens: number;
33
+ cacheReadTokens: number;
34
+ cacheWriteTokens: number;
35
+ inputCostUsd: number;
36
+ cacheReadCostUsd: number;
37
+ cacheWriteCostUsd: number;
38
+ cacheHitRate: number | undefined;
39
+ missPremiumUsd: number | undefined;
40
+ }
41
+ /**
42
+ * How a detected cache-miss pattern is attributed, per issue #2020.
43
+ *
44
+ * - `actionable`: usage evidence points to a user-controllable cause and SKC
45
+ * has a concrete remediation.
46
+ * - `diagnostic-only`: a miss pattern is observed but the cause is not
47
+ * determinable from usage alone; describe what is unknown, assert no cause.
48
+ * - `provider-side-suspected`: the provider returned no cache activity, so the
49
+ * miss cannot be attributed to the user's prompt; not user-actionable.
50
+ */
51
+ export type CacheMissAttribution = "actionable" | "diagnostic-only" | "provider-side-suspected";
52
+ export interface CacheBehaviorWarning {
53
+ code: "expensive_cache_miss" | "cache_write_spike" | "provider_side_cache_miss";
54
+ attribution: CacheMissAttribution;
55
+ reason: string;
56
+ /** Concrete remediation — present only when `attribution` is `actionable`. */
57
+ nextStep?: string;
58
+ /** What SKC cannot determine — present for non-actionable attributions. */
59
+ unknown?: string;
60
+ costUsd: number;
61
+ }
62
+ export interface CacheWarningBuildState {
63
+ warningsEmitted: number;
64
+ }
65
+ export declare function computeCacheMissCostSummary(usage: CacheEconomicsUsage | undefined, basis: CacheEconomicsBasis): CacheMissCostSummary | undefined;
66
+ export declare function formatCacheMissSummaryLines(summary: CacheMissCostSummary): string[];
67
+ export declare function buildCacheBehaviorWarning(usage: Usage | undefined, model: {
68
+ cost: CacheEconomicsModelCost;
69
+ } | undefined | null): CacheBehaviorWarning | undefined;
70
+ /**
71
+ * Render a cache warning as a single transcript line. Actionable attributions
72
+ * carry a concrete next step; non-actionable ones state what is unknown and,
73
+ * for provider-side patterns, that the miss is not user-actionable — never
74
+ * asserting a cause or blaming the provider (#2020).
75
+ */
76
+ export declare function formatCacheWarningLine(warning: CacheBehaviorWarning): string;
77
+ export declare function buildCacheEconomicsWarning(usage: Usage | undefined, model: {
78
+ cost: CacheEconomicsModelCost;
79
+ } | undefined | null, state: CacheWarningBuildState): string | undefined;
@@ -0,0 +1,31 @@
1
+ import type { Skill } from "../extensibility/skills";
2
+ import type { Tool } from "../tools";
3
+ import type { AgentSession } from "./agent-session";
4
+ export declare function estimateSkillsTokens(skills: readonly Skill[]): number;
5
+ export declare function estimateToolSchemaTokens(tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>): number;
6
+ /**
7
+ * Compute just the NON-MESSAGE token total: system prompt (with its skills
8
+ * section subtracted, since skills are tokenized separately) + system context
9
+ * (the rest of the system-prompt array) + tools + skills.
10
+ *
11
+ * Exposed so callers like `StatusLineComponent` can cache the non-message
12
+ * total separately from the message total. Non-message inputs (skills,
13
+ * tools, system prompt) change rarely; the message list grows on every
14
+ * streaming turn. Splitting the two lets the caller refresh each on its own
15
+ * cadence — non-message recomputed only when the inputs identity changes,
16
+ * messages walked incrementally as new entries append.
17
+ */
18
+ export declare function computeNonMessageTokens(session: AgentSession): number;
19
+ /**
20
+ * Shared helper for the four non-message token totals. Single source of truth
21
+ * for `computeNonMessageTokens` (session estimates + status-line incremental
22
+ * cache) and `computeContextBreakdown` (/context panel). The split avoids
23
+ * drift between the surfaces — they MUST report the same numbers.
24
+ */
25
+ export declare function computeNonMessageBreakdown(session: AgentSession): {
26
+ rulesTokens: number;
27
+ skillsTokens: number;
28
+ toolsTokens: number;
29
+ systemContextTokens: number;
30
+ systemPromptTokens: number;
31
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Recognizes legacy provider safety-stop labels persisted before `errorKind`.
3
+ * Keep this anchored and conservative so incidental transient-error prose stays retryable.
4
+ */
5
+ export declare function isLegacyProviderSafetyStopMessage(errorMessage: string): boolean;