@gajae-code/coding-agent 0.7.1 → 0.7.3

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 (135) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/types/cli/mcp-cli.d.ts +25 -0
  3. package/dist/types/cli/notify-cli.d.ts +2 -0
  4. package/dist/types/cli.d.ts +6 -0
  5. package/dist/types/commands/mcp.d.ts +70 -0
  6. package/dist/types/config/keybindings.d.ts +2 -2
  7. package/dist/types/config/settings-schema.d.ts +39 -2
  8. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  9. package/dist/types/extensibility/shared-events.d.ts +1 -0
  10. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +1 -1
  11. package/dist/types/lsp/types.d.ts +2 -0
  12. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  13. package/dist/types/modes/components/model-selector.d.ts +2 -0
  14. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  15. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  16. package/dist/types/notifications/attachment-registry.d.ts +17 -0
  17. package/dist/types/notifications/chat-adapters.d.ts +9 -0
  18. package/dist/types/notifications/config.d.ts +9 -1
  19. package/dist/types/notifications/engine.d.ts +59 -0
  20. package/dist/types/notifications/managed-daemon.d.ts +48 -0
  21. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  22. package/dist/types/notifications/telegram-daemon.d.ts +73 -16
  23. package/dist/types/notifications/threaded-inbound.d.ts +19 -0
  24. package/dist/types/notifications/threaded-render.d.ts +6 -1
  25. package/dist/types/notifications/topic-registry.d.ts +2 -0
  26. package/dist/types/session/agent-session.d.ts +2 -0
  27. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  28. package/dist/types/tools/fetch.d.ts +23 -0
  29. package/dist/types/tools/index.d.ts +1 -0
  30. package/dist/types/tools/telegram-send.d.ts +32 -0
  31. package/dist/types/web/insane/bridge.d.ts +103 -0
  32. package/dist/types/web/insane/url-guard.d.ts +25 -0
  33. package/dist/types/web/scrapers/types.d.ts +5 -0
  34. package/dist/types/web/scrapers/utils.d.ts +7 -1
  35. package/dist/types/web/search/provider.d.ts +18 -1
  36. package/dist/types/web/search/providers/insane.d.ts +53 -0
  37. package/dist/types/web/search/providers/text-citations.d.ts +23 -0
  38. package/dist/types/web/search/types.d.ts +12 -4
  39. package/package.json +10 -8
  40. package/scripts/verify-insane-vendor.ts +132 -0
  41. package/src/cli/args.ts +1 -1
  42. package/src/cli/fast-help.ts +1 -1
  43. package/src/cli/mcp-cli.ts +272 -0
  44. package/src/cli/notify-cli.ts +152 -5
  45. package/src/cli.ts +6 -2
  46. package/src/commands/mcp.ts +117 -0
  47. package/src/commands/team.ts +1 -1
  48. package/src/config/keybindings.ts +2 -2
  49. package/src/config/settings-schema.ts +30 -1
  50. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  51. package/src/defaults/gjc/skills/deep-interview/SKILL.md +4 -3
  52. package/src/defaults/gjc/skills/ralplan/SKILL.md +11 -4
  53. package/src/defaults/gjc/skills/team/SKILL.md +3 -2
  54. package/src/extensibility/extensions/runner.ts +1 -0
  55. package/src/extensibility/shared-events.ts +1 -0
  56. package/src/gjc-runtime/launch-tmux.ts +17 -3
  57. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  58. package/src/gjc-runtime/ralplan-runtime.ts +2 -2
  59. package/src/gjc-runtime/tmux-common.ts +3 -1
  60. package/src/gjc-runtime/ultragoal-guard.ts +25 -8
  61. package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
  62. package/src/gjc-runtime/workflow-manifest.ts +7 -2
  63. package/src/hooks/skill-state.ts +57 -0
  64. package/src/internal-urls/docs-index.generated.ts +14 -11
  65. package/src/lsp/config.ts +16 -3
  66. package/src/lsp/defaults.json +7 -0
  67. package/src/lsp/types.ts +2 -0
  68. package/src/modes/bridge/bridge-mode.ts +11 -0
  69. package/src/modes/components/custom-editor.ts +2 -0
  70. package/src/modes/components/footer.ts +2 -3
  71. package/src/modes/components/model-selector.ts +12 -0
  72. package/src/modes/components/status-line/git-utils.ts +25 -0
  73. package/src/modes/components/status-line.ts +10 -11
  74. package/src/modes/components/welcome.ts +2 -3
  75. package/src/modes/controllers/event-controller.ts +15 -0
  76. package/src/modes/controllers/selector-controller.ts +3 -0
  77. package/src/modes/interactive-mode.ts +48 -3
  78. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  79. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  80. package/src/modes/theme/defaults/index.ts +2 -0
  81. package/src/modes/utils/context-usage.ts +2 -2
  82. package/src/notifications/attachment-registry.ts +23 -0
  83. package/src/notifications/chat-adapters.ts +147 -0
  84. package/src/notifications/config.ts +23 -2
  85. package/src/notifications/engine.ts +100 -0
  86. package/src/notifications/index.ts +180 -38
  87. package/src/notifications/managed-daemon.ts +163 -0
  88. package/src/notifications/operator-runtime.ts +171 -0
  89. package/src/notifications/telegram-daemon.ts +553 -236
  90. package/src/notifications/threaded-inbound.ts +60 -4
  91. package/src/notifications/threaded-render.ts +20 -2
  92. package/src/notifications/topic-registry.ts +5 -0
  93. package/src/session/agent-session.ts +82 -51
  94. package/src/slash-commands/helpers/parse.ts +2 -1
  95. package/src/tools/bash.ts +9 -0
  96. package/src/tools/composer-bash-policy.ts +96 -0
  97. package/src/tools/fetch.ts +94 -1
  98. package/src/tools/index.ts +3 -0
  99. package/src/tools/telegram-send.ts +137 -0
  100. package/src/web/insane/bridge.ts +350 -0
  101. package/src/web/insane/url-guard.ts +159 -0
  102. package/src/web/scrapers/types.ts +143 -45
  103. package/src/web/scrapers/utils.ts +70 -19
  104. package/src/web/search/provider.ts +77 -18
  105. package/src/web/search/providers/anthropic.ts +70 -3
  106. package/src/web/search/providers/codex.ts +1 -119
  107. package/src/web/search/providers/gemini.ts +99 -0
  108. package/src/web/search/providers/insane.ts +551 -0
  109. package/src/web/search/providers/openai-compatible.ts +66 -32
  110. package/src/web/search/providers/text-citations.ts +111 -0
  111. package/src/web/search/types.ts +13 -2
  112. package/vendor/insane-search/LICENSE +21 -0
  113. package/vendor/insane-search/MANIFEST.json +24 -0
  114. package/vendor/insane-search/engine/__init__.py +23 -0
  115. package/vendor/insane-search/engine/__main__.py +128 -0
  116. package/vendor/insane-search/engine/bias_check.py +183 -0
  117. package/vendor/insane-search/engine/executor.py +254 -0
  118. package/vendor/insane-search/engine/fetch_chain.py +725 -0
  119. package/vendor/insane-search/engine/learning.py +175 -0
  120. package/vendor/insane-search/engine/phase0.py +214 -0
  121. package/vendor/insane-search/engine/safety.py +91 -0
  122. package/vendor/insane-search/engine/templates/package.json +11 -0
  123. package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
  124. package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
  125. package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
  126. package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
  127. package/vendor/insane-search/engine/tests/test_u1.py +200 -0
  128. package/vendor/insane-search/engine/tests/test_u4.py +131 -0
  129. package/vendor/insane-search/engine/tests/test_u5.py +163 -0
  130. package/vendor/insane-search/engine/tests/test_u7.py +124 -0
  131. package/vendor/insane-search/engine/transport.py +211 -0
  132. package/vendor/insane-search/engine/url_transforms.py +98 -0
  133. package/vendor/insane-search/engine/validators.py +331 -0
  134. package/vendor/insane-search/engine/waf_detector.py +214 -0
  135. package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
