@gajae-code/coding-agent 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +34 -8600
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +61 -0
  4. package/dist/types/cli/update-cli.d.ts +3 -0
  5. package/dist/types/config/settings-schema.d.ts +27 -3
  6. package/dist/types/config/settings.d.ts +1 -1
  7. package/dist/types/defaults/gjc-defaults.d.ts +19 -6
  8. package/dist/types/discovery/helpers.d.ts +1 -0
  9. package/dist/types/exec/bash-executor.d.ts +8 -1
  10. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +2 -0
  11. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  12. package/dist/types/modes/components/settings-selector.d.ts +4 -0
  13. package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
  14. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  15. package/dist/types/modes/interactive-mode.d.ts +2 -0
  16. package/dist/types/modes/theme/defaults/index.d.ts +45 -9351
  17. package/dist/types/modes/theme/theme.d.ts +6 -5
  18. package/dist/types/modes/types.d.ts +2 -0
  19. package/dist/types/sdk.d.ts +2 -0
  20. package/dist/types/session/streaming-output.d.ts +11 -0
  21. package/dist/types/skill-state/active-state.d.ts +1 -0
  22. package/dist/types/task/types.d.ts +1 -0
  23. package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
  24. package/dist/types/tools/bash.d.ts +24 -0
  25. package/dist/types/tools/cron.d.ts +110 -0
  26. package/dist/types/tools/index.d.ts +4 -0
  27. package/dist/types/tools/monitor.d.ts +54 -0
  28. package/dist/types/web/search/index.d.ts +1 -0
  29. package/dist/types/web/search/provider.d.ts +11 -4
  30. package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
  31. package/dist/types/web/search/types.d.ts +1 -1
  32. package/package.json +7 -7
  33. package/src/async/job-manager.ts +224 -0
  34. package/src/cli/agents-cli.ts +3 -0
  35. package/src/cli/update-cli.ts +67 -16
  36. package/src/config/settings-schema.ts +30 -2
  37. package/src/config/settings.ts +44 -7
  38. package/src/defaults/gjc/skills/deep-interview/SKILL.md +48 -6
  39. package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
  40. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
  41. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  42. package/src/defaults/gjc/skills/ultragoal/SKILL.md +9 -6
  43. package/src/defaults/gjc-defaults.ts +68 -16
  44. package/src/discovery/helpers.ts +5 -0
  45. package/src/eval/js/shared/rewrite-imports.ts +1 -2
  46. package/src/exec/bash-executor.ts +20 -9
  47. package/src/gjc-runtime/deep-interview-runtime.ts +44 -0
  48. package/src/gjc-runtime/ralplan-runtime.ts +2 -0
  49. package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
  50. package/src/gjc-runtime/state-runtime.ts +3 -2
  51. package/src/goals/tools/goal-tool.ts +5 -1
  52. package/src/hooks/skill-state.ts +1 -1
  53. package/src/internal-urls/docs-index.generated.ts +8 -4
  54. package/src/lsp/render.ts +1 -1
  55. package/src/memories/index.ts +5 -4
  56. package/src/modes/acp/acp-agent.ts +1 -1
  57. package/src/modes/acp/acp-client-bridge.ts +1 -1
  58. package/src/modes/components/agent-dashboard.ts +1 -1
  59. package/src/modes/components/diff.ts +2 -2
  60. package/src/modes/components/settings-selector.ts +25 -14
  61. package/src/modes/components/skill-hud/render.ts +7 -2
  62. package/src/modes/controllers/command-controller.ts +1 -1
  63. package/src/modes/controllers/input-controller.ts +10 -2
  64. package/src/modes/controllers/selector-controller.ts +67 -0
  65. package/src/modes/interactive-mode.ts +34 -3
  66. package/src/modes/theme/defaults/blue-crab.json +126 -0
  67. package/src/modes/theme/defaults/index.ts +2 -196
  68. package/src/modes/theme/theme.ts +75 -36
  69. package/src/modes/types.ts +2 -0
  70. package/src/prompts/agents/architect.md +5 -1
  71. package/src/prompts/agents/critic.md +5 -1
  72. package/src/prompts/agents/frontmatter.md +1 -0
  73. package/src/prompts/agents/planner.md +5 -1
  74. package/src/prompts/memories/unavailable.md +9 -0
  75. package/src/prompts/tools/bash.md +9 -0
  76. package/src/prompts/tools/cron.md +25 -0
  77. package/src/prompts/tools/monitor.md +30 -0
  78. package/src/runtime-mcp/oauth-flow.ts +4 -2
  79. package/src/sdk.ts +7 -0
  80. package/src/session/agent-session.ts +16 -5
  81. package/src/session/streaming-output.ts +21 -0
  82. package/src/skill-state/active-state.ts +163 -12
  83. package/src/slash-commands/builtin-registry.ts +11 -1
  84. package/src/task/agents.ts +1 -0
  85. package/src/task/executor.ts +1 -0
  86. package/src/task/types.ts +1 -0
  87. package/src/tools/bash-allowed-prefixes.ts +169 -0
  88. package/src/tools/bash.ts +190 -29
  89. package/src/tools/browser/tab-worker.ts +1 -1
  90. package/src/tools/cron.ts +665 -0
  91. package/src/tools/index.ts +20 -2
  92. package/src/tools/monitor.ts +136 -0
  93. package/src/vim/engine.ts +3 -3
  94. package/src/web/search/index.ts +31 -18
  95. package/src/web/search/provider.ts +57 -12
  96. package/src/web/search/providers/duckduckgo.ts +279 -0
  97. package/src/web/search/types.ts +2 -0
  98. package/src/modes/theme/dark.json +0 -95
  99. package/src/modes/theme/defaults/alabaster.json +0 -93
  100. package/src/modes/theme/defaults/amethyst.json +0 -96
  101. package/src/modes/theme/defaults/anthracite.json +0 -93
  102. package/src/modes/theme/defaults/basalt.json +0 -91
  103. package/src/modes/theme/defaults/birch.json +0 -95
  104. package/src/modes/theme/defaults/dark-abyss.json +0 -91
  105. package/src/modes/theme/defaults/dark-arctic.json +0 -104
  106. package/src/modes/theme/defaults/dark-aurora.json +0 -95
  107. package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
  108. package/src/modes/theme/defaults/dark-cavern.json +0 -91
  109. package/src/modes/theme/defaults/dark-copper.json +0 -95
  110. package/src/modes/theme/defaults/dark-cosmos.json +0 -90
  111. package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
  112. package/src/modes/theme/defaults/dark-dracula.json +0 -98
  113. package/src/modes/theme/defaults/dark-eclipse.json +0 -91
  114. package/src/modes/theme/defaults/dark-ember.json +0 -95
  115. package/src/modes/theme/defaults/dark-equinox.json +0 -90
  116. package/src/modes/theme/defaults/dark-forest.json +0 -96
  117. package/src/modes/theme/defaults/dark-github.json +0 -105
  118. package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
  119. package/src/modes/theme/defaults/dark-lavender.json +0 -95
  120. package/src/modes/theme/defaults/dark-lunar.json +0 -89
  121. package/src/modes/theme/defaults/dark-midnight.json +0 -95
  122. package/src/modes/theme/defaults/dark-monochrome.json +0 -94
  123. package/src/modes/theme/defaults/dark-monokai.json +0 -98
  124. package/src/modes/theme/defaults/dark-nebula.json +0 -90
  125. package/src/modes/theme/defaults/dark-nord.json +0 -97
  126. package/src/modes/theme/defaults/dark-ocean.json +0 -101
  127. package/src/modes/theme/defaults/dark-one.json +0 -100
  128. package/src/modes/theme/defaults/dark-poimandres.json +0 -141
  129. package/src/modes/theme/defaults/dark-rainforest.json +0 -91
  130. package/src/modes/theme/defaults/dark-reef.json +0 -91
  131. package/src/modes/theme/defaults/dark-retro.json +0 -92
  132. package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
  133. package/src/modes/theme/defaults/dark-sakura.json +0 -95
  134. package/src/modes/theme/defaults/dark-slate.json +0 -95
  135. package/src/modes/theme/defaults/dark-solarized.json +0 -97
  136. package/src/modes/theme/defaults/dark-solstice.json +0 -90
  137. package/src/modes/theme/defaults/dark-starfall.json +0 -91
  138. package/src/modes/theme/defaults/dark-sunset.json +0 -99
  139. package/src/modes/theme/defaults/dark-swamp.json +0 -90
  140. package/src/modes/theme/defaults/dark-synthwave.json +0 -103
  141. package/src/modes/theme/defaults/dark-taiga.json +0 -91
  142. package/src/modes/theme/defaults/dark-terminal.json +0 -95
  143. package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
  144. package/src/modes/theme/defaults/dark-tundra.json +0 -91
  145. package/src/modes/theme/defaults/dark-twilight.json +0 -91
  146. package/src/modes/theme/defaults/dark-volcanic.json +0 -91
  147. package/src/modes/theme/defaults/graphite.json +0 -92
  148. package/src/modes/theme/defaults/light-arctic.json +0 -107
  149. package/src/modes/theme/defaults/light-aurora-day.json +0 -91
  150. package/src/modes/theme/defaults/light-canyon.json +0 -91
  151. package/src/modes/theme/defaults/light-catppuccin.json +0 -106
  152. package/src/modes/theme/defaults/light-cirrus.json +0 -90
  153. package/src/modes/theme/defaults/light-coral.json +0 -95
  154. package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
  155. package/src/modes/theme/defaults/light-dawn.json +0 -90
  156. package/src/modes/theme/defaults/light-dunes.json +0 -91
  157. package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
  158. package/src/modes/theme/defaults/light-forest.json +0 -100
  159. package/src/modes/theme/defaults/light-frost.json +0 -95
  160. package/src/modes/theme/defaults/light-github.json +0 -115
  161. package/src/modes/theme/defaults/light-glacier.json +0 -91
  162. package/src/modes/theme/defaults/light-gruvbox.json +0 -108
  163. package/src/modes/theme/defaults/light-haze.json +0 -90
  164. package/src/modes/theme/defaults/light-honeycomb.json +0 -95
  165. package/src/modes/theme/defaults/light-lagoon.json +0 -91
  166. package/src/modes/theme/defaults/light-lavender.json +0 -95
  167. package/src/modes/theme/defaults/light-meadow.json +0 -91
  168. package/src/modes/theme/defaults/light-mint.json +0 -95
  169. package/src/modes/theme/defaults/light-monochrome.json +0 -101
  170. package/src/modes/theme/defaults/light-ocean.json +0 -99
  171. package/src/modes/theme/defaults/light-one.json +0 -99
  172. package/src/modes/theme/defaults/light-opal.json +0 -91
  173. package/src/modes/theme/defaults/light-orchard.json +0 -91
  174. package/src/modes/theme/defaults/light-paper.json +0 -95
  175. package/src/modes/theme/defaults/light-poimandres.json +0 -141
  176. package/src/modes/theme/defaults/light-prism.json +0 -90
  177. package/src/modes/theme/defaults/light-retro.json +0 -98
  178. package/src/modes/theme/defaults/light-sand.json +0 -95
  179. package/src/modes/theme/defaults/light-savanna.json +0 -91
  180. package/src/modes/theme/defaults/light-solarized.json +0 -102
  181. package/src/modes/theme/defaults/light-soleil.json +0 -90
  182. package/src/modes/theme/defaults/light-sunset.json +0 -99
  183. package/src/modes/theme/defaults/light-synthwave.json +0 -98
  184. package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
  185. package/src/modes/theme/defaults/light-wetland.json +0 -91
  186. package/src/modes/theme/defaults/light-zenith.json +0 -89
  187. package/src/modes/theme/defaults/limestone.json +0 -94
  188. package/src/modes/theme/defaults/mahogany.json +0 -97
  189. package/src/modes/theme/defaults/marble.json +0 -93
  190. package/src/modes/theme/defaults/obsidian.json +0 -91
  191. package/src/modes/theme/defaults/onyx.json +0 -91
  192. package/src/modes/theme/defaults/pearl.json +0 -93
  193. package/src/modes/theme/defaults/porcelain.json +0 -91
  194. package/src/modes/theme/defaults/quartz.json +0 -96
  195. package/src/modes/theme/defaults/sandstone.json +0 -95
  196. package/src/modes/theme/defaults/titanium.json +0 -90
  197. package/src/modes/theme/light.json +0 -93
