@gajae-code/coding-agent 0.12.0 → 0.12.2

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 (247) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +56 -2
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
  16. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
  17. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  18. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  19. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  20. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  21. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
  22. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  23. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  24. package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
  25. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  26. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  27. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  28. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  29. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  30. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  31. package/dist/types/modes/controllers/event-controller.d.ts +1 -1
  32. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  33. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  34. package/dist/types/modes/interactive-mode.d.ts +9 -0
  35. package/dist/types/modes/theme/theme.d.ts +3 -1
  36. package/dist/types/modes/types.d.ts +32 -0
  37. package/dist/types/reminders/star-reminder.d.ts +3 -13
  38. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  39. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  40. package/dist/types/sdk/broker/transport.d.ts +2 -0
  41. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
  43. package/dist/types/sdk/bus/config.d.ts +6 -2
  44. package/dist/types/sdk/bus/index.d.ts +29 -0
  45. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  46. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  47. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  48. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  49. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  50. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  51. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  52. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
  53. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  54. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  55. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  56. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  57. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  58. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  59. package/dist/types/sdk/prompt-status.d.ts +23 -3
  60. package/dist/types/sdk/session.d.ts +6 -0
  61. package/dist/types/session/agent-session.d.ts +40 -2
  62. package/dist/types/session/artifacts.d.ts +8 -0
  63. package/dist/types/session/blob-store.d.ts +57 -4
  64. package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
  65. package/dist/types/session/session-manager.d.ts +23 -6
  66. package/dist/types/session/session-storage.d.ts +4 -0
  67. package/dist/types/session/streaming-output.d.ts +36 -7
  68. package/dist/types/setup/credential-import.d.ts +12 -0
  69. package/dist/types/skill-state/active-state.d.ts +12 -0
  70. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  71. package/dist/types/task/executor.d.ts +3 -2
  72. package/dist/types/task/index.d.ts +7 -1
  73. package/dist/types/task/output-manager.d.ts +1 -0
  74. package/dist/types/task/provider-retry-status.d.ts +8 -0
  75. package/dist/types/task/receipt.d.ts +9 -1
  76. package/dist/types/task/types.d.ts +23 -0
  77. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  78. package/dist/types/tools/bash-interactive.d.ts +4 -1
  79. package/dist/types/tools/bash.d.ts +3 -1
  80. package/dist/types/tools/image-gen.d.ts +4 -0
  81. package/dist/types/tools/index.d.ts +6 -0
  82. package/dist/types/tools/output-meta.d.ts +23 -2
  83. package/dist/types/tools/path-utils.d.ts +1 -0
  84. package/dist/types/tools/read.d.ts +0 -6
  85. package/dist/types/tools/review.d.ts +5 -7
  86. package/dist/types/tools/subagent.d.ts +2 -0
  87. package/dist/types/utils/gh.d.ts +13 -0
  88. package/dist/types/utils/image-resize.d.ts +1 -0
  89. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  90. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  91. package/dist/types/web/search/providers/xai.d.ts +2 -0
  92. package/package.json +10 -8
  93. package/scripts/acp-conformance-agent.ts +218 -0
  94. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  95. package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
  96. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  97. package/scripts/resident-memory-bench.ts +1341 -0
  98. package/scripts/run-acp-conformance.ts +290 -0
  99. package/src/async/job-manager.ts +99 -39
  100. package/src/cli/fast-help.ts +1 -1
  101. package/src/cli/plugin-cli.ts +176 -18
  102. package/src/commands/team.ts +19 -1
  103. package/src/config/file-lock.ts +11 -2
  104. package/src/config/settings-schema.ts +53 -2
  105. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  106. package/src/coordinator-mcp/server.ts +5 -12
  107. package/src/debug/index.ts +42 -17
  108. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  109. package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
  110. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  111. package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
  112. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  113. package/src/defaults/gjc-defaults.ts +4 -4
  114. package/src/exa/mcp-client.ts +12 -3
  115. package/src/exec/bash-executor.ts +144 -27
  116. package/src/extensibility/extensions/runner.ts +15 -0
  117. package/src/extensibility/extensions/types.ts +18 -1
  118. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  119. package/src/extensibility/gjc-plugins/index.ts +19 -2
  120. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  121. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  122. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  123. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  124. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  125. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  126. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  127. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  128. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  129. package/src/extensibility/gjc-plugins/types.ts +141 -0
  130. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  131. package/src/gjc-runtime/launch-tmux.ts +25 -6
  132. package/src/gjc-runtime/launch-worktree.ts +103 -1
  133. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  134. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  135. package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
  136. package/src/gjc-runtime/ralplan-runtime.ts +491 -27
  137. package/src/gjc-runtime/team-launch.ts +8 -0
  138. package/src/gjc-runtime/team-runtime.ts +140 -32
  139. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  140. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  141. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  142. package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
  143. package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
  144. package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
  145. package/src/gjc-runtime/workflow-manifest.ts +21 -70
  146. package/src/internal-urls/docs-index.generated.ts +16 -16
  147. package/src/modes/DESIGN.md +104 -0
  148. package/src/modes/acp/acp-agent.ts +298 -186
  149. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  150. package/src/modes/components/irc-sidebar.ts +30 -1
  151. package/src/modes/components/notifications-settings-editor.ts +12 -1
  152. package/src/modes/components/oauth-selector.ts +68 -30
  153. package/src/modes/components/session-observer-overlay.ts +305 -86
  154. package/src/modes/components/settings-selector.ts +56 -5
  155. package/src/modes/components/status-line/gh.ts +16 -0
  156. package/src/modes/components/tool-status-header.ts +3 -15
  157. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  158. package/src/modes/controllers/command-controller.ts +42 -34
  159. package/src/modes/controllers/event-controller.ts +27 -16
  160. package/src/modes/controllers/extension-ui-controller.ts +193 -34
  161. package/src/modes/controllers/selector-controller.ts +160 -22
  162. package/src/modes/interactive-mode.ts +189 -62
  163. package/src/modes/jobs-observer.ts +1 -1
  164. package/src/modes/runtime-init.ts +3 -0
  165. package/src/modes/theme/theme.ts +11 -9
  166. package/src/modes/types.ts +88 -0
  167. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  168. package/src/prompts/system/eager-todo.md +8 -4
  169. package/src/prompts/system/system-prompt.md +11 -4
  170. package/src/prompts/tools/bash.md +5 -5
  171. package/src/prompts/tools/search-tool-bm25.md +5 -0
  172. package/src/prompts/tools/task.md +1 -1
  173. package/src/reminders/star-reminder.ts +4 -42
  174. package/src/runtime-mcp/manager.ts +19 -4
  175. package/src/sdk/acp/adapter.ts +22 -6
  176. package/src/sdk/broker/lifecycle.ts +18 -13
  177. package/src/sdk/broker/transport.ts +35 -3
  178. package/src/sdk/bus/attachment-registry.ts +5 -1
  179. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  180. package/src/sdk/bus/chat-daemon-control.ts +7 -3
  181. package/src/sdk/bus/config.ts +18 -2
  182. package/src/sdk/bus/index.ts +661 -126
  183. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  184. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  185. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  186. package/src/sdk/bus/reconciliation-store.ts +95 -16
  187. package/src/sdk/bus/rich-draft.ts +1 -1
  188. package/src/sdk/bus/rich-render.ts +2 -2
  189. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  190. package/src/sdk/bus/telegram-cli.ts +33 -8
  191. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  192. package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
  193. package/src/sdk/bus/telegram-daemon.ts +916 -23
  194. package/src/sdk/bus/telegram-reference.ts +40 -3
  195. package/src/sdk/bus/threaded-render.ts +48 -3
  196. package/src/sdk/bus/topic-registry.ts +20 -0
  197. package/src/sdk/host/query/revision-store.ts +36 -2
  198. package/src/sdk/host/reverse-leases.ts +44 -7
  199. package/src/sdk/prompt-status.ts +16 -3
  200. package/src/sdk/protocol/operation-inventory.generated.json +33 -0
  201. package/src/sdk/session.ts +89 -9
  202. package/src/session/agent-session.ts +844 -173
  203. package/src/session/artifacts.ts +22 -0
  204. package/src/session/auth-broker-config.ts +10 -3
  205. package/src/session/blob-store.ts +915 -24
  206. package/src/session/internal/managed-session-scope.ts +200 -111
  207. package/src/session/internal/managed-session-storage.ts +492 -62
  208. package/src/session/session-manager.ts +1614 -435
  209. package/src/session/session-storage.ts +38 -45
  210. package/src/session/streaming-output.ts +238 -68
  211. package/src/setup/credential-import.ts +75 -13
  212. package/src/skill-state/active-state.ts +32 -0
  213. package/src/skill-state/workflow-hud.ts +17 -0
  214. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  215. package/src/task/executor.ts +122 -39
  216. package/src/task/index.ts +280 -45
  217. package/src/task/output-manager.ts +24 -14
  218. package/src/task/provider-retry-status.ts +16 -0
  219. package/src/task/receipt.ts +34 -8
  220. package/src/task/render.ts +6 -2
  221. package/src/task/types.ts +108 -0
  222. package/src/tools/ask-answer-registry.ts +12 -6
  223. package/src/tools/ask.ts +86 -55
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash-interactive.ts +8 -3
  227. package/src/tools/bash.ts +503 -60
  228. package/src/tools/find.ts +1 -0
  229. package/src/tools/image-gen.ts +29 -5
  230. package/src/tools/index.ts +6 -0
  231. package/src/tools/output-meta.ts +108 -14
  232. package/src/tools/path-utils.ts +3 -2
  233. package/src/tools/read.ts +18 -0
  234. package/src/tools/review.ts +5 -7
  235. package/src/tools/search.ts +1 -0
  236. package/src/tools/subagent-render.ts +6 -2
  237. package/src/tools/subagent.ts +6 -0
  238. package/src/tools/telegram-send.ts +6 -3
  239. package/src/utils/gh.ts +39 -0
  240. package/src/utils/image-resize.ts +3 -0
  241. package/src/web/search/providers/anthropic.ts +4 -4
  242. package/src/web/search/providers/kimi.ts +15 -5
  243. package/src/web/search/providers/searxng.ts +47 -4
  244. package/src/web/search/providers/xai.ts +7 -2
  245. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  246. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  247. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -0,0 +1,508 @@