package/src/lsp/config.ts CHANGED
@@ -88,6 +88,7 @@ function normalizeServerConfig(name: string, config: RawServerConfig): ServerCon
88
88
  : isRecord(config.initializationOptions)
89
89
  ? config.initializationOptions
90
90
  : undefined;
91
+ const supersedes = normalizeStringArray(config.supersedes);
91
92
 
92
93
  return {
93
94
  ...config,
@@ -96,6 +97,7 @@ function normalizeServerConfig(name: string, config: RawServerConfig): ServerCon
96
97
  fileTypes,
97
98
  rootMarkers,
98
99
  ...(initOptions ? { initOptions } : {}),
100
+ ...(supersedes ? { supersedes } : {}),
99
101
  };
100
102
  }
101
103
 
@@ -144,6 +146,18 @@ function mergeServers(
144
146
  return merged;
145
147
  }
146
148
 
149
+ function applyServerPrecedence(servers: Record<string, ServerConfig>): Record<string, ServerConfig> {
150
+ const suppressed = new Set<string>();
151
+ for (const config of Object.values(servers)) {
152
+ for (const serverName of config.supersedes ?? []) {
153
+ suppressed.add(serverName);
154
+ }
155
+ }
156
+ if (suppressed.size === 0) return servers;
157
+
158
+ return Object.fromEntries(Object.entries(servers).filter(([name]) => !suppressed.has(name)));
159
+ }
160
+
147
161
  function applyRuntimeDefaults(servers: Record<string, ServerConfig>): Record<string, ServerConfig> {
148
162
  const updated: Record<string, ServerConfig> = { ...servers };
149
163
 
@@ -422,9 +436,8 @@ export function loadConfig(cwd: string): LspConfig {
422
436
  detected[name] = { ...config, resolvedCommand: resolved };
423
437
  }
424
438
 
425
- return { servers: detected, idleTimeoutMs };
439
+ return { servers: applyServerPrecedence(detected), idleTimeoutMs };
426
440
  }
