@gajae-code/coding-agent 0.11.10 → 0.12.0

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 (249) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/dist/types/cli/read-cli.d.ts +1 -0
  3. package/dist/types/cli.d.ts +8 -1
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-profile-activation.d.ts +1 -0
  8. package/dist/types/config/model-profile-contract.d.ts +2 -0
  9. package/dist/types/config/model-registry.d.ts +4 -0
  10. package/dist/types/config/provider-auth-health.d.ts +14 -0
  11. package/dist/types/config/provider-ranking.d.ts +59 -0
  12. package/dist/types/config/settings-schema.d.ts +86 -3
  13. package/dist/types/config/settings.d.ts +3 -0
  14. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  15. package/dist/types/extensibility/shared-events.d.ts +6 -0
  16. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  17. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  18. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  19. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  20. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  21. package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
  22. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  23. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
  24. package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
  25. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  26. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  28. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  29. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  30. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
  31. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  32. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  33. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  34. package/dist/types/main.d.ts +1 -1
  35. package/dist/types/memories/index.d.ts +2 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  37. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  38. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  39. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  40. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  41. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  42. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  43. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  44. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  45. package/dist/types/modes/components/welcome.d.ts +1 -0
  46. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  47. package/dist/types/modes/interactive-mode.d.ts +7 -1
  48. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  49. package/dist/types/modes/types.d.ts +2 -0
  50. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  51. package/dist/types/runtime/memory-domain.d.ts +9 -0
  52. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  53. package/dist/types/runtime/memory-guard.d.ts +39 -0
  54. package/dist/types/runtime/memory-limit.d.ts +11 -0
  55. package/dist/types/runtime-mcp/manager.d.ts +8 -0
  56. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  57. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  58. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  59. package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
  60. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  61. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  62. package/dist/types/sdk/bus/index.d.ts +18 -2
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  65. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  66. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
  69. package/dist/types/sdk/host/control/operations.d.ts +2 -1
  70. package/dist/types/sdk/host/host.d.ts +2 -0
  71. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  72. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  73. package/dist/types/sdk/startup-capability.d.ts +6 -0
  74. package/dist/types/session/agent-session.d.ts +51 -19
  75. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  76. package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
  77. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  78. package/dist/types/session/session-manager.d.ts +59 -13
  79. package/dist/types/session/streaming-output.d.ts +60 -5
  80. package/dist/types/skill-state/active-state.d.ts +0 -4
  81. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  82. package/dist/types/task/executor.d.ts +6 -0
  83. package/dist/types/task/types.d.ts +36 -0
  84. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  85. package/dist/types/tools/browser/launch.d.ts +7 -0
  86. package/dist/types/tools/fetch.d.ts +17 -1
  87. package/dist/types/tools/output-meta.d.ts +21 -1
  88. package/dist/types/tools/read-internals.d.ts +12 -0
  89. package/dist/types/tools/read.d.ts +45 -1
  90. package/dist/types/tools/resource-gc.d.ts +23 -0
  91. package/dist/types/tools/tool-result.d.ts +7 -1
  92. package/package.json +9 -13
  93. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  94. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  95. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  96. package/scripts/run-test-manifest.ts +6 -4
  97. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  98. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  99. package/src/capability/index.ts +8 -0
  100. package/src/cli/args.ts +14 -8
  101. package/src/cli/fast-help.ts +1 -1
  102. package/src/cli/read-cli.ts +2 -1
  103. package/src/cli.ts +49 -2
  104. package/src/commands/deep-interview.ts +29 -9
  105. package/src/commands/harness.ts +35 -12
  106. package/src/commands/read.ts +11 -2
  107. package/src/commands/sdk.ts +91 -7
  108. package/src/commands/team.ts +1 -1
  109. package/src/config/model-profile-activation.ts +1 -0
  110. package/src/config/model-profile-contract.ts +13 -0
  111. package/src/config/model-profiles.ts +24 -0
  112. package/src/config/model-registry.ts +21 -2
  113. package/src/config/provider-auth-health.ts +42 -0
  114. package/src/config/provider-ranking.ts +119 -0
  115. package/src/config/settings-schema.ts +84 -3
  116. package/src/config/settings.ts +254 -108
  117. package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
  118. package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
  119. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  120. package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
  121. package/src/eval/py/executor.ts +7 -2
  122. package/src/extensibility/extensions/runner.ts +4 -0
  123. package/src/extensibility/extensions/types.ts +42 -4
  124. package/src/extensibility/shared-events.ts +5 -0
  125. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
  126. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  127. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  128. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  129. package/src/gjc-runtime/deep-interview-state.ts +12 -921
  130. package/src/gjc-runtime/gc-render.ts +1 -0
  131. package/src/gjc-runtime/gc-runtime.ts +11 -1
  132. package/src/gjc-runtime/linux-proc.ts +70 -36
  133. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  134. package/src/gjc-runtime/ralplan-runtime.ts +979 -71
  135. package/src/gjc-runtime/state-runtime.ts +50 -12
  136. package/src/gjc-runtime/state-writer.ts +70 -222
  137. package/src/gjc-runtime/team-launch.ts +34 -5
  138. package/src/gjc-runtime/team-runtime.ts +970 -33
  139. package/src/gjc-runtime/team-store.ts +32 -0
  140. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  141. package/src/gjc-runtime/team-workers.ts +1 -0
  142. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
  143. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  144. package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
  145. package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
  146. package/src/gjc-runtime/workflow-manifest.ts +34 -179
  147. package/src/hooks/native-skill-hook.ts +17 -4
  148. package/src/hooks/skill-state.ts +1 -23
  149. package/src/internal-urls/docs-index.generated.ts +10 -11
  150. package/src/internal-urls/local-protocol.ts +23 -4
  151. package/src/internal-urls/local-root-gc.ts +127 -0
  152. package/src/main.ts +9 -5
  153. package/src/memories/index.ts +10 -0
  154. package/src/modes/DESIGN.md +47 -0
  155. package/src/modes/acp/acp-agent.ts +597 -67
  156. package/src/modes/acp/acp-event-mapper.ts +106 -12
  157. package/src/modes/acp/acp-mode.ts +39 -3
  158. package/src/modes/components/assistant-message.ts +123 -22
  159. package/src/modes/components/irc-sidebar.ts +176 -62
  160. package/src/modes/components/model-selector.ts +76 -5
  161. package/src/modes/components/notifications-settings-editor.ts +1 -1
  162. package/src/modes/components/oauth-selector.ts +42 -12
  163. package/src/modes/components/queue-pane.ts +55 -9
  164. package/src/modes/components/queued-message-selector.ts +53 -10
  165. package/src/modes/components/read-tool-group.ts +82 -14
  166. package/src/modes/components/session-observer-overlay.ts +187 -32
  167. package/src/modes/components/settings-selector.ts +60 -18
  168. package/src/modes/components/tool-execution.ts +138 -27
  169. package/src/modes/components/tool-status-header.ts +1 -4
  170. package/src/modes/components/welcome.ts +18 -3
  171. package/src/modes/controllers/command-controller.ts +4 -0
  172. package/src/modes/controllers/event-controller.ts +109 -27
  173. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  174. package/src/modes/controllers/input-controller.ts +62 -6
  175. package/src/modes/controllers/selector-controller.ts +83 -37
  176. package/src/modes/interactive-mode.ts +127 -29
  177. package/src/modes/irc-observation-ledger.ts +71 -15
  178. package/src/modes/runtime-init.ts +20 -2
  179. package/src/modes/types.ts +2 -0
  180. package/src/modes/utils/ui-helpers.ts +94 -1
  181. package/src/prompts/agents/architect.md +9 -1
  182. package/src/prompts/agents/critic.md +9 -0
  183. package/src/prompts/agents/executor.md +1 -1
  184. package/src/prompts/system/system-prompt.md +0 -1
  185. package/src/prompts/tools/read.md +4 -2
  186. package/src/runtime/memory-domain.ts +66 -0
  187. package/src/runtime/memory-guard-contract.ts +73 -0
  188. package/src/runtime/memory-guard.ts +292 -0
  189. package/src/runtime/memory-limit.ts +50 -0
  190. package/src/runtime-mcp/manager.ts +26 -6
  191. package/src/runtime-mcp/smithery-auth.ts +17 -2
  192. package/src/runtime-mcp/smithery-connect.ts +11 -1
  193. package/src/sdk/acp/adapter.ts +8 -1
  194. package/src/sdk/acp/final-text.ts +40 -0
  195. package/src/sdk/acp/mcp.ts +26 -0
  196. package/src/sdk/broker/lifecycle.ts +107 -7
  197. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  198. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  199. package/src/sdk/bus/config.ts +1 -1
  200. package/src/sdk/bus/control-drain-lease.ts +104 -0
  201. package/src/sdk/bus/index.ts +503 -60
  202. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  203. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  204. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  205. package/src/sdk/bus/reconciliation-store.ts +224 -0
  206. package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
  207. package/src/sdk/bus/telegram-daemon.ts +531 -53
  208. package/src/sdk/host/control/dispatch.ts +7 -1
  209. package/src/sdk/host/control/operations.ts +2 -1
  210. package/src/sdk/host/host.ts +25 -3
  211. package/src/sdk/host/query/handlers.ts +55 -0
  212. package/src/sdk/lifecycle-session.ts +19 -3
  213. package/src/sdk/mcp/server.ts +46 -18
  214. package/src/sdk/protocol/operation-inventory.generated.json +76 -0
  215. package/src/sdk/protocol/operation-registry.ts +8 -2
  216. package/src/sdk/session.ts +25 -2
  217. package/src/sdk/startup-capability.ts +6 -0
  218. package/src/session/agent-session.ts +720 -190
  219. package/src/session/fallback-chain-controller.ts +47 -8
  220. package/src/session/internal/managed-session-scope.ts +40 -10
  221. package/src/session/internal/native-publish-outcome.ts +8 -3
  222. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  223. package/src/session/session-manager.ts +1372 -42
  224. package/src/session/streaming-output.ts +262 -43
  225. package/src/setup/provider-onboarding.ts +19 -4
  226. package/src/skill-state/active-state.ts +0 -10
  227. package/src/skill-state/workflow-hud.ts +25 -2
  228. package/src/skill-state/workflow-mutation-guard.ts +65 -7
  229. package/src/task/executor.ts +11 -3
  230. package/src/task/index.ts +2 -0
  231. package/src/task/types.ts +22 -11
  232. package/src/task/ultragoal-redteam-activation.ts +75 -0
  233. package/src/tools/ask.ts +89 -69
  234. package/src/tools/browser/launch.ts +41 -6
  235. package/src/tools/fetch.ts +114 -23
  236. package/src/tools/output-meta.ts +176 -9
  237. package/src/tools/path-utils.ts +5 -1
  238. package/src/tools/read-internals.ts +44 -0
  239. package/src/tools/read.ts +1542 -310
  240. package/src/tools/resource-gc.ts +665 -125
  241. package/src/tools/tool-result.ts +14 -1
  242. package/src/web/search/providers/codex.ts +1 -1
  243. package/src/workflow/workflow-intent-diff.ts +4 -1
  244. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  245. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  246. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  247. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  248. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  249. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
