@gajae-code/coding-agent 0.15.6 → 0.16.1

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 (270) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/args.d.ts +2 -0
  5. package/dist/types/cli/root-flags.d.ts +14 -0
  6. package/dist/types/cli/update-cli.d.ts +14 -0
  7. package/dist/types/cli.d.ts +7 -0
  8. package/dist/types/commands/launch.d.ts +10 -0
  9. package/dist/types/commands/sdk.d.ts +2 -0
  10. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  11. package/dist/types/config/billing-path.d.ts +25 -0
  12. package/dist/types/config/config-schema-version.d.ts +13 -1
  13. package/dist/types/config/file-lock.d.ts +5 -0
  14. package/dist/types/config/model-registry.d.ts +22 -0
  15. package/dist/types/config/settings-schema.d.ts +27 -8
  16. package/dist/types/config/settings.d.ts +2 -0
  17. package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
  18. package/dist/types/crash/record-loader.d.ts +15 -0
  19. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  20. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  21. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  22. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  23. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  24. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  26. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  27. package/dist/types/main.d.ts +1 -0
  28. package/dist/types/master-mode/context.d.ts +21 -0
  29. package/dist/types/master-mode/first-request.d.ts +34 -0
  30. package/dist/types/master-mode/snapshot.d.ts +32 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  32. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  33. package/dist/types/modes/components/model-selector.d.ts +4 -0
  34. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  35. package/dist/types/modes/components/session-selector.d.ts +1 -1
  36. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  37. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  38. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  39. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  40. package/dist/types/modes/interactive-mode.d.ts +16 -2
  41. package/dist/types/modes/print-mode.d.ts +17 -0
  42. package/dist/types/modes/theme/theme.d.ts +2 -1
  43. package/dist/types/modes/types.d.ts +2 -0
  44. package/dist/types/modes/ui-language.d.ts +7 -1
  45. package/dist/types/sdk/broker/broker.d.ts +79 -2
  46. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  47. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  48. package/dist/types/sdk/broker/index.d.ts +3 -0
  49. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  50. package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
  51. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  52. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  53. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  54. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  55. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  56. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  57. package/dist/types/sdk/broker/state-version.d.ts +21 -1
  58. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  59. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  60. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  61. package/dist/types/sdk/bus/index.d.ts +9 -1
  62. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  63. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  64. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  65. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  66. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  67. package/dist/types/sdk/cli/index.d.ts +1 -0
  68. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  69. package/dist/types/sdk/cli/rows.d.ts +12 -5
  70. package/dist/types/sdk/cli/session-cli.d.ts +20 -3
  71. package/dist/types/sdk/client/client.d.ts +3 -2
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/events.d.ts +1 -0
  77. package/dist/types/sdk/host/host.d.ts +10 -0
  78. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  79. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  80. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +70 -17
  82. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  83. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  84. package/dist/types/sdk/lifecycle-session.d.ts +3 -1
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  86. package/dist/types/sdk/receipt-state.d.ts +2 -0
  87. package/dist/types/sdk/router/session-router.d.ts +6 -0
  88. package/dist/types/sdk/session.d.ts +16 -0
  89. package/dist/types/sdk/startup-capability.d.ts +2 -1
  90. package/dist/types/sdk/turn-result.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +52 -7
  92. package/dist/types/session/credential-store-errors.d.ts +1 -0
  93. package/dist/types/session/fold-coordinator.d.ts +29 -2
  94. package/dist/types/session/session-manager.d.ts +79 -2
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/tools/ask.d.ts +1 -0
  98. package/dist/types/tools/bash.d.ts +16 -0
  99. package/dist/types/tools/browser/launch.d.ts +10 -0
  100. package/dist/types/tools/browser/registry.d.ts +8 -0
  101. package/dist/types/tools/gh.d.ts +8 -0
  102. package/dist/types/tools/index.d.ts +10 -10
  103. package/dist/types/tools/job.d.ts +3 -1
  104. package/dist/types/utils/external-editor.d.ts +7 -0
  105. package/examples/extensions/README.md +1 -1
  106. package/examples/extensions/pirate.ts +7 -4
  107. package/package.json +7 -7
  108. package/scripts/generate-sdk-operation-inventory.ts +13 -1
  109. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  110. package/src/async/job-manager.ts +107 -9
  111. package/src/browser-backend.ts +29 -0
  112. package/src/cli/accounts-cli.ts +9 -5
  113. package/src/cli/args.ts +17 -0
  114. package/src/cli/fast-help.ts +1 -0
  115. package/src/cli/plugin-cli.ts +29 -0
  116. package/src/cli/root-flags.ts +5 -0
  117. package/src/cli/update-cli.ts +140 -23
  118. package/src/commands/launch.ts +86 -18
  119. package/src/commands/sdk.ts +135 -28
  120. package/src/config/autorouting-tier-map.ts +12 -0
  121. package/src/config/billing-path.ts +42 -0
  122. package/src/config/config-schema-version.ts +18 -1
  123. package/src/config/file-lock.ts +158 -83
  124. package/src/config/model-preset-registry.ts +178 -23
  125. package/src/config/model-profiles.ts +5 -5
  126. package/src/config/model-registry.ts +291 -42
  127. package/src/config/model-resolver.ts +5 -0
  128. package/src/config/settings-schema.ts +27 -8
  129. package/src/config/settings.ts +51 -15
  130. package/src/coordinator-mcp/projection-scan.ts +27 -6
  131. package/src/coordinator-mcp/question-state.ts +9 -11
  132. package/src/coordinator-mcp/server.ts +115 -21
  133. package/src/crash/index-store.ts +2 -0
  134. package/src/crash/record-loader.ts +10 -0
  135. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  136. package/src/extensibility/extensions/runner.ts +27 -0
  137. package/src/extensibility/extensions/types.ts +8 -3
  138. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  139. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  140. package/src/extensibility/skills.ts +22 -12
  141. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  142. package/src/gjc-runtime/launch-worktree.ts +285 -4
  143. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  144. package/src/gjc-runtime/session-state-lock.ts +213 -14
  145. package/src/gjc-runtime/session-state-sidecar.ts +938 -39
  146. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  147. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
  148. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  149. package/src/hooks/skill-state.ts +6 -0
  150. package/src/internal-urls/docs-index.generated.ts +15 -15
  151. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  152. package/src/main.ts +61 -3
  153. package/src/master-mode/context.ts +50 -0
  154. package/src/master-mode/first-request.ts +65 -0
  155. package/src/master-mode/snapshot.ts +205 -0
  156. package/src/modes/acp/acp-agent.ts +610 -161
  157. package/src/modes/components/footer.ts +4 -2
  158. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  159. package/src/modes/components/model-selector.ts +76 -8
  160. package/src/modes/components/oauth-selector.ts +6 -1
  161. package/src/modes/components/session-selector.ts +23 -15
  162. package/src/modes/components/sessions-dashboard.ts +8 -5
  163. package/src/modes/components/settings-selector.ts +1 -0
  164. package/src/modes/components/status-line/model-name.ts +53 -0
  165. package/src/modes/components/status-line/priority-row.ts +107 -0
  166. package/src/modes/components/status-line/segments.ts +23 -9
  167. package/src/modes/components/tool-status-header.ts +151 -15
  168. package/src/modes/controllers/command-controller.ts +1 -1
  169. package/src/modes/controllers/event-controller.ts +30 -4
  170. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  171. package/src/modes/controllers/input-controller.ts +30 -2
  172. package/src/modes/controllers/selector-controller.ts +200 -98
  173. package/src/modes/interactive-mode.ts +36 -12
  174. package/src/modes/print-mode.ts +64 -0
  175. package/src/modes/prompt-action-autocomplete.ts +30 -4
  176. package/src/modes/runtime-init.ts +12 -9
  177. package/src/modes/theme/theme.ts +5 -0
  178. package/src/modes/types.ts +2 -0
  179. package/src/modes/ui-language.ts +49 -2
  180. package/src/modes/utils/ui-helpers.ts +22 -3
  181. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  182. package/src/prompts/system/master-mode.md +28 -0
  183. package/src/prompts/tools/ask.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -0
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/sdk/acp/adapter.ts +5 -0
  187. package/src/sdk/broker/broker.ts +1646 -27
  188. package/src/sdk/broker/endpoint-authority.ts +86 -0
  189. package/src/sdk/broker/ensure.ts +81 -3
  190. package/src/sdk/broker/index.ts +3 -0
  191. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  192. package/src/sdk/broker/lifecycle.ts +440 -75
  193. package/src/sdk/broker/master-capability.ts +232 -0
  194. package/src/sdk/broker/session-index.ts +121 -17
  195. package/src/sdk/broker/session-scope.ts +272 -0
  196. package/src/sdk/broker/spawn-authority.ts +1031 -0
  197. package/src/sdk/broker/spawn-substrate.ts +591 -0
  198. package/src/sdk/broker/startup-budget.ts +144 -2
  199. package/src/sdk/broker/state-version.ts +43 -14
  200. package/src/sdk/broker/transport.ts +1 -0
  201. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  202. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  203. package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
  204. package/src/sdk/bus/discord-daemon.ts +50 -10
  205. package/src/sdk/bus/index.ts +84 -7
  206. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  207. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  208. package/src/sdk/bus/notification-service.ts +22 -5
  209. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  210. package/src/sdk/bus/reconciliation-store.ts +55 -31
  211. package/src/sdk/bus/slack-conversation.ts +1 -1
  212. package/src/sdk/bus/slack-daemon.ts +72 -38
  213. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  214. package/src/sdk/cli/index.ts +1 -0
  215. package/src/sdk/cli/master-cli.ts +259 -0
  216. package/src/sdk/cli/rows.ts +41 -15
  217. package/src/sdk/cli/session-cli.ts +297 -26
  218. package/src/sdk/client/client.ts +14 -8
  219. package/src/sdk/host/control/dispatch.ts +101 -40
  220. package/src/sdk/host/control/index.ts +2 -0
  221. package/src/sdk/host/control/operations.ts +2 -0
  222. package/src/sdk/host/control/runtime-gate.ts +203 -11
  223. package/src/sdk/host/events.ts +1 -0
  224. package/src/sdk/host/host.ts +29 -2
  225. package/src/sdk/host/query/cursor.ts +52 -7
  226. package/src/sdk/host/query/handlers.ts +33 -12
  227. package/src/sdk/host/query/revision-store.ts +12 -0
  228. package/src/sdk/host/session-runtime.test.ts +673 -14
  229. package/src/sdk/host/session-runtime.ts +674 -103
  230. package/src/sdk/host/surface-policy.ts +3 -3
  231. package/src/sdk/lifecycle/broker-client.ts +28 -0
  232. package/src/sdk/lifecycle/service.ts +279 -20
  233. package/src/sdk/lifecycle-session.ts +17 -4
  234. package/src/sdk/prompt-deadline-manager.ts +2 -1
  235. package/src/sdk/protocol/operation-inventory.generated.json +105 -3
  236. package/src/sdk/protocol/operation-registry.ts +14 -4
  237. package/src/sdk/receipt-state.ts +10 -0
  238. package/src/sdk/router/session-router.ts +219 -118
  239. package/src/sdk/session.ts +416 -195
  240. package/src/sdk/startup-capability.ts +1 -0
  241. package/src/sdk/turn-result.ts +22 -0
  242. package/src/session/agent-session.ts +1628 -318
  243. package/src/session/blob-store.ts +15 -5
  244. package/src/session/credential-store-errors.ts +2 -0
  245. package/src/session/fold-coordinator.ts +40 -3
  246. package/src/session/internal/managed-session-storage.ts +153 -78
  247. package/src/session/session-manager.ts +850 -97
  248. package/src/session/yield-queue.ts +24 -10
  249. package/src/setup/frictionless-onboarding.ts +2 -0
  250. package/src/slash-commands/builtin-registry.ts +41 -0
  251. package/src/task/executor.ts +9 -4
  252. package/src/telemetry/events.ts +1138 -12
  253. package/src/tools/ask-contract.test.ts +64 -0
  254. package/src/tools/ask-contract.ts +36 -1
  255. package/src/tools/ask.ts +10 -8
  256. package/src/tools/bash-interactive.ts +11 -2
  257. package/src/tools/bash.ts +412 -75
  258. package/src/tools/browser/launch.ts +37 -0
  259. package/src/tools/browser/registry.ts +99 -9
  260. package/src/tools/cron.ts +6 -4
  261. package/src/tools/descriptors.test.ts +15 -0
  262. package/src/tools/descriptors.ts +5 -1
  263. package/src/tools/gh.ts +104 -48
  264. package/src/tools/github-cache.ts +2 -2
  265. package/src/tools/index.ts +10 -6
  266. package/src/tools/job.ts +4 -1
  267. package/src/tools/monitor.ts +12 -9
  268. package/src/tools/resolve.ts +30 -10
  269. package/src/tools/tool-catalog.generated.ts +2 -2
  270. package/src/utils/external-editor.ts +72 -1
