@gajae-code/coding-agent 0.7.10 → 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 (88) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/dist/types/config/keybindings.d.ts +11 -5
  4. package/dist/types/defaults/gjc-defaults.d.ts +7 -0
  5. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  6. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
  7. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
  8. package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
  9. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  10. package/dist/types/modes/components/skill-message.d.ts +1 -0
  11. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  12. package/dist/types/modes/controllers/input-controller.d.ts +4 -0
  13. package/dist/types/modes/interactive-mode.d.ts +1 -0
  14. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
  15. package/dist/types/modes/slash-command-visibility.d.ts +1 -0
  16. package/dist/types/modes/types.d.ts +1 -0
  17. package/dist/types/sdk.d.ts +1 -1
  18. package/dist/types/session/agent-session.d.ts +8 -5
  19. package/dist/types/slash-commands/types.d.ts +2 -0
  20. package/dist/types/utils/cmux-workspace.d.ts +30 -1
  21. package/dist/types/web/search/index.d.ts +3 -1
  22. package/dist/types/web/search/provider.d.ts +9 -0
  23. package/dist/types/web/search/providers/utils.d.ts +42 -13
  24. package/package.json +7 -7
  25. package/src/cli/update-cli.ts +24 -0
  26. package/src/cli/web-search-cli.ts +2 -5
  27. package/src/cli.ts +1 -1
  28. package/src/commands/launch.ts +1 -1
  29. package/src/commands/ralplan.ts +1 -0
  30. package/src/config/keybindings.ts +14 -5
  31. package/src/config/model-profiles.ts +16 -0
  32. package/src/coordinator-mcp/server.ts +9 -3
  33. package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
  34. package/src/defaults/gjc-defaults.ts +16 -0
  35. package/src/extensibility/extensions/runner.ts +2 -1
  36. package/src/gjc-runtime/launch-tmux.ts +164 -42
  37. package/src/gjc-runtime/ralplan-runtime.ts +22 -6
  38. package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
  39. package/src/gjc-runtime/state-runtime.ts +8 -5
  40. package/src/gjc-runtime/state-writer.ts +5 -5
  41. package/src/internal-urls/docs-index.generated.ts +5 -4
  42. package/src/main.ts +2 -2
  43. package/src/migrate/executor.ts +23 -6
  44. package/src/modes/components/custom-editor.ts +8 -0
  45. package/src/modes/components/model-selector.ts +6 -2
  46. package/src/modes/components/settings-selector.ts +0 -2
  47. package/src/modes/components/skill-message.ts +18 -10
  48. package/src/modes/controllers/command-controller.ts +23 -0
  49. package/src/modes/controllers/input-controller.ts +41 -9
  50. package/src/modes/interactive-mode.ts +16 -7
  51. package/src/modes/prompt-action-autocomplete.ts +23 -1
  52. package/src/modes/slash-command-visibility.ts +9 -0
  53. package/src/modes/types.ts +1 -0
  54. package/src/modes/utils/hotkeys-markdown.ts +5 -2
  55. package/src/modes/utils/ui-helpers.ts +76 -3
  56. package/src/notifications/telegram-daemon.ts +4 -4
  57. package/src/prompts/agents/architect.md +7 -4
  58. package/src/prompts/agents/critic.md +15 -6
  59. package/src/prompts/agents/planner.md +8 -3
  60. package/src/prompts/tools/bash.md +1 -1
  61. package/src/sdk.ts +3 -6
  62. package/src/session/agent-session.ts +60 -17
  63. package/src/slash-commands/builtin-registry.ts +30 -10
  64. package/src/slash-commands/types.ts +2 -0
  65. package/src/tools/bash.ts +24 -8
  66. package/src/utils/cmux-workspace.ts +134 -7
  67. package/src/utils/title-generator.ts +1 -1
  68. package/src/web/kagi.ts +1 -1
  69. package/src/web/parallel.ts +2 -2
  70. package/src/web/search/index.ts +118 -45
  71. package/src/web/search/provider.ts +65 -0
  72. package/src/web/search/providers/anthropic.ts +1 -1
  73. package/src/web/search/providers/brave.ts +1 -1
  74. package/src/web/search/providers/codex.ts +1 -1
  75. package/src/web/search/providers/duckduckgo.ts +1 -1
  76. package/src/web/search/providers/exa.ts +1 -1
  77. package/src/web/search/providers/gemini.ts +7 -3
  78. package/src/web/search/providers/jina.ts +1 -1
  79. package/src/web/search/providers/kimi.ts +6 -1
  80. package/src/web/search/providers/openai-compatible.ts +1 -1
  81. package/src/web/search/providers/parallel.ts +1 -1
  82. package/src/web/search/providers/perplexity.ts +2 -2
  83. package/src/web/search/providers/searxng.ts +1 -1
  84. package/src/web/search/providers/synthetic.ts +1 -1
  85. package/src/web/search/providers/tavily.ts +1 -1
  86. package/src/web/search/providers/utils.ts +67 -17
  87. package/src/web/search/providers/xai.ts +1 -1
  88. package/src/web/search/providers/zai.ts +1 -1