@@ -12,7 +12,7 @@ import {
12
12
  readArgsTargetInternalUrl,
13
13
  } from "../../modes/components/read-tool-group";
14
14
  import { TodoReminderComponent } from "../../modes/components/todo-reminder";
15
- import { ToolExecutionComponent } from "../../modes/components/tool-execution";
15
+ import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/components/tool-execution";
16
16
  import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
17
17
  import { getSymbolTheme, theme } from "../../modes/theme/theme";
18
18
  import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
@@ -23,6 +23,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
23
23
  import { type CustomMessage, isSilentAbort, readPendingDisplayTag } from "../../session/messages";
24
24
  import { transferSessionMessageIdentity } from "../../session/session-manager";
25
25
  import type { ResolveToolDetails } from "../../tools/resolve";
26
+ import { computeIrcSplitWidths, getIrcSidebarSemanticToken } from "../components/irc-sidebar";
26
27
  import type { IrcObservationRecord } from "../irc-observation-ledger";
27
28
  import { interruptHint } from "../shared";
28
29
  import { buildAbortDisplayMessage } from "../utils/abort-message";
@@ -36,7 +37,14 @@ type AgentSessionEventKind = AgentSessionEvent["type"];
36
37
 
37
38
  /** Test-only performance counters for advisory baseline tests. */