@@ -28,6 +28,7 @@ import {
28
28
  resolveCurrentBranch,
29
29
  } from "./status-line/git-utils";
30
30
  import { getPreset } from "./status-line/presets";
31
+ import { buildPriorityRow, type PriorityItemSet } from "./status-line/priority-row";
31
32
  import { renderSegment, type SegmentContext } from "./status-line/segments";
32
33
  import { getSeparator } from "./status-line/separators";
33
34
  import { calculateTokensPerSecond } from "./status-line/token-rate";
@@ -132,6 +133,8 @@ interface CollectedStatusSegments {
132
133
  left: string[];
133
134
  leftSegIds: StatusLineSegmentId[];
134
135
  right: string[];
136
+ /** Parallel to `right`; null for action hints, job counts and the version tag. */
137
+ rightSegIds: (StatusLineSegmentId | null)[];
135
138
  previewHighlightSegment: StatusLineSegmentId | undefined;
136
139
  sessionAccent: boolean | undefined;
137
140
  leftSepWidth: number;
@@ -819,6 +822,7 @@ export class StatusLineComponent implements Component {
819
822
  }
820
823
 
821
824
  const right: string[] = [];
825
+ const rightSegIds: (StatusLineSegmentId | null)[] = [];
822
826
  const actionHints =
823
827
  effectiveSettings.showActionHints === false
824
828
  ? []
@@ -833,9 +837,13 @@ export class StatusLineComponent implements Component {
833
837
  const rendered = renderSegment(segId, ctx);
834
838
  if (rendered.visible && rendered.content) {
835
839
  right.push(highlightSegment(segId, rendered.content));
840
+ rightSegIds.push(segId);
836
841
  }
837
842
  }
838
- right.push(...actionHints.map(hint => hint.content));
843
+ for (const hint of actionHints) {
844
+ right.push(hint.content);
845
+ rightSegIds.push(null);
846
+ }
839
847
 
840
848
  const runningBackgroundJobs =
841
849
  this.session.getAsyncJobSnapshot()?.running.filter(job => job.metadata?.monitor !== true).length ?? 0;
@@ -843,9 +851,11 @@ export class StatusLineComponent implements Component {
843
851
  const icon = theme.icon.agents ? `${theme.icon.agents} ` : "";
844
852
  const label = `${formatCount("job", runningBackgroundJobs)} running`;
845
853
  right.push(theme.fg("statusLineSubagents", `${icon}${label}`));
854
+ rightSegIds.push(null);
846
855
  }
847
856
  if (this.#version) {
848
857
  right.push(theme.fg("dim", `v${this.#version}`));
858
+ rightSegIds.push(null);
849
859
  }
850
860
 
851
861
  return {
@@ -857,6 +867,7 @@ export class StatusLineComponent implements Component {
857
867
  left,
858
868
  leftSegIds,
859
869
  right,
870
+ rightSegIds,
860
871
  previewHighlightSegment,
861
872
  sessionAccent: effectiveSettings.sessionAccent,
862
873
  leftSepWidth: visibleWidth(separatorDef.left),
@@ -872,22 +883,108 @@ export class StatusLineComponent implements Component {
872
883
  return Math.max(1, Math.min(3, Math.trunc(raw)));
873
884
  }
874
885
 
886
+ /**
887
+ * Which priority items this layout is carrying, and where.
888
+ *
889
+ * Context % has no segment of its own in most presets — it rides inside the
890
+ * `model` segment — so `model` inherits the context rank whenever it does.
891
+ * Items whose data is absent (no context window, no active goal) carry no
892
+ * priority at all, which keeps a rail with nothing to protect evicting
893
+ * exactly as it always did.
894
+ */
895
+ #priorityItems(seg: CollectedStatusSegments): {
896
+ include: PriorityItemSet;
897
+ inlineContextPct: boolean;
898
+ } {
899
+ const collected = (id: StatusLineSegmentId): boolean =>
900
+ seg.leftSegIds.includes(id) || seg.rightSegIds.includes(id);
901
+ const inlineContextPct =
902
+ seg.ctx.options.model?.showContextPercent !== false && seg.ctx.contextPctSegmentActive !== true;
903
+ const goalMode = seg.ctx.goalMode;
904
+
905
+ return {
906
+ inlineContextPct,
907
+ include: {
908
+ context:
909
+ seg.ctx.contextWindow > 0 && (collected("context_pct") || (collected("model") && inlineContextPct)),
910
+ goal: collected("mode") && goalMode !== null && (goalMode.enabled || goalMode.paused),
911
+ model: collected("model"),
912
+ },
913
+ };
914
+ }
915
+
916
+ /**
917
+ * Eviction rank: 0 is ordinary telemetry and goes first, 3 is context % and
918
+ * goes last. The model name outranks telemetry but loses to the goal
919
+ * indicator, matching context % > goal > model.
920
+ */
921
+ #priorityRanker(seg: CollectedStatusSegments): (id: StatusLineSegmentId | null) => number {
922
+ const { include, inlineContextPct } = this.#priorityItems(seg);
923
+ const anyPriority = include.context || include.goal;
924
+
925
+ return (id: StatusLineSegmentId | null): number => {
926
+ if (id === null || !anyPriority) return 0;
927
+ if (id === "context_pct") return include.context ? 3 : 0;
928
+ if (id === "model") {
929
+ if (include.context && inlineContextPct) return 3;
930
+ return include.model ? 1 : 0;
931
+ }
932
+ if (id === "mode") return include.goal ? 2 : 0;
933
+ return 0;
934
+ };
935
+ }
936
+
937
+ /**
938
+ * Priority row for a layout that could not keep everything the user needs on
939
+ * screen, or null when the normal rail already carries context %, the goal
940
+ * indicator and the model name.
941
+ *
942
+ * Context % has no segment of its own in most presets — it rides inside the
943
+ * `model` segment — so losing `model` silently loses the highest-priority
944
+ * item too. Survival is therefore resolved per item, not per segment.
945
+ */
946
+ #priorityRowFor(
947
+ seg: CollectedStatusSegments,
948
+ survivingLeftIds: readonly (StatusLineSegmentId | null)[],
949
+ survivingRightIds: readonly (StatusLineSegmentId | null)[],
950
+ width: number,
951
+ ): string | null {
952
+ const survived = (id: StatusLineSegmentId): boolean =>
953
+ survivingLeftIds.includes(id) || survivingRightIds.includes(id);
954
+
955
+ const { include, inlineContextPct } = this.#priorityItems(seg);
956
+ if (!include.context && !include.goal) return null;
957
+
958
+ const contextLost = include.context && !survived("context_pct") && !(inlineContextPct && survived("model"));
959
+ const goalLost = include.goal && !survived("mode");
960
+ const modelLost = include.model && !survived("model");
961
+ if (!contextLost && !goalLost && !modelLost) return null;
962
+
963
+ return buildPriorityRow(seg.ctx, width, include);
964
+ }
965
+
875
966
  #buildStatusLine(width: number, precollected?: CollectedStatusSegments): string {
