@gajae-code/coding-agent 0.7.9 → 0.7.11

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 (123) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +2 -0
  3. package/dist/types/config/keybindings.d.ts +11 -5
  4. package/dist/types/config/settings-schema.d.ts +10 -1
  5. package/dist/types/defaults/gjc-defaults.d.ts +7 -0
  6. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  7. package/dist/types/gjc-runtime/launch-tmux.d.ts +10 -0
  8. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
  9. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
  10. package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
  11. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -0
  12. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -4
  13. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  14. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  15. package/dist/types/modes/components/skill-message.d.ts +1 -0
  16. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  17. package/dist/types/modes/controllers/input-controller.d.ts +4 -0
  18. package/dist/types/modes/interactive-mode.d.ts +1 -0
  19. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
  20. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +20 -6
  21. package/dist/types/modes/slash-command-visibility.d.ts +1 -0
  22. package/dist/types/modes/types.d.ts +1 -0
  23. package/dist/types/notifications/html-format.d.ts +2 -0
  24. package/dist/types/notifications/recent-activity.d.ts +5 -1
  25. package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
  26. package/dist/types/notifications/telegram-reference.d.ts +4 -0
  27. package/dist/types/notifications/threaded-render.d.ts +1 -0
  28. package/dist/types/sdk.d.ts +1 -1
  29. package/dist/types/session/agent-session.d.ts +8 -5
  30. package/dist/types/slash-commands/types.d.ts +2 -0
  31. package/dist/types/tools/ask.d.ts +2 -0
  32. package/dist/types/utils/cmux-workspace.d.ts +52 -0
  33. package/dist/types/web/search/index.d.ts +4 -2
  34. package/dist/types/web/search/provider.d.ts +14 -0
  35. package/dist/types/web/search/providers/utils.d.ts +42 -13
  36. package/dist/types/web/search/render.d.ts +1 -0
  37. package/package.json +7 -7
  38. package/src/cli/update-cli.ts +24 -0
  39. package/src/cli/web-search-cli.ts +5 -7
  40. package/src/cli.ts +1 -1
  41. package/src/commands/launch.ts +1 -1
  42. package/src/commands/ralplan.ts +1 -0
  43. package/src/config/keybindings.ts +14 -5
  44. package/src/config/model-profiles.ts +16 -0
  45. package/src/config/settings-schema.ts +11 -1
  46. package/src/coordinator-mcp/server.ts +9 -3
  47. package/src/defaults/gjc/skills/deep-interview/SKILL.md +26 -2
  48. package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
  49. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -112
  50. package/src/defaults/gjc-defaults.ts +16 -0
  51. package/src/extensibility/extensions/runner.ts +2 -1
  52. package/src/extensibility/extensions/types.ts +11 -0
  53. package/src/gjc-runtime/launch-tmux.ts +209 -26
  54. package/src/gjc-runtime/ralplan-runtime.ts +22 -6
  55. package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
  56. package/src/gjc-runtime/state-runtime.ts +8 -5
  57. package/src/gjc-runtime/state-writer.ts +5 -5
  58. package/src/gjc-runtime/ultragoal-guard.ts +139 -19
  59. package/src/gjc-runtime/ultragoal-runtime.ts +16 -124
  60. package/src/gjc-runtime/workflow-manifest.generated.json +0 -8
  61. package/src/gjc-runtime/workflow-manifest.ts +0 -1
  62. package/src/hooks/skill-state.ts +33 -73
  63. package/src/internal-urls/docs-index.generated.ts +6 -4
  64. package/src/main.ts +2 -2
  65. package/src/migrate/executor.ts +23 -6
  66. package/src/modes/components/custom-editor.ts +8 -0
  67. package/src/modes/components/hook-selector.ts +21 -3
  68. package/src/modes/components/model-selector.ts +6 -2
  69. package/src/modes/components/settings-selector.ts +47 -5
  70. package/src/modes/components/skill-message.ts +18 -10
  71. package/src/modes/components/status-line/presets.ts +11 -0
  72. package/src/modes/controllers/command-controller.ts +23 -0
  73. package/src/modes/controllers/extension-ui-controller.ts +18 -2
  74. package/src/modes/controllers/input-controller.ts +41 -9
  75. package/src/modes/interactive-mode.ts +16 -7
  76. package/src/modes/prompt-action-autocomplete.ts +23 -1
  77. package/src/modes/shared/agent-wire/deep-interview-gate.ts +53 -9
  78. package/src/modes/slash-command-visibility.ts +9 -0
  79. package/src/modes/types.ts +1 -0
  80. package/src/modes/utils/hotkeys-markdown.ts +5 -2
  81. package/src/modes/utils/ui-helpers.ts +76 -3
  82. package/src/notifications/html-format.ts +75 -1
  83. package/src/notifications/index.ts +12 -1
  84. package/src/notifications/recent-activity.ts +32 -8
  85. package/src/notifications/telegram-daemon-control.ts +9 -0
  86. package/src/notifications/telegram-daemon.ts +68 -25
  87. package/src/notifications/telegram-reference.ts +36 -14
  88. package/src/notifications/threaded-render.ts +14 -6
  89. package/src/prompts/agents/architect.md +7 -4
  90. package/src/prompts/agents/critic.md +15 -6
  91. package/src/prompts/agents/planner.md +8 -3
  92. package/src/prompts/system/system-prompt.md +5 -1
  93. package/src/prompts/tools/bash.md +1 -1
  94. package/src/sdk.ts +6 -11
  95. package/src/session/agent-session.ts +60 -17
  96. package/src/slash-commands/builtin-registry.ts +30 -10
  97. package/src/slash-commands/types.ts +2 -0
  98. package/src/tools/ask.ts +136 -16
  99. package/src/tools/bash.ts +24 -8
  100. package/src/utils/cmux-workspace.ts +233 -0
  101. package/src/utils/title-generator.ts +2 -0
  102. package/src/web/kagi.ts +1 -1
  103. package/src/web/parallel.ts +2 -2
  104. package/src/web/search/index.ts +137 -52
  105. package/src/web/search/provider.ts +80 -0
  106. package/src/web/search/providers/anthropic.ts +1 -1
  107. package/src/web/search/providers/brave.ts +1 -1
  108. package/src/web/search/providers/codex.ts +1 -1
  109. package/src/web/search/providers/duckduckgo.ts +1 -1
  110. package/src/web/search/providers/exa.ts +1 -1
  111. package/src/web/search/providers/gemini.ts +7 -3
  112. package/src/web/search/providers/jina.ts +1 -1
  113. package/src/web/search/providers/kimi.ts +6 -1
  114. package/src/web/search/providers/openai-compatible.ts +1 -1
  115. package/src/web/search/providers/parallel.ts +1 -1
  116. package/src/web/search/providers/perplexity.ts +2 -2
  117. package/src/web/search/providers/searxng.ts +1 -1
  118. package/src/web/search/providers/synthetic.ts +1 -1
  119. package/src/web/search/providers/tavily.ts +1 -1
  120. package/src/web/search/providers/utils.ts +67 -17
  121. package/src/web/search/providers/xai.ts +1 -1
  122. package/src/web/search/providers/zai.ts +1 -1
  123. package/src/web/search/render.ts +3 -0
