@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
@@ -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/ask.ts CHANGED
@@ -91,6 +91,7 @@ export interface QuestionResult {
91
91
  multi: boolean;
92
92
  selectedOptions: string[];
93
93
  customInput?: string;
94
+ clarificationQuestion?: string;
94
95
  }
95
96
 
96
97
  export interface AskToolDetails {
@@ -99,6 +100,7 @@ export interface AskToolDetails {
99
100
  multi?: boolean;
100
101
  selectedOptions?: string[];
101
102
  customInput?: string;
103
+ clarificationQuestion?: string;
102
104
  /** Multi-part question mode */
103
105
  results?: QuestionResult[];
104
106
  }
@@ -108,6 +110,7 @@ export interface AskToolDetails {
108
110
  // =============================================================================
109
111
 
110
112
  const OTHER_OPTION = "Other (type your own)";
113
+ const ASK_CLARIFICATION_OPTION = "Ask about these choices";
111
114
  const RECOMMENDED_SUFFIX = " (Recommended)";
112
115
  const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS = Number.MAX_SAFE_INTEGER;
113
116
  const DEEP_INTERVIEW_RECORDER_AWAIT_TIMEOUT_MS = 250;
@@ -183,6 +186,7 @@ function numberOptionLabels(labels: string[]): string[] {
183
186
  interface SelectionResult {
184
187
  selectedOptions: string[];
185
188
  customInput?: string;
189
+ clarificationQuestion?: string;
186
190
  timedOut: boolean;
187
191
  navigation?: "back" | "forward";
188
192
  cancelled?: boolean;
@@ -201,6 +205,7 @@ interface AskSingleQuestionOptions {
201
205
  navigation?: NavigationControls;
202
206
  scrollTitleRows?: number;
203
207
  otherOptionLabel?: string;
208
+ clarificationOptionLabel?: string;
204
209
  }
205
210
 
206
211
  interface UIContext {
@@ -219,6 +224,7 @@ interface UIContext {
219
224
  onRight?: () => void;
220
225
  helpText?: string;
221
226
  customInput?: { optionLabel: string; onSubmit: (text: string) => void };
227
+ clarificationInput?: { optionLabel: string; onSubmit: (text: string) => void; allowEmpty?: boolean };
222
228
  },
223
229
  ): Promise<string | undefined>;
224
230
  editor(
@@ -239,6 +245,7 @@ async function askSingleQuestion(
239
245
  const { recommended, timeout, signal, initialSelection, navigation, scrollTitleRows } = options;
240
246
  const doneLabel = getDoneOptionLabel();
241
247
  const otherOptionLabel = options.otherOptionLabel ?? OTHER_OPTION;
248
+ const clarificationOptionLabel = options.clarificationOptionLabel;
242
249
  let selectedOptions = [...(initialSelection?.selectedOptions ?? [])];
243
250
  let customInput = initialSelection?.customInput;
244
251
  let timedOut = false;
@@ -252,15 +259,17 @@ async function askSingleQuestion(
252
259
  timedOut: boolean;
253
260
  navigation?: "back" | "forward";
254
261
  inlineInput?: string;
262
+ inlineClarification?: string;
255
263
  }> => {
256
264
  let timeoutTriggered = false;
257
265
  const onTimeout = () => {
258
266
  timeoutTriggered = true;
259
267
  };
260
- // Inline custom input: the TUI selector keeps the question and option
261
- // list on screen and collects the "Other" text below the list, instead
262
- // of swapping to a separate editor screen that hides the question.
268
+ // Inline custom/clarification input: the TUI selector keeps the question
269
+ // and option list on screen and collects text below the list, instead of
270
+ // swapping to a separate editor screen that hides the question.
263
271
  let inlineInput: string | undefined;
272
+ let inlineClarification: string | undefined;
264
273
  let navigationAction: "back" | "forward" | undefined;
265
274
  const baseHelpText = navigation
266
275
  ? "up/down navigate enter select ←/→ question esc cancel"
@@ -286,6 +295,15 @@ async function askSingleQuestion(
286
295
  inlineInput = text;
287
296
  },
288
297
  },
298
+ clarificationInput: clarificationOptionLabel
299
+ ? {
300
+ optionLabel: clarificationOptionLabel,
301
+ allowEmpty: false,
302
+ onSubmit: (text: string) => {
303
+ inlineClarification = text;
304
+ },
305
+ }
306
+ : undefined,
289
307
  onLeft: navigation?.allowBack
290
308
  ? () => {
291
309
  navigationAction = "back";
@@ -304,7 +322,7 @@ async function askSingleQuestion(
304
322
  if (!timeoutTriggered && choice === undefined && typeof timeout === "number") {
305
323
  timeoutTriggered = Date.now() - startMs >= timeout;
306
324
  }
307
- return { choice, timedOut: timeoutTriggered, navigation: navigationAction, inlineInput };
325
+ return { choice, timedOut: timeoutTriggered, navigation: navigationAction, inlineInput, inlineClarification };
308
326
  };
309
327
 
310
328
  // Fallback for UI contexts that don't support inline custom input (they
@@ -315,6 +333,13 @@ async function askSingleQuestion(
315
333
  const input = signal ? await untilAborted(signal, showCustomInput) : await showCustomInput();
316
334
  return { input };
317
335
  };
336
+ const promptForClarificationInput = async (): Promise<{ input: string | undefined }> => {
337
+ const dialogOptions = signal ? { signal } : undefined;
338
+ const showClarificationInput = () =>
339
+ ui.editor("Ask a clarification question:", undefined, dialogOptions, { promptStyle: true });
340
+ const input = signal ? await untilAborted(signal, showClarificationInput) : await showClarificationInput();
341
+ return { input: input !== undefined && input.trim() === "" ? undefined : input };
342
+ };
318
343
 
319
344
  const promptWithProgress = navigation?.progressText ? `${question} (${navigation.progressText})` : question;
320
345
  if (multi) {
@@ -337,6 +362,9 @@ async function askSingleQuestion(
337
362
  opts.push(doneLabel);
338
363
  }
339
364
  opts.push(otherOptionLabel);
365
+ if (clarificationOptionLabel) {
366
+ opts.push(clarificationOptionLabel);
367
+ }
340
368
 
341
369
  const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
342
370
  const {
@@ -344,6 +372,7 @@ async function askSingleQuestion(
344
372
  timedOut: selectTimedOut,
345
373
  navigation: arrowNavigation,
346
374
  inlineInput,
375
+ inlineClarification,
347
376
  } = await selectOption(`${prefix}${promptWithProgress}`, opts, cursorIndex);
348
377
 
349
378
  if (arrowNavigation) {
@@ -370,6 +399,18 @@ async function askSingleQuestion(
370
399
  customInput = input;
371
400
  break;
372
401
  }
402
+ if (clarificationOptionLabel && choice === clarificationOptionLabel) {
403
+ if (selectTimedOut) {
404
+ timedOut = true;
405
+ break;
406
+ }
407
+ const input =
408
+ inlineClarification !== undefined ? inlineClarification : (await promptForClarificationInput()).input;
409
+ if (input === undefined) {
410
+ break;
411
+ }
412
+ return { selectedOptions: [], clarificationQuestion: input, timedOut };
413
+ }
373
414
 
374
415
  const selectedIdx = opts.indexOf(choice);
375
416
  if (selectedIdx >= 0) {
@@ -400,7 +441,9 @@ async function askSingleQuestion(
400
441
  selectedOptions = Array.from(selected);
401
442
  } else {
402
443
  const displayLabels = addRecommendedSuffix(optionLabels, recommended);
403
- const optionsWithNavigation = [...displayLabels, otherOptionLabel];
444
+ const optionsWithNavigation = clarificationOptionLabel
445
+ ? [...displayLabels, otherOptionLabel, clarificationOptionLabel]
446
+ : [...displayLabels, otherOptionLabel];
404
447
 
405
448
  let initialIndex = recommended;
406
449
  const previouslySelected = selectedOptions[0];
@@ -420,6 +463,7 @@ async function askSingleQuestion(
420
463
  timedOut: selectTimedOut,
421
464
  navigation: arrowNavigation,
422
465
  inlineInput,
466
+ inlineClarification,
423
467
  } = await selectOption(promptWithProgress, optionsWithNavigation, initialIndex);
424
468
  timedOut = selectTimedOut;
425
469
 
@@ -439,6 +483,15 @@ async function askSingleQuestion(
439
483
  }
440
484
  // If input was dismissed (undefined), keep prior selectedOptions/customInput intact
441
485
  }
486
+ } else if (clarificationOptionLabel && choice === clarificationOptionLabel) {
487
+ if (!selectTimedOut) {
488
+ const input =
489
+ inlineClarification !== undefined ? inlineClarification : (await promptForClarificationInput()).input;
490
+ if (input !== undefined) {
491
+ return { selectedOptions: [], clarificationQuestion: input, timedOut };
492
+ }
493
+ // If input was dismissed (undefined), keep prior selectedOptions/customInput intact
494
+ }
442
495
  } else {
443
496
  const stripped = stripRecommendedSuffix(choice);
444
497
  if (optionLabels.includes(stripped)) {
@@ -466,6 +519,9 @@ async function askSingleQuestion(
466
519
  }
467
520
 
468
521
  function formatQuestionResult(result: QuestionResult): string {
522
+ if (result.clarificationQuestion !== undefined) {
523
+ return `${result.id}: clarification requested: ${result.clarificationQuestion}`;
524
+ }
469
525
  if (result.customInput !== undefined) {
470
526
  return `${result.id}: "${result.customInput}"`;
471
527
  }
@@ -682,6 +738,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
682
738
  optionLabels: rawOptionLabels,
683
739
  selectedOptions: decoded.selectedOptions,
684
740
  customInput: decoded.customInput,
741
+ clarificationQuestion: decoded.clarificationQuestion,
685
742
  navigation: undefined as NavigationControls | undefined,
686
743
  cancelled: false,
687
744
  timedOut: false,
@@ -693,6 +750,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
693
750
  const displayQuestion = deepInterviewPrompt ?? q.question;
694
751
  const shouldNumberOptions = isDeepInterviewQuestion || isDeepInterviewAskQuestion(q.question);
695
752
  const optionLabels = shouldNumberOptions ? numberOptionLabels(rawOptionLabels) : rawOptionLabels;
753
+ const clarificationOptionLabel = shouldNumberOptions
754
+ ? formatNumberedOptionLabel(ASK_CLARIFICATION_OPTION, optionLabels.length + 1)
755
+ : undefined;
696
756
  const initialSelection =
697
757
  shouldNumberOptions && options?.previous
698
758
  ? {
@@ -706,6 +766,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
706
766
  const {
707
767
  selectedOptions: displaySelectedOptions,
708
768
  customInput,
769
+ clarificationQuestion,
709
770
  navigation,
710
771
  cancelled,
711
772
  timedOut,
@@ -719,6 +780,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
719
780
  otherOptionLabel: shouldNumberOptions
720
781
  ? formatNumberedOptionLabel(OTHER_OPTION, optionLabels.length)
721
782
  : undefined,
783
+ clarificationOptionLabel,
722
784
  });
723
785
  const selectedOptions = shouldNumberOptions
724
786
  ? displaySelectedOptions.map(selected => {
@@ -726,7 +788,15 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
726
788
  return displayIndex >= 0 ? (rawOptionLabels[displayIndex] ?? selected) : selected;
727
789
  })
728
790
  : displaySelectedOptions;
729
- return { optionLabels: rawOptionLabels, selectedOptions, customInput, navigation, cancelled, timedOut };
791
+ return {
792
+ optionLabels: rawOptionLabels,
793
+ selectedOptions,
794
+ customInput,
795
+ clarificationQuestion,
796
+ navigation,
797
+ cancelled,
798
+ timedOut,
799
+ };
730
800
  } catch (error) {
731
801
  if (error instanceof Error && error.name === "AbortError") {
732
802
  throw new ToolAbortError("Ask input was cancelled");
@@ -737,22 +807,40 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
737
807
 
738
808
  if (params.questions.length === 1) {
739
809
  const [q] = params.questions;
740
- const { optionLabels, selectedOptions, customInput, cancelled, timedOut } = await askQuestion(q);
741
-
742
- if (!timedOut && (cancelled || (selectedOptions.length === 0 && customInput === undefined))) {
810
+ const { optionLabels, selectedOptions, customInput, clarificationQuestion, cancelled, timedOut } =
811
+ await askQuestion(q);
812
+
813
+ if (
814
+ !timedOut &&
815
+ (cancelled ||
816
+ (selectedOptions.length === 0 && customInput === undefined && clarificationQuestion === undefined))
817
+ ) {
743
818
  context?.abort();
744
819
  throw new ToolAbortError("Ask tool was cancelled by the user");
745
820
  }
746
- await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
821
+ if (clarificationQuestion === undefined) {
822
+ await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
823
+ }
747
824
  const details: AskToolDetails = {
748
825
  question: q.question,
749
826
  options: optionLabels,
750
827
  multi: q.multi ?? false,
751
828
  selectedOptions,
752
829
  customInput,
830
+ clarificationQuestion,
753
831
  };
754
832
 
755
833
  const responseParts: string[] = [];
834
+ if (clarificationQuestion !== undefined) {
835
+ responseParts.push(
836
+ clarificationQuestion.includes("\n")
837
+ ? `User asked a clarification question about the choices:\n${clarificationQuestion
838
+ .split("\n")
839
+ .map(line => ` ${line}`)
840
+ .join("\n")}`
841
+ : `User asked a clarification question about the choices: ${clarificationQuestion}`,
842
+ );
843
+ }
756
844
  if (selectedOptions.length > 0) {
757
845
  responseParts.push(
758
846
  q.multi ? `User selected: ${selectedOptions.join(", ")}` : `User selected: ${selectedOptions[0]}`,
@@ -787,6 +875,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
787
875
  optionLabels,
788
876
  selectedOptions,
789
877
  customInput,
878
+ clarificationQuestion,
790
879
  navigation: navAction,
791
880
  cancelled,
792
881
  timedOut,
@@ -804,9 +893,12 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
804
893
  multi: q.multi ?? false,
805
894
  selectedOptions,
806
895
  customInput,
896
+ clarificationQuestion,
807
897
  };
808
898
 
809
- await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
899
+ if (clarificationQuestion === undefined) {
900
+ await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
901
+ }
810
902
 
811
903
  if (navAction === "back") {
812
904
  questionIndex = Math.max(0, questionIndex - 1);
@@ -995,7 +1087,10 @@ export const askToolRenderer = {
995
1087
  // Multi-part results
996
1088
  if (details.results && details.results.length > 0) {
997
1089
  const hasAnySelection = details.results.some(
998
- r => r.customInput !== undefined || (r.selectedOptions && r.selectedOptions.length > 0),
1090
+ r =>
1091
+ r.clarificationQuestion !== undefined ||
1092
+ r.customInput !== undefined ||
1093
+ (r.selectedOptions && r.selectedOptions.length > 0),
999
1094
  );
1000
1095
  const header = renderStatusLine(
1001
1096
  {
@@ -1013,7 +1108,8 @@ export const askToolRenderer = {
1013
1108
  const isLastQuestion = i === details.results.length - 1;
1014
1109
  const branch = isLastQuestion ? uiTheme.tree.last : uiTheme.tree.branch;
1015
1110
  const continuation = isLastQuestion ? " " : `${uiTheme.fg("dim", uiTheme.tree.vertical)} `;
1016
- const hasSelection = r.customInput !== undefined || r.selectedOptions.length > 0;
1111
+ const hasSelection =
1112
+ r.clarificationQuestion !== undefined || r.customInput !== undefined || r.selectedOptions.length > 0;
1017
1113
  const statusIcon = hasSelection
1018
1114
  ? uiTheme.styledSymbol("status.success", "success")
1019
1115
  : uiTheme.styledSymbol("status.warning", "warning");
@@ -1028,7 +1124,10 @@ export const askToolRenderer = {
1028
1124
 
1029
1125
  const answerLines: string[] = [];
1030
1126
  for (let j = 0; j < r.selectedOptions.length; j++) {
1031
- const isLast = j === r.selectedOptions.length - 1 && r.customInput === undefined;
1127
+ const isLast =
1128
+ j === r.selectedOptions.length - 1 &&
1129
+ r.customInput === undefined &&
1130
+ r.clarificationQuestion === undefined;
1032
1131
  const optBranch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
1033
1132
  const selectedLabel = renderInlineMarkdown(r.selectedOptions[j], mdTheme, t =>
1034
1133
  uiTheme.fg("toolOutput", t),
@@ -1042,6 +1141,14 @@ export const askToolRenderer = {
1042
1141
  }
1043
1142
  if (r.customInput !== undefined) {
1044
1143
  container.addChild(new Text(renderCustomInput(uiTheme, continuation, r.customInput, true, false), 0, 0));
1144
+ } else if (r.clarificationQuestion !== undefined) {
1145
+ container.addChild(
1146
+ new Text(
1147
+ renderCustomInput(uiTheme, continuation, `Clarification: ${r.clarificationQuestion}`, true, false),
1148
+ 0,
1149
+ 0,
1150
+ ),
1151
+ );
1045
1152
  } else if (r.selectedOptions.length === 0) {
1046
1153
  container.addChild(
1047
1154
  new Text(
@@ -1063,7 +1170,9 @@ export const askToolRenderer = {
1063
1170
  }
1064
1171
 
1065
1172
  const hasSelection =
1066
- details.customInput !== undefined || (details.selectedOptions && details.selectedOptions.length > 0);
1173
+ details.clarificationQuestion !== undefined ||
1174
+ details.customInput !== undefined ||
1175
+ (details.selectedOptions && details.selectedOptions.length > 0);
1067
1176
  const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
1068
1177
  const container = new Container();
1069
1178
  container.addChild(new Text(header, 0, 0));
@@ -1075,7 +1184,10 @@ export const askToolRenderer = {
1075
1184
  const answerLines: string[] = [];
1076
1185
  if (details.selectedOptions && details.selectedOptions.length > 0) {
1077
1186
  for (let i = 0; i < details.selectedOptions.length; i++) {
1078
- const isLast = i === details.selectedOptions.length - 1 && details.customInput === undefined;
1187
+ const isLast =
1188
+ i === details.selectedOptions.length - 1 &&
1189
+ details.customInput === undefined &&
1190
+ details.clarificationQuestion === undefined;
1079
1191
  const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
1080
1192
  const selectedLabel = renderInlineMarkdown(details.selectedOptions[i], mdTheme, t =>
1081
1193
  uiTheme.fg("toolOutput", t),
@@ -1090,6 +1202,14 @@ export const askToolRenderer = {
1090
1202
  }
1091
1203
  if (details.customInput !== undefined) {
1092
1204
  container.addChild(new Text(renderCustomInput(uiTheme, " ", details.customInput, true, false), 0, 0));
1205
+ } else if (details.clarificationQuestion !== undefined) {
1206
+ container.addChild(
1207
+ new Text(
1208
+ renderCustomInput(uiTheme, " ", `Clarification: ${details.clarificationQuestion}`, true, false),
1209
+ 0,
1210
+ 0,
1211
+ ),
1212
+ );
1093
1213
  } else if (!details.selectedOptions || details.selectedOptions.length === 0) {
1094
1214
  container.addChild(
1095
1215
  new Text(
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
  )