package/src/tools/bash.ts CHANGED
@@ -8,6 +8,7 @@ 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
12
  import { InternalUrlRouter } from "../internal-urls";
12
13
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
13
14
  import { highlightCode, type Theme } from "../modes/theme/theme";
@@ -18,6 +19,7 @@ import { renderStatusLine } from "../tui";
18
19
  import { CachedOutputBlock } from "../tui/output-block";
19
20
  import { getSixelLineMask } from "../utils/sixel";
20
21
  import type { ToolSession } from ".";
22
+ import { checkBashAllowedPrefixes } from "./bash-allowed-prefixes";
21
23
  import { applyBashFixups } from "./bash-command-fixup";
22
24
  import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
23
25
  import { checkBashInterception } from "./bash-interceptor";
@@ -253,6 +255,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
253
255
  hasAstEdit: this.session.settings.get("astEdit.enabled"),
254
256
  hasSearch: this.session.settings.get("search.enabled"),
255
257
  hasFind: this.session.settings.get("find.enabled"),
258
+ restrictedAllowedPrefixes: this.session.bashAllowedPrefixes,
256
259
  });
257
260
  }
258
261
 
@@ -377,6 +380,13 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
377
380
  latestText = tailBuffer.text();
378
381
  void reportProgress(latestText, { async: { state: "running", jobId, type: "bash" } });