876
967
  const seg = precollected ?? this.#collectStatusSegments(width, this.#resolveSettings());
877
968
  const { ctx, separatorDef, bgAnsi, fgAnsi, sepAnsi, previewHighlightSegment } = seg;
878
969
  const { leftSepWidth, rightSepWidth, leftCapWidth, rightCapWidth } = seg;
879
970
  const topFillWidth = Math.max(0, width);
880
971
 
972
+ const rank = this.#priorityRanker(seg);
973
+
881
974
  /**
882
- * Evict against `budget` using the existing configured-order semantics
883
- * (shrink path, then pop right, then pop left) and report how many
884
- * segments were dropped. Runs from the original segment lists each time so
885
- * the marker reservation can be recomputed without compounding evictions.
975
+ * Evict against `budget`: shrink the path first, then drop the
976
+ * lowest-priority segment still standing — right side before left, tail
977
+ * first within a side, which is the historical order for everything that
978
+ * carries no priority. Context %, the goal indicator and the model name
979
+ * outrank ordinary telemetry and are therefore the last to go. Runs from
980
+ * the original segment lists each time so the marker reservation can be
981
+ * recomputed without compounding evictions.
886
982
  */
887
983
  const layout = (budget: number) => {
888
984
  const left = [...seg.left];
889
985
  const leftIds = [...seg.leftSegIds];
890
986
  const right = [...seg.right];
987
+ const rightIds = [...seg.rightSegIds];
891
988
  let leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
892
989
  let rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
893
990
  const totalWidth = () => leftWidth + rightWidth + (left.length > 0 && right.length > 0 ? 1 : 0);
@@ -905,19 +1002,42 @@ export class StatusLineComponent implements Component {
905
1002
  leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
906
1003
  }
907
1004
  }