@@ -0,0 +1,52 @@
1
+ export interface CmuxWorkspaceRenameCommand {
2
+ command: string;
3
+ args: string[];
4
+ }
5
+ /** Current ownership state of a cmux workspace, read back from `cmux workspace list`. */
6
+ export interface CmuxWorkspaceOwnership {
7
+ /** cmux marks a workspace `has_custom_title` once an explicit title is set (by the user or by GJC). */
8
+ hasCustomTitle: boolean;
9
+ /** The workspace's current display title. */
10
+ title: string;
11
+ }
12
+ export interface CmuxWorkspaceRenameProcess {
13
+ exited: Promise<number>;
14
+ kill(): void;
15
+ unref(): void;
16
+ }
17
+ export interface CmuxWorkspaceTitleSyncOptions {
18
+ env?: NodeJS.ProcessEnv;
19
+ isTty?: boolean;
20
+ which?: (command: string) => string | null;
21
+ spawn?: (command: string[], options: {
22
+ env: NodeJS.ProcessEnv;
23
+ stdin: "ignore";
24
+ stdout: "ignore";
25
+ stderr: "ignore";
26
+ }) => CmuxWorkspaceRenameProcess;
27
+ /** Reads the current ownership state of `workspaceId`. Injectable for tests. */
28
+ readOwnership?: (cmuxCommand: string, workspaceId: string, env: NodeJS.ProcessEnv) => Promise<CmuxWorkspaceOwnership | null>;
29
+ }
30
+ export declare function sanitizeCmuxWorkspaceTitle(title: string | undefined): string | undefined;
31
+ export declare function formatCmuxWorkspaceTitle(title: string | undefined): string | undefined;
32
+ export declare function buildCmuxWorkspaceRenameCommand(sessionName: string | undefined, env?: NodeJS.ProcessEnv): CmuxWorkspaceRenameCommand | null;
33
+ /** Parse `cmux workspace list --json --id-format both` output and return the
34
+ * ownership state of the workspace matching `workspaceId` (by UUID `id` or `ref`). */
35
+ export declare function parseCmuxWorkspaceOwnership(jsonText: string, workspaceId: string): CmuxWorkspaceOwnership | null;
36
+ /** Only rename when GJC owns the name:
37
+ * - unknown ownership (read failed) → skip (fail safe, never clobber)
38
+ * - already the desired title → skip (no-op)
39
+ * - workspace still on its default title → rename
40
+ * - any custom title (user- or peer-set) → skip
41
+ * This makes GJC name a fresh workspace once and then leave it alone, so it
42
+ * never overwrites a user-pinned name and multiple sessions sharing one
43
+ * CMUX_WORKSPACE_ID do not thrash the workspace title. */
44
+ export declare function shouldRenameCmuxWorkspace(ownership: CmuxWorkspaceOwnership | null, desiredTitle: string): boolean;
45
+ /**
46
+ * Best-effort sync of the containing cmux workspace title to the current GJC
47
+ * session name. Ownership-guarded: GJC reads the current workspace title and
48
+ * only renames a workspace that still has its default title, so it never
49
+ * overwrites a name the user pinned or a name a peer session (sharing the same
50
+ * CMUX_WORKSPACE_ID) set. Opt out with GJC_NO_CMUX_RENAME.
51
+ */
52
+ export declare function syncCmuxWorkspaceTitle(sessionName: string | undefined, options?: CmuxWorkspaceTitleSyncOptions): Promise<void>;
@@ -44,6 +44,8 @@ export type SearchToolParams = z.infer<typeof webSearchSchema>;
44
44
  export interface SearchQueryParams extends SearchToolParams {
45
45
  provider?: SearchProviderId | "auto";
46
46
  }
47
+ /** Override the hedge delay (tests). Non-finite/non-positive resets to default. */
48
+ export declare function setDdgHedgeDelayMs(ms: number | undefined): void;
47
49
  /**
48
50
  * Execute a web search query for CLI/testing workflows.
49
51
  *
@@ -110,7 +112,7 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
110
112
  /** Web search tool as CustomTool (for TUI rendering support) */
111
113
  export declare const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRenderDetails>;
112
114
  export declare function getSearchTools(): CustomTool<any, any>[];
113
- export { getSearchProvider, setPreferredSearchProvider, setSearchFallbackProviders } from "./provider";
114
- export { setSearchHardTimeoutMs } from "./providers/utils";
115
+ export { getConfiguredSearchProviderPreference, getSearchProvider, setPreferredSearchProvider, setSearchFallbackProviders, } from "./provider";
116
+ export { applyConfiguredSearchTimeout, setSearchHardTimeoutMs } from "./providers/utils";
115
117
  export type { SearchProviderId as SearchProvider, SearchResponse } from "./types";
116
118
  export { isConfigurableSearchProviderId, isSearchProviderPreference } from "./types";
@@ -6,8 +6,22 @@ export { SearchProvider } from "./providers/base";
6
6
  export declare function getSearchProviderLabel(id: SearchProviderId): string;
7
7
  export declare function getSearchProvider(id: SearchProviderId): Promise<SearchProvider>;
8
8
  export declare const SEARCH_PROVIDER_ORDER: SearchProviderId[];