@@ -25,7 +25,7 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
25
25
 
26
26
  <constraints>
27
27
  - Read-only: never write, edit, format, commit, push, or mutate files.
28
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown inline in `--artifact`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
28
+ - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
29
29
  - Never approve code or plans you have not grounded in inspected files.
30
30
  - Never give generic advice detached from this codebase.
31
31
  - Never approve CRITICAL or HIGH severity issues.
@@ -63,6 +63,9 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
63
63
  ## Summary
64
64
  2-3 sentences with result and main recommendation.
65
65
 
66
+ ## Claims
67
+ Evidence-backed claims being reviewed or introduced.
68
+
66
69
  ## Analysis
67
70
  Evidence-backed findings.
68
71
 
@@ -81,12 +84,12 @@ Prioritized concrete actions.
81
84
  ## Code Review Recommendation
82
85
  `APPROVE` / `COMMENT` / `REQUEST CHANGES`
83
86
 
84
- ## Trade-offs
87
+ ## Tradeoffs
85
88
  Table or bullets comparing viable options when relevant.
86
89
 
87
- Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown inline (never a file path, never `/tmp`):
90
+ Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
88
91
 
89
- gjc ralplan --write --stage architect --stage_n <N> --artifact "<full review markdown>" --json
92
+ gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
90
93
 
91
94
  Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (Architectural Status + Code Review Recommendation). Never paste the full review body back into your response — the caller reads the persisted artifact when it needs the full text.
92
95
  </output_contract>
@@ -17,7 +17,7 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
17
17
 
18
18
  <constraints>
19
19
  - Read-only: do not write, edit, format, commit, push, or mutate files.
20
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown inline in `--artifact`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
20
+ - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
21
21
  - A lone file path is valid input; read and evaluate it.
22
22
  - Reject YAML-only plans as invalid plan format when a human-readable plan is required.
23
23
  - Do not invent problems; report no issues found when the plan passes.
@@ -42,11 +42,19 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
42
42
  </success_criteria>
43
43
 
44
44
  <output_contract>
45
+ ## Verdict
45
46
  **[OKAY / ITERATE / REJECT]**
46
47
 
47
- **Justification**: concise evidence-backed explanation.
48
+ ## Claim Checks
49
+ Concise evidence-backed explanation of verified claims.
48
50
 
49
- **Summary**:
51
+ ## Missing Evidence
52
+ Definitely missing or unverified evidence, or `None`.
53
+
54
+ ## Approval Boundary
55
+ What execution may proceed with, and what remains outside approval.
56
+
57
+ ## Summary
50
58
  - Clarity
51
59
  - Verifiability
52
60
  - Completeness
@@ -55,11 +63,12 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
55
63
  - Alternatives Depth
56
64
  - Risk/Verification Rigor
57
65
 
58
- If not OKAY, list concrete required fixes.
66
+ ## Required Changes
67
+ If not OKAY, list concrete required fixes; otherwise write `None`.
59
68
 
60
- Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown inline (never a file path, never `/tmp`):
69
+ Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
61
70
 
62
- gjc ralplan --write --stage critic --stage_n <N> --artifact "<full evaluation markdown>" --json
71
+ gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
63
72
 
64
73
  Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (OKAY / ITERATE / REJECT). Never paste the full evaluation body back into your response — the caller reads the persisted artifact when it needs the full text.
65
74
  </output_contract>
@@ -17,7 +17,7 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
17
17
 
18
18
  <constraints>