908
- while (totalWidth() > budget && right.length > 0) {
909
- right.pop();
910
- dropped += 1;
911
- rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
912
- }
913
- while (totalWidth() > budget && left.length > 0) {
914
- left.pop();
915
- leftIds.pop();
1005
+ // Lowest rank loses first; equal ranks fall back to the historical
1006
+ // right-then-left, tail-first order.
1007
+ while (totalWidth() > budget && (left.length > 0 || right.length > 0)) {
1008
+ let victimSide: "left" | "right" | null = null;
1009
+ let victimIndex = -1;
1010
+ let victimRank = Number.POSITIVE_INFINITY;
1011
+ for (let index = right.length - 1; index >= 0; index -= 1) {
1012
+ const candidate = rank(rightIds[index]);
1013
+ if (candidate < victimRank) {
1014
+ victimRank = candidate;
1015
+ victimSide = "right";
1016
+ victimIndex = index;
1017
+ }
1018
+ }
1019
+ for (let index = left.length - 1; index >= 0; index -= 1) {
1020
+ const candidate = rank(leftIds[index]);
1021
+ if (candidate < victimRank) {
1022
+ victimRank = candidate;
1023
+ victimSide = "left";
1024
+ victimIndex = index;
1025
+ }
1026
+ }
1027
+ if (victimSide === null) break;
1028
+ if (victimSide === "right") {
1029
+ right.splice(victimIndex, 1);
1030
+ rightIds.splice(victimIndex, 1);
1031
+ rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
1032
+ } else {
1033
+ left.splice(victimIndex, 1);
1034
+ leftIds.splice(victimIndex, 1);
1035
+ leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
1036
+ }
916
1037
  dropped += 1;
917
- leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
918
1038
  }
919
1039
  }