379
382
  },
383
+ onRawChunk: chunk => {
384
+ // Forward the unthrottled sanitized chunk to the async-job
385
+ // substrate so the Monitor tool can read the complete process
386
+ // stream by byte offset, independent of the throttled preview
387
+ // path above.
388
+ manager.appendOutput(jobId, chunk);
389
+ },
380
390
  onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
381
391
  });
382
392
  const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
@@ -454,27 +464,29 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
454
464
  return Math.max(0, Math.min(this.#autoBackgroundThresholdMs, timeoutMs - timeoutBufferMs));
455
465
  }
456
466
 
457
- async execute(
458
- _toolCallId: string,
459
- {
460
- command: rawCommand,
461
- env: rawEnv,
462
- timeout: rawTimeout = 300,
463
- cwd,
464
-
465
- async: asyncRequested = false,
466
- pty = false,
467
- }: BashToolInput,
468
- signal?: AbortSignal,
469
- onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
467
+ /**
468
+ * Build the fully-prepared parameters for a `bash`-flavored execution
469
+ * (interceptors, internal URL expansion, env resolution, cwd validation,
470
+ * timeout clamp). Used by both `execute()` and the public `startMonitorJob`
471
+ * helper after `AgentSession` has applied the public-tool permission gate, so
472
+ * Monitor inherits Bash's cwd / env / artifact / interceptor pipeline 1:1.
473
+ */
474
+ async #prepareBashExecution(
475
+ input: { command: string; env?: Record<string, string>; timeout?: number; cwd?: string },
470
476
  ctx?: AgentToolContext,
471
- ): Promise<AgentToolResult<BashToolDetails>> {
472
- let command = rawCommand;
473
- const env = normalizeBashEnv(rawEnv);
477
+ ): Promise<{
478
+ command: string;
479
+ commandCwd: string;
480
+ resolvedEnv: Record<string, string>;
481
+ requestedTimeoutSec: number;
482
+ timeoutSec: number;
483
+ timeoutMs: number;
484
+ notices: string[];
485
+ }> {
486
+ let command = input.command;
487
+ let cwd = input.cwd;
488
+ const env = normalizeBashEnv(input.env);
474
489
 
475
- // Apply conservative bash fixups (strip trailing `| head|tail` and redundant
476
- // `2>&1`). The helper is single-line only and refuses anything that could
477
- // change semantics.
478
490
  if (this.session.settings.get("bash.stripTrailingHeadTail")) {
479
491
  const fixup = applyBashFixups(command);
480
492
  if (fixup.stripped.length > 0) {
@@ -482,9 +494,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
482
494
  }
483
495
  }
484
496
 
485
- // Extract leading `cd <path> && ...` into cwd when the model ignores the cwd parameter.
486
- // Constrained to a single line so a `&&` that sits on a later line of a multiline
487
- // script can't pull the entire script into the "cwd" capture.
488
497
  if (!cwd) {
489
498
  const cdMatch = command.match(/^cd[ \t]+((?:[^&\\\n\r]|\\.)+?)[ \t]*&&[ \t]*/);
490
499
  if (cdMatch) {
@@ -492,8 +501,20 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
492
501
  command = command.slice(cdMatch[0].length);
493
502
  }
494
503
  }
495
- if (asyncRequested && !this.#asyncEnabled) {
496
- throw new ToolError("Async bash execution is disabled. Enable async.enabled to use async mode.");
504
+
505
+ const rawCommand = input.command;
506
+ const allowedPrefixes = this.session.bashAllowedPrefixes;
507
+ if (allowedPrefixes && allowedPrefixes.length > 0) {
508
+ if (env && Object.keys(env).length > 0) {
509
+ throw new ToolError("Restricted role-agent bash does not allow per-command env overrides.");
510
+ }
511
+ const commandsToCheck = rawCommand === command ? [command] : [rawCommand, command];
512
+ for (const commandToCheck of commandsToCheck) {
513
+ const allowlist = checkBashAllowedPrefixes(commandToCheck, allowedPrefixes);
514
+ if (!allowlist.allowed) {
515
+ throw new ToolError(allowlist.reason ?? "Command blocked by restricted role-agent bash allowlist.");
516
+ }
517
+ }
497
518
  }
498
519
 
499
520
  // Check both the original command and the cwd-normalized command so
@@ -541,9 +562,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
541
562
  cwd: this.session.cwd,
542
563
  }),
543
564
  ...expandedEnv,
565
+ ...(allowedPrefixes && allowedPrefixes.length > 0 ? { [GJC_RESTRICTED_ROLE_AGENT_BASH_ENV]: "1" } : {}),
544
566
  };