1
+ import { Container, type SelectItem, SelectList, Spacer, Text } from "@gajae-code/tui";
2
+ import {
3
+ applyGjcBundleUpdate,
4
+ type GjcLifecycleContext,
5
+ getGjcBundle,
6
+ listGjcBundles,
7
+ previewGjcBundleUpdate,
8
+ setGjcBundleEnabled,
9
+ setGjcBundleSurfaceEnabled,
10
+ } from "../../extensibility/gjc-plugins/lifecycle";
11
+ import { identityEquals, identityKey } from "../../extensibility/gjc-plugins/lifecycle-reconciliation";
12
+ import { findingsForBundle, type GjcRuntimeSnapshotProvider } from "../../extensibility/gjc-plugins/runtime-quarantine";
13
+ import type {
14
+ GjcBundleIdentity,
15
+ GjcBundleSummary,
16
+ GjcLifecycleResult,
17
+ GjcReviewedUpdateToken,
18
+ GjcToggleResult,
19
+ GjcUpdateApplyResult,
20
+ GjcUpdatePreview,
21
+ } from "../../extensibility/gjc-plugins/types";
22
+ import { getSelectListTheme, theme } from "../../modes/theme/theme";
23
+ import { DynamicBorder } from "./dynamic-border";
24
+
25
+ /** Injectable lifecycle boundary; Settings never reads registries or executes bundle code. */
26
+ export interface GjcBundleLifecyclePort {
27
+ listGjcBundles(ctx: GjcLifecycleContext): Promise<GjcBundleSummary[]>;
28
+ getGjcBundle(ctx: GjcLifecycleContext, identity: GjcBundleIdentity): Promise<GjcLifecycleResult<GjcBundleSummary>>;
29
+ previewGjcBundleUpdate(
30
+ ctx: GjcLifecycleContext,
31
+ identity: GjcBundleIdentity,
32
+ ): Promise<GjcLifecycleResult<GjcUpdatePreview>>;
33
+ applyGjcBundleUpdate(
34
+ ctx: GjcLifecycleContext,
35
+ token: GjcReviewedUpdateToken,
36
+ ): Promise<GjcLifecycleResult<GjcUpdateApplyResult>>;
37
+ setGjcBundleEnabled(
38
+ ctx: GjcLifecycleContext,
39
+ identity: GjcBundleIdentity,
40
+ enabled: boolean,
41
+ ): Promise<GjcLifecycleResult<GjcToggleResult>>;
42
+ setGjcBundleSurfaceEnabled(
43
+ ctx: GjcLifecycleContext,
44
+ identity: GjcBundleIdentity,
45
+ surfaceId: string,
46
+ enabled: boolean,
47
+ ): Promise<GjcLifecycleResult<GjcToggleResult>>;
48
+ }
49
+
50
+ export interface GjcBundleSettingsDependencies {
51
+ lifecycle?: GjcBundleLifecyclePort;
52
+ runtimeSnapshotProvider?: GjcRuntimeSnapshotProvider;
53
+ activationGeneration?: number;
54
+ }
55
+
56
+ export interface GjcBundleSettingsCallbacks {
57
+ onClose: () => void;
58
+ onBundlesChanged?: () => void;
59
+ }
60
+
61
+ export type GjcBundleSettingsState =
62
+ | "loading"
63
+ | "error"
64
+ | "empty"
65
+ | "list"
66
+ | "detail"
67
+ | "unsupported-source"
68
+ | "update-review"
69
+ | "update-confirm"
70
+ | "update-running"
71
+ | "update-result"
72
+ | "stale-result"
73
+ | "quarantined-blocked"
74
+ | "mutation-in-flight-locked";
75
+
76
+ const PRODUCTION_LIFECYCLE: GjcBundleLifecyclePort = {
77
+ listGjcBundles,
78
+ getGjcBundle,
79
+ previewGjcBundleUpdate,
80
+ applyGjcBundleUpdate,
81
+ setGjcBundleEnabled,
82
+ setGjcBundleSurfaceEnabled,
83
+ };
84
+
85
+ /** Scope-qualified, lifecycle-backed Settings UI for installed GJC bundles. */
86
+ export class GjcBundleSettingsComponent extends Container {
87
+ #input: SelectList | null = null;
88
+ #bundles: GjcBundleSummary[] = [];
89
+ #focused: GjcBundleIdentity | null = null;
90
+ #preview: GjcUpdatePreview | null = null;
91
+ #state: GjcBundleSettingsState = "loading";
92
+ #message: string | null = null;
93
+ #navigationLocked = false;
94
+ #disposed = false;
95
+
96
+ constructor(
97
+ cwd: string,
98
+ private readonly callbacks: GjcBundleSettingsCallbacks,
99
+ private readonly dependencies: GjcBundleSettingsDependencies = {},
100
+ ) {
101
+ super();
102
+ this.#context = { cwd };
103
+ void this.#load();
104
+ }
105
+
106
+ readonly #context: GjcLifecycleContext;
107
+
108
+ get stateId(): GjcBundleSettingsState {
109
+ return this.#state;
110
+ }
111
+
112
+ get navigationLocked(): boolean {
113
+ return this.#navigationLocked;
114
+ }
115
+
116
+ get #lifecycle(): GjcBundleLifecyclePort {
117
+ return this.dependencies.lifecycle ?? PRODUCTION_LIFECYCLE;
118
+ }
119
+
120
+ async #load(): Promise<void> {
121
+ this.#state = "loading";
122
+ this.#message = null;
123
+ this.#render();
124
+ try {
125
+ this.#bundles = await this.#lifecycle.listGjcBundles(this.#context);
126
+ if (this.#disposed) return;
127
+ this.#state = this.#bundles.length === 0 ? "empty" : "list";
128
+ } catch {
129
+ if (this.#disposed) return;
130
+ this.#state = "error";
131
+ this.#message = "Unable to load GJC bundles.";
132
+ }
133
+ this.#render();
134
+ }
135
+
136
+ #render(): void {
137
+ if (this.#disposed) return;
138
+ this.clear();
139
+ this.#input = null;
140
+ this.addChild(new DynamicBorder());
141
+ this.addChild(new Text(theme.bold(theme.fg("accent", " GJC Bundles")), 0, 0));
142
+ this.addChild(new Spacer(1));
143
+ switch (this.#state) {
144
+ case "loading":
145
+ this.addChild(new Text(theme.fg("muted", " Loading installed bundles…"), 0, 0));
146
+ this.#renderActionList([]);
147
+ break;
148
+ case "error":
149
+ this.#renderActionList([
150
+ { value: "retry", label: "Retry", description: this.#message ?? "Unable to load GJC bundles." },
151
+ ]);
152
+ break;
153
+ case "empty":
154
+ this.addChild(new Text(theme.fg("muted", " No GJC bundles installed"), 0, 0));
155
+ this.#renderActionList([]);
156
+ break;
157
+ case "list":
158
+ this.#renderList();
159
+ break;
160
+ case "detail":
161
+ case "unsupported-source":
162
+ case "quarantined-blocked":
163
+ case "update-result":
164
+ case "stale-result":
165
+ this.#renderDetail();
166
+ break;
167
+ case "update-review":
168
+ case "update-confirm":
169
+ this.#renderUpdateReview();
170
+ break;
171
+ case "update-running":
172
+ case "mutation-in-flight-locked":
173
+ this.addChild(new Text(theme.fg("muted", " Applying non-cancellable bundle change…"), 0, 0));
174
+ break;
175
+ }
176
+ this.addChild(new Spacer(1));
177
+ this.addChild(
178
+ new Text(
179
+ theme.fg(
180
+ "dim",
181
+ this.#navigationLocked
182
+ ? " Navigation locked while mutation runs"
183
+ : " Enter to select · Esc to go back",
184
+ ),
185
+ 0,
186
+ 0,
187
+ ),
188
+ );
189
+ this.addChild(new DynamicBorder());
190
+ }
191
+
192
+ #renderList(): void {
193
+ const items: SelectItem[] = this.#bundles.map(bundle => {
194
+ const runtime = findingsForBundle(
195
+ this.dependencies.runtimeSnapshotProvider,
196
+ bundle.identity,
197
+ this.dependencies.activationGeneration ?? 0,
198
+ );
199
+ const intent = bundle.enabled ? "enabled" : "disabled";
200
+ const quarantine = bundle.quarantined ? " · quarantined" : "";
201
+ const effective =
202
+ runtime.status === "unavailable"
203
+ ? "runtime unavailable"
204
+ : runtime.findings.length > 0
205
+ ? "runtime findings"
206
+ : "runtime current";
207
+ return {
208
+ value: identityKey(bundle.identity),
209
+ label: `${bundle.identity.name} (${bundle.identity.scope})`,
210
+ description: `v${bundle.version} · ${intent}${quarantine} · ${effective}`,
211
+ };
212
+ });
213
+ this.#renderActionList(items);
214
+ }
215
+
216
+ #renderDetail(): void {
217
+ const bundle = this.#bundleForFocus();
218
+ if (!bundle) {
219
+ this.#state = "list";
220
+ this.#render();
221
+ return;
222
+ }
223
+ this.addChild(
224
+ new Text(theme.fg("muted", ` ${bundle.identity.name} (${bundle.identity.scope}) · v${bundle.version}`), 0, 0),
225
+ );
226
+ if (bundle.description) this.addChild(new Text(theme.fg("muted", ` ${bundle.description}`), 0, 0));
227
+ this.addChild(new Text(theme.fg("dim", ` Source: ${bundle.source.display}`), 0, 0));
228
+ if (bundle.source.ref) this.addChild(new Text(theme.fg("dim", ` Ref: ${bundle.source.ref}`), 0, 0));
229
+ if (bundle.source.sha) this.addChild(new Text(theme.fg("dim", ` SHA: ${bundle.source.sha}`), 0, 0));
230
+ this.addChild(
231
+ new Text(theme.fg("dim", ` Installed: ${bundle.installedAt} · Updated: ${bundle.updatedAt}`), 0, 0),
232
+ );
233
+ if (!bundle.source.updatable) {
234
+ this.addChild(
235
+ new Text(
236
+ theme.fg(
237
+ "warning",
238
+ ` Update unsupported: ${bundle.source.unsupportedReason ?? "This source cannot be refreshed."}`,
239
+ ),
240
+ 0,
241
+ 0,
242
+ ),
243
+ );
244
+ }
245
+ if (this.#message) this.addChild(new Text(theme.fg("warning", ` ${this.#message}`), 0, 0));
246
+ const actions: SelectItem[] = [];
247
+ if (bundle.enabled || !bundle.quarantined) {
248
+ actions.push({
249
+ value: `bundle:${bundle.enabled ? "disable" : "enable"}`,
250
+ label: bundle.enabled ? "Disable bundle" : "Enable bundle",
251
+ });
252
+ } else {
253
+ actions.push({
254
+ value: "bundle:blocked",
255
+ label: "Enable bundle (blocked)",
256
+ description: "Quarantined bundles cannot be enabled.",
257
+ });
258
+ }
259
+ if (bundle.source.updatable) actions.push({ value: "update", label: "Review update" });
260
+ for (const surface of bundle.surfaces) {
261
+ const action = surface.enabled ? "disable" : surface.quarantined ? "blocked" : "enable";
262
+ actions.push({
263
+ value: `surface:${surface.extensionId}:${action}`,
264
+ label: `${surface.name} (${surface.kind})`,
265
+ description: `${surface.enabled ? "enabled" : "disabled"}${surface.quarantined ? " · quarantined" : ""}`,
266
+ });
267
+ }
268
+ this.#renderActionList(actions);
269
+ }
270
+
271
+ #renderUpdateReview(): void {
272
+ const preview = this.#preview;
273
+ if (!preview) return;
274
+ this.addChild(new Text(theme.fg("muted", ` ${preview.identity.name} (${preview.identity.scope})`), 0, 0));
275
+ this.addChild(new Text(theme.fg("muted", ` v${preview.current.version} → v${preview.candidateVersion}`), 0, 0));
276
+ this.addChild(
277
+ new Text(
278
+ theme.fg(
279
+ "dim",
280
+ ` Surfaces: +${preview.addedSurfaceIds.length} −${preview.removedSurfaceIds.length} =${preview.retainedSurfaceIds.length}`,
281
+ ),
282
+ 0,
283
+ 0,
284
+ ),
285
+ );
286
+ if (!preview.changed) this.addChild(new Text(theme.fg("muted", " No update changes were found."), 0, 0));
287
+ this.#renderActionList([
288
+ this.#state === "update-review"
289
+ ? { value: "continue-update", label: "Continue to confirmation" }
290
+ : { value: "apply-update", label: "Apply update", description: "This operation cannot be cancelled." },
291
+ ]);
292
+ }
293
+
294
+ #renderActionList(items: SelectItem[]): void {
295
+ this.#input = new SelectList(items, Math.min(Math.max(items.length, 1), 12), getSelectListTheme());
296
+ this.#input.onSelect = item => this.#select(item.value);
297
+ this.#input.onCancel = () => this.#back();
298
+ this.addChild(this.#input);
299
+ }
300
+
301
+ #select(value: string): void {
302
+ if (this.#navigationLocked) return;
303
+ if (value === "retry") {
304
+ void this.#load();
305
+ return;
306
+ }
307
+ if (this.#state === "list") {
308
+ const bundle = this.#bundles.find(candidate => identityKey(candidate.identity) === value);
309
+ if (bundle) {
310
+ this.#focused = bundle.identity;
311
+ this.#state = bundle.source.updatable ? "detail" : "unsupported-source";
312
+ this.#render();
313
+ }
314
+ return;
315
+ }
316
+ if (value === "update") {
317
+ void this.#reviewUpdate();
318
+ return;
319
+ }
320
+ if (value === "continue-update") {
321
+ this.#state = "update-confirm";
322
+ this.#render();
323
+ return;
324
+ }
325
+ if (value === "apply-update") {
326
+ void this.#applyUpdate();
327
+ return;
328
+ }
329
+ if (value === "bundle:blocked" || value.endsWith(":blocked")) {
330
+ this.#state = "quarantined-blocked";
331
+ this.#message = "quarantined: enable is blocked; disable remains available.";
332
+ this.#render();
333
+ return;
334
+ }
335
+ if (value.startsWith("bundle:")) {
336
+ void this.#toggleBundle(value === "bundle:enable");
337
+ return;
338
+ }
339
+ if (value.startsWith("surface:")) {
340
+ const encoded = value.slice("surface:".length);
341
+ const separator = encoded.lastIndexOf(":");
342
+ const surfaceId = encoded.slice(0, separator);
343
+ const action = encoded.slice(separator + 1);
344
+ if (separator > 0 && surfaceId && action) void this.#toggleSurface(surfaceId, action === "enable");
345
+ }
346
+ }
347
+
348
+ async #reviewUpdate(): Promise<void> {
349
+ const bundle = this.#bundleForFocus();
350
+ if (!bundle) return;
351
+ this.#message = null;
352
+ try {
353
+ const result = await this.#lifecycle.previewGjcBundleUpdate(this.#context, bundle.identity);
354
+ if (this.#disposed || !this.#isFocused(bundle.identity)) return;
355
+ if (!result.ok) {
356
+ this.#state = result.error.code === "source_unsupported" ? "unsupported-source" : "detail";
357
+ this.#message = result.error.message;
358
+ } else {
359
+ this.#preview = result.value;
360
+ this.#state = "update-review";
361
+ }
362
+ } catch {
363
+ if (this.#disposed || !this.#isFocused(bundle.identity)) return;
364
+ this.#state = "detail";
365
+ this.#message = "Unable to review this update.";
366
+ }
367
+ this.#render();
368
+ }
369
+
370
+ async #applyUpdate(): Promise<void> {
371
+ const preview = this.#preview;
372
+ if (!preview) return;
373
+ await this.#mutate(preview.identity, "update-running", async () => {
374
+ const result = await this.#lifecycle.applyGjcBundleUpdate(this.#context, preview.token);
375
+ if (this.#disposed || !this.#isFocused(preview.identity)) return;
376
+ if (!result.ok) {
377
+ if (result.error.code.startsWith("stale_")) {
378
+ this.#state = "stale-result";
379
+ this.#message = `${result.error.message} Re-review the update.`;
380
+ } else {
381
+ this.#state = "detail";
382
+ this.#message = result.error.message;
383
+ }
384
+ return;
385
+ }
386
+ this.#replaceSummary(result.value.summary);
387
+ this.#state = "update-result";
388
+ this.#message = result.value.status === "updated" ? "Update applied." : "Already up to date.";
389
+ this.callbacks.onBundlesChanged?.();
390
+ });
391
+ }
392
+
393
+ async #toggleBundle(enabled: boolean): Promise<void> {
394
+ const bundle = this.#bundleForFocus();
395
+ if (!bundle) return;
396
+ if (enabled && bundle.quarantined) {
397
+ this.#state = "quarantined-blocked";
398
+ this.#message = "quarantined: enable is blocked; disable remains available.";
399
+ this.#render();
400
+ return;
401
+ }
402
+ await this.#mutate(bundle.identity, "mutation-in-flight-locked", async () => {
403
+ const result = await this.#lifecycle.setGjcBundleEnabled(this.#context, bundle.identity, enabled);
404
+ if (this.#disposed || !this.#isFocused(bundle.identity)) return;
405
+ this.#applyToggleResult(result);
406
+ });
407
+ }
408
+
409
+ async #toggleSurface(surfaceId: string, enabled: boolean): Promise<void> {
410
+ const bundle = this.#bundleForFocus();
411
+ if (!bundle) return;
412
+ const surface = bundle.surfaces.find(candidate => candidate.extensionId === surfaceId);
413
+ if (enabled && surface?.quarantined) {
414
+ this.#state = "quarantined-blocked";
415
+ this.#message = "quarantined: enable is blocked; disable remains available.";
416
+ this.#render();
417
+ return;
418
+ }
419
+ await this.#mutate(bundle.identity, "mutation-in-flight-locked", async () => {
420
+ const result = await this.#lifecycle.setGjcBundleSurfaceEnabled(
421
+ this.#context,
422
+ bundle.identity,
423
+ surfaceId,
424
+ enabled,
425
+ );
426
+ if (this.#disposed || !this.#isFocused(bundle.identity)) return;
427
+ this.#applyToggleResult(result);
428
+ });
429
+ }
430
+
431
+ #applyToggleResult(result: GjcLifecycleResult<GjcToggleResult>): void {
432
+ if (!result.ok) {
433
+ this.#state = result.error.code === "quarantined" ? "quarantined-blocked" : "detail";
434
+ this.#message = result.error.message;
435
+ return;
436
+ }
437
+ this.#replaceSummary(result.value.summary);
438
+ this.#state = "detail";
439
+ this.#message = null;
440
+ this.callbacks.onBundlesChanged?.();
441
+ }
442
+
443
+ async #mutate(
444
+ identity: GjcBundleIdentity,
445
+ runningState: "update-running" | "mutation-in-flight-locked",
446
+ operation: () => Promise<void>,
447
+ ): Promise<void> {
448
+ if (this.#navigationLocked) return;
449
+ this.#navigationLocked = true;
450
+ this.#state = runningState;
451
+ this.#render();
452
+ try {
453
+ await operation();
454
+ } catch {
455
+ if (!this.#disposed && this.#isFocused(identity)) {
456
+ this.#state = "detail";
457
+ this.#message = "Unable to apply this bundle change.";
458
+ }
459
+ } finally {
460
+ if (!this.#disposed) {
461
+ this.#navigationLocked = false;
462
+ if (this.#isFocused(identity)) this.#render();
463
+ }
464
+ }
465
+ }
466
+
467
+ #replaceSummary(summary: GjcBundleSummary): void {
468
+ const index = this.#bundles.findIndex(bundle => identityEquals(bundle.identity, summary.identity));
469
+ if (index >= 0) this.#bundles[index] = summary;
470
+ }
471
+
472
+ #bundleForFocus(): GjcBundleSummary | undefined {
473
+ return this.#focused ? this.#bundles.find(bundle => identityEquals(bundle.identity, this.#focused!)) : undefined;
474
+ }
475
+
476
+ #isFocused(identity: GjcBundleIdentity): boolean {
477
+ return this.#focused !== null && identityEquals(this.#focused, identity);
478
+ }
479
+
480
+ #back(): void {
481
+ if (this.#navigationLocked) return;
482
+ if (this.#state === "list" || this.#state === "empty" || this.#state === "error") {
483
+ this.callbacks.onClose();
484
+ return;
485
+ }
486
+ if (this.#state === "update-review" || this.#state === "update-confirm") {
487
+ this.#state = "detail";
488
+ this.#preview = null;
489
+ } else {
490
+ this.#focused = null;
491
+ this.#preview = null;
492
+ this.#message = null;
493
+ this.#state = this.#bundles.length === 0 ? "empty" : "list";
494
+ }
495
+ this.#render();
496
+ }
497
+
498
+ override dispose(): void {
499
+ this.#disposed = true;
500
+ this.#input = null;
501
+ super.dispose();
502
+ }
503
+
504
+ handleInput(data: string): void {
505
+ if (this.#navigationLocked) return;
506
+ this.#input?.handleInput(data);
507
+ }
508
+ }
@@ -82,6 +82,35 @@ export function computeIrcSplitWidths(width: number): {
82
82
  rightWidth: sidebarWidth,
83
83
  };