920
- return { left, right, leftWidth, rightWidth, dropped };
1040
+ return { left, right, leftIds, rightIds, leftWidth, rightWidth, dropped };
921
1041
  };
922
1042
 
923
1043
  // First pass with the full budget establishes whether anything is lost at
@@ -939,6 +1059,11 @@ export class StatusLineComponent implements Component {
939
1059
  }
940
1060
  }
941
1061
  const marker = this.#renderOverflowMarker(placed.dropped, reserved);
1062
+ // Nothing survived that the user actually needs? Spend the rail on the
1063
+ // priority row instead of an overflow marker. Suppressing the marker here
1064
+ // is deliberate: at these widths its cells are worth more as context %.
1065
+ const priorityRow = this.#priorityRowFor(seg, placed.leftIds, placed.rightIds, topFillWidth);
1066
+ if (priorityRow !== null) return priorityRow;
942
1067
 
943
1068
  const leftGroup = this.#renderStatusGroup(placed.left, "left", separatorDef, bgAnsi, fgAnsi, sepAnsi);
944
1069
  const rightGroup = this.#renderStatusGroup(placed.right, "right", separatorDef, bgAnsi, fgAnsi, sepAnsi);
@@ -1078,6 +1203,17 @@ export class StatusLineComponent implements Component {
1078
1203
  if (row.length === 0) packedRows.splice(index, 1);
1079
1204
  }
