@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,5 @@
1
1
  import * as fs from "node:fs";
2
- import type { AgentMessage } from "@sayknow-cli/agent-core";
3
- import { estimateMessageTokensHeuristic } from "@sayknow-cli/agent-core/compaction";
2
+
4
3
  import { type Component, truncateToWidth, visibleWidth } from "@sayknow-cli/tui";
5
4
  import { formatCount, getProjectDir } from "@sayknow-cli/utils";
6
5
  import { $ } from "bun";
@@ -13,7 +12,6 @@ import * as git from "../../utils/git";
13
12
  import { getSessionAccentAnsi, getSessionAccentHex } from "../../utils/session-color";
14
13
  import { EMPTY_JOBS_SNAPSHOT, type JobsSnapshot } from "../jobs-observer";
15
14
  import { sanitizeStatusText } from "../shared";
16
- import { computeNonMessageTokens } from "../utils/context-usage";
17
15
  import { renderSkillHudBar } from "./skill-hud/render";
18
16
  import {
19
17
  canReuseCachedPr,
@@ -70,104 +68,6 @@ interface CollectedStatusSegments {
70
68
  rightCapWidth: number;
71
69
  }
72
70
 
73
- // ═══════════════════════════════════════════════════════════════════════════
74
- // Per-message token cache
75
- // ═══════════════════════════════════════════════════════════════════════════
76
-
77
- /**
78
- * Symbol-keyed sidecar tagged onto each `AgentMessage` to memoize its
79
- * `estimateMessageTokensHeuristic` result. Keyed by message identity (the object itself);
80
- * a cheap content fingerprint detects in-place mutations (post-hoc error
81
- * attachment, retry-truncated branch rebuild, etc.) and forces recompute.
82
- *
83
- * Cache lives on the message — multiple `StatusLineComponent` instances
84
- * share it for free, and entries collect with the message itself when the
85
- * conversation is replaced or compacted.
86
- */
87
- const kTokenCache = Symbol("statusLine.tokenCache");
88
- interface TaggedMessage {
89
- [kTokenCache]?: { fingerprint: string; tokens: number };
90
- }
91
-
92
- /**
93
- * Cheap structural fingerprint mirroring `estimateMessageTokensHeuristic`'s content walk.
94
- * O(blocks) — only reads string `.length` and primitives, never copies or
95
- * serializes content. Any in-place mutation that alters total tokenized
96
- * content also alters one of the byte-length sums or block counts captured
97
- * here, forcing the cached heuristic token value to be recomputed.
98
- */
99
- function messageFingerprint(msg: AgentMessage): string {
100
- const role = (msg as { role?: string }).role ?? "";
101
- const ts = (msg as { timestamp?: number }).timestamp ?? 0;
102
- let textLen = 0;
103
- let blocks = 0;
104
- let images = 0;
105
- if (role === "bashExecution") {
106
- const b = msg as { command?: unknown; output?: unknown };
107
- if (typeof b.command === "string") textLen += b.command.length;
108
- if (typeof b.output === "string") textLen += b.output.length;
109
- } else if (role === "user") {
110
- const content = (msg as { content?: unknown }).content;
111
- if (typeof content === "string") {
112
- textLen += content.length;
113
- } else if (Array.isArray(content)) {
114
- blocks = content.length;
115
- for (const block of content) {
116
- if (block?.type === "text" && typeof block.text === "string") textLen += block.text.length;
117
- }
118
- }
119
- } else if (role === "assistant") {
120
- const content = (msg as { content?: unknown }).content;
121
- if (Array.isArray(content)) {
122
- blocks = content.length;
123
- for (const block of content) {
124
- if (!block || typeof block !== "object") continue;
125
- const b = block as { type?: string; text?: string; thinking?: string; name?: string; arguments?: unknown };
126
- if (b.type === "text" && typeof b.text === "string") textLen += b.text.length;
127
- else if (b.type === "thinking" && typeof b.thinking === "string") textLen += b.thinking.length;
128
- else if (b.type === "toolCall") {
129
- if (typeof b.name === "string") textLen += b.name.length;
130
- // Argument bytes vary; a length proxy is enough to detect in-place edits.
131
- textLen += b.arguments === undefined ? 0 : JSON.stringify(b.arguments).length;
132
- }
133
- }
134
- }
135
- } else if (role === "toolResult" || role === "hookMessage") {
136
- const content = (msg as { content?: unknown }).content;
137
- if (typeof content === "string") {
138
- textLen += content.length;
139
- } else if (Array.isArray(content)) {
140
- blocks = content.length;
141
- for (const block of content) {
142
- if (!block || typeof block !== "object") continue;
143
- const b = block as { type?: string; text?: string };
144
- if (b.type === "text" && typeof b.text === "string") textLen += b.text.length;
145
- else if (b.type === "image") images++;
146
- }
147
- }
148
- } else if (role === "branchSummary" || role === "compactionSummary") {
149
- const s = (msg as { summary?: unknown }).summary;
150
- if (typeof s === "string") textLen += s.length;
151
- }
152
- return `${role}:${ts}:${textLen}:${blocks}:${images}`;
153
- }
154
-
155
- /**
156
- * Token count for a single message, using the per-message sidecar cache.
157
- * The caller MUST skip caching for the last message during streaming —
158
- * it may still be growing and its tokens belong recomputed each refresh.
159
- */
160
- function tokensForMessage(msg: AgentMessage): number {
161
- const fp = messageFingerprint(msg);
162
- const tagged = msg as TaggedMessage;
163
- const cached = tagged[kTokenCache];
164
- if (cached && cached.fingerprint === fp) return cached.tokens;
165
- const tokens = estimateMessageTokensHeuristic(msg);
166
- tagged[kTokenCache] = { fingerprint: fp, tokens };
167
- return tokens;
168
- }
169
-
170
- // ═══════════════════════════════════════════════════════════════════════════
171
71
  // StatusLineComponent
172
72
  // ═══════════════════════════════════════════════════════════════════════════
173
73
 
@@ -217,20 +117,6 @@ export class StatusLineComponent implements Component {
217
117
  #cachedUsage: SegmentContext["usage"] = null;
218
118
  #usageFetchedAt = 0;
219
119
  #usageInFlight = false;
220
- // Context breakdown — incremental cache. Replaces the previous 2-second
221
- // TTL design (which re-walked every message on each refresh and produced
222
- // ~1.1 s sync freezes on 2,000+ message sessions because `updateEditorTopBorder`
223
- // is called on every agent event in event-controller). The new scheme
224
- // caches by message-object identity (a Symbol-keyed sidecar on each
225
- // message) plus a cheap content fingerprint, so in-place mutations of
226
- // an existing message (post-hoc error attachment, retry-truncated
227
- // branch rebuild, replaceMessages with the same length) are detected
228
- // and recomputed.
229
- // Cached non-message total (system prompt + tools + skills). Invalidated
230
- // when the inputs-identity fingerprint changes (model swap, skill toggle,
231
- // tool registration).
232
- #nonMessageTokensCache: number | undefined;
233
- #nonMessageInputsKey: string | undefined;
234
120
 
235
121
  constructor(
236
122
  private readonly session: AgentSession,
@@ -618,57 +504,6 @@ export class StatusLineComponent implements Component {
618
504
  return windows.length > 0 ? { windows } : null;
619
505
  }
620
506
 
621
- /**
622
- * Compute the (cached) used-tokens / context-window totals for the
623
- * status-line context% segment. Exposed (non-private) so unit tests can
624
- * verify the incremental-cache invariants; not part of any external
625
- * API.
626
- */
627
- getCachedContextBreakdown(): { usedTokens: number; contextWindow: number } {
628
- const messages = this.session.messages ?? [];
629
- const contextWindow = this.session.model?.contextWindow ?? 0;
630
-
631
- // 1) Non-message tokens (system prompt + tools + skills). Refresh only
632
- // when the inputs identity fingerprint changes — usually never
633
- // during a streaming turn. ~10-30 ms when it does refresh.
634
- const inputsKey = this.#computeNonMessageInputsKey();
635
- if (this.#nonMessageTokensCache === undefined || this.#nonMessageInputsKey !== inputsKey) {
636
- this.#nonMessageTokensCache = computeNonMessageTokens(this.session);
637
- this.#nonMessageInputsKey = inputsKey;
638
- }
639
-
640
- // 2) Message tokens — incremental. The sidecar cache lives on the
641
- // message object itself (Symbol-keyed), keyed by identity and
642
- // validated by a cheap content fingerprint. Mutations that
643
- // replace messages (replaceMessages, branch rebuild, compaction)
644
- // yield fresh objects → cache miss → recompute. In-place
645
- // mutations on the same object are caught by fingerprint
646
- // mismatch. The LAST message is always recomputed because it
647
- // may still be growing during streaming.
648
- let messagesTokens = 0;
649
- const lastIdx = messages.length - 1;
650
- for (let i = 0; i < messages.length; i++) {
651
- messagesTokens += i === lastIdx ? estimateMessageTokensHeuristic(messages[i]) : tokensForMessage(messages[i]);
652
- }
653
-
654
- const usedTokens = this.#nonMessageTokensCache + messagesTokens;
655
- return { usedTokens, contextWindow };
656
- }
657
-
658
- /**
659
- * Build an identity fingerprint for the non-message inputs (system prompt,
660
- * tools, skills). When this changes, the non-message token cache must be
661
- * recomputed. Cheap: just lengths + first-string-length. Doesn't need to
662
- * be cryptographically unique — only stable for the same inputs.
663
- */
664
- #computeNonMessageInputsKey(): string {
665
- const sp = this.session.systemPrompt ?? [];
666
- const tools = this.session.agent?.state?.tools ?? [];
667
- const skills = this.session.skills ?? [];
668
- const modelId = this.session.model?.id ?? "";
669
- return `${modelId}|${sp.length}:${sp[0]?.length ?? 0}|${tools.length}|${skills.length}`;
670
- }
671
-
672
507
  #buildSegmentContext(
673
508
  width: number,
674
509
  effectiveSettings: Required<
@@ -695,11 +530,9 @@ export class StatusLineComponent implements Component {
695
530
  tokensPerSecond: this.#getTokensPerSecond(),
696
531
  };
697
532
 
698
- // Context usage — aligned with /context command so both surfaces report the same value
699
- const breakdown = this.getCachedContextBreakdown();
700
- const contextTokens = breakdown.usedTokens;
701
- const contextWindow = breakdown.contextWindow || state.model?.contextWindow || 0;
702
- const contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
533
+ const contextUsage = this.session.getContextUsage?.();
534
+ const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
535
+ const contextPercent = contextUsage?.percent ?? null;
703
536
  // Suppress the inline model percentage when a standalone context_pct
704
537
  // segment is also rendered, so the value is not shown twice.
705
538
  const contextPctSegmentActive =
@@ -8,6 +8,7 @@ import {
8
8
  Image,
9
9
  ImageProtocol,
10
10
  imageFallback,
11
+ isTerminalGraphicsFallbackActive,
11
12
  registerAnimationCallback,
12
13
  Spacer,
13
14
  TERMINAL,
@@ -33,7 +34,7 @@ import {
33
34
  import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
34
35
  import { toolRenderers } from "../../tools/renderers";
35
36
  import { renderStatusLine } from "../../tui";
36
- import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
37
+ import { containsSixelSequence, getSixelLineMask, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
37
38
  import { renderDiff } from "./diff";
38
39
 
39
40
  function ensureInvalidate(component: unknown): Component {
@@ -44,6 +45,23 @@ function ensureInvalidate(component: unknown): Component {
44
45
  return c as Component;
45
46
  }
46
47
 
48
+ const SIXEL_FALLBACK_PLACEHOLDER = "[SIXEL image hidden while IRC sidebar is visible]";
49
+
50
+ function replaceSixelOutputForGraphicsFallback(text: string): string {
51
+ const lines = text.split("\n");
52
+ const sixelLineMask = getSixelLineMask(lines);
53
+ if (!sixelLineMask.some(Boolean)) return text;
54
+
55
+ return lines
56
+ .flatMap((line, index) => {
57
+ if (!sixelLineMask[index]) return [line];
58
+ return index === 0 || !sixelLineMask[index - 1] || containsSixelSequence(line)
59
+ ? [SIXEL_FALLBACK_PLACEHOLDER]
60
+ : [];
61
+ })
62
+ .join("\n");
63
+ }
64
+
47
65
  function cloneToolArgs<T>(args: T): T {
48
66
  if (args === null || args === undefined) return args;
49
67
  try {
@@ -169,7 +187,14 @@ export class ToolExecutionComponent extends Container {
169
187
  isError?: boolean;
170
188
  details?: any;
171
189
  };
172
- #textOutputCache?: { content: unknown; showImages: boolean; terminalImageProtocol: unknown; output: string };
190
+ #textOutputCache?: {
191
+ content: unknown;
192
+ showImages: boolean;
193
+ terminalImageProtocol: unknown;
194
+ graphicsFallbackActive: boolean;
195
+ output: string;
196
+ };
197
+ #displayBuiltWithGraphicsFallback: boolean | undefined;
173
198
  // Edit preview state
174
199
  #editMode?: EditMode;
175
200
  #editDiffPreview?: PerFileDiffPreview[];
@@ -441,6 +466,13 @@ export class ToolExecutionComponent extends Container {
441
466
  this.#updateDisplay();
442
467
  }
443
468
 
469
+ override render(width: number): string[] {
470
+ if (this.#displayBuiltWithGraphicsFallback !== isTerminalGraphicsFallbackActive()) {
471
+ this.#updateDisplay();
472
+ }
473
+ return super.render(width);
474
+ }
475
+
444
476
  #updateDisplay(): void {
445
477
  // Set background based on state
446
478
  const bgFn = this.#isPartial
@@ -685,6 +717,7 @@ export class ToolExecutionComponent extends Container {
685
717
  }
686
718
  }
687
719
  }
720
+ this.#displayBuiltWithGraphicsFallback = isTerminalGraphicsFallbackActive();
688
721
  }
689
722
 
690
723
  #getCallArgsForRender(): any {
@@ -761,11 +794,13 @@ export class ToolExecutionComponent extends Container {
761
794
 
762
795
  const content = this.#result.content;
763
796
  const terminalImageProtocol = TERMINAL.imageProtocol;
797
+ const graphicsFallbackActive = isTerminalGraphicsFallbackActive();
764
798
  const cached = this.#textOutputCache;
765
799
  if (
766
800
  cached?.content === content &&
767
801
  cached.showImages === this.#showImages &&
768
- cached.terminalImageProtocol === terminalImageProtocol
802
+ cached.terminalImageProtocol === terminalImageProtocol &&
803
+ cached.graphicsFallbackActive === graphicsFallbackActive
769
804
  ) {
770
805
  return cached.output;
771
806
  }
@@ -773,8 +808,8 @@ export class ToolExecutionComponent extends Container {
773
808
  const textParts: string[] = [];
774
809
  for (const block of content ?? []) {
775
810
  if (block.type !== "text") continue;
776
- const text = block.text || "";
777
- textParts.push(sanitizeWithOptionalSixelPassthrough(text, sanitizeText));
811
+ const text = sanitizeWithOptionalSixelPassthrough(block.text || "", sanitizeText);
812
+ textParts.push(graphicsFallbackActive ? replaceSixelOutputForGraphicsFallback(text) : text);
778
813
  }
779
814
  let output = textParts.join("\n");
780
815
 
@@ -789,7 +824,13 @@ export class ToolExecutionComponent extends Container {
789
824
  output = output ? `${output}\n${imageIndicators}` : imageIndicators;
790
825
  }
791
826
 
792
- this.#textOutputCache = { content, showImages: this.#showImages, terminalImageProtocol, output };
827
+ this.#textOutputCache = {
828
+ content,
829
+ showImages: this.#showImages,
830
+ terminalImageProtocol,
831
+ graphicsFallbackActive,
832
+ output,
833
+ };
793
834
  return output;
794
835
  }
795
836
 
@@ -1,4 +1,12 @@
1
- import { type Component, Container, Markdown, Spacer, Text } from "@sayknow-cli/tui";
1
+ import {
2
+ type Component,
3
+ Container,
4
+ Markdown,
5
+ Spacer,
6
+ Text,
7
+ type ViewportAnchorRender,
8
+ type ViewportAnchorSource,
9
+ } from "@sayknow-cli/tui";
2
10
  import { getMarkdownTheme, theme } from "../../modes/theme/theme";
3
11
 
4
12
  // OSC 133 shell integration: marks prompt zones for terminal multiplexers
@@ -10,8 +18,11 @@ const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
10
18
  * Component that renders a user message
11
19
  */
12
20
  export class UserMessageComponent extends Container {
13
- constructor(text: string, synthetic = false) {
21
+ #viewportAnchorSource?: ViewportAnchorSource;
22
+
23
+ constructor(text: string, synthetic = false, viewportAnchorId?: string) {
14
24
  super();
25
+ if (viewportAnchorId) this.#viewportAnchorSource = { id: viewportAnchorId };
15
26
  const bgColor = (value: string) => theme.bg("userMessageBg", value);
16
27
  const color = synthetic
17
28
  ? (value: string) => theme.fg("dim", value)
@@ -19,7 +30,9 @@ export class UserMessageComponent extends Container {
19
30
  this.addChild(new Spacer(1));
20
31
  const label = synthetic ? "replay" : "user";
21
32
  this.addChild(new Text(theme.bold(theme.fg("accent", label)), 1, 0));
22
- this.addChild(new PromptZoneMarkdown(text, bgColor, color));
33
+ const prompt = new PromptZoneMarkdown(text, bgColor, color);
34
+ this.addChild(prompt);
35
+ if (this.#viewportAnchorSource) this.setViewportAnchorSource(prompt, this.#viewportAnchorSource);
23
36
  }
24
37
  }
25
38
 
@@ -37,15 +50,19 @@ class PromptZoneMarkdown implements Component {
37
50
  this.#markdown.invalidate();
38
51
  }
39
52
 
40
- render(width: number): string[] {
41
- const lines = this.#markdown.render(width);
42
- if (lines.length === 0) {
43
- return lines;
44
- }
45
-
53
+ #withPromptZone(lines: string[]): string[] {
54
+ if (lines.length === 0) return lines;
46
55
  const zoned = [...lines];
47
56
  zoned[0] = OSC133_ZONE_START + zoned[0];
48
57
  zoned[zoned.length - 1] = `${zoned[zoned.length - 1]}${OSC133_ZONE_END}${OSC133_ZONE_FINAL}`;
49
58
  return zoned;
50
59
  }
60
+
61
+ renderWithViewportAnchorSource(width: number, source: ViewportAnchorSource): ViewportAnchorRender {
62
+ const rendered = this.#markdown.renderWithViewportAnchorSource(width, source);
63
+ return { lines: this.#withPromptZone(rendered.lines), anchors: rendered.anchors };
64
+ }
65
+ render(width: number): string[] {
66
+ return this.#withPromptZone(this.#markdown.render(width));
67
+ }
51
68
  }
@@ -40,6 +40,7 @@ import { buildHotkeysMarkdown } from "../../modes/utils/hotkeys-markdown";
40
40
  import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
41
41
  import type { AsyncJobSnapshotItem } from "../../session/agent-session";
42
42
  import type { AuthStorage } from "../../session/auth-storage";
43
+ import { computeCacheMissCostSummary, formatCacheMissSummaryLines } from "../../session/cache-economics";
43
44
  import type { NewSessionOptions } from "../../session/session-manager";
44
45
  import { outputMeta } from "../../tools/output-meta";
45
46
  import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
@@ -48,6 +49,7 @@ import { getDisplayChangelogEntries } from "../../utils/changelog";
48
49
  import { copyToClipboard } from "../../utils/clipboard";
49
50
  import { openPath } from "../../utils/open";
50
51
  import { setSessionTerminalTitle } from "../../utils/title-generator";
52
+ import { prepareTranscriptRebuild } from "../utils/ui-helpers";
51
53
 
52
54
  function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
53
55
  ctx.chatContainer.addChild(new Spacer(1));
@@ -426,6 +428,24 @@ export class CommandController {
426
428
  info += `${theme.fg("dim", "Premium Requests:")} ${normalizedPremiumRequests.toLocaleString()}\n`;
427
429
  }
428
430
  }
431
+ const cacheMissSummary = stats.costBreakdown
432
+ ? computeCacheMissCostSummary(stats.tokens, {
433
+ kind: "persisted-aggregate",
434
+ costBreakdown: stats.costBreakdown,
435
+ })
436
+ : undefined;
437
+ if (cacheMissSummary) {
438
+ info += `\n${theme.bold("Cache Miss Cost")}`;
439
+ for (const line of formatCacheMissSummaryLines(cacheMissSummary)) {
440
+ const separator = line.indexOf(":");
441
+ if (separator === -1) {
442
+ info += `\n${line}`;
443
+ } else {
444
+ info += `\n${theme.fg("dim", `${line.slice(0, separator)}:`)}${line.slice(separator + 1)}`;
445
+ }
446
+ }
447
+ info += `\n`;
448
+ }
429
449
 
430
450
  if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
431
451
  info += `\n${theme.bold("LSP Servers")}\n`;
@@ -911,6 +931,8 @@ export class CommandController {
911
931
  }
912
932
  }
913
933
  if (!(await this.ctx.session.newSession(options))) return;
934
+ this.ctx.resetIrcSidebarSession();
935
+
914
936
  this.ctx.resetObserverRegistry();
915
937
  setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
916
938
 
@@ -920,6 +942,7 @@ export class CommandController {
920
942
  this.ctx.updateEditorBorderColor();
921
943
  this.ctx.ui.requestRender();
922
944
 
945
+ prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
923
946
  this.ctx.chatContainer.clear();
924
947
  this.ctx.pendingMessagesContainer.clear();
925
948
  this.ctx.compactionQueuedMessages = [];
@@ -958,6 +981,7 @@ export class CommandController {
958
981
  this.ctx.updateEditorBorderColor();
959
982
  this.ctx.ui.requestRender();
960
983
 
984
+ prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
961
985
  this.ctx.chatContainer.clear();
962
986
  this.ctx.pendingMessagesContainer.clear();
963
987
  this.ctx.compactionQueuedMessages = [];
@@ -996,6 +1020,7 @@ export class CommandController {
996
1020
  this.ctx.showError("Fork failed (session not persisted or cancelled)");
997
1021
  return;
998
1022
  }
1023
+ this.ctx.resetIrcSidebarSession();
999
1024
 
1000
1025
  this.ctx.statusLine.invalidate();
1001
1026
  this.ctx.updateEditorTopBorder();
@@ -1219,7 +1244,7 @@ export class CommandController {
1219
1244
  : undefined;
1220
1245
  await this.ctx.session.compact(instructions, options);
1221
1246
 
1222
- this.ctx.rebuildChatFromMessages();
1247
+ this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
1223
1248
 
1224
1249
  this.ctx.statusLine.invalidate();
1225
1250
  this.ctx.updateEditorTopBorder();
@@ -1279,9 +1304,10 @@ export class CommandController {
1279
1304
  this.ctx.showError("Handoff cancelled");
1280
1305
  return;
1281
1306
  }
1307
+ this.ctx.resetIrcSidebarSession();
1282
1308
 
1283
1309
  // Rebuild chat from the new session (which now contains the handoff document)
1284
- this.ctx.rebuildChatFromMessages();
1310
+ this.ctx.rebuildChatFromMessages("replace-identity");
1285
1311
 
1286
1312
  this.ctx.statusLine.invalidate();
1287
1313
  this.ctx.updateEditorTopBorder();