@gajae-code/coding-agent 0.2.3 → 0.2.5

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 (197) hide show
  1. package/CHANGELOG.md +34 -8600
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +61 -0
  4. package/dist/types/cli/update-cli.d.ts +3 -0
  5. package/dist/types/config/settings-schema.d.ts +27 -3
  6. package/dist/types/config/settings.d.ts +1 -1
  7. package/dist/types/defaults/gjc-defaults.d.ts +19 -6
  8. package/dist/types/discovery/helpers.d.ts +1 -0
  9. package/dist/types/exec/bash-executor.d.ts +8 -1
  10. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +2 -0
  11. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  12. package/dist/types/modes/components/settings-selector.d.ts +4 -0
  13. package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
  14. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  15. package/dist/types/modes/interactive-mode.d.ts +2 -0
  16. package/dist/types/modes/theme/defaults/index.d.ts +45 -9351
  17. package/dist/types/modes/theme/theme.d.ts +6 -5
  18. package/dist/types/modes/types.d.ts +2 -0
  19. package/dist/types/sdk.d.ts +2 -0
  20. package/dist/types/session/streaming-output.d.ts +11 -0
  21. package/dist/types/skill-state/active-state.d.ts +1 -0
  22. package/dist/types/task/types.d.ts +1 -0
  23. package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
  24. package/dist/types/tools/bash.d.ts +24 -0
  25. package/dist/types/tools/cron.d.ts +110 -0
  26. package/dist/types/tools/index.d.ts +4 -0
  27. package/dist/types/tools/monitor.d.ts +54 -0
  28. package/dist/types/web/search/index.d.ts +1 -0
  29. package/dist/types/web/search/provider.d.ts +11 -4
  30. package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
  31. package/dist/types/web/search/types.d.ts +1 -1
  32. package/package.json +7 -7
  33. package/src/async/job-manager.ts +224 -0
  34. package/src/cli/agents-cli.ts +3 -0
  35. package/src/cli/update-cli.ts +67 -16
  36. package/src/config/settings-schema.ts +30 -2
  37. package/src/config/settings.ts +44 -7
  38. package/src/defaults/gjc/skills/deep-interview/SKILL.md +48 -6
  39. package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
  40. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
  41. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  42. package/src/defaults/gjc/skills/ultragoal/SKILL.md +9 -6
  43. package/src/defaults/gjc-defaults.ts +68 -16
  44. package/src/discovery/helpers.ts +5 -0
  45. package/src/eval/js/shared/rewrite-imports.ts +1 -2
  46. package/src/exec/bash-executor.ts +20 -9
  47. package/src/gjc-runtime/deep-interview-runtime.ts +44 -0
  48. package/src/gjc-runtime/ralplan-runtime.ts +2 -0
  49. package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
  50. package/src/gjc-runtime/state-runtime.ts +3 -2
  51. package/src/goals/tools/goal-tool.ts +5 -1
  52. package/src/hooks/skill-state.ts +1 -1
  53. package/src/internal-urls/docs-index.generated.ts +8 -4
  54. package/src/lsp/render.ts +1 -1
  55. package/src/memories/index.ts +5 -4
  56. package/src/modes/acp/acp-agent.ts +1 -1
  57. package/src/modes/acp/acp-client-bridge.ts +1 -1
  58. package/src/modes/components/agent-dashboard.ts +1 -1
  59. package/src/modes/components/diff.ts +2 -2
  60. package/src/modes/components/settings-selector.ts +25 -14
  61. package/src/modes/components/skill-hud/render.ts +7 -2
  62. package/src/modes/controllers/command-controller.ts +1 -1
  63. package/src/modes/controllers/input-controller.ts +10 -2
  64. package/src/modes/controllers/selector-controller.ts +67 -0
  65. package/src/modes/interactive-mode.ts +34 -3
  66. package/src/modes/theme/defaults/blue-crab.json +126 -0
  67. package/src/modes/theme/defaults/index.ts +2 -196
  68. package/src/modes/theme/theme.ts +75 -36
  69. package/src/modes/types.ts +2 -0
  70. package/src/prompts/agents/architect.md +5 -1
  71. package/src/prompts/agents/critic.md +5 -1
  72. package/src/prompts/agents/frontmatter.md +1 -0
  73. package/src/prompts/agents/planner.md +5 -1
  74. package/src/prompts/memories/unavailable.md +9 -0
  75. package/src/prompts/tools/bash.md +9 -0
  76. package/src/prompts/tools/cron.md +25 -0
  77. package/src/prompts/tools/monitor.md +30 -0
  78. package/src/runtime-mcp/oauth-flow.ts +4 -2
  79. package/src/sdk.ts +7 -0
  80. package/src/session/agent-session.ts +16 -5
  81. package/src/session/streaming-output.ts +21 -0
  82. package/src/skill-state/active-state.ts +163 -12
  83. package/src/slash-commands/builtin-registry.ts +11 -1
  84. package/src/task/agents.ts +1 -0
  85. package/src/task/executor.ts +1 -0
  86. package/src/task/types.ts +1 -0
  87. package/src/tools/bash-allowed-prefixes.ts +169 -0
  88. package/src/tools/bash.ts +190 -29
  89. package/src/tools/browser/tab-worker.ts +1 -1
  90. package/src/tools/cron.ts +665 -0
  91. package/src/tools/index.ts +20 -2
  92. package/src/tools/monitor.ts +136 -0
  93. package/src/vim/engine.ts +3 -3
  94. package/src/web/search/index.ts +31 -18
  95. package/src/web/search/provider.ts +57 -12
  96. package/src/web/search/providers/duckduckgo.ts +279 -0
  97. package/src/web/search/types.ts +2 -0
  98. package/src/modes/theme/dark.json +0 -95
  99. package/src/modes/theme/defaults/alabaster.json +0 -93
  100. package/src/modes/theme/defaults/amethyst.json +0 -96
  101. package/src/modes/theme/defaults/anthracite.json +0 -93
  102. package/src/modes/theme/defaults/basalt.json +0 -91
  103. package/src/modes/theme/defaults/birch.json +0 -95
  104. package/src/modes/theme/defaults/dark-abyss.json +0 -91
  105. package/src/modes/theme/defaults/dark-arctic.json +0 -104
  106. package/src/modes/theme/defaults/dark-aurora.json +0 -95
  107. package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
  108. package/src/modes/theme/defaults/dark-cavern.json +0 -91
  109. package/src/modes/theme/defaults/dark-copper.json +0 -95
  110. package/src/modes/theme/defaults/dark-cosmos.json +0 -90
  111. package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
  112. package/src/modes/theme/defaults/dark-dracula.json +0 -98
  113. package/src/modes/theme/defaults/dark-eclipse.json +0 -91
  114. package/src/modes/theme/defaults/dark-ember.json +0 -95
  115. package/src/modes/theme/defaults/dark-equinox.json +0 -90
  116. package/src/modes/theme/defaults/dark-forest.json +0 -96
  117. package/src/modes/theme/defaults/dark-github.json +0 -105
  118. package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
  119. package/src/modes/theme/defaults/dark-lavender.json +0 -95
  120. package/src/modes/theme/defaults/dark-lunar.json +0 -89
  121. package/src/modes/theme/defaults/dark-midnight.json +0 -95
  122. package/src/modes/theme/defaults/dark-monochrome.json +0 -94
  123. package/src/modes/theme/defaults/dark-monokai.json +0 -98
  124. package/src/modes/theme/defaults/dark-nebula.json +0 -90
  125. package/src/modes/theme/defaults/dark-nord.json +0 -97
  126. package/src/modes/theme/defaults/dark-ocean.json +0 -101
  127. package/src/modes/theme/defaults/dark-one.json +0 -100
  128. package/src/modes/theme/defaults/dark-poimandres.json +0 -141
  129. package/src/modes/theme/defaults/dark-rainforest.json +0 -91
  130. package/src/modes/theme/defaults/dark-reef.json +0 -91
  131. package/src/modes/theme/defaults/dark-retro.json +0 -92
  132. package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
  133. package/src/modes/theme/defaults/dark-sakura.json +0 -95
  134. package/src/modes/theme/defaults/dark-slate.json +0 -95
  135. package/src/modes/theme/defaults/dark-solarized.json +0 -97
  136. package/src/modes/theme/defaults/dark-solstice.json +0 -90
  137. package/src/modes/theme/defaults/dark-starfall.json +0 -91
  138. package/src/modes/theme/defaults/dark-sunset.json +0 -99
  139. package/src/modes/theme/defaults/dark-swamp.json +0 -90
  140. package/src/modes/theme/defaults/dark-synthwave.json +0 -103
  141. package/src/modes/theme/defaults/dark-taiga.json +0 -91
  142. package/src/modes/theme/defaults/dark-terminal.json +0 -95
  143. package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
  144. package/src/modes/theme/defaults/dark-tundra.json +0 -91
  145. package/src/modes/theme/defaults/dark-twilight.json +0 -91
  146. package/src/modes/theme/defaults/dark-volcanic.json +0 -91
  147. package/src/modes/theme/defaults/graphite.json +0 -92
  148. package/src/modes/theme/defaults/light-arctic.json +0 -107
  149. package/src/modes/theme/defaults/light-aurora-day.json +0 -91
  150. package/src/modes/theme/defaults/light-canyon.json +0 -91
  151. package/src/modes/theme/defaults/light-catppuccin.json +0 -106
  152. package/src/modes/theme/defaults/light-cirrus.json +0 -90
  153. package/src/modes/theme/defaults/light-coral.json +0 -95
  154. package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
  155. package/src/modes/theme/defaults/light-dawn.json +0 -90
  156. package/src/modes/theme/defaults/light-dunes.json +0 -91
  157. package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
  158. package/src/modes/theme/defaults/light-forest.json +0 -100
  159. package/src/modes/theme/defaults/light-frost.json +0 -95
  160. package/src/modes/theme/defaults/light-github.json +0 -115
  161. package/src/modes/theme/defaults/light-glacier.json +0 -91
  162. package/src/modes/theme/defaults/light-gruvbox.json +0 -108
  163. package/src/modes/theme/defaults/light-haze.json +0 -90
  164. package/src/modes/theme/defaults/light-honeycomb.json +0 -95
  165. package/src/modes/theme/defaults/light-lagoon.json +0 -91
  166. package/src/modes/theme/defaults/light-lavender.json +0 -95
  167. package/src/modes/theme/defaults/light-meadow.json +0 -91
  168. package/src/modes/theme/defaults/light-mint.json +0 -95
  169. package/src/modes/theme/defaults/light-monochrome.json +0 -101
  170. package/src/modes/theme/defaults/light-ocean.json +0 -99
  171. package/src/modes/theme/defaults/light-one.json +0 -99
  172. package/src/modes/theme/defaults/light-opal.json +0 -91
  173. package/src/modes/theme/defaults/light-orchard.json +0 -91
  174. package/src/modes/theme/defaults/light-paper.json +0 -95
  175. package/src/modes/theme/defaults/light-poimandres.json +0 -141
  176. package/src/modes/theme/defaults/light-prism.json +0 -90
  177. package/src/modes/theme/defaults/light-retro.json +0 -98
  178. package/src/modes/theme/defaults/light-sand.json +0 -95
  179. package/src/modes/theme/defaults/light-savanna.json +0 -91
  180. package/src/modes/theme/defaults/light-solarized.json +0 -102
  181. package/src/modes/theme/defaults/light-soleil.json +0 -90
  182. package/src/modes/theme/defaults/light-sunset.json +0 -99
  183. package/src/modes/theme/defaults/light-synthwave.json +0 -98
  184. package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
  185. package/src/modes/theme/defaults/light-wetland.json +0 -91
  186. package/src/modes/theme/defaults/light-zenith.json +0 -89
  187. package/src/modes/theme/defaults/limestone.json +0 -94
  188. package/src/modes/theme/defaults/mahogany.json +0 -97
  189. package/src/modes/theme/defaults/marble.json +0 -93
  190. package/src/modes/theme/defaults/obsidian.json +0 -91
  191. package/src/modes/theme/defaults/onyx.json +0 -91
  192. package/src/modes/theme/defaults/pearl.json +0 -93
  193. package/src/modes/theme/defaults/porcelain.json +0 -91
  194. package/src/modes/theme/defaults/quartz.json +0 -96
  195. package/src/modes/theme/defaults/sandstone.json +0 -95
  196. package/src/modes/theme/defaults/titanium.json +0 -90
  197. package/src/modes/theme/light.json +0 -93