19
19
  - Read-only: never write, edit, format, commit, push, or mutate files.
20
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown inline in `--artifact`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
20
+ - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
21
21
  - Persist durable plans only through `gjc ralplan --write`. Never write plan files to `/tmp`, the repository, or any other path, and never rely on a file the caller must read back. The CLI is your only persistence channel.
22
22
  - Inspect the repository before asking about code facts.
23
23
  - Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences that repository inspection cannot resolve.
@@ -45,16 +45,21 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
45
45
  <output_contract>
46
46
  Build the full plan as a single markdown document containing:
47
47
  - Summary
48
+ - Intent Diff
49
+ - Decision Drivers
50
+ - Options
48
51
  - In scope / out of scope
49
52
  - File-level changes
50
53
  - Sequencing and dependencies
51
54
  - Acceptance criteria
52
55
  - Verification
56
+ - Escalation/Risk Gate
57
+ - Verification Plan
53
58
  - Risks and mitigations
54
59
 
55
- Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan inline (never a file path, never `/tmp`):
60
+ Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
56
61
 
57
- gjc ralplan --write --stage planner --stage_n <N> --artifact "<full plan markdown>" --json
62
+ gjc ralplan --write --stage planner --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
58
63
 
59
64
  Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back into your response — the caller reads the persisted artifact when it needs the full text.
60
65
  </output_contract>