427
-
428
441
  // Merge overrides with defaults and filter to available servers
429
442
  const mergedWithRuntime = applyRuntimeDefaults(mergedServers);
430
443
  const available: Record<string, ServerConfig> = {};
@@ -437,7 +450,7 @@ export function loadConfig(cwd: string): LspConfig {
437
450
  available[name] = { ...config, resolvedCommand: resolved };
438
451
  }
439
452
 
440
- return { servers: available, idleTimeoutMs };
453
+ return { servers: applyServerPrecedence(available), idleTimeoutMs };
441
454
  }
442
455
 
443
456
  // =============================================================================
@@ -342,6 +342,13 @@
342
342
  "fileTypes": [".php"],
343
343
  "rootMarkers": ["composer.json", ".phpactor.json", ".phpactor.yml"]
344
344
  },
345
+ "csharp-ls": {
346
+ "command": "csharp-ls",
347
+ "args": [],
348
+ "fileTypes": [".cs", ".csx"],
349
+ "rootMarkers": ["*.sln", "*.slnx", "*.csproj", "global.json"],
350
+ "supersedes": ["omnisharp"]
351
+ },
345
352
  "omnisharp": {
346
353
  "command": "omnisharp",
347
354
  "args": ["-z", "--hostPID", "$PID", "--encoding", "utf-8", "--languageserver"],
package/src/lsp/types.ts CHANGED
@@ -358,6 +358,8 @@ export interface ServerConfig {
358
358
  /** Per-server warmup timeout in milliseconds. Overrides the global WARMUP_TIMEOUT_MS for this server during startup. */
359
359
  warmupTimeoutMs?: number;
360
360
  capabilities?: ServerCapabilities;
361
+ /** Names of lower-precedence servers to drop when this server is available. */
362
+ supersedes?: string[];
361
363
  /** If true, this is a linter/formatter server (e.g., Biome) - used only for diagnostics/actions, not type intelligence */
362
364
  isLinter?: boolean;
363
365
  /** Resolved absolute path to the command binary (set during config loading) */
@@ -153,6 +153,14 @@ function jsonResponse(status: number, body: unknown): Response {
153
153
  });
154
154
  }
155
155
 