9
+ /** Drop every cached resolved chain (settings changed, tests). */
10
+ export declare function clearResolvedChainCache(): void;
9
11
  export declare function setPreferredSearchProvider(provider: SearchProviderId | "auto"): void;
10
12
  export declare function setSearchFallbackProviders(ids: readonly string[]): void;
13
+ /**
14
+ * Fire-and-forget warm-up: resolve the provider chain once so the provider
15
+ * modules are imported and the chain cache is primed before the first
16
+ * user-visible search. Errors are intentionally swallowed — prewarming must
17
+ * never surface a failure the real search path would handle itself.
18
+ */
19
+ export declare function prewarmSearchProviders(options: ResolveProviderChainOptions): void;
20
+ export interface SearchProviderPreferenceSource {
21
+ get(path: "providers.webSearch" | "web_search.provider"): SearchProviderId | "auto";
22
+ has(path: "providers.webSearch" | "web_search.provider"): boolean;
23
+ }
24
+ export declare function getConfiguredSearchProviderPreference(settings: SearchProviderPreferenceSource): SearchProviderId | "auto";
11
25
  export interface ResolveProviderChainOptions {
12
26
  authStorage: AuthStorage;
13
27
  sessionId?: string;
@@ -15,27 +15,53 @@ import { SearchProviderError, type SearchProviderId, type SearchSource } from ".
15
15
  */
16
16
  export declare function findCredential(storage: AgentStorage | null | undefined, envKey: string | null | undefined, ...storageProviders: string[]): string | null;
17
17
  /**
18
- * Default hard ceiling for a single web-search round-trip. 300s tolerates
19
- * legitimate slow LLM-mediated responses (anthropic web_search_20250305,
20
- * perplexity, gemini, OpenAI code backend) while still guaranteeing the session unfreezes
21
- * if Bun's `AbortSignal` fails to propagate on Windows.
22
- *
23
- * Pure search APIs (brave, exa, jina, tavily, searxng, synthetic, zai)
24
- * settle far faster in practice; reusing the same ceiling keeps the wiring
25
- * uniform without compromising correctness.
18
+ * Legacy hard ceiling for a single web-search round-trip, retained as the
19
+ * fallback for call sites that do not declare a timeout class. 300s tolerates
20
+ * pathological cases while still guaranteeing the session unfreezes if Bun's
21
+ * `AbortSignal` fails to propagate on Windows.
26
22
  */
27
23
  export declare const SEARCH_HARD_TIMEOUT_MS = 300000;
24
+ /**
25
+ * Hard ceiling for pure search APIs (brave, exa, jina, tavily, kagi,
26
+ * parallel, searxng, synthetic, zai, duckduckgo). These settle in ~1-3s in
27
+ * practice; a hung TCP/TLS connection should fall through to the next
28
+ * provider in seconds, not minutes. Kimi is the exception: it declares an
29
+ * explicit ceiling aligned with its upstream 30s request budget.
30
+ */
31
+ export declare const SEARCH_API_TIMEOUT_MS = 15000;
32
+ /**
33
+ * Hard ceiling for LLM-mediated search providers (anthropic, codex, gemini,
34
+ * perplexity, xai, openai-compatible). These legitimately take tens of
35
+ * seconds because a model performs the search and synthesizes an answer.
36
+ */
37
+ export declare const SEARCH_LLM_TIMEOUT_MS = 120000;
38
+ /** Timeout class a provider declares for its outbound round-trips. */
39
+ export type SearchTimeoutClass = "api" | "llm";
28
40
  /**
29
41
  * Override the hard timeout applied to every web-search round-trip.
30
42
  *
31
43
  * @param ms - Hard timeout in milliseconds. Non-finite or non-positive
32
- * values reset the timeout to {@link SEARCH_HARD_TIMEOUT_MS}.
44
+ * values clear the override so per-class defaults apply.
33
45
  */
34
46
  export declare function setSearchHardTimeoutMs(ms: number | undefined): void;
47
+ /** Structural settings source for {@link applyConfiguredSearchTimeout}. */
48
+ export interface SearchTimeoutSettingSource {
49
+ get(path: "web_search.timeout"): unknown;
50
+ has(path: "web_search.timeout"): boolean;
51
+ }
52
+ /**
53
+ * Install the `web_search.timeout` override from settings — but only when the
54
+ * user explicitly configured it. The settings schema ships a default value
55
+ * (300), and consuming that default via `get()` alone would reinstall a
56
+ * uniform 300s ceiling on every session, silently disabling the per-class
57
+ * timeout defaults. `has()` distinguishes loaded/overridden settings from
58
+ * schema defaults; unset clears any previous override so class defaults apply.
59
+ */
60
+ export declare function applyConfiguredSearchTimeout(settings: SearchTimeoutSettingSource): void;
35
61
  /**
36
62
  * Compose a caller-supplied {@link AbortSignal} with a hard timeout so an
37
- * outbound `fetch()` is guaranteed to settle within `ms` even when the
38
- * runtime fails to propagate cancellation to the underlying transport.
63
+ * outbound `fetch()` is guaranteed to settle even when the runtime fails to
64
+ * propagate cancellation to the underlying transport.
39
65
  *
40
66
  * Bun's WinHTTP backend on Windows is known to ignore `AbortSignal` once a
41
67
  * TCP/TLS connection stalls (oven-sh/bun#15275, oven-sh/bun#18536); without
@@ -43,9 +69,12 @@ export declare function setSearchHardTimeoutMs(ms: number | undefined): void;
43
69
  * because the user's Esc is never delivered to the native layer.
44
70
  *
45
71
  * @param signal - Caller cancellation signal, if any.
46
- * @param ms - Hard timeout in milliseconds. Defaults to the configured value.
72
+ * @param msOrClass - Explicit timeout in milliseconds, or a
73
+ * {@link SearchTimeoutClass} resolved to its class default. Omitted falls
74
+ * back to the legacy {@link SEARCH_HARD_TIMEOUT_MS} ceiling. A user-set
75
+ * `web_search.timeout` overrides class defaults (but not explicit ms).
47
76
  */
48
- export declare function withHardTimeout(signal: AbortSignal | undefined, ms?: number): AbortSignal;
77
+ export declare function withHardTimeout(signal: AbortSignal | undefined, msOrClass?: number | SearchTimeoutClass): AbortSignal;
49
78
  /**
50
79
  * Map a provider's raw source list to the unified SearchSource shape,
51
80
  * clamped to the requested result count and annotated with ageSeconds.
@@ -10,6 +10,7 @@ import type { SearchResponse } from "./types";
10
10
  export interface SearchRenderDetails {
11
11
  response: SearchResponse;
12
12
  error?: string;
13
+ warning?: string;
13
14
  }
14
15
  /** Render web search result with tree-based layout */
15
16
  export declare function renderSearchResult(result: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.7.9",
4
+ "version": "0.7.11",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -52,12 +52,12 @@
52
52
  "@agentclientprotocol/sdk": "0.21.0",
53
53
  "@babel/parser": "^7.29.3",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@gajae-code/stats": "0.7.9",
56
- "@gajae-code/agent-core": "0.7.9",
57
- "@gajae-code/ai": "0.7.9",
58
- "@gajae-code/natives": "0.7.9",
59
- "@gajae-code/tui": "0.7.9",
60
- "@gajae-code/utils": "0.7.9",
55
+ "@gajae-code/stats": "0.7.11",
56
+ "@gajae-code/agent-core": "0.7.11",
57
+ "@gajae-code/ai": "0.7.11",
58
+ "@gajae-code/natives": "0.7.11",
59
+ "@gajae-code/tui": "0.7.11",
60
+ "@gajae-code/utils": "0.7.11",
61
61
  "@puppeteer/browsers": "^2.13.0",
62
62
  "@types/turndown": "5.0.6",
63
63
  "@xterm/headless": "^6.0.0",
@@ -10,6 +10,7 @@ import { pipeline } from "node:stream/promises";
10
10
  import { $which, APP_NAME, isEnoent, VERSION } from "@gajae-code/utils";
11
11
  import { $ } from "bun";
12
12
  import chalk from "chalk";
13
+ import { installDefaultGjcDefinitions } from "../defaults/gjc-defaults";
13
14
  import { theme } from "../modes/theme/theme";
14
15
 
15
16
  const RELEASE_REPO = "Yeachan-Heo/gajae-code";
@@ -628,6 +629,29 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
628
629
  console.error(chalk.red(`Update failed: ${err}`));
629
630
  process.exit(1);
630
631
  }
632
+
633
+ await refreshInstalledDefaultSkills();
634
+ }
635
+
636
+ /**
637
+ * Refresh opted-in on-disk default workflow skill copies after a successful
638
+ * update. The four default skills ship embedded in the binary, so most users
639
+ * need nothing here. But users who ran `gjc setup defaults` have on-disk copies
640
+ * under the agent dir that shadow the embedded defaults; those would otherwise
641
+ * go stale after an update. Only rewrite files that already exist and differ —
642
+ * never materialize new copies for users who never opted in.
643
+ */
644
+ async function refreshInstalledDefaultSkills(): Promise<void> {
645
+ try {
646
+ const result = await installDefaultGjcDefinitions({ refreshOnly: true });
647
+ if (result.written > 0) {
648
+ console.log(
649
+ chalk.dim(`Refreshed ${result.written} local default workflow skill file(s) at ${result.targetRoot}`),
650
+ );
651
+ }
652
+ } catch (err) {
653
+ console.error(chalk.yellow(`Warning: failed to refresh local default workflow skills: ${err}`));
654
+ }
631
655
  }