545
567
 
546
- // Resolve protocol URLs (agent://, artifact://, etc.) in extracted cwd.
547
568
  if (cwd?.includes("://") || cwd?.includes("local:/")) {
548
569
  cwd = await expandInternalUrls(cwd, { ...internalUrlOptions, noEscape: true });
549
570
  }
@@ -562,13 +583,153 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
562
583
  throw new ToolError(`Working directory is not a directory: ${commandCwd}`);
563
584
  }
564
585
 
565
- // Clamp to reasonable range: 1s - 3600s (1 hour)
566
- const requestedTimeoutSec = rawTimeout;
586
+ const requestedTimeoutSec = input.timeout ?? 300;
567
587
  const timeoutSec = clampTimeout("bash", requestedTimeoutSec);
568
588
  const timeoutMs = timeoutSec * 1000;
569
- const pendingNotices: string[] = [];
589
+ const notices: string[] = [];
570
590
  const timeoutClampNotice = formatTimeoutClampNotice(requestedTimeoutSec, timeoutSec);
571
- if (timeoutClampNotice) pendingNotices.push(timeoutClampNotice);
591
+ if (timeoutClampNotice) notices.push(timeoutClampNotice);
592
+
593
+ return { command, commandCwd, resolvedEnv, requestedTimeoutSec, timeoutSec, timeoutMs, notices };
594
+ }
595
+
596
+ /**
597
+ * Start a background bash job for the Monitor tool. Reuses the full Bash
598
+ * pipeline (interceptors, internal-URL expansion, env, cwd, timeout); the
599
+ * public `monitor` tool itself is ACP-gated by `AgentSession` before this
600
+ * helper is called. The caller-supplied `onRawLine` callback is invoked once
601
+ * per newline-terminated stdout chunk, between turns, so the upstream Claude
602
+ * Code "Each stdout line is a task-notification event" semantics are preserved
603
+ * through the agent's existing background-task delivery path.
604
+ */
605
+ async startMonitorJob(
606
+ input: { command: string; cwd?: string; timeout?: number; env?: Record<string, string> },
607
+ opts: {
608
+ ownerId?: string;
609
+ label?: string;
610
+ ctx?: AgentToolContext;
611
+ onRawLine?: (line: string, jobId: string) => void;
612
+ } = {},
613
+ ): Promise<{ jobId: string; label: string; commandCwd: string }> {
614
+ const manager = AsyncJobManager.instance();
615
+ if (!manager) {
616
+ throw new ToolError("Async job manager unavailable for this session.");
617
+ }
618
+ const prepared = await this.#prepareBashExecution(input, opts.ctx);
619
+ const label =
620
+ opts.label ?? (prepared.command.length > 120 ? `${prepared.command.slice(0, 117)}...` : prepared.command);
621
+ const monitorTimeoutMs = input.timeout === undefined ? null : prepared.timeoutMs;
622
+ const onRawLine = opts.onRawLine;
623
+ let currentJobId = "";
624
+ let cursorOffset = 0;
625
+ let lineBuffer = "";
626
+ const dispatchLines = (chunk: string) => {
627
+ if (!onRawLine) return;
628
+ lineBuffer += chunk;
629
+ let newlineIndex = lineBuffer.indexOf("\n");
630
+ while (newlineIndex !== -1) {
631
+ const line = lineBuffer.slice(0, newlineIndex);
632
+ lineBuffer = lineBuffer.slice(newlineIndex + 1);
633
+ try {
634
+ onRawLine(line, currentJobId);
635
+ } catch (error) {
636
+ logger.warn("Monitor onRawLine callback failed", {
637
+ error: error instanceof Error ? error.message : String(error),
638
+ });
639
+ }
640
+ newlineIndex = lineBuffer.indexOf("\n");
641
+ }
642
+ };
643
+ const flushTrailingLine = () => {
644
+ if (!onRawLine) return;
645
+ if (lineBuffer.length === 0) return;
646
+ const remainder = lineBuffer;
647
+ lineBuffer = "";
648
+ try {
649
+ onRawLine(remainder, currentJobId);
650
+ } catch (error) {
651
+ logger.warn("Monitor onRawLine callback failed (trailing)", {
652
+ error: error instanceof Error ? error.message : String(error),
653
+ });
654
+ }
655
+ };
656
+
657
+ const ownerId = opts.ownerId ?? this.session.getAgentId?.() ?? undefined;
658
+ const jobId = manager.register(
659
+ "bash",
660
+ label,
661
+ async ({ jobId: id, signal, reportProgress }) => {
662
+ const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("bash")) ?? {};
663
+ const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES);
664
+ try {
665
+ const result = await executeBash(prepared.command, {
666
+ cwd: prepared.commandCwd,
667
+ sessionKey: `${this.session.getSessionId?.() ?? ""}:monitor:${id}`,
668
+ timeout: monitorTimeoutMs,
669
+ signal,
670
+ env: prepared.resolvedEnv,
671
+ artifactPath,
672
+ artifactId,
673
+ onChunk: chunk => {
674
+ tailBuffer.append(chunk);
675
+ void reportProgress(tailBuffer.text(), {
676
+ async: { state: "running", jobId: id, type: "bash" },
677
+ });
678
+ },
679
+ onRawChunk: chunk => {
680
+ manager.appendOutput(id, chunk);
681
+ const slice = manager.readOutputSince(id, cursorOffset, ownerId ? { ownerId } : undefined);
682
+ if (!slice) return;
683
+ cursorOffset = slice.nextOffset;
684
+ dispatchLines(slice.text);
685
+ },
686
+ onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
687
+ });
688
+ flushTrailingLine();
689
+ this.#buildResultText(result, prepared.timeoutSec, result.output || "(no output)");
690
+ return result.output;
691
+ } catch (error) {
692
+ flushTrailingLine();
693
+ throw error instanceof Error ? error : new Error(String(error));
694
+ }
695
+ },
696
+ { ownerId },
697
+ );
698
+ currentJobId = jobId;
699
+ return { jobId, label, commandCwd: prepared.commandCwd };
700
+ }
701
+
702
+ async execute(
703
+ _toolCallId: string,
704
+ {
705
+ command: rawCommand,
706
+ env: rawEnv,
707
+ timeout: rawTimeout = 300,
708
+ cwd,
709
+
710
+ async: asyncRequested = false,
711
+ pty = false,
712
+ }: BashToolInput,
713
+ signal?: AbortSignal,
714
+ onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
715
+ ctx?: AgentToolContext,
716
+ ): Promise<AgentToolResult<BashToolDetails>> {
717
+ if (asyncRequested && !this.#asyncEnabled) {
718
+ throw new ToolError("Async bash execution is disabled. Enable async.enabled to use async mode.");
719
+ }
720
+ const prepared = await this.#prepareBashExecution(
721
+ { command: rawCommand, env: rawEnv, timeout: rawTimeout, cwd },
722
+ ctx,
723
+ );
724
+ const {
725
+ command,
726
+ commandCwd,
727
+ resolvedEnv,
728
+ requestedTimeoutSec,
729
+ timeoutSec,
730
+ timeoutMs,
731
+ notices: pendingNotices,
732
+ } = prepared;
572
733
 
573
734
  if (asyncRequested) {
574
735
  if (!AsyncJobManager.instance()) {
@@ -916,7 +916,7 @@ export class WorkerCore {
916
916
  dispatchEvent: (event: unknown) => boolean;
917
917
  }
918
918
  const select = el as unknown as SelectLike;
919
- if (!select || select.tagName !== "SELECT") throw new Error("tab.select() requires a <select> element");
919
+ if (select?.tagName !== "SELECT") throw new Error("tab.select() requires a <select> element");
920
920
  const EventCtor = (
921
921
  globalThis as unknown as { Event: new (type: string, init?: { bubbles: boolean }) => unknown }
922
922
  ).Event;