1080
1205
 
1206
+ // Wrapping is supposed to be the lossless path. When even `maxRows` rows
1207
+ // cannot hold everything, the priority row beats a partial rail plus a
1208
+ // count of what is missing.
1209
+ if (dropped > 0) {
1210
+ const priorityRow = this.#priorityRowFor(seg, [], [], topFillWidth);
1211
+ if (priorityRow !== null) {
1212
+ this.#renderedRowsCache = { key: cacheKey, rows: [priorityRow] };
1213
+ return [priorityRow];
1214
+ }
1215
+ }
1216
+
1081
1217
  // Reserve marker cells on whichever row ends up last, evicting from its
1082
1218
  // tail until the cue fits. Never opens a new row: that would contradict
1083
1219
  // maxRows. Emptying that row is fine -- the marker then occupies it alone.
@@ -1161,7 +1161,7 @@ export class CommandController {
1161
1161
  }
1162
1162
 
1163
1163
  try {
1164
- await this.ctx.sessionManager.runExclusiveCwdTransition(async () => {
1164
+ await this.ctx.sessionManager.runExclusiveCwdMoveTransition(async () => {
1165
1165
  await this.ctx.sessionManager.flush();
1166
1166
  await this.ctx.sessionManager.moveTo(resolvedPath);
1167
1167
  setProjectDir(resolvedPath);
@@ -57,6 +57,17 @@ export const __eventControllerPerfCounters = {
57
57
  };
58
58
 
59
59
  const COMPLETION_NOTIFY_COMMAND_TIMEOUT_MS = 10_000;
60
+ const TEXTLESS_LENGTH_WARNING =
61
+ 'Response ended with stopReason "length" before producing visible text or a tool call. Check the model\'s output and context token limits (maxTokens in models.yml) or lower the reasoning level (/reasoning), then retry.';
62
+
63
+ function isTextlessLengthStop(message: AssistantMessage | undefined): boolean {
64
+ return (
65
+ message?.stopReason === "length" &&
66
+ !message.content.some(
67
+ block => (block.type === "text" && block.text.trim().length > 0) || block.type === "toolCall",
68
+ )
69
+ );
70
+ }
60
71
 
61
72
  interface CompletionNotifyPayload {
62
73
  type: "agent-turn-complete";
@@ -904,7 +915,7 @@ export class EventController {
904
915
  }
905
916
  }
906
917
 
907
- async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
918
+ async #handleAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
908
919
  this.ctx.setWorkingMessage(undefined);
909
920
  stopInteractiveActivityIndicator(this.ctx, { foregroundSettled: true });
910
921
  if (this.ctx.streamingComponent) {
@@ -929,6 +940,14 @@ export class EventController {
929
940
  this.ctx.ui.requestRender();
930
941
  this.#scheduleIdleCompaction();
931
942
  emitHostStatus("finished");
943
+ const lastAssistant = this.ctx.session.getLastAssistantMessage();
944
+ if (
945
+ event.stopReason !== "cancelled" &&
946
+ event.stopReason !== "maintenance" &&
947
+ isTextlessLengthStop(lastAssistant)
948
+ ) {
949
+ this.ctx.showWarning(TEXTLESS_LENGTH_WARNING);
950
+ }
932
951
  this.sendCompletionNotification();
933
952
  this.ctx.promptSuggestion?.onAgentEnd();
934
953
  }
@@ -1204,13 +1223,20 @@ export class EventController {
1204
1223
  // errored — those are not "Task complete" events. Mirrors the gate
1205
1224
  // already used by #currentContextTokens, #handleMessageEnd, and the
1206
1225
  // retry / TTSR / compaction skip paths across agent-session.ts.
1207
- const last = this.ctx.session.getLastAssistantMessage?.();
1226
+ const last = this.ctx.session.getLastAssistantMessage();
1208
1227
  if (last?.stopReason === "aborted" || last?.stopReason === "error") return;
1209
1228
 
1210
1229
  const sessionName = this.ctx.sessionManager.getSessionName();
1211
- const title = sessionName ? `${sessionName}: Complete` : "Complete";
1230
+ const textlessLengthStop = isTextlessLengthStop(last);
1231
+ const title = textlessLengthStop
1232
+ ? sessionName
1233
+ ? `${sessionName}: Output limit reached`
1234
+ : "Output limit reached"
1235
+ : sessionName
1236
+ ? `${sessionName}: Complete`
1237
+ : "Complete";
1212
1238
  const summary = summaryFromMessage(last, 1000);
1213
- const body = summary ?? "Complete";
1239
+ const body = textlessLengthStop ? TEXTLESS_LENGTH_WARNING : (summary ?? "Complete");
1214
1240
  const sessionManager = this.ctx.sessionManager as { getCwd?: () => string; getSessionId?: () => string };
1215
1241
  const payload: CompletionNotifyPayload = {
1216
1242
  type: "agent-turn-complete",
@@ -39,6 +39,7 @@ import {
39
39
  SYNTHETIC_PROVIDER_ID,
40
40
  syntheticNamespaceCollision,
41
41
  } from "../../sdk/model-profile-model";
42
+ import { bashBackgroundControlError } from "../../session/fold-coordinator";
42
43
  import { createReadonlySessionManager } from "../../session/session-manager";
43
44
  import { parseThinkingLevel } from "../../thinking";
44
45
  import type { TodoPhase } from "../../tools/todo-write";
@@ -311,12 +312,11 @@ export class ExtensionUiController {
311
312
  throw Object.assign(new Error("No retry backoff is pending."), { code: "retry_not_pending" });
312
313
  session.retryNow();
313
314
  return { retried: true, immediate: true };
314
- case "bash.background":
315
- if (!(await session.requestForegroundBashBackground()))
316
- throw Object.assign(new Error("The active bash command cannot be moved to a managed background job."), {
317
- code: "not_foldable",
318
- });
319
- return { backgrounded: true };
315
+ case "bash.background": {
316
+ const outcome = await session.requestForegroundBashBackgroundOutcome("sdk_control");
317
+ if (outcome.status !== "folded") throw bashBackgroundControlError(outcome);
318
+ return { backgrounded: true, jobId: outcome.jobId };
319
+ }
320
320
  case "compaction.auto.set":
321
321
  session.setAutoCompactionEnabled(input.on === true);
322
322
  return { changed: true };
@@ -591,8 +591,8 @@ export class ExtensionUiController {
591
591
  this.ctx.session.setFollowUpMode(mode);
592
592
  return true;
593
593
  }
594
- if (kind === "interrupt" && (mode === "immediate" || mode === "wait")) {
595
- this.ctx.session.setInterruptMode(mode);
594
+ if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
595
+ this.ctx.session.setToolInterruptPolicy(mode);
596
596
  return true;
597
597
  }
598
598
  return false;
@@ -821,8 +821,8 @@ export class ExtensionUiController {
821
821
  this.ctx.session.setFollowUpMode(mode);
822
822
  return true;
823
823
  }
824
- if (kind === "interrupt" && (mode === "immediate" || mode === "wait")) {
825
- this.ctx.session.setInterruptMode(mode);
824
+ if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
825
+ this.ctx.session.setToolInterruptPolicy(mode);
826
826
  return true;
827
827
  }
828
828
  return false;
@@ -962,8 +962,8 @@ export class ExtensionUiController {
962
962
  this.ctx.session.setFollowUpMode(mode);
963
963
  return true;
964
964
  }
965
- if (kind === "interrupt" && (mode === "immediate" || mode === "wait")) {
966
- this.ctx.session.setInterruptMode(mode);
965
+ if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
966
+ this.ctx.session.setToolInterruptPolicy(mode);
967
967
  return true;
968
968
  }
969
969
  return false;
@@ -973,7 +973,7 @@ export class ExtensionUiController {
973
973
  getConfigItems: () => ({
974
974
  steeringMode: this.ctx.session.steeringMode,
975
975
  followUpMode: this.ctx.session.followUpMode,
976
- interruptMode: this.ctx.session.interruptMode,
976
+ toolInterruptPolicy: this.ctx.session.toolInterruptPolicy,
977
977
  }),
978
978
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
979
979
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
@@ -420,6 +420,34 @@ export class InputController {
420
420
  if (this.ctx.cancelPendingSubmission()) {
421
421
  return true;
422
422
  }
423
+ if (options.processes && this.ctx.session.isBashRunning) {
424
+ this.ctx.session.abortBash();
425
+ return true;
426
+ }
427
+ if (options.processes && this.ctx.session.isEvalRunning) {
428
+ this.ctx.session.abortEval();
429
+ return true;
430
+ }
431
+ // A queued steer on a live stream is owned by the steering branch below,
432
+ // which silently consumes it and auto-continues. This branch counts the same
433
+ // queue as cancellable work, and a streaming turn always has the indicator
434
+ // mounted, so without this hand-off it short-circuited first and turned the
435
+ // documented steer-on-interrupt into a loud abort that dumped the steer back
436
+ // into the composer. `cancelPendingSubmission()` above still wins: a
437
+ // submission that has not started yet is cancelled before the stream owns
438
+ // the key, and one that has started returns false there by design.
439
+ if (
440
+ options.streaming === true &&
441
+ this.ctx.session.isStreaming &&
442
+ this.ctx.session.hasQueuedSteering &&
443
+ !this.ctx.session.isBashRunning &&
444
+ !this.ctx.session.isEvalRunning &&
445
+ !this.#steerConsumePending
446
+ ) {
447
+ this.#steerConsumePending = true;
448
+ void this.#abortInteractive({ silent: true });
449
+ return true;
450
+ }
423
451
  // Only count queues this handler can actually drain. The aggregate
424
452
  // `queuedMessageCount` also counts hidden next-turn context, which
425
453
  // `restoreQueuedMessagesToEditor()` never returns and which survives turn
@@ -1142,7 +1170,7 @@ export class InputController {
1142
1170
  const promptOptions =
1143
1171
  streamingBehavior === "followUp"
1144
1172
  ? { streamingBehavior, images, followUpQueuePolicy: "sequential" as const }
1145
- : { streamingBehavior, images };
1173
+ : { streamingBehavior, images, steerQueuePolicy: "sequential" as const };
1146
1174
  await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, promptOptions), {
1147
1175
  imageCount: images?.length ?? 0,
1148
1176
  });
@@ -1655,7 +1683,7 @@ export class InputController {
1655
1683
  },
1656
1684
  streamingBehavior === "followUp"
1657
1685
  ? { streamingBehavior, followUpQueuePolicy: "sequential" }
1658
- : { streamingBehavior },
1686
+ : { streamingBehavior, steerQueuePolicy: "sequential" },
1659
1687
  );
1660
1688
  }
1661
1689
  if (this.ctx.session.isStreaming) {