632
656
 
633
657
  /**
@@ -9,13 +9,14 @@ import chalk from "chalk";
9
9
  import { Settings } from "../config/settings";
10
10
  import { initTheme, theme } from "../modes/theme/theme";
11
11
  import {
12
+ getConfiguredSearchProviderPreference,
12
13
  isConfigurableSearchProviderId,
13
14
  isSearchProviderPreference,
14
15
  runSearchQuery,
15
16
  type SearchQueryParams,
16
17
  } from "../web/search/index";
17
18
  import { SEARCH_PROVIDER_ORDER, setPreferredSearchProvider, setSearchFallbackProviders } from "../web/search/provider";
18
- import { setSearchHardTimeoutMs } from "../web/search/providers/utils";
19
+ import { applyConfiguredSearchTimeout } from "../web/search/providers/utils";
19
20
  import { renderSearchResult } from "../web/search/render";
20
21
  import type { SearchProviderId } from "../web/search/types";
21
22
 
@@ -170,8 +171,8 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
170
171
 
171
172
  await initTheme();
172
173
  const settings = await Settings.init();
173
- const configuredProvider = settings.get("providers.webSearch");
174
- if (typeof configuredProvider === "string" && isSearchProviderPreference(configuredProvider)) {
174
+ const configuredProvider = getConfiguredSearchProviderPreference(settings);
175
+ if (isSearchProviderPreference(configuredProvider)) {
175
176
  setPreferredSearchProvider(configuredProvider);
176
177
  }
177
178
  const configuredFallback = settings.get("web_search.fallback");
@@ -180,10 +181,7 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
180
181
  configuredFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
181
182
  );
182
183
  }
183
- const configuredTimeout = settings.get("web_search.timeout");
184
- if (typeof configuredTimeout === "number" && Number.isFinite(configuredTimeout) && configuredTimeout > 0) {
185
- setSearchHardTimeoutMs(configuredTimeout * 1000);
186
- }
184
+ applyConfiguredSearchTimeout(settings);
187
185
 
188
186
  const params: SearchQueryParams = {
189
187
  query: cmd.query,
package/src/cli.ts CHANGED
@@ -141,7 +141,7 @@ class RootHelpCommand extends Command {
141
141
  resume: Flags.string({ char: "r", description: "Resume a session (by ID prefix, path, or picker if omitted)" }),
142
142
  "session-dir": Flags.string({ description: "Directory for session storage and lookup" }),
143
143
  "no-session": Flags.boolean({ description: "Don't save session (ephemeral)" }),
144
- models: Flags.string({ description: "Comma-separated model patterns for Ctrl+P cycling" }),
144
+ models: Flags.string({ description: "Comma-separated model patterns for Alt+N cycling" }),
145
145
  "no-tools": Flags.boolean({ description: "Disable all built-in tools" }),
146
146
  "no-lsp": Flags.boolean({ description: "Disable LSP tools, formatting, and diagnostics" }),
147
147
  "no-pty": Flags.boolean({ description: "Disable PTY-based interactive bash execution" }),
@@ -119,7 +119,7 @@ export default class Index extends Command {
119
119
  description: "Don't save session (ephemeral)",
120
120
  }),
121
121
  models: Flags.string({
122
- description: "Comma-separated model patterns for Ctrl+P cycling",
122
+ description: "Comma-separated model patterns for Alt+N cycling",
123
123
  }),
124
124
  "no-tools": Flags.boolean({
125
125
  description: "Disable all built-in tools",
@@ -8,6 +8,7 @@ export default class Ralplan extends Command {
8
8
  '$ gjc ralplan "<task description>"',
9
9
  '$ gjc ralplan --interactive --deliberate "<task description>"',
10
10
  '$ gjc ralplan --write --stage planner --stage_n 1 --artifact "<markdown or path>"',
11
+ "$ gjc ralplan --write --stage critic --stage_n 1 --artifact-env GJC_RALPLAN_ARTIFACT",
11
12
  ];
12
13
 
13
14
  async run(): Promise<void> {
@@ -22,6 +22,7 @@ interface AppKeybindings {
22
22
  "app.suspend": true;
23
23
  "app.thinking.cycle": true;
24
24
  "app.thinking.toggle": true;
25
+ "app.commandPalette.open": true;
25
26
  "app.model.cycleForward": true;
26
27
  "app.model.cycleBackward": true;
27
28
  "app.model.select": true;
@@ -59,6 +60,10 @@ declare module "@gajae-code/tui" {
59
60
  interface Keybindings extends AppKeybindings {}
60
61
  }
61
62
 
63
+ export function defaultMessageQueueKeysForPlatform(platform: NodeJS.Platform = process.platform): KeyId {
64
+ return platform === "win32" ? "alt+q" : "alt+enter";
65
+ }
66
+
62
67
  /**
63
68
  * All keybindings definitions: TUI + app-specific.
64
69
  */