package/src/lsp/render.ts CHANGED
@@ -103,7 +103,7 @@ export function renderResult(
103
103
  args?: LspParams,
104
104
  ): Component {
105
105
  const content = result.content?.[0];
106
- if (!content || content.type !== "text" || !("text" in content) || !content.text) {
106
+ if (content?.type !== "text" || !("text" in content) || !content.text) {
107
107
  const icon = formatStatusIcon("warning", theme, options.spinnerFrame);
108
108
  const header = `${icon} LSP`;
109
109
  return new Text([header, theme.fg("dim", "No result")].join("\n"), 0, 0);
@@ -12,6 +12,7 @@ import consolidationTemplate from "../prompts/memories/consolidation.md" with {
12
12
  import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
13
13
  import stageOneInputTemplate from "../prompts/memories/stage_one_input.md" with { type: "text" };
14
14
  import stageOneSystemTemplate from "../prompts/memories/stage_one_system.md" with { type: "text" };
15
+ import unavailableTemplate from "../prompts/memories/unavailable.md" with { type: "text" };
15
16
  import type { AgentSession } from "../session/agent-session";
16
17
  import {
17
18
  claimStage1Jobs,
@@ -153,20 +154,20 @@ export async function buildMemoryToolDeveloperInstructions(
153
154
  ): Promise<string | undefined> {
154
155
  const cfg = loadMemoryConfig(settings);
155
156
  if (!cfg.enabled) return undefined;
156
- const memoryRoot = getMemoryRoot(agentDir, session?.sessionManager.getCwd() ?? settings.getCwd());
157
+ const memoryRoot = getMemoryRoot(agentDir, session?.sessionManager?.getCwd() ?? settings.getCwd());
157
158
  const summaryPath = path.join(memoryRoot, "memory_summary.md");
158
159
 
159
160
  let text: string;
160
161
  try {
161
162
  text = await Bun.file(summaryPath).text();
162
163
  } catch {
163
- return undefined;
164
+ return unavailableTemplate;
164
165
  }
165
166
 
166
167
  const summary = text.trim();
167
- if (!summary) return undefined;
168
+ if (!summary) return unavailableTemplate;
168
169
  const truncated = truncateByApproxTokens(summary, cfg.summaryInjectionTokenLimit);
169
- if (!truncated.trim()) return undefined;
170
+ if (!truncated.trim()) return unavailableTemplate;
170
171
 
171
172
  return prompt.render(readPathTemplate, {
172
173
  memory_summary: truncated,
@@ -272,7 +272,7 @@ async function elicitFromAcpClient(
272
272
  finish(undefined);
273
273
  });
274
274
  const response = await promise;
275
- if (!response || response.action !== "accept" || !response.content) {
275
+ if (response?.action !== "accept" || !response.content) {
276
276
  return undefined;
277
277
  }
278
278
  return response.content.value;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ACP-side `ClientBridge` implementation. Wraps `AgentSideConnection` so the
3
- * `read`/`write`/`bash`/`edit` tools (and the permission gate in
3
+ * `read`/`write`/`bash`/`monitor`/`edit` tools (and the permission gate in
4
4
  * `AgentSession`) can route through the client when it advertises the
5
5
  * relevant capabilities at `initialize` time.
6
6
  */
@@ -121,7 +121,7 @@ function matchAgent(agent: DashboardAgent, query: string): boolean {
121
121
  function extractAssistantText(messages: AgentMessage[]): string | null {
122
122
  for (let i = messages.length - 1; i >= 0; i--) {
123
123
  const message = messages[i];
124
- if (!message || message.role !== "assistant") continue;
124
+ if (message?.role !== "assistant") continue;
125
125
  const blocks = message.content;
126
126
  if (!Array.isArray(blocks)) continue;
127
127
  const text = blocks
@@ -151,7 +151,7 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
151
151
  const removedLines: { lineNum: string; content: string }[] = [];
152
152
  while (i < lines.length) {
153
153
  const p = parseDiffLine(lines[i]);
154
- if (!p || p.prefix !== "-") break;
154
+ if (p?.prefix !== "-") break;
155
155
  removedLines.push({ lineNum: p.lineNum, content: p.content });
156
156
  i++;
157
157
  }
@@ -159,7 +159,7 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
159
159
  const addedLines: { lineNum: string; content: string }[] = [];
160
160
  while (i < lines.length) {
161
161
  const p = parseDiffLine(lines[i]);
162
- if (!p || p.prefix !== "+") break;
162
+ if (p?.prefix !== "+") break;
163
163
  addedLines.push({ lineNum: p.lineNum, content: p.content });
164
164
  i++;
165
165
  }
@@ -21,7 +21,7 @@ import type {
21
21
  StatusLineSeparatorStyle,
22
22
  } from "../../config/settings-schema";
23
23
  import { SETTING_TABS, TAB_METADATA } from "../../config/settings-schema";
24
- import { getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
24
+ import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
25
25
  import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
26
26
  import { getTabBarTheme } from "../shared";
27
27
  import { DynamicBorder } from "./dynamic-border";
@@ -200,6 +200,10 @@ export interface StatusLinePreviewSettings {
200
200
  export interface SettingsCallbacks {
201
201
  /** Called when any setting value changes */
202
202
  onChange: (path: SettingPath, newValue: unknown) => void;
203
+ /** Called for theme preview while browsing theme settings */
204
+ onThemePreview?: (theme: string) => void | Promise<void>;
205
+ /** Called to restore the rendered theme when theme settings preview is cancelled */
206
+ onThemePreviewCancel?: (theme: string) => void | Promise<void>;
203
207
  /** Called for status line preview while configuring */
204
208
  onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
205
209
  /** Get current rendered status line for inline preview */
@@ -217,7 +221,6 @@ export interface SettingsCallbacks {
217
221
  export class SettingsSelectorComponent extends Container {
218
222
  #tabBar: TabBar;
219
223
  #currentList: SettingsList | null = null;
220
- #currentSubmenu: Container | null = null;
221
224
  #pluginComponent: PluginSettingsComponent | null = null;
222
225
  #statusPreviewContainer: Container | null = null;
223
226
  #statusPreviewText: Text | null = null;
@@ -374,10 +377,15 @@ export class SettingsSelectorComponent extends Container {
374
377
  let onPreview: ((value: string) => void | Promise<void>) | undefined;
375
378
  let onPreviewCancel: (() => void) | undefined;
376
379
 
377
- // Theme selection is confirm-only: moving through the list must not mutate
378
- // the rendered theme while the displayed/persisted setting still names
379
- // the previous value. Confirmation persists through Settings hooks.
380
- if (def.path === "statusLine.preset") {
380
+ if (def.path === "theme.dark" || def.path === "theme.light") {
381
+ const activeThemeBeforePreview = getCurrentThemeName() ?? currentValue;
382
+ onPreview = value => {
383
+ return this.callbacks.onThemePreview?.(value);
384
+ };
385
+ onPreviewCancel = () => {
386
+ return this.callbacks.onThemePreviewCancel?.(activeThemeBeforePreview);
387
+ };
388
+ } else if (def.path === "statusLine.preset") {
381
389
  onPreview = value => {
382
390
  const presetDef = getPreset(
383
391
  value as "default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "custom",
@@ -612,17 +620,20 @@ export class SettingsSelectorComponent extends Container {
612
620
  return;
613
621
  }
614
622
 
615
- // Escape at top level cancels
616
- if (matchesAppInterrupt(data) && !this.#currentSubmenu) {
617
- this.callbacks.onCancel();
618
- return;
619
- }
620
-
621
- // Pass to current content
623
+ // Pass to current content. SettingsList owns Escape routing so open
624
+ // submenus can run their cancel/restore callbacks before closing.
622
625
  if (this.#currentList) {
623
626
  this.#currentList.handleInput(data);
624
- } else if (this.#pluginComponent) {
627
+ return;
628
+ }
629
+ if (this.#pluginComponent) {
625
630
  this.#pluginComponent.handleInput(data);
631
+ return;
632
+ }
633
+
634
+ // Fallback for future top-level content that does not own cancellation.
635
+ if (matchesAppInterrupt(data)) {
636
+ this.callbacks.onCancel();
626
637
  }
627
638
  }
628
639
  }
@@ -1,4 +1,8 @@
1
- import type { SkillActiveEntry, WorkflowHudChip } from "../../../skill-state/active-state";
1
+ import {
2
+ collapsePlanningPipeline,
3
+ type SkillActiveEntry,
4
+ type WorkflowHudChip,
5
+ } from "../../../skill-state/active-state";
2
6
  import { workflowReceiptStatus } from "../../../skill-state/workflow-state-contract";
3
7
 
4
8
  const ANSI_RESET_FG = "\x1b[39m";
@@ -69,7 +73,8 @@ function formatEntry(entry: SkillActiveEntry): string {
69
73
  }
70
74
 
71
75
  export function renderSkillHudBar(entries: readonly SkillActiveEntry[], width: number): string | null {
72
- const active = entries.filter(entry => entry.active !== false && sanitizeHudPart(entry.skill)).sort(compareEntries);
76
+ const visible = collapsePlanningPipeline(entries.filter(entry => entry.active !== false));
77
+ const active = visible.filter(entry => sanitizeHudPart(entry.skill)).sort(compareEntries);
73
78
  if (active.length === 0 || width <= 0) return null;
74
79
  const body = active.map(formatEntry).join(" + ");
75
80
  const prefix = `${ANSI_BORDER}◆${ANSI_RESET_FG} ${ANSI_BOLD}${ANSI_ACCENT}hud${ANSI_RESET_FG}${ANSI_RESET_BOLD} `;
@@ -585,7 +585,7 @@ export class CommandController {
585
585
  if (action === "view") {
586
586
  const payload = await backend.buildDeveloperInstructions(agentDir, this.ctx.settings, this.ctx.session);
587
587
  if (!payload) {
588
- this.ctx.showWarning("Memory payload is empty (memory backend off, disabled, or no memory available).");
588
+ this.ctx.showWarning("Memory payload is empty; durable memory is unavailable or unconfirmed.");
589
589
  return;
590
590
  }
591
591
  this.ctx.chatContainer.addChild(new Spacer(1));
@@ -64,6 +64,7 @@ export class InputController {
64
64
  } else if (this.ctx.isBashMode) {
65
65
  this.ctx.editor.setText("");
66
66
  this.ctx.isBashMode = false;
67
+ this.ctx.isBashNoContext = false;
67
68
  this.ctx.updateEditorBorderColor();
68
69
  } else if (this.ctx.session.isEvalRunning) {
69
70
  this.ctx.session.abortEval();
@@ -172,11 +173,17 @@ export class InputController {
172
173
 
173
174
  this.ctx.editor.onChange = (text: string) => {
174
175
  const wasBashMode = this.ctx.isBashMode;
176
+ const wasBashNoContext = this.ctx.isBashNoContext;
175
177
  const wasPythonMode = this.ctx.isPythonMode;
176
178
  const trimmed = text.trimStart();
177
- this.ctx.isBashMode = text.trimStart().startsWith("!");
179
+ this.ctx.isBashMode = trimmed.startsWith("!");
180
+ this.ctx.isBashNoContext = trimmed.startsWith("!!");
178
181
  this.ctx.isPythonMode = trimmed.startsWith("$") && !trimmed.startsWith("${");
179
- if (wasBashMode !== this.ctx.isBashMode || wasPythonMode !== this.ctx.isPythonMode) {
182
+ if (
183
+ wasBashMode !== this.ctx.isBashMode ||
184
+ wasBashNoContext !== this.ctx.isBashNoContext ||
185
+ wasPythonMode !== this.ctx.isPythonMode
186
+ ) {
180
187
  this.ctx.updateEditorBorderColor();
181
188
  }
182
189
  };
@@ -260,6 +267,7 @@ export class InputController {
260
267
  this.ctx.editor.addToHistory(text);
261
268
  await this.ctx.handleBashCommand(command, isExcluded);
262
269
  this.ctx.isBashMode = false;
270
+ this.ctx.isBashNoContext = false;
263
271
  this.ctx.updateEditorBorderColor();
264
272
  return;
265
273
  }
@@ -17,7 +17,11 @@ import {
17
17
  } from "../../extensibility/plugins/marketplace";
18
18
  import {
19
19
  getAvailableThemes,
20
+ getCurrentThemeName,
21
+ getDetectedThemeSettingsPath,
20
22
  getSymbolTheme,
23
+ previewTheme,
24
+ restoreThemePreview,
21
25
  setColorBlindMode,
22
26
  setSymbolPreset,
23
27
  setTheme,
@@ -47,6 +51,7 @@ import {
47
51
  import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
48
52
  import { SessionSelectorComponent } from "../components/session-selector";
49
53
  import { SettingsSelectorComponent } from "../components/settings-selector";
54
+ import { ThemeSelectorComponent } from "../components/theme-selector";
50
55
  import { ToolExecutionComponent } from "../components/tool-execution";
51
56
  import { TreeSelectorComponent } from "../components/tree-selector";
52
57
  import { UserMessageSelectorComponent } from "../components/user-message-selector";
@@ -62,6 +67,10 @@ const CALLBACK_SERVER_PROVIDERS = new Set<OAuthProvider>([
62
67
 
63
68
  const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
64
69
 
70
+ function isThemePreviewSuperseded(result: { success: boolean; error?: string }): boolean {
71
+ return !result.success && result.error?.includes("superseded by a newer request") === true;
72
+ }
73
+
65
74
  function formatProviderOnboardingCommandGuide(): string {
66
75
  return [
67
76
  "Provider preset setup:",
@@ -134,6 +143,22 @@ export class SelectorController {
134
143
  },
135
144
  {
136
145
  onChange: (id, value) => this.handleSettingChange(id, value),
146
+ onThemePreview: themeName => {
147
+ return previewTheme(themeName).then(result => {
148
+ if (!result.success && result.error && !isThemePreviewSuperseded(result)) {
149
+ this.ctx.showError(`Failed to preview theme: ${result.error}`);
150
+ }
151
+ this.#refreshThemeUi();
152
+ });
153
+ },
154
+ onThemePreviewCancel: themeName => {
155
+ return restoreThemePreview(themeName).then(result => {
156
+ if (!result.success && result.error && !isThemePreviewSuperseded(result)) {
157
+ this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
158
+ }
159
+ this.#refreshThemeUi();
160
+ });
161
+ },
137
162
  onStatusLinePreview: previewSettings => {
138
163
  // Update status line with preview settings
139
164
  this.ctx.statusLine.updateSettings({
@@ -177,6 +202,48 @@ export class SelectorController {
177
202
  });
178
203
  }
179
204
 
205
+ #refreshThemeUi(): void {
206
+ this.ctx.statusLine.invalidate();
207
+ this.ctx.updateEditorTopBorder();
208
+ this.ctx.ui.requestRender();
209
+ }
210
+
211
+ showThemeSelector(): void {
212
+ getAvailableThemes().then(availableThemes => {
213
+ const initialTheme = getCurrentThemeName() ?? "red-claw";
214
+ this.showSelector(done => {
215
+ const selector = new ThemeSelectorComponent(
216
+ initialTheme,
217
+ availableThemes,
218
+ themeName => {
219
+ const settingPath = getDetectedThemeSettingsPath();
220
+ settings.set(settingPath, themeName);
221
+ this.#refreshThemeUi();
222
+ done();
223
+ },
224
+ () => {
225
+ void restoreThemePreview(initialTheme).then(result => {
226
+ if (!result.success && result.error) {
227
+ this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
228
+ }
229
+ this.#refreshThemeUi();
230
+ });
231
+ done();
232
+ },
233
+ themeName => {
234
+ void previewTheme(themeName).then(result => {
235
+ if (!result.success && result.error) {
236
+ this.ctx.showError(`Failed to preview theme: ${result.error}`);
237
+ }
238
+ this.#refreshThemeUi();
239
+ });
240
+ },
241
+ );
242
+ return { component: selector, focus: selector.getSelectList() };
243
+ });
244
+ });
245
+ }
246
+
180
247
  showHistorySearch(): void {
181
248
  const historyStorage = this.ctx.historyStorage;
182
249
  if (!historyStorage) return;
@@ -112,12 +112,23 @@ const HINT_SHIMMER_PALETTE: ShimmerPalette = {
112
112
  high: "borderAccent",
113
113
  };
114
114
 
115
+ function getDefaultInputPrefix(): string {
116
+ return `${theme.fg("accent", ">")} `;
117
+ }
118
+
119
+ function getShellInputPrefix(isNoContext: boolean): string {
120
+ const shellLabel = isNoContext
121
+ ? theme.fg("warning", theme.bold("shell no-context"))
122
+ : theme.fg("bashMode", theme.bold("shell"));
123
+ return `${shellLabel} ${getDefaultInputPrefix()}`;
124
+ }
125
+
115
126
  function configureDefaultComposerChrome(editor: CustomEditor): void {
116
127
  editor.setBorderVisible(true);
117
128
  editor.setBorderStyle("sharp");
118
129
  editor.setClosedBorderBox(true);
119
130
  editor.setPromptGutter(undefined);
120
- editor.setInputPrefix(`${theme.fg("accent", ">")} `);
131
+ editor.setInputPrefix(getDefaultInputPrefix());
121
132
  editor.setPlaceholder("Type your message...");
122
133
  editor.setPaddingX(1);
123
134
  editor.setTopBorder(undefined);
@@ -236,6 +247,7 @@ export class InteractiveMode implements InteractiveModeContext {
236
247
  isInitialized = false;
237
248
  isBackgrounded = false;
238
249
  isBashMode = false;
250
+ isBashNoContext = false;
239
251
  toolOutputExpanded = false;
240
252
  todoExpanded = false;
241
253
  planModeEnabled = false;
@@ -808,7 +820,10 @@ export class InteractiveMode implements InteractiveModeContext {
808
820
 
809
821
  updateEditorChrome(): void {
810
822
  if (this.isBashMode) {
811
- this.editor.borderColor = theme.getBashModeBorderColor();
823
+ this.editor.borderColor = this.isBashNoContext
824
+ ? (str: string) => theme.fg("warning", str)
825
+ : theme.getBashModeBorderColor();
826
+ this.editor.setInputPrefix(getShellInputPrefix(this.isBashNoContext));
812
827
  } else if (this.isPythonMode) {
813
828
  this.editor.borderColor = theme.getPythonModeBorderColor();
814
829
  } else {
@@ -823,6 +838,9 @@ export class InteractiveMode implements InteractiveModeContext {
823
838
  this.editor.borderColor = theme.getThinkingBorderColor(level);
824
839
  }
825
840
  }
841
+ if (!this.isBashMode) {
842
+ this.editor.setInputPrefix(getDefaultInputPrefix());
843
+ }
826
844
  this.#setComposerTopBorder();
827
845
  this.ui.requestRender();
828
846
  }
@@ -1281,7 +1299,16 @@ export class InteractiveMode implements InteractiveModeContext {
1281
1299
  reason?: "completed" | "paused" | "dropped";
1282
1300
  }): Promise<void> {
1283
1301
  const previousTools = this.#goalModePreviousTools;
1284
- if (this.goalModeEnabled && previousTools) {
1302
+ // Drop keeps the `goal` tool callable so the agent can immediately create a new
1303
+ // goal in the same session without a leader-side cleanup. Complete (and pause)
1304
+ // exit goal mode and restore the pre-goal tool set even when the goal_updated
1305
+ // event has already cleared goalModeEnabled (see #completeGoalFromTool emitting
1306
+ // state.enabled = false before #exitGoalMode runs). Spec: deep-interview-ultragoal-goal-tool-wiring AC1+AC2.
1307
+ const shouldRestoreTools =
1308
+ previousTools &&
1309
+ options?.reason !== "dropped" &&
1310
+ (this.goalModeEnabled || options?.reason === "completed" || options?.paused === true);
1311
+ if (shouldRestoreTools) {
1285
1312
  await this.session.setActiveToolsByName(previousTools);
1286
1313
  }
1287
1314
  const currentState = this.session.getGoalModeState();
@@ -2339,6 +2366,10 @@ export class InteractiveMode implements InteractiveModeContext {
2339
2366
  this.#selectorController.showSettingsSelector();
2340
2367
  }
2341
2368
 
2369
+ showThemeSelector(): void {
2370
+ this.#selectorController.showThemeSelector();
2371
+ }
2372
+
2342
2373
  showHistorySearch(): void {
2343
2374
  this.#selectorController.showHistorySearch();
2344
2375
  }
@@ -0,0 +1,126 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "blue-crab",
4
+ "vars": {
5
+ "cyan": "#66e8ff",
6
+ "blue": "#4ea1ff",
7
+ "green": "#72e6b8",
8
+ "red": "#ff6b7a",
9
+ "yellow": "#f3c76d",
10
+ "gray": "#b8c7d9",
11
+ "dimGray": "#5d7288",
12
+ "darkGray": "#344456",
13
+ "accent": "#2f9bff",
14
+ "selectedBg": "#12314a",
15
+ "userMsgBg": "#081b2a",
16
+ "toolPendingBg": "#0a2235",
17
+ "toolSuccessBg": "#0b2b25",
18
+ "toolErrorBg": "#321923",
19
+ "customMsgBg": "#0c1a2c",
20
+ "abyss": "#020812",
21
+ "deepNavy": "#061321",
22
+ "crabShell": "#2f9bff",
23
+ "claw": "#5ec8ff",
24
+ "ocean": "#0b4f8a",
25
+ "azure": "#7dd3fc",
26
+ "seafoam": "#80f2d3",
27
+ "kelp": "#62d9a8",
28
+ "sand": "#f4d6a0",
29
+ "coral": "#ff8a7a",
30
+ "ink": "#020812",
31
+ "mantle": "#061321",
32
+ "surface": "#0a1d31",
33
+ "surfaceBright": "#12314a",
34
+ "foam": "#e6f7ff",
35
+ "mutedShell": "#9db5c9",
36
+ "dimShell": "#55708a",
37
+ "brandBlue": "#2f9bff",
38
+ "shell": "#e6f7ff",
39
+ "dangerRed": "#ff5f75",
40
+ "warningAmber": "#f5bd58",
41
+ "diffRemovalRed": "#df5b61"
42
+ },
43
+ "colors": {
44
+ "accent": "claw",
45
+ "border": "ocean",
46
+ "borderAccent": "brandBlue",
47
+ "borderMuted": "dimShell",
48
+ "success": "kelp",
49
+ "error": "dangerRed",
50
+ "warning": "warningAmber",
51
+ "muted": "mutedShell",
52
+ "dim": "dimShell",
53
+ "text": "shell",
54
+ "thinkingText": "mutedShell",
55
+ "selectedBg": "surfaceBright",
56
+ "userMessageBg": "surface",
57
+ "userMessageText": "shell",
58
+ "customMessageBg": "mantle",
59
+ "customMessageText": "shell",
60
+ "customMessageLabel": "claw",
61
+ "toolPendingBg": "mantle",
62
+ "toolSuccessBg": "#0b2b25",
63
+ "toolErrorBg": "#321923",
64
+ "toolTitle": "shell",
65
+ "toolOutput": "mutedShell",
66
+ "mdHeading": "brandBlue",
67
+ "mdLink": "azure",
68
+ "mdLinkUrl": "dimShell",
69
+ "mdCode": "sand",
70
+ "mdCodeBlock": "seafoam",
71
+ "mdCodeBlockBorder": "ocean",
72
+ "mdQuote": "mutedShell",
73
+ "mdQuoteBorder": "ocean",
74
+ "mdHr": "dimShell",
75
+ "mdListBullet": "claw",
76
+ "toolDiffAdded": "kelp",
77
+ "toolDiffRemoved": "diffRemovalRed",
78
+ "toolDiffContext": "mutedShell",
79
+ "link": "azure",
80
+ "syntaxComment": "dimShell",
81
+ "syntaxKeyword": "brandBlue",
82
+ "syntaxFunction": "sand",
83
+ "syntaxVariable": "foam",
84
+ "syntaxString": "seafoam",
85
+ "syntaxNumber": "azure",
86
+ "syntaxType": "claw",
87
+ "syntaxOperator": "mutedShell",
88
+ "syntaxPunctuation": "dimShell",
89
+ "thinkingOff": "dimShell",
90
+ "thinkingMinimal": "mutedShell",
91
+ "thinkingLow": "azure",
92
+ "thinkingMedium": "claw",
93
+ "thinkingHigh": "brandBlue",
94
+ "thinkingXhigh": "coral",
95
+ "bashMode": "claw",
96
+ "statusLineBg": "ocean",
97
+ "statusLineSep": "dimShell",
98
+ "statusLineModel": "claw",
99
+ "statusLinePath": "azure",
100
+ "statusLineGitClean": "kelp",
101
+ "statusLineGitDirty": "sand",
102
+ "statusLineContext": "seafoam",
103
+ "statusLineSpend": "sand",
104
+ "statusLineStaged": "kelp",
105
+ "statusLineDirty": "sand",
106
+ "statusLineUntracked": "diffRemovalRed",
107
+ "statusLineOutput": "foam",
108
+ "statusLineCost": "claw",
109
+ "statusLineSubagents": "brandBlue",
110
+ "pythonMode": "sand"
111
+ },
112
+ "export": {
113
+ "pageBg": "#020812",
114
+ "cardBg": "#061321",
115
+ "infoBg": "#0a1d31"
116
+ },
117
+ "symbols": {
118
+ "preset": "unicode",
119
+ "overrides": {
120
+ "icon.pi": "🦀",
121
+ "icon.agents": "🦞",
122
+ "status.running": "🌊",
123
+ "md.bullet": "▸"
124
+ }
125
+ }
126
+ }