@@ -33,7 +33,7 @@ This session's bash tool is restricted. It only accepts commands beginning with:
33
33
  {{#each restrictedAllowedPrefixes}}
34
34
  - `{{this}}`
35
35
  {{/each}}
36
- Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; per-command env overrides and all other shell command shapes are blocked before execution.
36
+ Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; the only per-command env override allowed is `GJC_RALPLAN_ARTIFACT` when paired with `gjc ralplan --write ... --artifact-env GJC_RALPLAN_ARTIFACT`, and all other shell command shapes are blocked before execution.
37
37
  {{/when}}
38
38
  </restricted-bash-mode>
39
39
  {{/if}}
package/src/sdk.ts CHANGED
@@ -119,6 +119,7 @@ import { AgentOutputManager } from "./task/output-manager";
119
119
  import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
120
120
  import { collectDiscoverableTools, type DiscoverableTool } from "./tool-discovery/tool-index";
121
121
  import {
122
+ applyConfiguredSearchTimeout,
122
123
  BashTool,
123
124
  BUILTIN_TOOLS,
124
125
  computeEssentialBuiltinNames,
@@ -140,7 +141,6 @@ import {
140
141
  setPreferredImageProvider,
141
142
  setPreferredSearchProvider,
142
143
  setSearchFallbackProviders,
143
- setSearchHardTimeoutMs,
144
144
  type Tool,
145
145
  type ToolSession,
146
146
  WebSearchTool,
@@ -255,7 +255,7 @@ export interface CreateAgentSessionOptions {
255
255
  thinkingLevel?: ThinkingLevel;
256
256
  /** Runtime substitution metadata for the initial model_change session event. */
257
257
  modelSubstitution?: { requestedModel: Model; reason: string };
258
- /** Models available for cycling (Ctrl+P in interactive mode) */
258
+ /** Models available for cycling (Alt+N in interactive mode) */
259
259
  scopedModels?: ScopedModelSelection[];
260
260
 
261
261
  /** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
@@ -940,10 +940,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
940
940
  webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
941
941
  );
942
942
  }
943
- const webSearchTimeout = settings.get("web_search.timeout");
944
- if (typeof webSearchTimeout === "number" && Number.isFinite(webSearchTimeout) && webSearchTimeout > 0) {
945
- setSearchHardTimeoutMs(webSearchTimeout * 1000);
946
- }
943
+ applyConfiguredSearchTimeout(settings);
947
944
 
948
945
  const imageProvider = settings.get("providers.image");
949
946
  if (
@@ -353,7 +353,7 @@ export interface AgentSessionConfig {
353
353
  agent: Agent;
354
354
  sessionManager: SessionManager;
355
355
  settings: Settings;
356
- /** Models to cycle through with Ctrl+P (from --models flag) */
356
+ /** Models to cycle through with Alt+N (from --models flag) */
357
357
  scopedModels?: ScopedModelSelection[];
358
358
  /** Initial session thinking selector. */
359
359
  thinkingLevel?: ThinkingLevel;
@@ -901,8 +901,9 @@ function extractPermissionLocations(
901
901
  * `tag` is set only by `enqueueCustomMessageDisplay` (used for skill-prompt
902
902
  * custom messages queued during streaming) and is matched by the custom-role
903
903
  * `message_start` dequeue branch; user-message pushes leave it undefined and
904
- * rely on the existing text-equality match. */
905
- type QueuedDisplayEntry = { text: string; tag?: string };
904
+ * rely on the existing text-equality match. `sequence` preserves cross-queue
905
+ * insertion order for one-at-a-time dequeue/edit UX. */
906
+ type QueuedDisplayEntry = { text: string; tag?: string; sequence: number };
906
907
 
907
908
  /** A custom message contributed at the before-agent-start point. */
908
909
  export type BeforeAgentStartInternalMessage = Pick<
@@ -956,6 +957,7 @@ export class AgentSession {
956
957
  /** Tracks pending follow-up messages for UI display. Removed when delivered.
957
958
  * See `#steeringMessages` for entry shape. */
958
959
  #followUpMessages: QueuedDisplayEntry[] = [];
960
+ #queuedDisplaySequence = 0;
959
961
  /** Messages queued to be included with the next user prompt as context ("asides"). */
960
962
  #pendingNextTurnMessages: CustomMessage[] = [];
961
963
  #scheduledHiddenNextTurnGeneration: number | undefined = undefined;
@@ -1625,6 +1627,14 @@ export class AgentSession {
1625
1627
  this.#planCompactAbortPending = false;
1626
1628
  }
1627
1629
 
1630
+ #createQueuedDisplayEntry(text: string, tag?: string): QueuedDisplayEntry {
1631
+ const entry: QueuedDisplayEntry = { text, sequence: ++this.#queuedDisplaySequence };
1632
+ if (tag !== undefined) {
1633
+ entry.tag = tag;
1634
+ }
1635
+ return entry;
1636
+ }
1637
+
1628
1638
  /** Register a compact display string for a custom message that the caller is
1629
1639
  * about to dispatch via `promptCustomMessage` / `sendCustomMessage`.
1630
1640
  * Returns a stable tag the caller MUST embed in
@@ -1638,7 +1648,7 @@ export class AgentSession {
1638
1648
  const tag = `gjc-cmd-${Date.now()}-${++this.#customDisplayTagCounter}`;
1639
1649
  const displayText = text.trim();
1640
1650
  if (!displayText) return tag;
1641
- const entry: QueuedDisplayEntry = { text: displayText, tag };
1651
+ const entry = this.#createQueuedDisplayEntry(displayText, tag);
1642
1652
  if (mode === "steer") {
1643
1653
  this.#steeringMessages.push(entry);
1644
1654
  } else {
@@ -5285,7 +5295,7 @@ export class AgentSession {
5285
5295
  */
5286
5296
  async #queueSteer(text: string, images?: ImageContent[]): Promise<void> {
5287
5297
  const displayText = text || (images && images.length > 0 ? "[Image]" : "");
5288
- this.#steeringMessages.push({ text: displayText });
5298
+ this.#steeringMessages.push(this.#createQueuedDisplayEntry(displayText));
5289
5299
  const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
5290
5300
  if (images && images.length > 0) {
5291
5301
  content.push(...images);
@@ -5316,7 +5326,7 @@ export class AgentSession {
5316
5326
  */
5317
5327
  async #queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
5318
5328
  const displayText = text || (images && images.length > 0 ? "[Image]" : "");
5319
- this.#followUpMessages.push({ text: displayText });
5329
+ this.#followUpMessages.push(this.#createQueuedDisplayEntry(displayText));
5320
5330
  const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
5321
5331
  if (images && images.length > 0) {
5322
5332
  content.push(...images);
@@ -5686,24 +5696,27 @@ export class AgentSession {
5686
5696
  }
5687
5697
 
5688
5698
  /**
5689
- * Pop the last queued message (steering first, then follow-up).
5699
+ * Pop the newest queued message across steering and follow-up queues.
5690
5700
  * Used by dequeue keybinding to restore messages to editor one at a time.
5691
5701
  * Returns the popped entry's `.text`; the tag (if any) dies with the
5692
5702
  * record — no orphan state can outlive the queue entry.
5693
5703
  */
5694
5704
  popLastQueuedMessage(): string | undefined {
5695
- // Pop from steering first (LIFO)
5696
- if (this.#steeringMessages.length > 0) {
5705
+ const steeringEntry = this.#steeringMessages.at(-1);
5706
+ const followUpEntry = this.#followUpMessages.at(-1);
5707
+
5708
+ if (steeringEntry && (!followUpEntry || steeringEntry.sequence > followUpEntry.sequence)) {
5697
5709
  const entry = this.#steeringMessages.pop();
5698
5710
  this.agent.popLastSteer();
5699
5711
  return entry?.text;
5700
5712
  }
5701
- // Then from follow-up
5702
- if (this.#followUpMessages.length > 0) {
5713
+
5714
+ if (followUpEntry) {
5703
5715
  const entry = this.#followUpMessages.pop();
5704
5716
  this.agent.popLastFollowUp();
5705
5717
  return entry?.text;
5706
5718
  }
5719
+
5707
5720
  return undefined;
5708
5721
  }
5709
5722
 
@@ -8932,22 +8945,52 @@ export class AgentSession {
8932
8945
  setAutoRetryEnabled(enabled: boolean): void {
8933
8946
  this.settings.set("retry.enabled", enabled);
8934
8947
  }
8948
+ #isInterruptedRetryTail(message: AgentMessage | undefined): boolean {
8949
+ if (!message) return false;
8950
+ return (
8951
+ message.role === "user" ||
8952
+ message.role === "developer" ||
8953
+ message.role === "toolResult" ||
8954
+ message.role === "fileMention" ||
8955
+ message.role === "custom" ||
8956
+ message.role === "hookMessage"
8957
+ );
8958
+ }
8959
+
8960
+ #isUnresolvedToolUseAssistant(message: AssistantMessage): boolean {
8961
+ return message.stopReason === "toolUse" && message.content.some(content => content.type === "toolCall");
8962
+ }
8963
+
8935
8964
  /**
8936
- * Manually retry the last failed assistant turn.
8937
- * Removes the error message from agent state and re-attempts with a fresh retry budget.
8938
- * @returns true if retry was initiated, false if no failed turn to retry or agent is busy
8965
+ * Manually retry the last failed assistant turn, or resume an interrupted tail
8966
+ * left by a non-graceful process exit after the user/custom/tool-result message
8967
+ * was persisted but before the agent emitted a terminal assistant response.
8968
+ * Removes failed/aborted/unresolved tool-use assistant tails before
8969
+ * re-attempting with a fresh retry budget.
8970
+ * @returns true if retry/resume was initiated, false if no retryable tail exists or agent is busy
8939
8971
  */
8940
8972
  async retry(): Promise<boolean> {
8941
8973
  if (this.isStreaming || this.isCompacting || this.isRetrying) return false;
8942
8974
 
8943
8975
  const messages = this.agent.state.messages;
8944
8976
  const lastMsg = messages[messages.length - 1];
8945
- if (lastMsg?.role !== "assistant") return false;
8977
+ if (!lastMsg) return false;
8978
+
8979
+ if (lastMsg.role !== "assistant") {
8980
+ if (!this.#isInterruptedRetryTail(lastMsg)) return false;
8981
+ this.#retryAttempt = 0;
8982
+ this.#scheduleAgentContinue({ delayMs: 1 });
8983
+ return true;
8984
+ }
8946
8985
 
8947
8986
  const assistantMsg = lastMsg as AssistantMessage;
8948
- if (assistantMsg.stopReason !== "error" && assistantMsg.stopReason !== "aborted") return false;
8987
+ const shouldDropAssistant =
8988
+ assistantMsg.stopReason === "error" ||
8989
+ assistantMsg.stopReason === "aborted" ||
8990
+ this.#isUnresolvedToolUseAssistant(assistantMsg);
8991
+ if (!shouldDropAssistant) return false;
8949
8992
 
8950
- // Remove the failed/aborted assistant message (same as auto-retry does before re-attempting)
8993
+ // Remove the failed/aborted/incomplete assistant message before re-attempting.
8951
8994
  this.agent.replaceMessages(messages.slice(0, -1));
8952
8995
 
8953
8996
  // Reset retry budget for a fresh attempt
@@ -322,7 +322,8 @@ const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashComma
322
322
  const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
323
323
  {
324
324
  name: "settings",
325
- description: "Open settings menu",
325
+ priority: 40,
326
+ description: "Open settings and preferences",
326
327
  handleTui: (_command, runtime) => {
327
328
  runtime.ctx.showSettingsSelector();
328
329
  runtime.ctx.editor.setText("");
@@ -338,7 +339,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
338
339
  },
339
340
  {
340
341
  name: "goal",
341
- description: "Toggle goal mode (persistent autonomous objective for this session)",
342
+ priority: 84,
343
+ description: "Plan and track an autonomous goal",
342
344
  subcommands: [
343
345
  { name: "set", description: "Set or replace the goal", usage: "<objective>" },
344
346
  { name: "show", description: "Show current goal details" },
@@ -541,7 +543,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
541
543
  },
542
544
  {
543
545
  name: "export",
544
- description: "Export session to HTML file",
546
+ priority: 50,
547
+ description: "Export this session to an HTML file",
545
548
  inlineHint: "[path]",
546
549
  allowArgs: true,
547
550
  handle: async (command, runtime) => {
@@ -568,7 +571,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
568
571
  },
569
572
  {
570
573
  name: "copy",
571
- description: "Copy last response as markdown",
574
+ priority: 55,
575
+ description: "Copy the last response for review or sharing",
572
576
  // Public `/copy` is strict zero-argument, but `allowArgs` lets the
573
577
  // TUI dispatcher route `/copy <arg>` here so it can be rejected locally
574
578
  // instead of falling through as a model prompt.
@@ -585,7 +589,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
585
589
  },
586
590
  {
587
591
  name: "dump",
588
- description: "Copy session transcript to clipboard",
592
+ priority: 54,
593
+ description: "Dump the full transcript for review or sharing",
589
594
  acpDescription: "Return full transcript as plain text",
590
595
  handle: async (_command, runtime) => {
591
596
  const text = runtime.session.formatSessionAsText();
@@ -599,11 +604,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
599
604
  },
600
605
  {
601
606
  name: "session",
602
- description: "Session management commands",
607
+ priority: 88,
608
+ description: "Show current session info or delete current session",
603
609
  acpDescription: "Show session information",
604
610
  acpInputHint: "info|delete",
605
611
  subcommands: [
606
- { name: "info", description: "Show session info and stats" },
612
+ { name: "info", description: "Show current session id, title, and workspace" },
607
613
  { name: "delete", description: "Delete current session and return to selector" },
608
614
  ],
609
615
  allowArgs: true,
@@ -713,6 +719,15 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
713
719
  runtime.ctx.editor.setText("");
714
720
  },
715
721
  },
722
+ {
723
+ name: "help",
724
+ priority: 100,
725
+ description: "Learn commands and beginner workflows",
726
+ handleTui: (_command, runtime) => {
727
+ runtime.ctx.handleHelpCommand();
728
+ runtime.ctx.editor.setText("");
729
+ },
730
+ },
716
731
  {
717
732
  name: "hotkeys",
718
733
  description: "Show all keyboard shortcuts",
@@ -953,6 +968,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
953
968
  },
954
969
  {
955
970
  name: "new",
971
+ priority: 96,
956
972
  description: "Start a new session",
957
973
  handleTui: async (_command, runtime) => {
958
974
  runtime.ctx.editor.setText("");
@@ -969,7 +985,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
969
985
  },
970
986
  {
971
987
  name: "compact",
972
- description: "Manually compact the session context",
988
+ priority: 72,
989
+ description: "Compact context and continue this session",
973
990
  acpDescription: "Compact the conversation",
974
991
  inlineHint: "[focus instructions]",
975
992
  allowArgs: true,
@@ -1026,7 +1043,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1026
1043
  },
1027
1044
  {
1028
1045
  name: "resume",
1029
- description: "Resume a different session",
1046
+ priority: 92,
1047
+ description: "Resume a previous session",
1030
1048
  handleTui: (_command, runtime) => {
1031
1049
  runtime.ctx.showSessionSelector();
1032
1050
  runtime.ctx.editor.setText("");
@@ -1045,7 +1063,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1045
1063
  },
1046
1064
  {
1047
1065
  name: "retry",
1048
- description: "Retry the last failed agent turn",
1066
+ priority: 70,
1067
+ description: "Retry or continue the last interrupted turn",
1049
1068
  handleTui: async (_command, runtime) => {
1050
1069
  const didRetry = await runtime.ctx.session.retry();
1051
1070
  if (!didRetry) {
@@ -1242,6 +1261,7 @@ export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = AC
1242
1261
  description: command.description,
1243
1262
  subcommands: command.subcommands,
1244
1263
  inlineHint: command.inlineHint,
1264
+ priority: command.priority,
1245
1265
  }),
1246
1266
  );
1247
1267
 
@@ -19,6 +19,8 @@ export interface BuiltinSlashCommand {
19
19
  subcommands?: SubcommandDef[];
20
20
  /** Static inline hint when command takes a simple argument (no subcommands). */
21
21
  inlineHint?: string;
22
+ /** Higher values sort earlier in TUI slash-command autocomplete. */
23
+ priority?: number;
22
24
  }
23
25
 
24
26
  /** Parsed slash-command text after stripping the leading "/". */
package/src/tools/bash.ts CHANGED
@@ -8,7 +8,10 @@ import { AsyncJobManager } from "../async";
8
8
  import { type BashResult, executeBash } from "../exec/bash-executor";
9
9
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
10
10
  import { buildGjcRuntimeSessionEnv } from "../gjc-runtime/goal-mode-request";
11
- import { GJC_RESTRICTED_ROLE_AGENT_BASH_ENV } from "../gjc-runtime/restricted-role-agent-bash";
11
+ import {
12
+ GJC_RALPLAN_ARTIFACT_ENV,
13
+ GJC_RESTRICTED_ROLE_AGENT_BASH_ENV,
14
+ } from "../gjc-runtime/restricted-role-agent-bash";
12
15
  import { InternalUrlRouter } from "../internal-urls";
13
16
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
14
17
  import { highlightCode, type Theme } from "../modes/theme/theme";
@@ -530,13 +533,24 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
530
533
 
531
534
  const rawCommand = input.command;
532
535
  const allowedPrefixes = this.session.bashAllowedPrefixes;
536
+ const isRestrictedRalplanArtifactEnv =
537
+ allowedPrefixes &&
538
+ allowedPrefixes.length > 0 &&
539
+ this.session.bashRestrictionProfile !== "read-only" &&
540
+ env &&
541
+ Object.keys(env).length === 1 &&
542
+ Object.hasOwn(env, GJC_RALPLAN_ARTIFACT_ENV) &&
543
+ rawCommand.includes(`--artifact-env ${GJC_RALPLAN_ARTIFACT_ENV}`);
533
544
  if (
534
545
  (this.session.bashRestrictionProfile === "read-only" || (allowedPrefixes && allowedPrefixes.length > 0)) &&
535
546
  env &&
536
- Object.keys(env).length > 0
547
+ Object.keys(env).length > 0 &&
548
+ !isRestrictedRalplanArtifactEnv
537
549
  ) {
538
550
  const mode = this.session.bashRestrictionProfile === "read-only" ? "Read-only" : "Restricted role-agent";
539
- throw new ToolError(`${mode} bash does not allow per-command env overrides.`);
551
+ throw new ToolError(
552
+ `${mode} bash only allows the ${GJC_RALPLAN_ARTIFACT_ENV} env override for --artifact-env.`,
553
+ );
540
554
  }
541
555
  if (allowedPrefixes && allowedPrefixes.length > 0) {
542
556
  const commandsToCheck = rawCommand === command ? [command] : [rawCommand, command];
@@ -597,11 +611,13 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
597
611
  await Promise.all(
598
612
  Object.entries(env).map(async ([key, value]) => [
599
613
  key,
600
- await expandInternalUrls(value, {
601
- ...internalUrlOptions,
602
- ensureLocalParentDirs: true,
603
- noEscape: true,
604
- }),
614
+ key === GJC_RALPLAN_ARTIFACT_ENV
615
+ ? value
616
+ : await expandInternalUrls(value, {
617
+ ...internalUrlOptions,
618
+ ensureLocalParentDirs: true,
619
+ noEscape: true,
620
+ }),
605
621
  ]),
606
622
  ),
607
623
  )