38
39
  export const __eventControllerPerfCounters = {
40
+ enabled: false,
39
41
  messageUpdateContentVisits: 0,
42
+ enable(): void {
43
+ this.enabled = true;
44
+ },
45
+ disable(): void {
46
+ this.enabled = false;
47
+ },
40
48
  reset(): void {
41
49
  this.messageUpdateContentVisits = 0;
42
50
  },
@@ -118,6 +126,9 @@ export class EventController {
118
126
  #toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
119
127
  #thinkingContentIndices = new Set<number>();
120
128
  #handlers: AgentSessionEventHandlers;
129
+ #visibleTranscriptChanged = false;
130
+ #handlingEvent = false;
131
+ #eventQueue: Promise<void> = Promise.resolve();
121
132
 
122
133
  constructor(private ctx: InteractiveModeContext) {
123
134
  this.#handlers = {
@@ -162,6 +173,26 @@ export class EventController {
162
173
  this.clearIrcExpiryTimers();
163
174
  }
164
175
 
176
+ #recordVisibleTranscriptMutation(): void {
177
+ this.#visibleTranscriptChanged = true;
178
+ }
179
+
180
+ #observeVisibleTranscriptMutation(): void {
181
+ if (this.#handlingEvent) {
182
+ this.#recordVisibleTranscriptMutation();
183
+ return;
184
+ }
185
+ this.ctx.recordVisibleTranscriptMutation?.();
186
+ }
187
+
188
+ #consumeToolVisibleChange(handle: ToolExecutionHandle): void {
189
+ // Structural third-party renderers may not implement the optional exact-consume
190
+ // capability; preserve revision correctness conservatively until they opt in.
191
+ if (typeof handle.consumeVisibleTranscriptChange !== "function" || handle.consumeVisibleTranscriptChange()) {
192
+ this.#recordVisibleTranscriptMutation();
193
+ }
194
+ }
195
+
165
196
  #resetReadGroup(): void {
166
197
  this.#lastReadGroup = undefined;
