@gajae-code/coding-agent 0.16.3 → 0.16.6

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 (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -16,6 +16,7 @@ import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/co
16
16
  import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
17
17
  import { getSymbolTheme, theme } from "../../modes/theme/theme";
18
18
  import {
19
+ clearInteractiveActivityIndicator,
19
20
  type InteractiveModeContext,
20
21
  stopInteractiveActivityIndicator,
21
22
  syncInteractiveActivityIndicator,
@@ -128,6 +129,15 @@ type AgentSessionEventHandlers = {
128
129
  [E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
129
130
  };
130
131
 
132
+ interface AssistantTextPresentation {
133
+ component: AssistantMessageComponent;
134
+ latestMessage: AssistantMessage;
135
+ sessionIdentity: InteractiveModeContext["session"];
136
+ sessionEpoch: number;
137
+ paintEpoch: number;
138
+ cancel: () => void;
139
+ }
140
+
131
141
  export class EventController {
132
142
  #lastReadGroup: ReadToolGroupComponent | undefined = undefined;
133
143
  #lastThinkingCount = 0;
@@ -146,6 +156,16 @@ export class EventController {
146
156
  #visibleTranscriptChanged = false;
147
157
  #handlingEvent = false;
148
158
  #eventQueue: Promise<void> = Promise.resolve();
159
+ #disposed = false;
160
+ #assistantTextSuspended = false;
161
+ #sessionPresentationEpoch = 0;
162
+ #paintEpoch = 0;
163
+ #pendingAssistantText: AssistantTextPresentation | undefined;
164
+ #assistantTextMutationScope: AssistantTextPresentation | undefined;
165
+ #assistantLifetimes = new WeakMap<
166
+ AssistantMessageComponent,
167
+ { sessionIdentity: InteractiveModeContext["session"]; sessionEpoch: number }
168
+ >();
149
169
 
150
170
  constructor(private ctx: InteractiveModeContext) {
151
171
  this.#handlers = {
@@ -177,6 +197,8 @@ export class EventController {
177
197
  }
178
198
 
179
199
  dispose(): void {
200
+ this.#disposed = true;
201
+ this.resetAssistantTextPresentation();
180
202
  this.#cancelIdleCompaction();
181
203
  this.#clearRetryCountdown();
182
204
  if (this.ctx.retryEscapeHandler) {
@@ -199,6 +221,104 @@ export class EventController {
199
221
  this.clearIrcExpiryTimers();
200
222
  }
201
223
 
224
+ #cancelAssistantTextPresentation(): void {
225
+ const pending = this.#pendingAssistantText;
226
+ this.#pendingAssistantText = undefined;
227
+ pending?.cancel();
228
+ }
229
+
230
+ /** Temporary TUI stop discards scheduling, not historical assistant callback lifetimes. */
231
+ suspendAssistantTextPresentation(): void {
232
+ this.#assistantTextSuspended = true;
233
+ this.#paintEpoch += 1;
234
+ this.#cancelAssistantTextPresentation();
235
+ }
236
+
237
+ /** Rearm before the first restarted frame, even without a new provider event. */
238
+ resumeAssistantTextPresentation(): void {
239
+ this.#cancelAssistantTextPresentation();
240
+ this.#assistantTextSuspended = false;
241
+ const component = this.ctx.streamingComponent;
242
+ const message = this.ctx.streamingMessage;
243
+ if (component && message && this.#isLiveAssistant(component)) {
244
+ this.#queueAssistantText(component, message);
245
+ }
246
+ }
247
+
248
+ /** Session/transcript replacement invalidates callbacks before removing old children. */
249
+ resetAssistantTextPresentation(): void {
250
+ this.#sessionPresentationEpoch += 1;
251
+ this.#cancelAssistantTextPresentation();
252
+ }
253
+
254
+ #isLiveAssistant(component: AssistantMessageComponent): boolean {
255
+ const lifetime = this.#assistantLifetimes.get(component);
256
+ return (
257
+ !this.#disposed &&
258
+ !this.ctx.isStopped?.() &&
259
+ lifetime !== undefined &&
260
+ lifetime.sessionIdentity === this.ctx.session &&
261
+ lifetime.sessionEpoch === this.#sessionPresentationEpoch &&
262
+ this.ctx.chatContainer.hasLiveChild(component)
263
+ );
264
+ }
265
+
266
+ #isValidAssistantText(record: AssistantTextPresentation): boolean {
267
+ return (
268
+ !this.#assistantTextSuspended &&
269
+ this.#isLiveAssistant(record.component) &&
270
+ record.sessionIdentity === this.ctx.session &&
271
+ record.sessionEpoch === this.#sessionPresentationEpoch &&
272
+ record.paintEpoch === this.#paintEpoch &&
273
+ record.component === this.ctx.streamingComponent
274
+ );
275
+ }
276
+
277
+ #queueAssistantText(component: AssistantMessageComponent, latestMessage: AssistantMessage): void {
278
+ if (this.#assistantTextSuspended || !this.#isLiveAssistant(component)) return;
279
+ const pending = this.#pendingAssistantText;
280
+ if (pending && this.#isValidAssistantText(pending)) {
281
+ pending.latestMessage = latestMessage;
282
+ return;
283
+ }
284
+ this.#cancelAssistantTextPresentation();
285
+ const record: AssistantTextPresentation = {
286
+ component,
287
+ latestMessage,
288
+ sessionIdentity: this.ctx.session,
289
+ sessionEpoch: this.#sessionPresentationEpoch,
290
+ paintEpoch: this.#paintEpoch,
291
+ cancel: () => {},
292
+ };
293
+ this.#pendingAssistantText = record;
294
+ record.cancel = this.ctx.ui.enqueueBeforeRender(() => this.#flushAssistantText(record));
295
+ }
296
+
297
+ #flushAssistantText(record = this.#pendingAssistantText): void {
298
+ if (!record || this.#pendingAssistantText !== record) return;
299
+ this.#cancelAssistantTextPresentation();
300
+ if (!this.#isValidAssistantText(record)) return;
301
+ const previousScope = this.#assistantTextMutationScope;
302
+ this.#assistantTextMutationScope = record;
303
+ try {
304
+ record.component.updateContent(record.latestMessage, { streaming: true });
305
+ } finally {
306
+ this.#assistantTextMutationScope = previousScope;
307
+ }
308
+ }
309
+
310
+ #observeAssistantMutation(component: AssistantMessageComponent): void {
311
+ if (!this.#isLiveAssistant(component)) return;
312
+ const scope = this.#assistantTextMutationScope;
313
+ if (scope?.component === component && this.#isValidAssistantText(scope)) {
314
+ // Only this synchronous projection publishes before frame source selection.
315
+ // An unrelated awaited event retains its own dirty flag and settlement semantics.
316
+ this.ctx.recordVisibleTranscriptMutation?.();
317
+ return;
318
+ }
319
+ this.#observeVisibleTranscriptMutation();
320
+ }
321
+
202
322
  #recordVisibleTranscriptMutation(): void {
203
323
  this.#visibleTranscriptChanged = true;
204
324
  }
@@ -292,9 +412,10 @@ export class EventController {
292
412
  }
293
413
 
294
414
  async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
415
+ if (this.#disposed) return;
295
416
  if (this.ctx.isStopped?.()) return;
296
417
  if (!this.ctx.isInitialized) await this.ctx.init();
297
- if (this.ctx.isStopped?.()) return;
418
+ if (this.#disposed || this.ctx.isStopped?.()) return;
298
419
  this.#visibleTranscriptChanged = false;
299
420
  this.#handlingEvent = true;
300
421
  try {
@@ -462,18 +583,26 @@ export class EventController {
462
583
  this.ctx.ui.requestRender();
463
584
  this.#recordVisibleTranscriptMutation();
464
585
  } else if (event.message.role === "assistant") {
586
+ this.#cancelAssistantTextPresentation();
465
587
  this.#lastThinkingCount = 0;
466
588
  this.#resetReadGroup();
467
589
  this.#toolIntentCache.clear();
468
590
  this.#thinkingContentIndices.clear();
469
- this.ctx.streamingComponent = new AssistantMessageComponent(
591
+ const component: AssistantMessageComponent = new AssistantMessageComponent(
470
592
  undefined,
471
593
  this.ctx.hideThinkingBlock,
472
- () => this.ctx.ui.requestRender(),
594
+ () => {
595
+ if (this.#isLiveAssistant(component)) this.ctx.ui.requestRender();
596
+ },
473
597
  this.ctx.getAssistantViewportAnchorId?.(event.message),
474
- () => this.#observeVisibleTranscriptMutation(),
598
+ () => this.#observeAssistantMutation(component),
475
599
  this.ctx.session,
476
600
  );
601
+ this.#assistantLifetimes.set(component, {
602
+ sessionIdentity: this.ctx.session,
603
+ sessionEpoch: this.#sessionPresentationEpoch,
604
+ });
605
+ this.ctx.streamingComponent = component;
477
606
  this.ctx.streamingMessage = event.message;
478
607
  addChatChild(this.ctx, this.ctx.streamingComponent);
479
608
  this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
@@ -605,8 +734,20 @@ export class EventController {
605
734
  transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
606
735
  }
607
736
  this.ctx.streamingMessage = event.message;
608
- this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
609
737
  const contentIndex = event.assistantMessageEvent?.contentIndex;
738
+ if (
739
+ event.assistantMessageEvent?.type === "text_delta" &&
740
+ typeof contentIndex === "number" &&
741
+ Number.isInteger(contentIndex) &&
742
+ contentIndex >= 0 &&
743
+ contentIndex < event.message.content.length &&
744
+ event.message.content[contentIndex]?.type === "text"
745
+ ) {
746
+ this.#queueAssistantText(this.ctx.streamingComponent, event.message);
747
+ } else {
748
+ this.#cancelAssistantTextPresentation();
749
+ this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
750
+ }
610
751
  const changedContent =
611
752
  typeof contentIndex === "number" && contentIndex >= 0
612
753
  ? this.ctx.streamingMessage.content[contentIndex]
@@ -714,6 +855,7 @@ export class EventController {
714
855
 
715
856
  async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
716
857
  if (event.message.role === "user") return;
858
+ if (event.message.role === "assistant") this.#cancelAssistantTextPresentation();
717
859
  if (this.ctx.streamingComponent && event.message.role === "assistant") {
718
860
  if (this.ctx.streamingMessage?.role === "assistant") {
719
861
  transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
@@ -916,6 +1058,8 @@ export class EventController {
916
1058
  }
917
1059
 
918
1060
  async #handleAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
1061
+ this.#flushAssistantText();
1062
+ this.#cancelAssistantTextPresentation();
919
1063
  this.ctx.setWorkingMessage(undefined);
920
1064
  stopInteractiveActivityIndicator(this.ctx, { foregroundSettled: true });
921
1065
  if (this.ctx.streamingComponent) {
@@ -961,7 +1105,7 @@ export class EventController {
961
1105
  this.ctx.editor.onEscape = () => {
962
1106
  this.ctx.session.abortCompaction();
963
1107
  };
964
- stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false, foregroundSettled: true });
1108
+ clearInteractiveActivityIndicator(this.ctx);
965
1109
  const reasonText =
966
1110
  event.reason === "overflow" ? "Context overflow detected, " : event.reason === "idle" ? "Idle " : "";
967
1111
  const actionLabel = event.action === "handoff" ? "Auto-handoff" : "Auto context-full maintenance";
@@ -1062,7 +1206,7 @@ export class EventController {
1062
1206
  this.ctx.session.abortRetry();
1063
1207
  }
1064
1208
  };
1065
- stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false, foregroundSettled: true });
1209
+ clearInteractiveActivityIndicator(this.ctx);
1066
1210
  // Stop any prior retry loader/timer before installing a new one.
1067
1211
  this.ctx.retryLoader?.stop();
1068
1212
  this.#clearRetryCountdown();
@@ -292,6 +292,7 @@ export class ExtensionUiController {
292
292
  const result = await session.executeBash(input.cmd, undefined, { excludeFromContext: true });
293
293
  return {
294
294
  exitCode: result.exitCode,
295
+ signal: result.signal,
295
296
  cancelled: result.cancelled,
296
297
  output: result.output,
297
298
  truncated: result.truncated,
@@ -273,16 +273,19 @@ export class InputController {
273
273
 
274
274
  #syncTranscriptTurnPosition(): readonly string[] {
275
275
  const anchorIds = getUserMessageViewportAnchorIds(this.ctx.session.messages);
276
+ const previousAnchorIds = new Set(this.#transcriptTurnAnchorIds);
276
277
  if (
277
278
  anchorIds.length !== this.#transcriptTurnAnchorIds.length ||
278
- anchorIds.some(id => !this.#transcriptTurnAnchorIds.includes(id))
279
+ anchorIds.some(id => !previousAnchorIds.has(id))
279
280
  ) {
280
281
  const hadAnchors = this.#transcriptTurnAnchorIds.length > 0;
281
282
  const currentAnchorId = this.#transcriptTurnAnchorIds[this.#transcriptTurnPosition];
282
- const retainedAnchorIds = this.#transcriptTurnAnchorIds.filter(id => anchorIds.includes(id));
283
+ const incomingAnchorIds = new Set(anchorIds);
284
+ const retainedAnchorIds = this.#transcriptTurnAnchorIds.filter(id => incomingAnchorIds.has(id));
285
+ const retainedAnchorIdSet = new Set(retainedAnchorIds);
283
286
  this.#transcriptTurnAnchorIds = [
284
287
  ...retainedAnchorIds,
285
- ...anchorIds.filter(id => !retainedAnchorIds.includes(id)),
288
+ ...anchorIds.filter(id => !retainedAnchorIdSet.has(id)),
286
289
  ];
287
290
  const currentPosition = currentAnchorId ? this.#transcriptTurnAnchorIds.indexOf(currentAnchorId) : -1;
288
291
  this.#transcriptTurnPosition =
@@ -1976,6 +1976,22 @@ export class SelectorController {
1976
1976
  width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
1977
1977
  return this.ctx.statusLine.getPreviewContent(availableWidth);
1978
1978
  },
1979
+ getStatusLinePreviewForSettings: (previewSettings, width?: number) => {
1980
+ const availableWidth =
1981
+ width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
1982
+ return this.ctx.statusLine.getPreviewContentForSettings(availableWidth, {
1983
+ ...buildStatusLineSettings(settings),
1984
+ ...previewSettings,
1985
+ });
1986
+ },
1987
+ getStatusLinePreviewPartsForSettings: (previewSettings, width?: number) => {
1988
+ const availableWidth =
1989
+ width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
1990
+ return this.ctx.statusLine.getPreviewPartsForSettings(availableWidth, {
1991
+ ...buildStatusLineSettings(settings),
1992
+ ...previewSettings,
1993
+ });
1994
+ },
1979
1995
  onPluginsChanged: () => {
1980
1996
  this.ctx.ui.requestRender();
1981
1997
  },
@@ -2982,6 +2998,8 @@ export class SelectorController {
2982
2998
  this.ctx.ui.requestRender();
2983
2999
  try {
2984
3000
  await mgr.uninstallPlugin(pluginId, scope);
3001
+ await this.ctx.session.reloadSkills();
3002
+ await this.ctx.refreshSlashCommandState();
2985
3003
  this.ctx.showStatus(`Uninstalled ${pluginId}`);
2986
3004
  } catch (err) {
2987
3005
  this.ctx.showStatus(`Uninstall failed: ${err}`);
@@ -3019,6 +3037,8 @@ export class SelectorController {
3019
3037
  try {
3020
3038
  const force = installedIds.has(`${name}@${marketplace}`);
3021
3039
  await mgr.installPlugin(name, marketplace, { force });
3040
+ await this.ctx.session.reloadSkills();
3041
+ await this.ctx.refreshSlashCommandState();
3022
3042
  this.ctx.showStatus(`Installed ${name} from ${marketplace}`);
3023
3043
  } catch (err) {
3024
3044
  this.ctx.showStatus(`Install failed: ${err}`);
@@ -3245,6 +3265,7 @@ export class SelectorController {
3245
3265
  }
3246
3266
 
3247
3267
  #clearTransientSessionUi(options?: { restoreBackground?: boolean; clearSpecializedLoaders?: boolean }): void {
3268
+ this.ctx.resetAssistantTextPresentation();
3248
3269
  if (options?.clearSpecializedLoaders) clearInteractiveActivityLoaders(this.ctx);
3249
3270
  stopInteractiveActivityIndicator(this.ctx, {
3250
3271
  restoreBackground: options?.restoreBackground,
@@ -13,7 +13,7 @@ import {
13
13
  Text,
14
14
  TUI,
15
15
  } from "@gajae-code/tui";
16
- import { APP_NAME, adjustHsv, getProjectDir, logger, postmortem, sanitizeText } from "@gajae-code/utils";
16
+ import { APP_NAME, adjustHsv, getAgentDir, getProjectDir, logger, postmortem, sanitizeText } from "@gajae-code/utils";
17
17
  import chalk from "chalk";
18
18
  import { AsyncJobManager } from "../async";
19
19
  import {
@@ -49,6 +49,7 @@ import type { AgentSession, AgentSessionEvent, AsyncJobSnapshotItem } from "../s
49
49
  import type { HistoryStorage } from "../session/history-storage";
50
50
  import type { SessionContext, SessionManager } from "../session/session-manager";
51
51
  import { getRecentSessions, getSessionMessageEntryId } from "../session/session-manager";
52
+ import { createDefaultPaseoAnnounceDependencies, startPaseoAnnouncement } from "../setup/paseo/announce";
52
53
  import type { LspStartupServerInfo } from "../tools";
53
54
  import { formatPhaseDisplayName } from "../tools/todo-write";
54
55
  import { copyToClipboard } from "../utils/clipboard";
@@ -745,6 +746,10 @@ export class InteractiveMode implements InteractiveModeContext {
745
746
  version: this.#version,
746
747
  focusDomain: "composer",
747
748
  keyDisplayContext: this.#keyDisplayContext,
749
+ onUpdate: () => {
750
+ this.updateEditorChrome();
751
+ this.ui.requestRender();
752
+ },
748
753
  });
749
754
  this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
750
755
 
@@ -776,6 +781,10 @@ export class InteractiveMode implements InteractiveModeContext {
776
781
  this.#btwController = new BtwController(this);
777
782
  this.#extensionUiController = new ExtensionUiController(this);
778
783
  this.#eventController = new EventController(this);
784
+ this.ui.setRenderPreparationLifecycleCallbacks({
785
+ invalidate: () => this.#eventController.suspendAssistantTextPresentation(),
786
+ beforeStart: () => this.#eventController.resumeAssistantTextPresentation(),
787
+ });
779
788
  this.#commandController = new CommandController(this);
780
789
  this.#todoCommandController = new TodoCommandController(this);
781
790
  this.#selectorController = new SelectorController(this);
@@ -1065,6 +1074,37 @@ export class InteractiveMode implements InteractiveModeContext {
1065
1074
  crashRelayTimer.unref?.();
1066
1075
  }
1067
1076
 
1077
+ // Make this session visible in Paseo. Interactive-only by construction: a
1078
+ // `gjc acp` provider process never builds an InteractiveMode, so an imported
1079
+ // session can never announce itself back into Paseo.
1080
+ if (this.settings.getGlobal("paseo.autoImport") === true) {
1081
+ const sessionId = this.sessionManager.getSessionId();
1082
+ const announcement = startPaseoAnnouncement(
1083
+ {
1084
+ sessionId,
1085
+ cwd: this.sessionManager.getCwd(),
1086
+ isEnabled: () => this.settings.getGlobal("paseo.autoImport") === true,
1087
+ onOutcome: outcome => {
1088
+ if (this.#stopped || outcome.kind === "skipped") return;
1089
+ this.showStatus(
1090
+ outcome.kind === "failed"
1091
+ ? `Paseo import failed: ${outcome.detail}`
1092
+ : `Controllable in Paseo as ${outcome.providerKey}`,
1093
+ { dim: true },
1094
+ );
1095
+ },
1096
+ },
1097
+ createDefaultPaseoAnnounceDependencies(getAgentDir()),
1098
+ );
1099
+ const stopWatchingSettings = this.settings.onChanged(path => {
1100
+ if (path === "paseo.autoImport" && this.settings.getGlobal(path) !== true) announcement.cancel();
1101
+ });
1102
+ this.#stopListeners.add(() => {
1103
+ stopWatchingSettings();
1104
+ announcement.cancel();
1105
+ });
1106
+ }
1107
+
1068
1108
  if (starReminderGate.schedule) {
1069
1109
  scheduleLaunchStarReminderAfterFirstRender({
1070
1110
  confirm: (title, message) =>
@@ -1517,6 +1557,7 @@ export class InteractiveMode implements InteractiveModeContext {
1517
1557
 
1518
1558
  rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void {
1519
1559
  prepareTranscriptRebuild(this.ui, policy);
1560
+ this.resetAssistantTextPresentation();
1520
1561
  this.chatContainer.clear();
1521
1562
  const context = this.session.buildDisplaySessionContext();
1522
1563
  this.renderSessionContext(context);
@@ -1649,6 +1690,7 @@ export class InteractiveMode implements InteractiveModeContext {
1649
1690
  stop(): void {
1650
1691
  const wasInitialized = this.isInitialized;
1651
1692
  this.#stopped = true;
1693
+ this.ui.setRenderPreparationLifecycleCallbacks(undefined);
1652
1694
  this.#inputController.discardDeferredSubmission();
1653
1695
  for (const listener of this.#stopListeners) {
1654
1696
  try {
@@ -1658,7 +1700,7 @@ export class InteractiveMode implements InteractiveModeContext {
1658
1700
  }
1659
1701
  }
1660
1702
  this.#stopListeners.clear();
1661
- this.#stopLoadingAnimation();
1703
+ this.clearLoadingAnimation();
1662
1704
  this.#suspendedActivityIndicator = undefined;
1663
1705
  this.#petProtocolUnsubscribe?.();
1664
1706
  this.#petProtocolUnsubscribe = undefined;
@@ -1954,7 +1996,8 @@ export class InteractiveMode implements InteractiveModeContext {
1954
1996
  return tallyBackgroundActivity(this.session.getAsyncJobSnapshot()?.running ?? []);
1955
1997
  }
1956
1998
 
1957
- #stopLoadingAnimation(): void {
1999
+ /** Clear the status display without settling the foreground run during maintenance/retry. */
2000
+ clearLoadingAnimation(): void {
1958
2001
  this.loadingAnimation?.stop();
1959
2002
  this.loadingAnimation = undefined;
1960
2003
  this.statusContainer.clear();
@@ -1965,7 +2008,7 @@ export class InteractiveMode implements InteractiveModeContext {
1965
2008
  return;
1966
2009
  const foregroundActive = this.#foregroundActivity || (!this.#foregroundTurnSettled && this.session.isStreaming);
1967
2010
  if (!this.isInitialized && !foregroundActive) {
1968
- this.#stopLoadingAnimation();
2011
+ this.clearLoadingAnimation();
1969
2012
  return;
1970
2013
  }
1971
2014
  const message = resolveActivityIndicatorMessage(
@@ -1974,7 +2017,7 @@ export class InteractiveMode implements InteractiveModeContext {
1974
2017
  this.#pendingWorkingMessage ?? this.#defaultWorkingMessage,
1975
2018
  );
1976
2019
  if (!message) {
1977
- this.#stopLoadingAnimation();
2020
+ this.clearLoadingAnimation();
1978
2021
  return;
1979
2022
  }
1980
2023
  if (!this.loadingAnimation) {
@@ -2005,7 +2048,7 @@ export class InteractiveMode implements InteractiveModeContext {
2005
2048
  this.#foregroundActivity = false;
2006
2049
  if (options?.foregroundSettled) this.#foregroundTurnSettled = true;
2007
2050
  if (options?.restoreBackground === false) {
2008
- this.#stopLoadingAnimation();
2051
+ this.clearLoadingAnimation();
2009
2052
  return;
2010
2053
  }
2011
2054
  this.syncActivityIndicator();
@@ -2077,6 +2120,10 @@ export class InteractiveMode implements InteractiveModeContext {
2077
2120
  return this.#uiHelpers.isKnownSlashCommand(text);
2078
2121
  }
2079
2122
 
2123
+ resetAssistantTextPresentation(): void {
2124
+ this.#eventController.resetAssistantTextPresentation();
2125
+ }
2126
+
2080
2127
  /** Advances the sticky-viewport source only for semantic transcript output. */
2081
2128
  recordVisibleTranscriptMutation(): void {
2082
2129
  const identity = `session:${this.sessionManager.getSessionId()}`;
@@ -10,6 +10,7 @@ import { type AssistantMessage, type ImageContent, isContextOverflow } from "@ga
10
10
  import { isKnownSinkPeerClosedError, logger, sanitizeText } from "@gajae-code/utils";
11
11
  import { loadSlashCommands } from "../extensibility/slash-commands";
12
12
  import type { AgentSession } from "../session/agent-session";
13
+ import { ManagedAppendIdentityMismatchError } from "../session/internal/managed-session-storage";
13
14
  import { isSilentAbort } from "../session/messages";
14
15
  import { resolveProviderSafetyStopHint } from "../session/provider-safety-stop-hint";
15
16
  import { executeLocalHeadlessBuiltinSlashCommand } from "../slash-commands/builtin-registry";
@@ -388,7 +389,10 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
388
389
  // Observe callback and stream errors from every preceding print-mode write.
389
390
  await stdout.flush();
390
391
  } catch (error) {
391
- failures.push(error);
392
+ if (error instanceof ManagedAppendIdentityMismatchError) {
393
+ process.exitCode = 1;
394
+ await writeStderrAndQuiesce(`${error.operatorMessage}\n`);
395
+ } else failures.push(error);
392
396
  } finally {
393
397
  // The JSON subscriber remains live while disposal emits its final events.
394
398
  try {
@@ -89,23 +89,37 @@ function getSlashCommandPriority(command: SlashCommand | undefined, item: Autoco
89
89
  return ADVANCED_SLASH_COMMAND_PRIORITIES.get(item.value) ?? 0;
90
90
  }
91
91
 
92
+ interface SlashCommandMetadata {
93
+ command: SlashCommand;
94
+ index: number;
95
+ lowerName: string;
96
+ normalizedName: string;
97
+ normalizedDescription: string | undefined;
98
+ }
99
+
100
+ interface SkillCommandMetadata {
101
+ command: SlashCommand;
102
+ lowerSkillName: string;
103
+ searchTargets: string[];
104
+ collisionSearchTargets: string[];
105
+ }
106
+
92
107
  function sortSlashCommandSuggestions(
93
108
  suggestions: { items: AutocompleteItem[]; prefix: string } | null,
94
- commands: SlashCommand[],
109
+ commandMetadata: ReadonlyMap<string, SlashCommandMetadata>,
95
110
  ): { items: AutocompleteItem[]; prefix: string } | null {
96
111
  if (!suggestions) return null;
97
112
  const query = suggestions.prefix.slice(1).toLowerCase();
98
113
  const normalizedQuery = normalizeFuzzyText(query);
99
- const commandIndexes = new Map(commands.map((command, index) => [command.name, index]));
100
- const commandByName = new Map(commands.map(command => [command.name, command]));
101
114
  const items = suggestions.items
102
115
  .map((item, index) => {
103
- const command = commandByName.get(item.value);
104
- const commandIndex = commandIndexes.get(item.value) ?? index;
105
- const lowerName = item.value.toLowerCase();
106
- const lowerDesc = command?.description?.toLowerCase() ?? item.description?.toLowerCase() ?? "";
107
- const normalizedName = normalizeFuzzyText(item.value);
108
- const normalizedDesc = normalizeFuzzyText(lowerDesc);
116
+ const metadata = commandMetadata.get(item.value);
117
+ const command = metadata?.command;
118
+ const commandIndex = metadata?.index ?? index;
119
+ const lowerName = metadata?.lowerName ?? item.value.toLowerCase();
120
+ const normalizedName = metadata?.normalizedName ?? normalizeFuzzyText(item.value);
121
+ const normalizedDesc =
122
+ metadata?.normalizedDescription ?? normalizeFuzzyText(item.description?.toLowerCase() ?? "");
109
123
  const nameScore = autocompleteFuzzyMatch(normalizedQuery, normalizedName)
110
124
  ? autocompleteFuzzyScore(normalizedQuery, normalizedName)
111
125
  : 0;
@@ -180,7 +194,8 @@ function isSlashTokenInsideInlineCodeSpan(
180
194
  export class PromptActionAutocompleteProvider implements AutocompleteProvider {
181
195
  #baseProvider: CombinedAutocompleteProvider;
182
196
  #actions: PromptActionDefinition[];
183
- #commands: SlashCommand[];
197
+ #commandMetadata = new Map<string, SlashCommandMetadata>();
198
+ #skillCommandMetadata: SkillCommandMetadata[] = [];
184
199
  #getPromptSuggestion: (() => string | null) | undefined;
185
200
 
186
201
  constructor(
@@ -191,7 +206,28 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
191
206
  ) {
192
207
  this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath);
193
208
  this.#actions = actions;
194
- this.#commands = commands;
209
+ for (const [index, command] of commands.entries()) {
210
+ this.#commandMetadata.set(command.name, {
211
+ command,
212
+ index,
213
+ lowerName: command.name.toLowerCase(),
214
+ normalizedName: normalizeFuzzyText(command.name),
215
+ normalizedDescription:
216
+ command.description === undefined ? undefined : normalizeFuzzyText(command.description.toLowerCase()),
217
+ });
218
+ if (command.name.startsWith("skill:")) {
219
+ const skillName = command.name.slice("skill:".length);
220
+ const name = normalizeFuzzyText(command.name);
221
+ const alias = normalizeFuzzyText(`skill-${skillName}`);
222
+ const description = normalizeFuzzyText(command.description ?? "");
223
+ this.#skillCommandMetadata.push({
224
+ command,
225
+ lowerSkillName: skillName.toLowerCase(),
226
+ searchTargets: [name, alias, normalizeFuzzyText(skillName), description],
227
+ collisionSearchTargets: [name, alias, description],
228
+ });
229
+ }
230
+ }
195
231
  this.#getPromptSuggestion = getPromptSuggestion;
196
232
  }
197
233
 
@@ -244,7 +280,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
244
280
  });
245
281
  return sortSlashCommandSuggestions(
246
282
  mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
247
- this.#commands,
283
+ this.#commandMetadata,
248
284
  );
249
285
  }
250
286
  if (rawSlashPrefix && slashTokenInsideCode) {
@@ -334,7 +370,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
334
370
  const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: false });
335
371
  return sortSlashCommandSuggestions(
336
372
  mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
337
- this.#commands,
373
+ this.#commandMetadata,
338
374
  );
339
375
  }
340
376
  trySyncInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
@@ -354,20 +390,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
354
390
  query.startsWith("skill-") ? `skill:${query.slice("skill-".length)}` : query,
355
391
  );
356
392
  if (query.length > 0 && normalizedQuery.length === 0) return null;
357
- const exactNonSkillCommand = this.#commands.some(
358
- command => command.name === query && !command.name.startsWith("skill:"),
359
- );
360
- const items = this.#commands
361
- .filter(command => command.name.startsWith("skill:"))
362
- .map(command => {
363
- const skillName = command.name.slice("skill:".length);
364
- if (exactNonSkillCommand && query === skillName.toLowerCase()) return null;
365
- const searchTargets = [
366
- command.name,
367
- `skill-${skillName}`,
368
- ...(exactNonSkillCommand ? [] : [skillName]),
369
- command.description ?? "",
370
- ].map(target => normalizeFuzzyText(target));
393
+ const exactNonSkillCommand = this.#commandMetadata.has(query) && !query.startsWith("skill:");
394
+ const items = this.#skillCommandMetadata
395
+ .map(({ command, lowerSkillName, searchTargets: allSearchTargets, collisionSearchTargets }) => {
396
+ if (exactNonSkillCommand && query === lowerSkillName) return null;
397
+ const searchTargets = exactNonSkillCommand ? collisionSearchTargets : allSearchTargets;
371
398
  if (
372
399
  !searchTargets.some(
373
400
  target => autocompleteFuzzyMatch(normalizedQuery, target) || target.includes(normalizedQuery),
@@ -201,6 +201,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
201
201
  },
202
202
  getJobs: () => session.getAsyncJobSnapshot(),
203
203
  onJobFold: listener => session.onJobFold(listener),
204
+ onSessionEvent: listener => session.subscribe(listener),
204
205
  setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
205
206
  setSdkClientBridge: bridge => session.setClientBridge(bridge),
206
207
  sdkControl: async (operation, input) => {
@@ -325,6 +326,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
325
326
  const result = await session.executeBash(input.cmd, undefined, { excludeFromContext: true });
326
327
  return {
327
328
  exitCode: result.exitCode,
329
+ signal: result.signal,
328
330
  cancelled: result.cancelled,
329
331
  output: result.output,
330
332
  truncated: result.truncated,