84
84
  }
85
+ /**
86
+ * Resolves the single shared work-lane geometry. The left lane is used by both
87
+ * transcript and todo content whenever the IRC lane is effective.
88
+ */
89
+ export function computeIrcWorkLaneWidths(
90
+ width: number,
91
+ sidebarVisible: boolean,
92
+ ): { leftWidth: number; separatorWidth: number; rightWidth: number } {
93
+ const fullWidth = Math.max(0, Math.floor(width));
94
+ return sidebarVisible
95
+ ? computeIrcSplitWidths(fullWidth)
96
+ : { leftWidth: fullWidth, separatorWidth: 0, rightWidth: 0 };
97
+ }
98
+
99
+ export class IrcLeftLaneComponent implements Component {
100
+ constructor(
101
+ private readonly content: Component,
102
+ private readonly isSidebarVisible: (width: number) => boolean,
103
+ ) {}
104
+
105
+ render(width: number): string[] {
106
+ const layout = computeIrcWorkLaneWidths(width, this.isSidebarVisible(width));
107
+ return this.content.render(layout.leftWidth);
108
+ }
109
+
110
+ invalidate(): void {
111
+ this.content.invalidate?.();
112
+ }
113
+ }
85
114
 
86
115
  type SemanticLine = Readonly<{
87
116
  kind: "header" | "body" | "elision" | "blank";
@@ -288,7 +317,7 @@ export class IrcSplitViewComponent implements ViewportAnchorProvider {
288
317
  renderWithViewportAnchors(width: number): ViewportAnchorRender {
289
318
  if (!this.#visible) return renderComponentWithViewportAnchors(this.leftPane, width);
290
319
  const componentTheme = typeof this.componentTheme === "function" ? this.componentTheme() : this.componentTheme;
291
- const { leftWidth, separatorWidth, rightWidth } = computeIrcSplitWidths(width);
320
+ const { leftWidth, separatorWidth, rightWidth } = computeIrcWorkLaneWidths(width, this.#visible);
292
321
  if (rightWidth === 0) return renderComponentWithViewportAnchors(this.leftPane, width);
293
322
  const separator = separatorWidth > 0 ? componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `) : "";
294
323
  const leftRender = withTerminalGraphicsFallback(
@@ -32,6 +32,7 @@ export interface NotificationsEditorPreferences {
32
32
  richDraftEnabled: boolean;
33
33
  toolActivityEnabled: boolean;
34
34
  streamingEnabled: boolean;
35
+ sound: "all" | "important" | "none";
35
36
  }
36
37
 
37
38
  /** Secret-safe snapshot used to render the Notifications tab. */
@@ -160,7 +161,8 @@ type HomeActionId =
160
161
  | "reconnect"
161
162
  | "remove"
162
163
  | "preferences"
163
- | "tool-activity";
164
+ | "tool-activity"
165
+ | "sound";
164
166
 
165
167
  interface Action {
166
168
  id: HomeActionId | "telegram" | "external" | "save" | "save-inactive" | "cancel" | "confirm";
@@ -197,6 +199,7 @@ function emptyState(): NotificationsEditorState {
197
199
  richDraftEnabled: false,
198
200
  toolActivityEnabled: false,
199
201
  streamingEnabled: true,
202
+ sound: "all",
200
203
  },
201
204
  };
202
205
  }
@@ -942,6 +945,9 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
942
945
  case "test":
943
946
  draft.streamingEnabled = !draft.streamingEnabled;
944
947
  return;
948
+ case "sound":
949
+ draft.sound = draft.sound === "all" ? "important" : draft.sound === "important" ? "none" : "all";
950
+ return;
945
951
  default:
946
952
  return;
947
953
  }
@@ -1092,6 +1098,11 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
1092
1098
  label: `Telegram streaming: ${draft.streamingEnabled ? "on" : "off"}`,
1093
1099
  description: "Toggle the unsaved Telegram streaming preference.",
1094
1100
  },
1101
+ {
1102
+ id: "sound",
1103
+ label: `Telegram notification sound: ${draft.sound}`,
1104
+ description: "Cycle between all, important, and none notification sounds.",
1105
+ },
1095
1106
  { id: "save", label: "Save preferences", description: "Atomically persist this preference draft." },
1096
1107
  { id: "cancel", label: "Cancel and discard draft", description: "Leave saved preferences unchanged." },
1097
1108
  ];