156
+ function isBridgeControllerOwner(options: BridgeFetchHandlerOptions, ownerToken: string): boolean {
157
+ if (!ownerToken) return false;
158
+ const ownerTokens = [options.permissionBroker?.ownerToken, options.uiBroker?.ownerToken].filter(
159
+ (token): token is string => typeof token === "string" && token.length > 0,
160
+ );
161
+ return ownerTokens.length > 0 && ownerTokens.every(token => token === ownerToken);
162
+ }
163
+
156
164
  function parseBridgeScopes(value: string | undefined): readonly BridgeCommandScope[] {
157
165
  if (!value?.trim()) return DEFAULT_BRIDGE_SCOPES;
158
166
  const allowed = new Set(BRIDGE_COMMAND_SCOPES);
@@ -425,6 +433,9 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
425
433
  "answer" in payload &&
426
434
  (correlationId === (payload as RpcWorkflowGateResponse).gate_id || correlationId.startsWith("wg_"))
427
435
  ) {
436
+ if (!isBridgeControllerOwner(options, ownerToken)) {
437
+ return jsonResponse(403, { status: "rejected", code: "not_controller" });
438
+ }
428
439
  try {
429
440
  const resolution = await options.unattendedControlPlane?.resolveGate({
430
441
  gate_id: (payload as RpcWorkflowGateResponse).gate_id,
@@ -18,6 +18,7 @@ type ConfigurableEditorAction = Extract<
18
18
  | "app.editor.external"
19
19
  | "app.history.search"
20
20
  | "app.message.dequeue"
21
+ | "app.message.followUp"
21
22
  | "app.message.queue"
22
23
  | "app.clipboard.pasteImage"
23
24
  | "app.clipboard.copyPrompt"
@@ -40,6 +41,7 @@ const CONFIGURABLE_EDITOR_ACTIONS = [
40
41
  "app.thinking.toggle",
41
42
  "app.editor.external",
42
43
  "app.history.search",
44
+ "app.message.followUp",
43
45
  "app.message.queue",
44
46
  "app.message.dequeue",
45
47
  "app.clipboard.pasteImage",
@@ -8,6 +8,7 @@ import { shortenPath } from "../../tools/render-utils";
8
8
  import * as git from "../../utils/git";
9
9
  import { sanitizeStatusText } from "../shared";
10
10
  import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
11
+ import { resolveCurrentBranch } from "./status-line/git-utils";
11
12
 
12
13
  /**
13
14
  * Footer component that shows pwd, token stats, and context usage
@@ -103,9 +104,7 @@ export class FooterComponent implements Component {
103
104
  return this.#cachedBranch;
104
105
  }
105
106
 
106
- const headState = git.head.resolveSync(getProjectDir());
107
- this.#cachedBranch =
108
- headState === null ? null : headState.kind === "ref" ? (headState.branchName ?? headState.ref) : "detached";
107
+ this.#cachedBranch = resolveCurrentBranch(getProjectDir()).branch;
109
108
  return this.#cachedBranch;
110
109
  }
111
110
 
@@ -221,6 +221,8 @@ export class ModelSelectorComponent extends Container {
221
221
  #scopedModels: ReadonlyArray<ScopedModelItem>;
222
222
  #temporaryOnly: boolean;
223
223
  #currentModel?: Model;
224
+ #currentThinkingLevel?: ThinkingLevel;
225
+ #activeModelProfile?: string;
224
226
  #isFastForProvider: (provider?: string) => boolean = () => false;
225
227
  #isFastForSubagentProvider: (provider?: string) => boolean = () => false;
226
228
  #pendingActionItem?: ModelItem | CanonicalModelItem;
@@ -258,6 +260,8 @@ export class ModelSelectorComponent extends Container {
258
260
  sessionId?: string;
259
261
  isFastForProvider?: (provider?: string) => boolean;
260
262
  isFastForSubagentProvider?: (provider?: string) => boolean;
263
+ currentThinkingLevel?: ThinkingLevel;
264
+ activeModelProfile?: string;
261
265
  },
262
266
  ) {
263
267
  super();
@@ -271,6 +275,8 @@ export class ModelSelectorComponent extends Container {
271
275
  this.#temporaryOnly = options?.temporaryOnly ?? false;
272
276
  this.#authSessionId = options?.sessionId;
273
277
  this.#currentModel = _currentModel;
278
+ this.#currentThinkingLevel = options?.currentThinkingLevel;
279
+ this.#activeModelProfile = options?.activeModelProfile;
274
280
  this.#isFastForProvider = options?.isFastForProvider ?? (() => false);
275
281
  this.#isFastForSubagentProvider = options?.isFastForSubagentProvider ?? (() => false);
276
282
  const initialSearchInput = options?.initialSearchInput;
@@ -367,6 +373,12 @@ export class ModelSelectorComponent extends Container {
367
373
  };
368
374
  }
369
375
  }
376
+ if (this.#activeModelProfile && this.#currentModel) {
377
+ this.#roles.default = {
378
+ model: this.#currentModel,
379
+ thinkingLevel: this.#currentThinkingLevel ?? ThinkingLevel.Inherit,
380
+ };
381
+ }
370
382
  }
371
383
 
372
384
  #sortModels(models: ModelItem[]): void {
@@ -1,3 +1,6 @@
1
+ import type { GitHeadState } from "../../../utils/git";
2
+ import * as git from "../../../utils/git";
3
+
1
4
  /**
2
5
  * Extract "owner/repo" from a GitHub remote URL.
3
6
  * Handles HTTPS, SSH (scp-style), and git:// protocols.
@@ -40,3 +43,25 @@ export function canReuseCachedPr(
40
43
  ): boolean {
41
44
  return cachedPr !== undefined && currentContext !== null && isSamePrCacheContext(cachedContext, currentContext);
42
45
  }
46
+
47
+ export interface CurrentBranchState {
48
+ readonly branch: string | null;
49
+ readonly repoId: string | null;
50
+ }
51
+
52
+ export function resolveCurrentBranch(
53
+ cwd: string,
54
+ resolveHead: (cwd: string) => GitHeadState | null = git.head.resolveSync,
55
+ ): CurrentBranchState {
56
+ try {
57
+ const head = resolveHead(cwd);
58
+ if (!head) return { branch: null, repoId: null };
59
+ return {
60
+ branch: head.kind === "ref" ? (head.branchName ?? head.ref) : "detached",
61
+ repoId: head.headPath,
62
+ };
63
+ } catch (error) {
64
+ if (error instanceof Error) return { branch: null, repoId: null };
65
+ throw error;
66
+ }
67
+ }
@@ -20,6 +20,7 @@ import {
20
20
  createPrCacheContext,
21
21
  isSamePrCacheContext,
22
22
  type PrCacheContext,
23
+ resolveCurrentBranch,
23
24
  } from "./status-line/git-utils";
24
25
  import { getPreset } from "./status-line/presets";
25
26
  import { renderSegment, type SegmentContext } from "./status-line/segments";
@@ -303,19 +304,13 @@ export class StatusLineComponent implements Component {
303
304
  this.#cachedPrContext = undefined;
304
305
  }
305
306
  #getCurrentBranch(): string | null {
306
- const head = git.head.resolveSync(getProjectDir());
307
- const gitHeadPath = head?.headPath ?? null;
308
- if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === gitHeadPath) {
307
+ const current = resolveCurrentBranch(getProjectDir());
308
+ if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === current.repoId) {
309
309
  return this.#cachedBranch;
310
310
  }
311
311
 
312
- this.#cachedBranchRepoId = gitHeadPath;
313
- if (!head) {
314
- this.#cachedBranch = null;
315
- return null;
316
- }
317
-
318
- this.#cachedBranch = head.kind === "ref" ? (head.branchName ?? head.ref) : "detached";
312
+ this.#cachedBranchRepoId = current.repoId;
313
+ this.#cachedBranch = current.branch;
319
314
 
320
315
  return this.#cachedBranch ?? null;
321
316
  }
@@ -680,7 +675,11 @@ export class StatusLineComponent implements Component {
680
675
  const effectiveSettings = this.#resolveSettings();
681
676
  const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
682
677
 
683
- const bgAnsi = theme.getBgAnsi("statusLineBg");
678
+ // Use the subtle surface tone (the same elevated background as user-message
679
+ // bubbles) instead of the heavy `statusLineBg` block, so the rail layers
680
+ // just above the base background as a quiet zone rather than a solid bar.
681
+ // Resolving through a semantic slot keeps it correct across every theme.
682
+ const bgAnsi = theme.getBgAnsi("userMessageBg");
684
683
  const fgAnsi = theme.getFgAnsi("text");
685
684
  const sepAnsi = theme.getFgAnsi("statusLineSep");
686
685
 
@@ -74,9 +74,8 @@ export class WelcomeComponent implements Component {
74
74
  }
75
75
 
76
76
  render(termWidth: number): string[] {
77
- // Box dimensions - responsive with max width and small-terminal support
78
- const maxWidth = 100;
79
- const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
77
+ // Box dimensions track the live viewport so wide terminals feel intentionally full-screen.
78
+ const boxWidth = Math.max(0, termWidth - 2);
80
79
  if (boxWidth < 4) {
81
80
  return [];
82
81
  }
@@ -201,6 +201,7 @@ export class EventController {
201
201
  this.ctx.statusContainer.clear();
202
202
  }
203
203
  this.#cancelIdleCompaction();
204
+ this.ctx.updateEditorBorderColor();
204
205
  this.ctx.ensureLoadingAnimation();
205
206
  this.ctx.ui.requestRender();
206
207
  }
@@ -633,6 +634,7 @@ export class EventController {
633
634
  this.#readToolCallArgs.clear();
634
635
  this.#readToolCallAssistantComponents.clear();
635
636
  this.#lastAssistantComponent = undefined;
637
+ this.ctx.updateEditorBorderColor();
636
638
  this.ctx.ui.requestRender();
637
639
  this.#scheduleIdleCompaction();
638
640
  this.sendCompletionNotification();
@@ -642,6 +644,7 @@ export class EventController {
642
644
  event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
643
645
  ): Promise<void> {
644
646
  this.#cancelIdleCompaction();
647
+ this.ctx.updateEditorBorderColor();
645
648
  this.ctx.autoCompactionEscapeHandler = this.ctx.editor.onEscape;
646
649
  this.ctx.editor.onEscape = () => {
647
650
  this.ctx.session.abortCompaction();
@@ -672,13 +675,20 @@ export class EventController {
672
675
  this.ctx.autoCompactionLoader = undefined;
673
676
  this.ctx.statusContainer.clear();
674
677
  }
678
+ this.ctx.updateEditorBorderColor();
675
679
  const isHandoffAction = event.action === "handoff";
680
+ const continuationDisabled = event.continuationSkipReason === "auto_continue_disabled_non_resumable_tail";
676
681
  if (event.aborted) {
677
682
  this.ctx.showStatus(isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled");
678
683
  } else if (event.result) {
679
684
  this.ctx.rebuildChatFromMessages();
680
685
  this.ctx.statusLine.invalidate();
681
686
  this.ctx.updateEditorTopBorder();
687
+ if (continuationDisabled && !isHandoffAction) {
688
+ this.ctx.showStatus("Context overflow recovery skipped: auto_continue_disabled_non_resumable_tail");
689
+ } else if (event.willRetry && !isHandoffAction) {
690
+ this.ctx.showStatus("Context overflow maintenance completed");
691
+ }
682
692
  } else if (event.errorMessage) {
683
693
  this.ctx.showWarning(event.errorMessage);
684
694
  } else if (isHandoffAction) {
@@ -691,6 +701,11 @@ export class EventController {
691
701
  } else if (event.skipped) {
692
702
  // Benign skip: no model selected, no candidate models available, or nothing
693
703
  // to compact yet. Not a failure — suppress the warning.
704
+ if (continuationDisabled) {
705
+ this.ctx.showStatus("Context overflow recovery skipped: auto_continue_disabled_non_resumable_tail");
706
+ } else if (event.willRetry && !isHandoffAction) {
707
+ this.ctx.showStatus("Context overflow maintenance skipped");
708
+ }
694
709
  } else {
695
710
  this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
696
711
  }
@@ -745,6 +745,9 @@ export class SelectorController {
745
745
  {
746
746
  ...options,
747
747
  sessionId: this.ctx.session.sessionId,
748
+ currentThinkingLevel: this.ctx.session.thinkingLevel,
749
+ activeModelProfile:
750
+ this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
748
751
  isFastForProvider: provider => this.ctx.session.isFastForProvider(provider),
749
752
  isFastForSubagentProvider: provider => this.ctx.session.isFastForSubagentProvider(provider),
750
753
  },
@@ -29,7 +29,7 @@ import {
29
29
  import { APP_NAME, adjustHsv, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@gajae-code/utils";
30
30
  import chalk from "chalk";
31
31
  import { AsyncJobManager } from "../async";
32
- import { KeybindingsManager } from "../config/keybindings";
32
+ import { type AppKeybinding, KeybindingsManager } from "../config/keybindings";
33
33
  import { isSettingsInitialized, type Settings, settings } from "../config/settings";
34
34
  import { DEFAULT_GJC_DEFINITION_NAMES } from "../defaults/gjc-defaults";
35
35
  import type {
@@ -117,6 +117,24 @@ import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInpu
117
117
  import { UiHelpers } from "./utils/ui-helpers";
118
118
 
119
119
  const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
120
+ const DEFAULT_COMPOSER_PLACEHOLDER = "Type your message...";
121
+ const FRIENDLY_KEY_PARTS: Record<string, string> = {
122
+ alt: "Alt",
123
+ cmd: "Command",
124
+ command: "Command",
125
+ ctrl: "Control",
126
+ enter: "Enter",
127
+ meta: process.platform === "darwin" ? "Command" : "Meta",
128
+ option: "Option",
129
+ shift: "Shift",
130
+ };
131
+
132
+ function formatShortcutForPlaceholder(key: string): string {
133
+ return key
134
+ .split("+")
135
+ .map(part => FRIENDLY_KEY_PARTS[part.toLowerCase()] ?? part)
136
+ .join("+");
137
+ }
120
138
 
121
139
  const HINT_SHIMMER_PALETTE: ShimmerPalette = {
122
140
  low: "dim",
@@ -137,11 +155,11 @@ function getShellInputPrefix(isNoContext: boolean): string {
137
155
 
138
156
  function configureDefaultComposerChrome(editor: CustomEditor): void {
139
157
  editor.setBorderVisible(true);
140
- editor.setBorderStyle("sharp");
158
+ editor.setBorderStyle("round");
141
159
  editor.setClosedBorderBox(true);
142
160
  editor.setPromptGutter(undefined);
143
161
  editor.setInputPrefix(getDefaultInputPrefix());
144
- editor.setPlaceholder("Type your message...");
162
+ editor.setPlaceholder(DEFAULT_COMPOSER_PLACEHOLDER);
145
163
  editor.setPaddingX(1);
146
164
  editor.setTopBorder(undefined);
147
165
  }
@@ -550,6 +568,7 @@ export class InteractiveMode implements InteractiveModeContext {
550
568
  this.ui.addChild(this.hookWidgetContainerAbove);
551
569
  this.ui.addChild(this.editorContainer);
552
570
  this.ui.addChild(this.hookWidgetContainerBelow);
571
+ this.ui.setBottomPinnedComponent(this.statusLine);
553
572
  this.ui.setFocus(this.editor);
554
573
 
555
574
  this.#inputController.setupKeyHandlers();
@@ -903,6 +922,31 @@ export class InteractiveMode implements InteractiveModeContext {
903
922
  this.editor.setMaxHeight(this.#computeEditorMaxHeight());
904
923
  }
905
924
 
925
+ #isPromptDeliveryBusy(): boolean {
926
+ return this.session.isStreaming || this.session.isCompacting;
927
+ }
928
+
929
+ #getFirstKeyForAction(action: AppKeybinding): string | undefined {
930
+ return this.keybindings.getKeys(action)[0];
931
+ }
932
+
933
+ #getMessageQueueShortcut(): string | undefined {
934
+ const preferredAction: AppKeybinding =
935
+ process.platform === "darwin" ? "app.message.followUp" : "app.message.queue";
936
+ const fallbackAction: AppKeybinding =
937
+ process.platform === "darwin" ? "app.message.queue" : "app.message.followUp";
938
+ return this.#getFirstKeyForAction(preferredAction) ?? this.#getFirstKeyForAction(fallbackAction);
939
+ }
940
+
941
+ #getComposerPlaceholder(): string {
942
+ if (!this.#isPromptDeliveryBusy()) return DEFAULT_COMPOSER_PLACEHOLDER;
943
+ const enterAction = this.settings.get("busyPromptMode") === "steer" ? "Steering" : "Message Queueing";
944
+ const parts = [`Enter: ${enterAction}`];
945
+ const queueKey = this.#getMessageQueueShortcut();
946
+ if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}: Message Queueing`);
947
+ return `${DEFAULT_COMPOSER_PLACEHOLDER} ${parts.join(" · ")}`;
948
+ }
949
+
906
950
  updateEditorChrome(): void {
907
951
  if (this.isBashMode) {
908
952
  this.editor.borderColor = this.isBashNoContext
@@ -926,6 +970,7 @@ export class InteractiveMode implements InteractiveModeContext {
926
970
  if (!this.isBashMode) {
927
971
  this.editor.setInputPrefix(getDefaultInputPrefix());
928
972
  }
973
+ this.editor.setPlaceholder(this.#getComposerPlaceholder());
929
974
  this.#setComposerTopBorder();
930
975
  this.ui.requestRender();
931
976
  }
@@ -73,7 +73,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
73
73
  get_login_providers: "admin",
74
74
  login: "admin",
75
75
  negotiate_unattended: "control",
76
- workflow_gate_response: "prompt",
76
+ workflow_gate_response: "control",
77
77
  };
78
78
 
79
79
  export const RPC_COMMAND_TYPES: readonly RpcCommandType[] = Object.keys(RPC_COMMAND_SCOPE_REGISTRY) as RpcCommandType[];
@@ -0,0 +1,99 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "gruvbox-dark",
4
+ "vars": {
5
+ "bgHard": "#1d2021",
6
+ "surface": "#32302f",
7
+ "surfaceBright": "#504945",
8
+ "borderNeutral": "#504945",
9
+ "borderSubtle": "#3c3836",
10
+ "fg": "#ebdbb2",
11
+ "muted": "#a89984",
12
+ "dim": "#7c6f64",
13
+ "gray": "#928374",
14
+ "red": "#fb4934",
15
+ "green": "#b8bb26",
16
+ "yellow": "#fabd2f",
17
+ "blue": "#83a598",
18
+ "purple": "#d3869b",
19
+ "aqua": "#8ec07c",
20
+ "orange": "#fe8019",
21
+ "diffRemovalRed": "#cc241d"
22
+ },
23
+ "colors": {
24
+ "accent": "orange",
25
+ "border": "borderNeutral",
26
+ "borderAccent": "orange",
27
+ "borderMuted": "borderSubtle",
28
+ "success": "green",
29
+ "error": "red",
30
+ "warning": "yellow",
31
+ "muted": "muted",
32
+ "dim": "dim",
33
+ "text": "fg",
34
+ "thinkingText": "muted",
35
+ "selectedBg": "surfaceBright",
36
+ "userMessageBg": "surface",
37
+ "userMessageText": "fg",
38
+ "customMessageBg": "surface",
39
+ "customMessageText": "fg",
40
+ "customMessageLabel": "orange",
41
+ "toolPendingBg": "surface",
42
+ "toolSuccessBg": "#283626",
43
+ "toolErrorBg": "#3c2323",
44
+ "toolTitle": "fg",
45
+ "toolOutput": "muted",
46
+ "mdHeading": "yellow",
47
+ "mdLink": "blue",
48
+ "mdLinkUrl": "muted",
49
+ "mdCode": "aqua",
50
+ "mdCodeBlock": "fg",
51
+ "mdCodeBlockBorder": "borderNeutral",
52
+ "mdQuote": "muted",
53
+ "mdQuoteBorder": "borderNeutral",
54
+ "mdHr": "dim",
55
+ "mdListBullet": "orange",
56
+ "toolDiffAdded": "green",
57
+ "toolDiffRemoved": "diffRemovalRed",
58
+ "toolDiffContext": "muted",
59
+ "syntaxComment": "gray",
60
+ "syntaxKeyword": "red",
61
+ "syntaxFunction": "green",
62
+ "syntaxVariable": "blue",
63
+ "syntaxString": "green",
64
+ "syntaxNumber": "purple",
65
+ "syntaxType": "yellow",
66
+ "syntaxOperator": "aqua",
67
+ "syntaxPunctuation": "muted",
68
+ "thinkingOff": "dim",
69
+ "thinkingMinimal": "muted",
70
+ "thinkingLow": "aqua",
71
+ "thinkingMedium": "yellow",
72
+ "thinkingHigh": "orange",
73
+ "thinkingXhigh": "red",
74
+ "bashMode": "green",
75
+ "pythonMode": "yellow",
76
+ "statusLineBg": "bgHard",
77
+ "statusLineSep": "dim",
78
+ "statusLineModel": "orange",
79
+ "statusLinePath": "blue",
80
+ "statusLineGitClean": "green",
81
+ "statusLineGitDirty": "yellow",
82
+ "statusLineContext": "aqua",
83
+ "statusLineSpend": "yellow",
84
+ "statusLineStaged": "green",
85
+ "statusLineDirty": "yellow",
86
+ "statusLineUntracked": "diffRemovalRed",
87
+ "statusLineOutput": "fg",
88
+ "statusLineCost": "orange",
89
+ "statusLineSubagents": "purple"
90
+ },
91
+ "export": {
92
+ "pageBg": "#1d2021",
93
+ "cardBg": "#282828",
94
+ "infoBg": "#32302f"
95
+ },
96
+ "symbols": {
97
+ "preset": "unicode"
98
+ }
99
+ }
@@ -1,6 +1,7 @@
1
1
  import blue_crab from "./blue-crab.json" with { type: "json" };
2
2
  import claude_code from "./claude-code.json" with { type: "json" };
3
3
  import codex from "./codex.json" with { type: "json" };
4
+ import gruvbox_dark from "./gruvbox-dark.json" with { type: "json" };
4
5
  import opencode from "./opencode.json" with { type: "json" };
5
6
  import red_claw from "./red-claw.json" with { type: "json" };
6
7
 
@@ -8,6 +9,7 @@ export const defaultThemes = {
8
9
  "blue-crab": blue_crab,
9
10
  "claude-code": claude_code,
10
11
  codex,
12
+ "gruvbox-dark": gruvbox_dark,
11
13
  opencode,
12
14
  "red-claw": red_claw,
13
15
  };
@@ -204,14 +204,14 @@ export function computeContextBreakdown(
204
204
  if (contextWindow > 0) {
205
205
  const compactionSettings = session.settings.getGroup("compaction") as CompactionSettings;
206
206
  if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
207
- const threshold = resolveThresholdTokens(contextWindow, compactionSettings, model?.maxTokens ?? 0);
207
+ const threshold = resolveThresholdTokens(contextWindow, compactionSettings);
208
208
  autoCompactBufferTokens = Math.max(0, contextWindow - threshold);
209
209
  } else {
210
210
  autoCompactBufferTokens = 0;
211
211
  }
212
212
  // Even when fully disabled, fall back to a sensible reserve floor for display.
213
213
  if (autoCompactBufferTokens === 0 && compactionSettings.enabled) {
214
- autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings, model?.maxTokens ?? 0);
214
+ autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings);
215
215
  }
216
216
  }
217
217
  autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow - usedTokens));
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Process-wide registry mapping a session id to a sink that delivers a local
3
+ * file to the session's connected Telegram chat as a document. Registered by
4
+ * the notifications extension; consumed by the telegram_send tool.
5
+ */
6
+
7
+ /** Delivers a local file to the session's Telegram chat. */
8
+ export type TelegramFileSink = (file: { path: string; caption?: string }) => Promise<{ ok: boolean; error?: string }>;
9
+
10
+ const sinks = new Map<string, TelegramFileSink>();
11
+
12
+ /** Register `sink` for `sessionId`. Returns a disposer that clears it. */
13
+ export function registerTelegramFileSink(sessionId: string, sink: TelegramFileSink): () => void {
14
+ sinks.set(sessionId, sink);
15
+ return () => {
16
+ if (sinks.get(sessionId) === sink) sinks.delete(sessionId);
17
+ };
18
+ }
19
+
20
+ /** The Telegram file sink for `sessionId`, if one is registered. */
21
+ export function getTelegramFileSink(sessionId: string): TelegramFileSink | undefined {
22
+ return sinks.get(sessionId);
23
+ }