167
198
  }
@@ -228,17 +259,26 @@ export class EventController {
228
259
  });
229
260
  }
230
261
 
231
- async handleEvent(event: AgentSessionEvent): Promise<void> {
232
- if (!this.ctx.isInitialized) {
233
- await this.ctx.init();
234
- }
235
-
236
- this.ctx.statusLine.invalidate();
237
- this.ctx.updateEditorTopBorder();
262
+ handleEvent(event: AgentSessionEvent): Promise<void> {
263
+ const dispatch = this.#eventQueue.then(() => this.#dispatchEvent(event));
264
+ this.#eventQueue = dispatch.catch(() => {});
265
+ return dispatch;
266
+ }
238
267
 
239
- const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
240
- await run(event);
241
- if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
268
+ async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
269
+ if (!this.ctx.isInitialized) await this.ctx.init();
270
+ this.#visibleTranscriptChanged = false;
271
+ this.#handlingEvent = true;
272
+ try {
273
+ this.ctx.statusLine.invalidate();
274
+ this.ctx.updateEditorTopBorder();
275
+ const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
276
+ await run(event);
277
+ if (this.#visibleTranscriptChanged) this.ctx.recordVisibleTranscriptMutation?.();
278
+ if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
279
+ } finally {
280
+ this.#handlingEvent = false;
281
+ }
242
282
  }
243
283
 
244
284
  async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
@@ -268,8 +308,12 @@ export class EventController {
268
308
  const parsed = parseIrcMessage(message);
269
309
  if (!parsed) return;
270
310
  const arrival = this.ctx.captureIrcArrivalSnapshot();
311
+ const rightWidth = arrival.panelVisible
312
+ ? computeIrcSplitWidths(this.ctx.ui.terminal?.columns ?? 0).rightWidth
313
+ : 0;
314
+ const beforeToken = rightWidth > 0 ? getIrcSidebarSemanticToken(this.ctx.ircLedger, rightWidth) : "";
271
315
  const record = this.ctx.ircLedger.observe(parsed, arrival.panelVisible);
272
- this.#cleanupEvictedIrcObservations();
316
+ const evictedInlineRemoved = this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
273
317
  if (!record) return;
274
318
  const signature = `irc:${record.observationId}`;
275
319
  if (this.#renderedCustomMessages.has(signature)) return;
@@ -279,9 +323,13 @@ export class EventController {
279
323
  this.#renderedIrcComponents.set(record.observationId, components);
280
324
  this.#scheduleIrcExpiry(record, components);
281
325
  this.ctx.ui.requestRender();
326
+ const afterToken = rightWidth > 0 ? getIrcSidebarSemanticToken(this.ctx.ircLedger, rightWidth) : "";
327
+ if (components.length > 0 || evictedInlineRemoved || (rightWidth > 0 && beforeToken !== afterToken)) {
328
+ this.#recordVisibleTranscriptMutation();
329
+ }
282
330
  }
283
331
 
284
- #cleanupIrcObservationIds(observationIds: readonly string[]): void {
332
+ #cleanupIrcObservationIds(observationIds: readonly string[]): boolean {
285
333
  const removedComponents = new Set<Component>();
286
334
  for (const observationId of observationIds) {
287
335
  const timer = this.#ircExpiryTimers.get(observationId);
@@ -294,23 +342,26 @@ export class EventController {
294
342
  for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
295
343
  removedComponents.add(component);
296
344
  }
297
-
298
345
  this.#renderedCustomMessages.delete(`irc:${observationId}`);
299
346
  }
300
347
  for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
348
+ return removedComponents.size > 0;
301
349
  }
302
350
 
303
351
  #cleanupEvictedIrcObservations(): void {
304
- this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
352
+ if (this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds())) {
353
+ this.#recordVisibleTranscriptMutation();
354
+ }
305
355
  }
306
356
 
307
- #cleanupExpiredIrcInlineComponents(observationId: string): void {
357
+ #cleanupExpiredIrcInlineComponents(observationId: string): boolean {
308
358
  const removedComponents = new Set<Component>(this.#renderedIrcComponents.get(observationId));
309
359
  this.#renderedIrcComponents.delete(observationId);
310
360
  for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
311
361
  removedComponents.add(component);
312
362
  }
313
363
  for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
364
+ return removedComponents.size > 0;
314
365
  }
315
366
 
316
367
  async #handleMessageStart(event: Extract<AgentSessionEvent, { type: "message_start" }>): Promise<void> {