@@ -88,12 +93,16 @@ export const KEYBINDINGS = {
88
93
  defaultKeys: "ctrl+t",
89
94
  description: "Toggle thinking mode",
90
95
  },
91
- "app.model.cycleForward": {
96
+ "app.commandPalette.open": {
92
97
  defaultKeys: "ctrl+p",
98
+ description: "Open command palette",
99
+ },
100
+ "app.model.cycleForward": {
101
+ defaultKeys: "alt+n",
93
102
  description: "Cycle to next model",
94
103
  },
95
104
  "app.model.cycleBackward": {
96
- defaultKeys: "shift+ctrl+p",
105
+ defaultKeys: "alt+shift+n",
97
106
  description: "Cycle to previous model",
98
107
  },
99
108
  "app.model.select": {
@@ -121,7 +130,7 @@ export const KEYBINDINGS = {
121
130
  description: "Send follow-up message (no default; Ctrl+Enter submits)",
122
131
  },
123
132
  "app.message.queue": {
124
- defaultKeys: "alt+enter",
133
+ defaultKeys: defaultMessageQueueKeysForPlatform(),
125
134
  description: "Queue message for next turn",
126
135
  },
127
136
  "app.message.dequeue": {
@@ -141,8 +150,8 @@ export const KEYBINDINGS = {
141
150
  description: "Copy prompt",
142
151
  },
143
152
  "app.session.new": {
144
- defaultKeys: [],
145
- description: "Create new session",
153
+ defaultKeys: "ctrl+n",
154
+ description: "Start a new session",
146
155
  },
147
156
  "app.session.tree": {
148
157
  defaultKeys: [],
@@ -104,6 +104,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
104
104
  critic: "anthropic/claude-opus-4-8:high",
105
105
  architect: "anthropic/claude-opus-4-8:xhigh",
106
106
  }),
107
+ profile("claude-fable", ["anthropic"], {
108
+ default: "anthropic/claude-fable-5:xhigh",
109
+ executor: "anthropic/claude-sonnet-5",
110
+ planner: "anthropic/claude-fable-5:low",
111
+ critic: "anthropic/claude-fable-5:high",
112
+ architect: "anthropic/claude-fable-5:xhigh",
113
+ }),
107
114
  profile("glm-eco", ["zai"], {
108
115
  default: "zai/glm-5.2:low",
109
116
  executor: "zai/glm-5.2:minimal",
@@ -261,6 +268,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
261
268
  critic: "opencode-go/mimo-v2.5-pro",
262
269
  architect: "openai-codex/gpt-5.5:xhigh",
263
270
  }),
271
+ profile("fable-opus-codex", ["anthropic", "openai-codex"], {
272
+ default: "anthropic/claude-fable-5:high",
273
+ executor: "openai-codex/gpt-5.5:high",
274
+ planner: "anthropic/claude-opus-4-8:medium",
275
+ critic: "anthropic/claude-opus-4-8:high",
276
+ architect: "openai-codex/gpt-5.5:xhigh",
277
+ }),
264
278
  ];
265
279
 
266
280
  export interface ModelProfilePresentation {
@@ -278,6 +292,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
278
292
  "codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
279
293
  opencodego: { displayName: "OpenCodeGo", providerGroup: "OPENCODEGO" },
280
294
  "claude-opus": { displayName: "Claude Opus", providerGroup: "CLAUDE" },
295
+ "claude-fable": { displayName: "Claude Fable", providerGroup: "CLAUDE" },
281
296
  "glm-eco": { displayName: "GLM Eco", providerGroup: "GLM" },
282
297
  "glm-medium": { displayName: "GLM Medium", providerGroup: "GLM" },
283
298
  "glm-pro": { displayName: "GLM Pro", providerGroup: "GLM" },
@@ -299,6 +314,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
299
314
  "minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
300
315
  "opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
301
316
  "codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
317
+ "fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
302
318
  };
303
319
 
304
320
  const PROFILE_GROUP_ORDER = [
@@ -455,7 +455,7 @@ export const SETTINGS_SCHEMA = {
455
455
  // Status line
456
456
  "statusLine.preset": {
457
457
  type: "enum",
458
- values: ["default", "minimal", "compact", "full", "nerd", "ascii", "custom"] as const,
458
+ values: ["default", "default-usage", "minimal", "compact", "full", "nerd", "ascii", "custom"] as const,
459
459
  default: "default",
460
460
  ui: {
461
461
  tab: "appearance",
@@ -463,6 +463,11 @@ export const SETTINGS_SCHEMA = {
463
463
  description: "Pre-built status line configurations",
464
464
  options: [
465
465
  { value: "default", label: "Default", description: "Model, path, git, context, tokens, cost" },
466
+ {
467
+ value: "default-usage",
468
+ label: "Default + Usage",
469
+ description: "Default layout with provider usage quota",
470
+ },
466
471
  { value: "minimal", label: "Minimal", description: "Path and git only" },
467
472
  { value: "compact", label: "Compact", description: "Model, git, cost, context" },
468
473
  { value: "full", label: "Full", description: "All segments including time" },
@@ -2205,6 +2210,11 @@ export const SETTINGS_SCHEMA = {
2205
2210
  ui: { tab: "tools", label: "Web Search", description: "Enable the web_search tool for web searching" },
2206
2211
  },
2207
2212
 
2213
+ "web_search.provider": {
2214
+ type: "enum",
2215
+ values: ["auto", ...CONFIGURABLE_SEARCH_PROVIDER_IDS] as const,
2216
+ default: "auto",
2217
+ },
2208
2218
  "web_search.fallback": {
2209
2219
  type: "array",
2210
2220
  default: EMPTY_STRING_ARRAY,
@@ -1273,9 +1273,15 @@ async function sendTmuxPromptKeys(
1273
1273
  prompt: string,
1274
1274
  runner: CommandRunner = runCommand,
1275
1275
  ): Promise<boolean> {
1276
- const typed = await runner(["tmux", "send-keys", "-t", target, "-l", prompt]);
1277
- if (typed.exitCode !== 0) return false;
1278
- const submitted = await runner(["tmux", "send-keys", "-t", target, "-l", "\x1b[13;5u"]);
1276
+ const bufferName = `gjc-coordinator-prompt-${randomUUID()}`;
1277
+ const buffered = await runner(["tmux", "set-buffer", "-b", bufferName, "--", prompt]);
1278
+ if (buffered.exitCode !== 0) return false;
1279
+ const pasted = await runner(["tmux", "paste-buffer", "-d", "-b", bufferName, "-t", target]);
1280
+ if (pasted.exitCode !== 0) {
1281
+ await runner(["tmux", "delete-buffer", "-b", bufferName]);
1282
+ return false;
1283
+ }
1284
+ const submitted = await runner(["tmux", "send-keys", "-t", target, "Enter"]);
1279
1285
  return submitted.exitCode === 0;
1280
1286
  }
1281
1287
 
@@ -21,13 +21,14 @@ Deep Interview implements Ouroboros-inspired Socratic questioning with mathemati
21
21
  - User wants to avoid "that's not what I meant" outcomes from autonomous execution
22
22
  - Task is complex enough that jumping to code would waste cycles on scope discovery
23
23
  - User wants mathematically-validated clarity before committing to execution
24
+ - User explicitly requests deep-interview even after being told the request is already clear, bounded, and low-risk
24
25
  </Use_When>
25
26
 
26
27
  <Do_Not_Use_When>
27
28
  - User has a detailed, specific request with file paths, function names, or acceptance criteria -- execute directly
28
29
  - User wants to explore options or brainstorm -- use `ralplan` skill instead
29
- - User wants a quick fix or single change -- delegate to executor or execution
30
- - User says "just do it" or "skip the questions" without an explicit execution path -- respect their intent by ending interview and writing a `pending approval` spec, not by mutating files or delegating execution
30
+ - User wants a quick fix or single change -- use direct execution, not deep-interview or role-agent delegation
31
+ - User says "just do it" or "skip the questions" without an explicit execution path -- respect their intent by exiting deep-interview, not by writing a `pending approval` spec
31
32
  - User already has a PRD or plan file and explicitly asks to execute it -- use the requested execution skill with that plan
32
33
  </Do_Not_Use_When>
33
34
 
@@ -111,6 +112,27 @@ Deep Interview threshold: <resolvedThresholdPercent> (source: <resolvedThreshold
111
112
  - Include both threshold and source in the final spec metadata.
112
113
  - Read any `language` object from active deep-interview state and carry `language.instruction` forward mechanically. If absent, default to English unless `{{ARGUMENTS}}` makes another user/session language obvious or the user explicitly requests another language. Do not add language-specific special cases.
113
114
 
115
+ ## Phase 0.5: Suitability Gate
116
+
117
+ Run this gate after the Phase 0 threshold marker and before Phase 1, brownfield exploration, `gjc state write`, Round 0, ambiguity scoring, or spec writing.
118
+
119
+ If `{{ARGUMENTS}}` is already clear, bounded, low-risk, and asks for a quick fix, single change, known file/symbol edit, explicit command, or direct answer:
120
+
121
+ 1. **Stop deep-interview immediately**:
122
+ - First inspect current-session state with `gjc state read --mode deep-interview --json` (include `--session-id <current-session-id>` when available).
123
+ - Clear through `gjc state clear --force --mode deep-interview --json` only when the state is a newly seeded empty interview: no recorded `rounds`, no `spec_path`, no `handoff_from`, no final/pending spec, and no user-confirmed topology.
124
+ - If state already contains rounds, a spec path, handoff metadata, pending approval, or confirmed topology, do not clear it. Preserve the active interview and ask the user whether to continue, cancel, or explicitly clear the workflow.
125
+ - Do not initialize deep-interview state.
126
+ - Do not run Round 0.
127
+ - Do not write a pending-approval spec.
128
+ - Do not hand off to `ralplan`, `ultragoal`, `team`, or a role agent.
129
+ 2. **Return the request to direct implementation**:
130
+ - Say briefly that deep-interview is unnecessary because the request is already clear and small.
131
+ - State the direct implementation path the normal coding agent should take.
132
+ - If the user explicitly insists on deep-interview anyway, continue to Phase 1.
133
+
134
+ This gate exists to prevent deep-interview from making easy problems harder. A small verification need does not make a request interview-worthy.
135
+
114
136
  ## Phase 1: Initialize
115
137
 
116
138
  1. **Parse the user's idea** from `{{ARGUMENTS}}`
@@ -314,6 +336,8 @@ After applying `language.instruction` to the visible question, options, and gene
314
336
 
315
337
  When calling `ask`, SHOULD include optional structured metadata so the runtime can record the round without manual state writes: `deepInterview.round_id?`, `deepInterview.round`, `deepInterview.component`, `deepInterview.dimension`, and `deepInterview.ambiguity`. Keep this metadata aligned with the visible Round/Component/Targeting/Ambiguity line; if metadata cannot be supplied, the legacy formatted question text remains the fallback.
316
338
 
339
+ If the `ask` tool returns `clarificationQuestion`, treat it as a non-answer about the displayed choices. Answer the clarification briefly from the current interview context, then call `ask` again with the exact original question, options, and `deepInterview.*` metadata. A clarification bypasses Step 2b′ auto-answer, Step 2b″ free-text refine, Step 2c ambiguity scoring, Step 2d progress reporting, and Step 2e state updates; it must not be recorded as a round answer. This does not violate the one-question-per-round rule because the round remains unresolved until the user submits a real listed option or `Other` answer.
340
+
317
341
  ### Step 2b′: Auto-Answer Opted-Out Questions
318
342
 
319
343
  After the `ask` tool resolves and before ambiguity scoring, if the user opts out of answering the current question or explicitly asks the agent to decide, load `auto-answer-uncertain.md` as an internal `kind: "skill-fragment"` prompt for a fork-context architect. Pass the opted-out question, prompt-safe transcript summary, locked topology, current scores/gaps, and any auto-research candidates used for the round. The architect must return exactly one decisive answer with rationale, confidence, and explicit uncertainty. Validate the response shape before using it; if valid, record it as the tentative answer for scoring, append the round number to `auto_answered_rounds`, and mark the transcript answer as architect-assisted.
@@ -45,13 +45,15 @@ Planning artifacts and stage handoffs MUST be persisted through the ralplan CLI
45
45
 
46
46
  ```bash
47
47
  gjc ralplan --write --stage <type> --stage_n <N> --artifact "markdown file path or markdown string"
48
+ # restricted role agents use:
49
+ gjc ralplan --write --stage <type> --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT
48
50
  ```
49
51
 
50
- Use stage values that match the producer or artifact kind, such as `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`. Increment `--stage_n` for each consensus-loop pass. The `--artifact` value may be either a markdown file path prepared outside `.gjc/` for ingestion or the markdown content string itself. The native `--write` handler persists markdown under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/stage-<NN>-<stage>.md`, maintains an `index.jsonl` audit log, and for `final` stages additionally writes a `pending-approval.md` copy. Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
52
+ Use stage values that match the producer or artifact kind, such as `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`. Increment `--stage_n` for each consensus-loop pass. The `--artifact` value may be either a markdown file path prepared outside `.gjc/` for ingestion or the markdown content string itself. The native `--write` handler also accepts `--artifact-env GJC_RALPLAN_ARTIFACT` to read markdown from that per-command env override. It persists markdown under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/stage-<NN>-<stage>.md`, maintains an `index.jsonl` audit log, and for `final` stages additionally writes a `pending-approval.md` copy. Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
51
53
 
52
- While ralplan is active it is a pre-approval planning phase: product-code mutation tools (`write`/`edit`/`ast_edit`) and product-mutating `bash` (e.g. `tee src/...`, redirects into the project tree) are blocked, exactly like deep-interview. Prefer passing the `--artifact` markdown **inline** (the content string) so no scratch file is needed; this is mandatory for restricted role agents (see below). Only the leader, and only when an artifact is too large to pass inline, may stage it as a file in a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree and pass that path — never write scratch files into the repo or `.gjc/`. Product code is mutated only after the plan is approved and execution begins.
54
+ While ralplan is active it is a pre-approval planning phase: product-code mutation tools (`write`/`edit`/`ast_edit`) and product-mutating `bash` (e.g. `tee src/...`, redirects into the project tree) are blocked, exactly like deep-interview. Leaders may pass `--artifact` markdown inline or, when an artifact is too large to pass inline, stage it as a file in a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree and pass that path — never write scratch files into the repo or `.gjc/`. Product code is mutated only after the plan is approved and execution begins.
53
55
 
54
- Restricted read-only role agents (`planner`, `architect`, and `critic`) must pass markdown content directly in `--artifact`; their restricted bash environment intentionally disables artifact file-path ingestion so a verdict command cannot persist arbitrary file contents.
56
+ Restricted read-only role agents (`planner`, `architect`, and `critic`) must pass markdown content through the `GJC_RALPLAN_ARTIFACT` env override with `--artifact-env GJC_RALPLAN_ARTIFACT`; their restricted bash environment intentionally disables artifact file-path ingestion so a verdict command cannot persist arbitrary file contents.
55
57
 
56
58
  After a role agent persists a stage artifact, its model-facing response to the caller SHOULD be receipt-only: return the `gjc ralplan --write --json` receipt (`run_id`, `path`, `stage`, `stage_n`, `sha256`, `created_at`) plus the minimal verdict/status fields the caller needs for routing, and do **not** paste the full persisted markdown back into the parent conversation. Downstream reviewers should receive the artifact path/receipt and read the persisted file themselves when they actually need the body. This preserves the audit trail while preventing Planner/Architect/Critic verdict bodies from being duplicated into the main-agent context.
57
59
 
@@ -60,7 +62,7 @@ RECEIPT-ONLY guideline: role agents (`planner`, `architect`, and `critic`) persi
60
62
  This skill runs GJC planning in consensus mode for the provided arguments.
61
63
 
62
64
  The consensus workflow:
63
- 1. **Planner** creates the initial plan and a compact **RALPLAN-DR summary** before review. Launch the Planner ONCE per run as a detached, resumable subagent (await it before the Architect) and record its returned subagent id as the run's persisted Planner id; persist the stage with `gjc ralplan --write --stage planner --stage_n 1 --artifact "..." --planner-id <id> --planner-resumable <true|false>` (see **Persisted Planner** below):
65
+ 1. **Planner** creates the initial plan and a compact **RALPLAN-DR summary** before review. Launch the Planner ONCE per run as a detached, resumable subagent (await it before the Architect) and record its returned subagent id as the run's persisted Planner id; persist the stage with `gjc ralplan --write --stage planner --stage_n 1 --artifact-env GJC_RALPLAN_ARTIFACT --planner-id <id> --planner-resumable <true|false>` (see **Persisted Planner** below):
64
66
  - After persistence, return only the receipt/path plus compact planning status; do not paste the full plan markdown back to the caller unless explicitly requested.
65
67
  - Principles (3-5)
66
68
  - Decision Drivers (top 3)
@@ -69,26 +71,26 @@ The consensus workflow:
69
71
  - Deliberate mode only: pre-mortem (3 scenarios) + expanded test plan (unit/integration/e2e/observability)
70
72
  2. **User feedback** *(--interactive only)*: If `--interactive` is set, use the `ask` tool to present the draft plan **plus the Principles / Drivers / Options summary** before review (Proceed to review / Request changes / Skip review). Otherwise, automatically proceed to review.
71
73
  3. **Architect** reviews for architectural soundness and must provide the strongest steelman antithesis, at least one real tradeoff tension, and (when possible) synthesis — **await completion before step 4**. In deliberate mode, Architect should explicitly flag principle violations.
72
- - The Architect agent/subagent must persist its review with `gjc ralplan --write --stage architect --stage_n <N> --artifact "..." --json`, then return the receipt/path plus compact verdict/status (`CLEAR`/`WATCH`/`BLOCK`, `APPROVE`/`COMMENT`/`REQUEST CHANGES`) instead of pasting the full review body.
74
+ - The Architect agent/subagent must persist its review with `gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus compact verdict/status (`CLEAR`/`WATCH`/`BLOCK`, `APPROVE`/`COMMENT`/`REQUEST CHANGES`) instead of pasting the full review body.
73
75
  4. **Critic** evaluates against quality criteria — run only after step 3 completes. Critic must enforce principle-option consistency, fair alternatives, risk mitigation clarity, testable acceptance criteria, and concrete verification steps. In deliberate mode, Critic must reject missing/weak pre-mortem or expanded test plan.
74
- - The Critic agent/subagent must persist its evaluation with `gjc ralplan --write --stage critic --stage_n <N> --artifact "..." --json`, then return the receipt/path plus compact verdict/status (`OKAY`/`ITERATE`/`REJECT`) instead of pasting the full evaluation body.
75
- 5. **Re-review loop** (max 5 iterations): Any non-`APPROVE` Critic verdict (`ITERATE` or `REJECT`) MUST run the same full closed loop:
76
+ - The Critic agent/subagent must persist its evaluation with `gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus compact verdict/status (`OKAY`/`ITERATE`/`REJECT`) instead of pasting the full evaluation body.
77
+ 5. **Re-review loop** (max 5 iterations): Any non-`OKAY` Critic verdict (`ITERATE` or `REJECT`) MUST run the same full closed loop:
76
78
  a. Collect Architect + Critic feedback
77
79
  b. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback (see **Persisted Planner** below); fall back to a fresh Planner spawn only per the fallback routing table
78
80
  c. Return to Architect review
79
- - Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact "..." --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
81
+ - Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
80
82
  d. Return to Critic evaluation
81
- e. Repeat this loop until Critic returns `APPROVE` or 5 iterations are reached
82
- f. If 5 iterations are reached without `APPROVE`, present the best version to the user
83
- 6. **Post-ralplan interview** (intent reconciliation gate): After Critic returns `APPROVE` and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
83
+ e. Repeat this loop until Critic returns `OKAY` or 5 iterations are reached
84
+ f. If 5 iterations are reached without `OKAY`, present the best version to the user
85
+ 6. **Post-ralplan interview** (intent reconciliation gate): After Critic returns `OKAY` and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
84
86
  a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
85
87
  b. **Cross-check prior context for conflicts**: glob `.gjc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
86
88
  c. **Reconcile with the user via the `ask` tool (always, regardless of `--interactive`)**: Never stop idle with plain-text prose after the consensus loop. Every reconciliation question MUST go through the `ask` tool with contextual options plus free-text.
87
89
  - If open items exist, confirm the open assumptions and conflicts **one at a time** with the `ask` tool, weakest/highest-impact first, polishing intent. If any confirmation reveals that the plan diverges from user intent, route the consolidated correction back into the re-review loop (step 5b Planner revision) and re-run Architect + Critic before returning here. Cap at the same 5-iteration ceiling.
88
90
  - If the plan is crystal clear (no open assumptions or prior-context conflicts), skip straight to the step 8 final-options `ask` instead of inventing filler questions.
89
91
  - For every confirmed open item, embed the resolved outcome into the final plan under an **## Intent Reconciliation** section so the `pending approval` artifact records each decision; record any item the user explicitly defers as an open confirmation under that same section.
90
- d. Persist the reconciliation with `gjc ralplan --write --stage post-interview --stage_n <N> --artifact "..." --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
91
- 7. On reconciliation completion, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact "..."`, and do not directly edit `.gjc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
92
+ d. Persist the reconciliation with `gjc ralplan --write --stage post-interview --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
93
+ 7. On reconciliation completion, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT`, and do not directly edit `.gjc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
92
94
  8. **Always** present the finalized plan via the `ask` tool (regardless of `--interactive`) with `workflowGate: { stage: "ralplan", kind: "approval" }` on the final question so RPC/headless clients receive a `ralplan`/`approval` workflow gate, not a deep-interview question gate. Use these options:
93
95
  - **Refine further** — re-run the consensus loop / request changes, then return here
94
96
  - **Approve execution via ultragoal (Recommended)** — goal-tracked autonomous execution
@@ -128,7 +130,7 @@ The Planner is a **same-session persisted subagent**: launched detached once, aw
128
130
  **Recording persisted-Planner metadata** (audit/routing only — never claim `subagent list` proves resumability, since the snapshot does not expose `resumable`). Ride these optional flags on the normal `--write` for the planner/revision stage of the pass:
129
131
 
130
132
  ```
131
- gjc ralplan --write --stage revision --stage_n <N> --artifact "..." \
133
+ gjc ralplan --write --stage revision --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT \
132
134
  --planner-id <id> --planner-resumable <true|false> \
133
135
  --fallback-reason <context_unavailable|not_found|no_runner|resume_failed|process_restart|missing_record> \
134
136
  --fallback-attempted-id <id> --fallback-stage-n <N> \