@@ -326,6 +377,7 @@ export class EventController {
326
377
  this.#renderedCustomMessages.add(signature);
327
378
  this.#resetReadGroup();
328
379
  this.ctx.addMessageToChat(event.message);
380
+ if (event.message.display) this.#recordVisibleTranscriptMutation();
329
381
  // Tag-keyed pending-bar refresh: when AgentSession.#handleAgentEvent
330
382
  // spliced this dequeued custom message out of #steeringMessages /
331
383
  // #followUpMessages (it ran before this emit), the array state is
@@ -373,10 +425,12 @@ export class EventController {
373
425
  this.ctx.updatePendingMessagesDisplay();
374
426
  }
375
427
  this.ctx.ui.requestRender();
428
+ if (!wasOptimistic) this.#recordVisibleTranscriptMutation();
376
429
  } else if (event.message.role === "fileMention") {
377
430
  this.#resetReadGroup();
378
431
  this.ctx.addMessageToChat(event.message);
379
432
  this.ctx.ui.requestRender();
433
+ this.#recordVisibleTranscriptMutation();
380
434
  } else if (event.message.role === "assistant") {
381
435
  this.#lastThinkingCount = 0;
382
436
  this.#resetReadGroup();
@@ -387,6 +441,7 @@ export class EventController {
387
441
  this.ctx.hideThinkingBlock,
388
442
  () => this.ctx.ui.requestRender(),
389
443
  this.ctx.getAssistantViewportAnchorId?.(event.message),
444
+ () => this.#observeVisibleTranscriptMutation(),
390
445
  );
391
446
  this.ctx.streamingMessage = event.message;
392
447
  addChatChild(this.ctx, this.ctx.streamingComponent);
@@ -429,15 +484,10 @@ export class EventController {
429
484
  this.#cleanupEvictedIrcObservations();
430
485
  const projectedObservationIds = new Set(inlineProjection.map(record => record.observationId));
431
486
  for (const [observationId, components] of componentsByObservationId) {
432
- const record = this.ctx.ircLedger.getRecord(observationId);
433
- if (
434
- !projectedObservationIds.has(observationId) ||
435
- (record?.mode === "ephemeral" && record.expiresAt! - now <= 0)
436
- ) {
487
+ if (!projectedObservationIds.has(observationId)) {
437
488
  for (const component of components) {
438
489
  this.ctx.chatContainer.removeChild(component);
439
490
  }
440
- this.#renderedIrcComponents.delete(observationId);
441
491
  componentsByObservationId.delete(observationId);
442
492
  }
443
493
  }
@@ -468,12 +518,12 @@ export class EventController {
468
518
  }
469
519
  const remainingMs = record.expiresAt! - Date.now();
470
520
  if (remainingMs <= 0) {
471
- this.#cleanupExpiredIrcInlineComponents(record.observationId);
521
+ if (this.#cleanupExpiredIrcInlineComponents(record.observationId)) this.#observeVisibleTranscriptMutation();
472
522
  return false;
473
523
  }
474
524
  const timer = setTimeout(() => {
475
525
  this.#ircExpiryTimers.delete(record.observationId);
476
- this.#cleanupExpiredIrcInlineComponents(record.observationId);
526
+ if (this.#cleanupExpiredIrcInlineComponents(record.observationId)) this.#observeVisibleTranscriptMutation();
477
527
  this.ctx.ui.requestRender();
478
528
  }, remainingMs);
479
529
  timer.unref?.();
@@ -496,6 +546,7 @@ export class EventController {
496
546
  this.#resetReadGroup();
497
547
  this.ctx.addMessageToChat(event.message);
498
548
  this.ctx.ui.requestRender();
549
+ this.#recordVisibleTranscriptMutation();
499
550
  }
500
551
 
501
552
  async #handleModelFallbackSwitched(
@@ -549,19 +600,24 @@ export class EventController {
549
600
  }
550
601
 
551
602
  for (const content of contentsToProcess) {
552
- __eventControllerPerfCounters.messageUpdateContentVisits += 1;
603
+ if (__eventControllerPerfCounters.enabled) __eventControllerPerfCounters.messageUpdateContentVisits += 1;
553
604
  if (content.type !== "toolCall") continue;
554
605
  if (content.name === "read") {
555
606
  if (!readArgsHaveTarget(content.arguments)) continue;
556
607
  if (!readArgsTargetInternalUrl(content.arguments)) {
557
608
  this.#trackReadToolCall(content.id, content.arguments);
558
609
  const component = this.ctx.pendingTools.get(content.id);
559
- if (component) component.updateArgs(content.arguments, content.id);
560
- else {
610
+ if (component) {
611
+ component.updateArgs(content.arguments, content.id);
612
+ this.#consumeToolVisibleChange(component);
613
+ } else {
561
614
  const group = this.#getReadGroup();
562
615
  group.updateArgs(content.arguments, content.id);
563
616
  this.ctx.pendingTools.set(content.id, group);
617
+ this.#recordVisibleTranscriptMutation();
618
+ group.consumeVisibleTranscriptChange();
564
619
  }
620
+
565
621
  continue;
566
622
  }
567
623
  }
@@ -582,6 +638,7 @@ export class EventController {
582
638
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
583
639
  editAllowFuzzy: settings.get("edit.fuzzyMatch"),
584
640
  hashlineAutoDropPureInsertDuplicates: settings.get("edit.hashlineAutoDropPureInsertDuplicates"),
641
+ onVisibleTranscriptMutation: () => this.#observeVisibleTranscriptMutation(),
585
642
  },
586
643
  tool,
587
644
  this.ctx.ui,
@@ -591,8 +648,10 @@ export class EventController {
591
648
  component.setExpanded(this.ctx.toolOutputExpanded);
592
649
  this.ctx.pendingTools.set(content.id, component);
593
650
  addChatChild(this.ctx, component);
651
+ this.#recordVisibleTranscriptMutation();
594
652
  } else {
595
653
  this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
654
+ this.#consumeToolVisibleChange(this.ctx.pendingTools.get(content.id)!);
596
655
  }
597
656
 
598
657
  const args = content.arguments;
@@ -659,6 +718,7 @@ export class EventController {
659
718
  if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
660
719
  for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
661
720
  component.setArgsComplete(toolCallId);
721
+ this.#consumeToolVisibleChange(component);
662
722
  }
663
723
  }
664
724
  this.#lastAssistantComponent = this.ctx.streamingComponent;
@@ -685,6 +745,8 @@ export class EventController {
685
745
  this.ctx.pendingTools.set(event.toolCallId, group);
686
746
  }
687
747
  this.ctx.ui.requestRender();
748
+ this.#consumeToolVisibleChange(this.ctx.pendingTools.get(event.toolCallId)!);
749
+
688
750
  return;
689
751
  }
690
752
 
@@ -698,6 +760,7 @@ export class EventController {
698
760
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
699
761
  editAllowFuzzy: settings.get("edit.fuzzyMatch"),
700
762
  hashlineAutoDropPureInsertDuplicates: settings.get("edit.hashlineAutoDropPureInsertDuplicates"),
763
+ onVisibleTranscriptMutation: () => this.#observeVisibleTranscriptMutation(),
701
764
  },
702
765
  tool,
703
766
  this.ctx.ui,
@@ -707,6 +770,8 @@ export class EventController {
707
770
  component.setExpanded(this.ctx.toolOutputExpanded);
708
771
  this.ctx.pendingTools.set(event.toolCallId, component);
709
772
  addChatChild(this.ctx, component);
773
+ this.#recordVisibleTranscriptMutation();
774
+
710
775
  this.ctx.ui.requestRender();
711
776
  }
712
777
  }
@@ -723,6 +788,8 @@ export class EventController {
723
788
  !isFinalAsyncState,
724
789
  event.toolCallId,
725
790
  );
791
+ this.#consumeToolVisibleChange(component);
792
+
726
793
  if (isFinalAsyncState) {
727
794
  this.ctx.pendingTools.delete(event.toolCallId);
728
795
  this.#backgroundToolCallIds.delete(event.toolCallId);
@@ -737,6 +804,8 @@ export class EventController {
737
804
  const component = this.ctx.pendingTools.get(event.toolCallId);
738
805
  if (component) {
739
806
  component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
807
+ this.#consumeToolVisibleChange(component);
808
+
740
809
  this.ctx.pendingTools.delete(event.toolCallId);
741
810
  }
742
811
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
@@ -761,6 +830,8 @@ export class EventController {
761
830
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
762
831
  const isBackgroundRunning = asyncState === "running";
763
832
  component.updateResult({ ...event.result, isError: event.isError }, isBackgroundRunning, event.toolCallId);
833
+ this.#consumeToolVisibleChange(component);
834
+
764
835
  if (isBackgroundRunning) {
765
836
  this.#backgroundToolCallIds.add(event.toolCallId);
766
837
  } else {
@@ -776,6 +847,8 @@ export class EventController {
776
847
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
777
848
  const isBackgroundRunning = asyncState === "running";
778
849
  component.updateResult({ ...event.result, isError: event.isError }, isBackgroundRunning, event.toolCallId);
850
+ this.#consumeToolVisibleChange(component);
851
+
779
852
  if (isBackgroundRunning) {
780
853
  this.#backgroundToolCallIds.add(event.toolCallId);
781
854
  } else {
@@ -893,6 +966,12 @@ export class EventController {
893
966
  } else if (event.willRetry && !isHandoffAction) {
894
967
  this.ctx.showStatus("Context overflow maintenance completed");
895
968
  }
969
+ } else if (
970
+ event.skipped &&
971
+ event.errorMessage?.startsWith("Context overflow recovery skipped: nothing eligible to compact.") &&
972
+ !isHandoffAction
973
+ ) {
974
+ this.ctx.showStatus(event.errorMessage);
896
975
  } else if (event.errorMessage) {
897
976
  this.ctx.showWarning(event.errorMessage);
898
977
  } else if (isHandoffAction) {
@@ -999,12 +1078,15 @@ export class EventController {
999
1078
  const component = new TtsrNotificationComponent(event.rules);
1000
1079
  component.setExpanded(this.ctx.toolOutputExpanded);
1001
1080
  addChatChild(this.ctx, component);
1081
+ this.#recordVisibleTranscriptMutation();
1082
+
1002
1083
  this.ctx.ui.requestRender();
1003
1084
  }
1004
1085
 
1005
1086
  async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
1006
1087
  const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
1007
1088
  addChatChild(this.ctx, component);
1089
+ this.#recordVisibleTranscriptMutation();
1008
1090
  this.ctx.ui.requestRender();
1009
1091
  }
1010
1092
 
@@ -281,7 +281,24 @@ export class ExtensionUiController {
281
281
  const disabled = [...(session.settings.get("disabledExtensions") ?? [])];
282
282
  const on = input.on === true;
283
283
  const next = on ? disabled.filter(value => value !== id) : [...new Set([...disabled, id])];
284
- session.settings.set("disabledExtensions", next);
284
+ if (!session.settings.canWriteDurableConfig()) {
285
+ throw Object.assign(
286
+ new Error(
287
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
288
+ ),
289
+ { code: "invalid_request" },
290
+ );
291
+ }
292
+ try {
293
+ session.settings.set("disabledExtensions", next);
294
+ } catch (error) {
295
+ if (!session.settings.canWriteDurableConfig()) {
296
+ throw Object.assign(new Error(error instanceof Error ? error.message : String(error)), {
297
+ code: "invalid_request",
298
+ });
299
+ }
300
+ throw error;
301
+ }
285
302
  return { changed: true, enabled: on };
286
303
  }
287
304
  case "session.delete":
@@ -425,6 +442,7 @@ export class ExtensionUiController {
425
442
  getSystemPrompt: () => this.ctx.session.systemPrompt,
426
443
  clearContext: () => this.ctx.session.clearContext(),
427
444
  cycleModel: () => this.ctx.session.cycleModel(),
445
+ setModelProfile: name => this.ctx.session.activateModelProfileForControl(name),
428
446
  cycleThinkingLevel: () => this.ctx.session.cycleThinkingLevel(),
429
447
  setQueueMode: (kind, mode) => {
430
448
  if (kind === "steering" && (mode === "all" || mode === "one-at-a-time")) {
@@ -441,16 +459,12 @@ export class ExtensionUiController {
441
459
  }
442
460
  return false;
443
461
  },
444
- invokeSkill: (name, args) => this.ctx.session.invokeSkill(name, args),
462
+ invokeSkill: (name, args, options) => this.ctx.session.invokeSkill(name, args, options),
445
463
  setPlanMode: on => this.ctx.session.setSdkPlanMode(on),
446
464
  operateGoal: (op, objective) => this.ctx.session.operateGoal(op, objective),
447
465
  getSkillState: () =>
448
466
  this.ctx.session.skills.map(skill => ({ name: skill.name, description: skill.description })),
449
- getConfigItems: () => ({
450
- steeringMode: this.ctx.session.steeringMode,
451
- followUpMode: this.ctx.session.followUpMode,
452
- interruptMode: this.ctx.session.interruptMode,
453
- }),
467
+ getConfigItems: () => this.ctx.session.getSdkConfigItems(),
454
468
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
455
469
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
456
470
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
@@ -727,6 +741,7 @@ export class ExtensionUiController {
727
741
  getSystemPrompt: () => this.ctx.session.systemPrompt,
728
742
  clearContext: () => this.ctx.session.clearContext(),
729
743
  cycleModel: () => this.ctx.session.cycleModel(),
744
+ setModelProfile: name => this.ctx.session.activateModelProfileForControl(name),
730
745
  cycleThinkingLevel: () => this.ctx.session.cycleThinkingLevel(),
731
746
  setQueueMode: (kind, mode) => {
732
747
  if (kind === "steering" && (mode === "all" || mode === "one-at-a-time")) {
@@ -745,11 +760,7 @@ export class ExtensionUiController {
745
760
  },
746
761
  getSkillState: () =>
747
762
  this.ctx.session.skills.map(skill => ({ name: skill.name, description: skill.description })),
748
- getConfigItems: () => ({
749
- steeringMode: this.ctx.session.steeringMode,
750
- followUpMode: this.ctx.session.followUpMode,
751
- interruptMode: this.ctx.session.interruptMode,
752
- }),
763
+ getConfigItems: () => this.ctx.session.getSdkConfigItems(),
753
764
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
754
765
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
755
766
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
@@ -33,6 +33,13 @@ import type { PasteTextContext } from "../components/custom-editor";
33
33
  import { QueuePaneComponent } from "../components/queue-pane";
34
34
  import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
35
35
 
36
+ const QUEUE_SELECTOR_NAVIGATION_ACTIONS = [
37
+ "tui.select.up",
38
+ "tui.select.down",
39
+ "tui.select.pageUp",
40
+ "tui.select.pageDown",
41
+ ] as const;
42
+
36
43
  interface Expandable {
37
44
  setExpanded(expanded: boolean): void;
38
45
  setManuallyExpanded?(expanded: boolean): void;
@@ -151,15 +158,24 @@ export class InputController {
151
158
  case "app.editor.external":
152
159
  return Boolean(getEditorCommand());
153
160
  case "app.message.followUp":
154
- case "app.message.queue":
155
161
  return (
156
162
  this.ctx.session.isStreaming ||
157
163
  this.ctx.session.isCompacting ||
158
164
  this.ctx.session.isBashRunning ||
159
165
  this.ctx.session.isEvalRunning
160
166
  );
167
+ case "app.message.queue":
168
+ return (
169
+ this.ctx.editor.getText().trim().length > 0 &&
170
+ (this.ctx.session.isStreaming ||
171
+ this.ctx.session.isCompacting ||
172
+ this.ctx.session.isBashRunning ||
173
+ this.ctx.session.isEvalRunning)
174
+ );
161
175
  case "app.message.dequeue":
162
- return this.ctx.session.queuedMessageCount > 0;
176
+ return (
177
+ this.ctx.session.getQueuedMessageEntries().length > 0 || this.ctx.compactionQueuedMessages.length > 0
178
+ );
163
179
  case "app.clipboard.copyPrompt":
164
180
  return this.ctx.editor.getText().length > 0;
165
181
  case "app.session.tree":
@@ -1037,6 +1053,21 @@ export class InputController {
1037
1053
  };
1038
1054
  const pane = new QueuePaneComponent(entries, {
1039
1055
  selectedIndex,
1056
+ formatKeyHint: key => this.ctx.keybindings.formatKeyHint(key),
1057
+ formatSelectAction: action => this.ctx.keybindings.getDisplayString(action),
1058
+ matchesSelectAction: (keyData, action) =>
1059
+ this.ctx.keybindings.getKeys(action).some(key => matchesKey(keyData, key)),
1060
+ resolveSelectNavigation: keyData =>
1061
+ QUEUE_SELECTOR_NAVIGATION_ACTIONS.find(action =>
1062
+ this.ctx.keybindings.getKeys(action).some(key => matchesKey(keyData, key)),
1063
+ ),
1064
+ onSelect: entry => {
1065
+ const restored = this.#restoreQueuedMessageToEditor(entry);
1066
+ close();
1067
+ this.ctx.showStatus(
1068
+ restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
1069
+ );
1070
+ },
1040
1071
  onDelete: (entry, index) => {
1041
1072
  const deleted = this.ctx.session.removeQueuedMessageForEditing(entry.id) !== undefined;
1042
1073
  const remaining = this.ctx.session.getQueuedMessageEntries();
@@ -1209,7 +1240,17 @@ export class InputController {
1209
1240
  this.#restoreEditorFocus();
1210
1241
  this.ctx.ui.requestRender();
1211
1242
  },
1212
- { selectedIndex },
1243
+ {
1244
+ selectedIndex,
1245
+ formatKeyHint: key => this.ctx.keybindings.formatKeyHint(key),
1246
+ formatSelectAction: action => this.ctx.keybindings.getDisplayString(action),
1247
+ matchesSelectAction: (keyData, action) =>
1248
+ this.ctx.keybindings.getKeys(action).some(key => matchesKey(keyData, key)),
1249
+ resolveSelectNavigation: keyData =>
1250
+ QUEUE_SELECTOR_NAVIGATION_ACTIONS.find(action =>
1251
+ this.ctx.keybindings.getKeys(action).some(key => matchesKey(keyData, key)),
1252
+ ),
1253
+ },
1213
1254
  );
1214
1255
  this.ctx.editorContainer.clear();
1215
1256
  this.ctx.editorContainer.addChild(selector);
@@ -2003,9 +2044,24 @@ export class InputController {
2003
2044
  }
2004
2045
 
2005
2046
  toggleThinkingBlockVisibility(): void {
2006
- this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
2007
- settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
2008
- this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
2047
+ if (!settings.canWriteDurableConfig()) {
2048
+ this.ctx.showError(
2049
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
2050
+ );
2051
+ return;
2052
+ }
2053
+ const hideThinkingBlock = !this.ctx.hideThinkingBlock;
2054
+ try {
2055
+ settings.set("hideThinkingBlock", hideThinkingBlock);
2056
+ } catch (error) {
2057
+ if (!settings.canWriteDurableConfig()) {
2058
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
2059
+ return;
2060
+ }
2061
+ throw error;
2062
+ }
2063
+ this.ctx.hideThinkingBlock = hideThinkingBlock;
2064
+ this.ctx.session.setThinkingVisibility(hideThinkingBlock ? "hidden" : "visible");
2009
2065
 
2010
2066
  // Rebuild chat from session messages
2011
2067
  // Detach the live streaming component before the disposing clear() so the