@dimi-agent/cli 0.6.6 → 0.6.8

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 (2) hide show
  1. package/dist/main.mjs +2383 -94
  2. package/package.json +4 -4
package/dist/main.mjs CHANGED
@@ -34346,7 +34346,7 @@ var init_telemetry$2 = __esmMin((() => {
34346
34346
  };
34347
34347
  ITelemetryService = createDecorator("agentTelemetryService");
34348
34348
  }));
34349
- var init_events$7 = __esmMin((() => {}));
34349
+ var init_events$8 = __esmMin((() => {}));
34350
34350
  //#endregion
34351
34351
  //#region ../../packages/agent-core-v2/src/app/telemetry/telemetryService.ts
34352
34352
  var TelemetryService, TelemetryContextView;
@@ -99307,6 +99307,11 @@ function loadNative() {
99307
99307
  function coldRebuild(recordsJson) {
99308
99308
  return loadNative().coldRebuild(recordsJson);
99309
99309
  }
99310
+ /** Release an agent's scoped subagent registry (tasks + steering queues).
99311
+ * Call when the owning agent scope is disposed. */
99312
+ function dropTaskRegistry(registryId) {
99313
+ loadNative().dropTaskRegistry(registryId);
99314
+ }
99310
99315
  /** `RustHostProcess.spawn` — the M2 exec spawn socket. */
99311
99316
  async function rustHostProcessSpawn(command, args, options) {
99312
99317
  return loadNative().RustHostProcess.spawn(command, [...args], options);
@@ -99337,7 +99342,7 @@ function rustHostEnvironmentProbe() {
99337
99342
  function rustTerminalSpawn(options) {
99338
99343
  return loadNative().RustTerminal.spawn(options);
99339
99344
  }
99340
- var nodeRequire$4, binding, PLATFORM_SUBPACKAGE, RustAgentTranscript, RustFileSystem;
99345
+ var nodeRequire$4, binding, PLATFORM_SUBPACKAGE, RustAgentTranscript, RustFileSystem, RustTurnSession;
99341
99346
  var init_src$7 = __esmMin((() => {
99342
99347
  nodeRequire$4 = createRequire(import.meta.url);
99343
99348
  PLATFORM_SUBPACKAGE = `@dimi-agent/dimi-native-${process.platform}-${process.arch}`;
@@ -99397,6 +99402,89 @@ var init_src$7 = __esmMin((() => {
99397
99402
  return loadNative().RustFileSystem.realpath(path);
99398
99403
  }
99399
99404
  };
99405
+ RustTurnSession = class {
99406
+ #inner;
99407
+ constructor(inputJson, policyJson, scriptedSegmentsJson, registryId) {
99408
+ const NativeClass = loadNative().RustTurnSession;
99409
+ this.#inner = new NativeClass(inputJson, policyJson, scriptedSegmentsJson ?? null, registryId);
99410
+ }
99411
+ /** Release the agent-scoped subagent registry (tasks + steering queues).
99412
+ * Call when the owning agent scope is disposed. */
99413
+ static dropTaskRegistry(registryId) {
99414
+ dropTaskRegistry(registryId);
99415
+ }
99416
+ /** Register the per-event callback: every engine event emitted by `run()` /
99417
+ * `resume()` is pushed through it as a JSON string, in emission order, as
99418
+ * it happens. Register before the first `run()`. */
99419
+ setOnEvent(callback) {
99420
+ this.#inner.setOnEvent(callback);
99421
+ }
99422
+ async run() {
99423
+ return this.#inner.run();
99424
+ }
99425
+ async resume(decisionJson) {
99426
+ return this.#inner.resume(decisionJson);
99427
+ }
99428
+ /** Record a session-scope approval (P1-6): the engine's policy is re-read
99429
+ * on every run/resume, so a pattern approved for the session mid-turn is
99430
+ * honored by the SAME turn's remaining batch. */
99431
+ addSessionApproval(pattern) {
99432
+ this.#inner.addSessionApproval(pattern);
99433
+ }
99434
+ /** Register the native-tool PreToolUse gate (A2 review): every registry
99435
+ * tool call is announced through `callback` (`{requestId, toolName,
99436
+ * arguments}` JSON); answer with `completeToolGate(requestId,
99437
+ * {decision:'allow'|'block', reason?})`. A block short-circuits the call. */
99438
+ setToolGate(callback) {
99439
+ this.#inner.setToolGate(callback);
99440
+ }
99441
+ /** Answer a pending gate request (see `setToolGate`). */
99442
+ completeToolGate(requestId, verdictJson) {
99443
+ this.#inner.completeToolGate(requestId, verdictJson);
99444
+ }
99445
+ /** Register a TS-side tool; `completeToolCall` finishes each call. The
99446
+ * definition (description + JSON parameters schema) is advertised to the
99447
+ * model from the next request on. */
99448
+ registerExternalTool(name, description, parametersJson, callback) {
99449
+ this.#inner.registerExternalTool(name, description, parametersJson, callback);
99450
+ }
99451
+ /** Advertise the LLM-facing definition (description + JSON parameters
99452
+ * schema) of a Rust-native tool (Agent / AgentOutput / WaitFor) registered
99453
+ * executor-first at construction. The executor stays the same; only the
99454
+ * def is updated, so the engine's request `tools` field carries it from
99455
+ * the next request on. */
99456
+ registerNativeToolDef(name, description, parametersJson) {
99457
+ this.#inner.registerNativeToolDef(name, description, parametersJson);
99458
+ }
99459
+ /** Steer the running turn (drained into its next LLM request). Returns
99460
+ * `false` when the turn has already finished — the caller must start a
99461
+ * new turn instead (the steer is never dropped). */
99462
+ steer(message) {
99463
+ return this.#inner.steer(message);
99464
+ }
99465
+ /** Steer a background subagent spawned by the `Agent` tool. */
99466
+ steerSubagent(agentId, message) {
99467
+ this.#inner.steerSubagent(agentId, message);
99468
+ }
99469
+ /** Cancel the running turn (engine stops at the next boundary). */
99470
+ cancel() {
99471
+ this.#inner.cancel();
99472
+ }
99473
+ /** Cancel a background task (TaskStop parity): the engine kills the
99474
+ * subagent nested turn / backgrounded bash command and settles "killed",
99475
+ * carrying the stop reason on the wire. */
99476
+ cancelTask(taskId, reason) {
99477
+ this.#inner.cancelTask(taskId, reason);
99478
+ }
99479
+ /** Close the session (agent dispose): task events stop forwarding, the
99480
+ * in-flight turn is cancelled. Background workers observe the close. */
99481
+ close() {
99482
+ this.#inner.close();
99483
+ }
99484
+ completeToolCall(requestId, resultJson) {
99485
+ this.#inner.completeToolCall(requestId, resultJson);
99486
+ }
99487
+ };
99400
99488
  }));
99401
99489
  //#endregion
99402
99490
  //#region ../../packages/agent-core-v2/src/os/backends/rust-local/rustHostEnvironmentService.ts
@@ -104346,7 +104434,7 @@ var init_abort = __esmMin((() => {
104346
104434
  //#region ../../packages/agent-core-v2/src/agent/tools/os/bash/bash.md?raw
104347
104435
  var bash_default;
104348
104436
  var init_bash = __esmMin((() => {
104349
- bash_default = "Execute a `${SHELL_NAME}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command exits non-zero, the output ends with a `Command failed with exit code: N` line; a command killed by its timeout or interrupted by the user ends with its own message instead.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a ${DEFAULT_BACKGROUND_TIMEOUT_S}s timeout and `timeout` is capped at ${MAX_BACKGROUND_TIMEOUT_S}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. After starting one, default to returning control to the user instead of immediately waiting on it. Use `TaskOutput` only for a non-blocking status/output snapshot — do not wait on a task you just launched, since its completion arrives automatically. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls. To run a command in a particular directory, pass the `cwd` argument (or use absolute paths) rather than relying on a `cd` from an earlier call.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to ${DEFAULT_TIMEOUT_S}s and allow up to ${MAX_TIMEOUT_S}s. When a foreground command hits its timeout it is moved to the background instead of being killed, and you will be automatically notified when it completes.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- Use `&&` to chain commands that genuinely depend on each other, e.g. `npm install && npm test`. Independent read-only commands (separate `git show`, `ls`, or status checks) should be issued as separate parallel Bash calls in one response, not chained into a single call — chaining serializes their execution and mixes their output. Do not stitch outputs together with `echo` separators.\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`; for GitHub-hosted work (PRs, issues, CI runs, API queries) prefer the `gh` CLI when installed — it carries the user's GitHub auth and can return structured JSON\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
104437
+ bash_default = "Execute a `${SHELL_NAME}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command exits non-zero, the output ends with a `Command failed with exit code: N` line; a command killed by its timeout or interrupted by the user ends with its own message instead.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a ${DEFAULT_BACKGROUND_TIMEOUT_S}s timeout and `timeout` is capped at ${MAX_BACKGROUND_TIMEOUT_S}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. After starting one, default to returning control to the user instead of immediately waiting on it. Use `TaskOutput` only for a non-blocking status/output snapshot — do not wait on a task you just launched, since its completion arrives automatically. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Detached processes:** If a command leaves processes running in its process group after the shell exits (e.g. `nohup … &`, `… &`, `disown`), the result includes a notice listing them. Those processes are not tracked by dimi: you will not be notified when they finish, `TaskStop` cannot stop them, and a `WaitFor` only wakes on timeout. If the detach was intentional (e.g. starting a daemon), you may ignore the notice; otherwise prefer `run_in_background=true` so dimi manages the process and notifies you. Note this detection covers processes that stayed in the command's process group; a process that deliberately escapes to its own session (e.g. daemonizers, `setsid`) is not detectable after the command exits and is typically intentional.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls. To run a command in a particular directory, pass the `cwd` argument (or use absolute paths) rather than relying on a `cd` from an earlier call.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to ${DEFAULT_TIMEOUT_S}s and allow up to ${MAX_TIMEOUT_S}s. When a foreground command hits its timeout it is moved to the background instead of being killed, and you will be automatically notified when it completes.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- Use `&&` to chain commands that genuinely depend on each other, e.g. `npm install && npm test`. Independent read-only commands (separate `git show`, `ls`, or status checks) should be issued as separate parallel Bash calls in one response, not chained into a single call — chaining serializes their execution and mixes their output. Do not stitch outputs together with `echo` separators.\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`; for GitHub-hosted work (PRs, issues, CI runs, API queries) prefer the `gh` CLI when installed — it carries the user's GitHub auth and can return structured JSON\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
104350
104438
  }));
104351
104439
  //#endregion
104352
104440
  //#region ../../packages/agent-core-v2/src/agent/tools/os/bash/process-task.ts
@@ -104571,6 +104659,75 @@ function foregroundDescription(args) {
104571
104659
  if (explicit !== void 0 && explicit.length > 0) return explicit;
104572
104660
  return `Bash: ${args.command.length > 60 ? `${args.command.slice(0, 60)}…` : args.command}`;
104573
104661
  }
104662
+ /**
104663
+ * Detect processes that outlived the command's process group on Unix.
104664
+ *
104665
+ * dimi spawns every shell in its own session/process group (`setsid`, pid ==
104666
+ * sid == pgid), so `nohup`/`&`/`disown` children that survive the shell stay
104667
+ * in that same process group. Probing `kill(-pid, 0)` (process-group kill)
104668
+ * tells us whether any process is still there after the command finished;
104669
+ * `ps -g <pid>` then lists who.
104670
+ *
104671
+ * Scope: this covers processes that stayed in the command's process group
104672
+ * (the common accidental detach: `nohup … &`, `… &`, `disown`). A process
104673
+ * that actively escapes (calls `setsid`, or moves to its own group with
104674
+ * `setpgid`/`setpgrp`, e.g. daemonizers, `start_new_session=True`, node
104675
+ * `{detached:true}`) leaves the group entirely and cannot be attributed back
104676
+ * after the shell exits — such escapes are typically intentional and are out
104677
+ * of scope here. Windows has no session/process-group kill, so detection is
104678
+ * skipped there (returns []).
104679
+ */
104680
+ async function detectDetachedProcesses(pid) {
104681
+ if (process.platform === "win32" || !Number.isInteger(pid) || pid <= 0) return [];
104682
+ let alive = false;
104683
+ try {
104684
+ process.kill(-pid, 0);
104685
+ alive = true;
104686
+ } catch (error) {
104687
+ alive = error.code === "EPERM";
104688
+ }
104689
+ if (!alive) return [];
104690
+ return listProcessGroupProcesses(pid);
104691
+ }
104692
+ async function listProcessGroupProcesses(pid) {
104693
+ const { execFile } = await import("node:child_process");
104694
+ const ps = await new Promise((resolve) => {
104695
+ execFile("ps", [
104696
+ "-o",
104697
+ "pid=,ppid=,command=",
104698
+ "-g",
104699
+ String(pid)
104700
+ ], (error, stdout) => {
104701
+ resolve(error === null ? stdout : "");
104702
+ });
104703
+ });
104704
+ const infos = [];
104705
+ for (const line of ps.split("\n")) {
104706
+ const trimmed = line.trim();
104707
+ if (trimmed.length === 0) continue;
104708
+ const match = /^(\d+)\s+(\d+)\s+(.*)$/.exec(trimmed);
104709
+ if (match === null) continue;
104710
+ const childPid = Number(match[1]);
104711
+ if (childPid === pid) continue;
104712
+ infos.push({
104713
+ pid: childPid,
104714
+ ppid: Number(match[2]),
104715
+ command: match[3] ?? ""
104716
+ });
104717
+ }
104718
+ return infos;
104719
+ }
104720
+ /**
104721
+ * Rendered guidance appended to a Bash result when the finished command left
104722
+ * detached processes behind. Informational only — the command result itself
104723
+ * is unchanged; the agent decides whether the detach was intended.
104724
+ */
104725
+ function formatDetachedProcessNotice(infos) {
104726
+ return `
104727
+
104728
+ ⚠ Command left processes running outside dimi control:
104729
+ ${infos.map((info) => ` - pid ${String(info.pid)} (ppid ${String(info.ppid)}): ${info.command}`).join("\n")}\nThese processes are NOT tracked by dimi: dimi cannot notify you when they finish, cannot stop them with TaskStop, and a WaitFor will only wake on timeout. If this was intentional (e.g. starting a daemon), ignore this notice. Otherwise, prefer running the command with run_in_background=true so dimi manages it.`;
104730
+ }
104574
104731
  async function killSpawnedProcess(proc) {
104575
104732
  try {
104576
104733
  await proc.kill("SIGTERM");
@@ -104581,6 +104738,15 @@ async function killSpawnedProcess(proc) {
104581
104738
  function shellQuote(s) {
104582
104739
  return `'${s.replaceAll("'", "'\\''")}'`;
104583
104740
  }
104741
+ function partToResultText(part) {
104742
+ switch (part.type) {
104743
+ case "text": return part.text;
104744
+ case "think": return part.think;
104745
+ case "image_url": return "[image]";
104746
+ case "audio_url": return "[audio]";
104747
+ case "video_url": return "[video]";
104748
+ }
104749
+ }
104584
104750
  function windowsPathToPosixPath(path) {
104585
104751
  if (path.startsWith("\\\\")) return path.replaceAll("\\", "/");
104586
104752
  const driveMatch = /^([A-Za-z]):(?:[\\/]|$)/.exec(path);
@@ -104819,7 +104985,31 @@ var init_bashTool = __esmMin((() => {
104819
104985
  if (builder.nChars === 0) builder.write(`Process exited with code ${String(exitCode)}`);
104820
104986
  result = builder.error(`Command failed with exit code: ${String(exitCode)}.`, { brief: `Failed with exit code: ${String(exitCode)}` });
104821
104987
  }
104822
- return this.addForegroundOutputReference(taskId, result);
104988
+ const withReference = await this.addForegroundOutputReference(taskId, result);
104989
+ return this.addDetachedProcessNotice(proc, withReference);
104990
+ }
104991
+ /**
104992
+ * After a foreground command finishes, check whether it left processes
104993
+ * running in the command's process group (e.g. `nohup … &`) that dimi can
104994
+ * no longer track. When it did, append an informational notice to the
104995
+ * result — the exit code / error status is untouched, and the agent
104996
+ * decides whether the detach was intended. Processes that actively escape
104997
+ * to their own session/group (setsid, daemonizers) cannot be detected
104998
+ * after the shell exits and are out of scope.
104999
+ */
105000
+ async addDetachedProcessNotice(proc, result) {
105001
+ const infos = await this.detectDetached(proc.pid);
105002
+ if (infos.length === 0) return result;
105003
+ const output = result.output;
105004
+ const text = typeof output === "string" ? output : output.map(partToResultText).join("");
105005
+ return {
105006
+ ...result,
105007
+ output: `${text}${formatDetachedProcessNotice(infos)}`
105008
+ };
105009
+ }
105010
+ /** Overridable seam for tests: probe for processes that escaped the session. */
105011
+ detectDetached(pid) {
105012
+ return detectDetachedProcesses(pid);
104823
105013
  }
104824
105014
  async addForegroundOutputReference(taskId, result) {
104825
105015
  if (!result.truncated) return result;
@@ -121834,7 +122024,7 @@ function emptyOutputSnapshot() {
121834
122024
  function agentTaskNotificationChildren(output) {
121835
122025
  if (output.fullOutputAvailable && output.outputPath !== void 0) return [renderOutputFileBlock(output.outputPath, output.outputSizeBytes)];
121836
122026
  if (output.preview.length === 0) return void 0;
121837
- return [renderOutputPreviewBlock(output)];
122027
+ return [renderOutputPreviewBlock$1(output)];
121838
122028
  }
121839
122029
  function renderOutputFileBlock(outputPath, outputSizeBytes) {
121840
122030
  return [
@@ -121843,7 +122033,7 @@ function renderOutputFileBlock(outputPath, outputSizeBytes) {
121843
122033
  "</output-file>"
121844
122034
  ].join("\n");
121845
122035
  }
121846
- function renderOutputPreviewBlock(output) {
122036
+ function renderOutputPreviewBlock$1(output) {
121847
122037
  return [
121848
122038
  `<output-preview bytes="${String(output.previewBytes)}" total_bytes="${String(output.outputSizeBytes)}" truncated="${String(output.truncated)}">`,
121849
122039
  output.truncated ? `Showing the last ${String(output.previewBytes)} bytes. No persisted full output is available.` : "No persisted full output is available; this preview is the currently buffered task output.",
@@ -121925,7 +122115,7 @@ function errorMessage$7(error) {
121925
122115
  if (error instanceof Error) return error.message;
121926
122116
  return String(error);
121927
122117
  }
121928
- var TaskNotificationDeliveryModel, MAX_OUTPUT_BYTES, TERMINAL_OUTPUT_TAIL_BYTES, MAX_TASK_OUTPUT_BYTES, SIGTERM_GRACE_MS, TASK_ID_ALPHABET, SESSION_CLOSED_REASON, NOTIFICATION_FALLBACK_PREVIEW_BYTES, ACTIVE_BACKGROUND_TASK_INJECTION_VARIANT, ACTIVE_BACKGROUND_TASK_GUIDANCE, TaskNotificationStepRequest, taskGhostsKey, taskScheduledNotificationKeysKey, taskDeliveredNotificationKeysKey, taskActiveTaskReminderPendingKey, AgentTaskService;
122118
+ var TaskNotificationDeliveryModel, MAX_OUTPUT_BYTES, TERMINAL_OUTPUT_TAIL_BYTES, MAX_TASK_OUTPUT_BYTES, TASK_ID_ALPHABET, SESSION_CLOSED_REASON, NOTIFICATION_FALLBACK_PREVIEW_BYTES, ACTIVE_BACKGROUND_TASK_INJECTION_VARIANT, ACTIVE_BACKGROUND_TASK_GUIDANCE, TaskNotificationStepRequest, taskGhostsKey, taskScheduledNotificationKeysKey, taskDeliveredNotificationKeysKey, taskActiveTaskReminderPendingKey, AgentTaskService;
121929
122119
  var init_taskService = __esmMin((() => {
121930
122120
  init_dist$5();
121931
122121
  init_scope();
@@ -121973,7 +122163,6 @@ var init_taskService = __esmMin((() => {
121973
122163
  MAX_OUTPUT_BYTES = 1024 * 1024;
121974
122164
  TERMINAL_OUTPUT_TAIL_BYTES = 4 * 1024;
121975
122165
  MAX_TASK_OUTPUT_BYTES = 16 * 1024 * 1024;
121976
- SIGTERM_GRACE_MS = 5e3;
121977
122166
  TASK_ID_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
121978
122167
  SESSION_CLOSED_REASON = "Session closed";
121979
122168
  NOTIFICATION_FALLBACK_PREVIEW_BYTES = 3e3;
@@ -122172,7 +122361,7 @@ var init_taskService = __esmMin((() => {
122172
122361
  };
122173
122362
  this.assertCanRegister(detached);
122174
122363
  const entry = {
122175
- taskId: generateTaskId(task.idPrefix),
122364
+ taskId: options.taskId ?? generateTaskId(task.idPrefix),
122176
122365
  task,
122177
122366
  handle: void 0,
122178
122367
  outputChunks: [],
@@ -122482,7 +122671,7 @@ var init_taskService = __esmMin((() => {
122482
122671
  entry.stopReason = options.stopReason;
122483
122672
  if (entry.handle) entry.handle.cancel();
122484
122673
  else entry.abortController.abort(options.abortReason);
122485
- const graceMs = resolveAgentTaskConfig(this.config)?.killGracePeriodMs ?? SIGTERM_GRACE_MS;
122674
+ const graceMs = resolveAgentTaskConfig(this.config)?.killGracePeriodMs ?? 5e3;
122486
122675
  let graceTimer;
122487
122676
  const graceful = await Promise.race([entry.lifecyclePromise.then(() => true, () => true), new Promise((resolve) => {
122488
122677
  graceTimer = setTimeout(() => {
@@ -123160,6 +123349,258 @@ var init_waitForTool = __esmMin((() => {
123160
123349
  });
123161
123350
  }));
123162
123351
  //#endregion
123352
+ //#region ../../packages/agent-core-v2/src/agent/tools/agent-output/agent-output.ts
123353
+ var AgentOutputInputSchema, AGENT_NOT_FOUND_MESSAGE, IAgentOutputTool;
123354
+ var init_agent_output$1 = __esmMin((() => {
123355
+ init_zod$1();
123356
+ init_instantiation();
123357
+ AgentOutputInputSchema = object({
123358
+ agent_id: string$2().describe("Agent ID of the subagent to inspect (returned by the Agent tool)."),
123359
+ tail_lines: number$2().int().min(1).max(500).optional().describe("How many of the most recent renderable records to show. Defaults to 60. The output is a transcript-style view of the subagent's latest activity.")
123360
+ });
123361
+ AGENT_NOT_FOUND_MESSAGE = "Agent instance not found.";
123362
+ IAgentOutputTool = createDecorator("agentOutputTool");
123363
+ }));
123364
+ //#endregion
123365
+ //#region ../../packages/agent-core-v2/src/agent/contextSize/contextSize.ts
123366
+ var IAgentContextSizeService;
123367
+ var init_contextSize = __esmMin((() => {
123368
+ init_instantiation();
123369
+ IAgentContextSizeService = createDecorator("agentContextSizeService");
123370
+ }));
123371
+ //#endregion
123372
+ //#region ../../packages/agent-core-v2/src/agent/tools/agent-output/agent-output.md?raw
123373
+ var agent_output_default;
123374
+ var init_agent_output = __esmMin((() => {
123375
+ agent_output_default = "Read the recent rendered output of a subagent — the same transcript-style view a human sees in the TUI: its latest assistant text, thinking, tool calls, and task progress, in time order.\n\nSubagents run fully asynchronously. Use this tool to check on one:\n\n- While you still have other work, do that work instead — the subagent's result (and completion notification) arrives on its own.\n- When you have nothing else to do and want to see what the subagent is doing, call this tool with the `agent_id` returned by the Agent tool.\n- If it is still working and you want to park until it progresses, call `WaitFor` with a reasonable `timeout_seconds` instead of polling this tool in a loop; the wait wakes you on the completion notification or the timeout, then check again with this tool.\n\nPass `tail_lines` to control how much of the recent activity to show (default 60). The `agent_id` is the Agent tool's `agent_id` parameter value (e.g. \"agent-6\"), NOT the `task_id` from its output.\n";
123376
+ }));
123377
+ //#endregion
123378
+ //#region ../../packages/agent-core-v2/src/agent/tools/agent-output/agentOutputTool.ts
123379
+ /**
123380
+ * `tools` domain (L7) — `AgentOutputTool` implementation (the `AgentOutput`
123381
+ * tool).
123382
+ *
123383
+ * Returns a transcript-style view of a subagent's recent activity by reading
123384
+ * the tail of its `wire.jsonl` journal (`<sessionDir>/agents/<agentId>/`):
123385
+ * assistant text, thinking, tool calls, and task progress in time order —
123386
+ * the same surface a human sees in the TUI. Subagents run fully
123387
+ * asynchronously, so this is the primary progress-check tool for the calling
123388
+ * agent (park with `WaitFor` instead of polling when there is nothing else
123389
+ * to do).
123390
+ *
123391
+ * Registered via the module-level `registerAgentToolService(IAgentOutputTool,
123392
+ * AgentOutputTool)` at the bottom of this file — the same "import = register"
123393
+ * pattern used by every agent tool. Bound at Agent scope.
123394
+ */
123395
+ function toRenderable(line) {
123396
+ const trimmed = line.trim();
123397
+ if (trimmed.length === 0) return void 0;
123398
+ let parsed;
123399
+ try {
123400
+ parsed = JSON.parse(trimmed);
123401
+ } catch {
123402
+ return;
123403
+ }
123404
+ if (typeof parsed !== "object" || parsed === null) return void 0;
123405
+ const record = parsed;
123406
+ const timeMs = typeof record.time === "number" ? normalizeTimestampMs$1(record.time) : void 0;
123407
+ const turnStep = turnStepLabel(record.event);
123408
+ switch (record.type) {
123409
+ case "turn.prompt": return {
123410
+ timeMs: timeMs ?? 0,
123411
+ turnStep: "",
123412
+ lines: [`user: ${clip(textOfPrompt(record.input), PROMPT_MAX_CHARS)}`]
123413
+ };
123414
+ case "context.append_loop_event": {
123415
+ const event = record.event;
123416
+ const eventType = event?.type;
123417
+ if (eventType === "content.part" && typeof event?.part?.type === "string") {
123418
+ const partType = event.part.type;
123419
+ const text = partType === "think" ? typeof event.part.think === "string" ? event.part.think : "" : typeof event.part.text === "string" ? event.part.text : "";
123420
+ return {
123421
+ timeMs: timeMs ?? 0,
123422
+ turnStep,
123423
+ lines: [`${partType === "think" ? "think" : "assistant"}: ${clip(text, LINE_MAX_CHARS)}`]
123424
+ };
123425
+ }
123426
+ if (eventType === "tool.call" && typeof event?.name === "string") return {
123427
+ timeMs: timeMs ?? 0,
123428
+ turnStep,
123429
+ lines: [`tool: ${event.name}${argsLabel(event.args)}`]
123430
+ };
123431
+ if (eventType === "tool.result") {
123432
+ const done = event?.isError === true ? "error" : "done";
123433
+ return {
123434
+ timeMs: timeMs ?? 0,
123435
+ turnStep,
123436
+ lines: [` → ${done}`]
123437
+ };
123438
+ }
123439
+ return;
123440
+ }
123441
+ case "task.started":
123442
+ case "task.terminated": {
123443
+ const info = record.info;
123444
+ if (info === void 0) return void 0;
123445
+ const description = typeof info.description === "string" ? info.description : "";
123446
+ const taskId = typeof info.taskId === "string" ? info.taskId : "";
123447
+ const status = typeof info.status === "string" ? info.status : "";
123448
+ const label = record.type === "task.started" ? `task: ${clip(description, LINE_MAX_CHARS)} (running, ${taskId})` : `task: ${clip(description, LINE_MAX_CHARS)} (${status}, ${taskId})`;
123449
+ return {
123450
+ timeMs: timeMs ?? 0,
123451
+ turnStep: "",
123452
+ lines: [label]
123453
+ };
123454
+ }
123455
+ default: return;
123456
+ }
123457
+ }
123458
+ function turnStepLabel(event) {
123459
+ if (event === void 0) return "";
123460
+ const turn = typeof event.turnId === "string" ? event.turnId : void 0;
123461
+ const step = typeof event.step === "number" ? event.step : void 0;
123462
+ if (turn === void 0 && step === void 0) return "";
123463
+ return `[turn ${turn ?? "?"} step ${step ?? "?"}] `;
123464
+ }
123465
+ function argsLabel(args) {
123466
+ if (typeof args !== "object" || args === null) return "";
123467
+ return `(${clip(JSON.stringify(args), ARGS_MAX_CHARS)})`;
123468
+ }
123469
+ function textOfPrompt(input) {
123470
+ if (!Array.isArray(input)) return "";
123471
+ return input.flatMap((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("").replaceAll(/\s+/g, " ").trim();
123472
+ }
123473
+ function clip(text, max) {
123474
+ const collapsed = text.replaceAll(/\s+/g, " ").trim();
123475
+ if (collapsed.length <= max) return collapsed;
123476
+ return `${collapsed.slice(0, max)}…`;
123477
+ }
123478
+ function normalizeTimestampMs$1(value) {
123479
+ return value > 0xe8d4a51000 ? Math.floor(value) : Math.floor(value * 1e3);
123480
+ }
123481
+ function formatTime(timeMs) {
123482
+ if (timeMs <= 0) return "";
123483
+ const date = new Date(timeMs);
123484
+ const pad = (n) => String(n).padStart(2, "0");
123485
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
123486
+ }
123487
+ function formatTokens$2(size) {
123488
+ if (size < 1024) return `${String(size)} tokens`;
123489
+ return `${(size / 1024).toFixed(1)}k tokens`;
123490
+ }
123491
+ function isMissingPath$3(error) {
123492
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
123493
+ }
123494
+ var WIRE_FILENAME$1, TAIL_READ_BYTES, LINE_MAX_CHARS, ARGS_MAX_CHARS, PROMPT_MAX_CHARS, AgentOutputTool;
123495
+ var init_agentOutputTool = __esmMin((() => {
123496
+ init_dist$5();
123497
+ init_input_schema();
123498
+ init_rule_match();
123499
+ init_toolContribution();
123500
+ init_contextSize();
123501
+ init_loop();
123502
+ init_profile();
123503
+ init_agentLifecycle();
123504
+ init_sessionContext();
123505
+ init_agent_output$1();
123506
+ init_agent_output();
123507
+ init_decorateParam();
123508
+ init_decorate();
123509
+ WIRE_FILENAME$1 = "wire.jsonl";
123510
+ TAIL_READ_BYTES = 256 * 1024;
123511
+ LINE_MAX_CHARS = 240;
123512
+ ARGS_MAX_CHARS = 120;
123513
+ PROMPT_MAX_CHARS = 300;
123514
+ AgentOutputTool = class AgentOutputTool {
123515
+ lifecycle;
123516
+ session;
123517
+ name = "AgentOutput";
123518
+ description = agent_output_default;
123519
+ parameters = toInputJsonSchema(AgentOutputInputSchema);
123520
+ constructor(lifecycle, session) {
123521
+ this.lifecycle = lifecycle;
123522
+ this.session = session;
123523
+ }
123524
+ resolveExecution(args) {
123525
+ return {
123526
+ description: `Reading output of agent ${args.agent_id}`,
123527
+ taskMode: "control",
123528
+ approvalRule: this.name,
123529
+ matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, args.agent_id),
123530
+ execute: () => this.execute(args)
123531
+ };
123532
+ }
123533
+ async execute(args) {
123534
+ const target = this.lifecycle.get(args.agent_id);
123535
+ if (target === void 0) return {
123536
+ isError: true,
123537
+ output: `${AGENT_NOT_FOUND_MESSAGE} agent_id=${args.agent_id}`
123538
+ };
123539
+ const records = await this.readRecentRecords(args.agent_id, args.tail_lines ?? 60);
123540
+ const profileName = target.accessor.get(IAgentProfileService).data().profileName ?? "agent";
123541
+ const status = target.accessor.get(IAgentLoopService).status();
123542
+ const contextSize = target.accessor.get(IAgentContextSizeService)?.get().size ?? 0;
123543
+ const lines = [[
123544
+ `agent_id: ${args.agent_id}`,
123545
+ `type: ${profileName}`,
123546
+ `status: ${status.state}${status.activeTurnId !== void 0 ? ` · turn ${String(status.activeTurnId)}` : ""}`,
123547
+ `context: ${formatTokens$2(contextSize)}`
123548
+ ].join(" · "), ""];
123549
+ if (records.length === 0) lines.push("(no activity recorded yet)");
123550
+ else for (const record of records) {
123551
+ const stamp = formatTime(record.timeMs);
123552
+ for (const line of record.lines) lines.push(`${stamp} ${record.turnStep}${line}`);
123553
+ }
123554
+ return {
123555
+ output: lines.join("\n"),
123556
+ isError: false
123557
+ };
123558
+ }
123559
+ async readRecentRecords(agentId, tailLines) {
123560
+ const path = join$5(this.session.sessionDir, "agents", agentId, WIRE_FILENAME$1);
123561
+ let file;
123562
+ try {
123563
+ file = await open(path, "r");
123564
+ } catch (error) {
123565
+ if (isMissingPath$3(error)) return [];
123566
+ throw error;
123567
+ }
123568
+ let input;
123569
+ try {
123570
+ const size = (await file.stat()).size;
123571
+ const start = Math.max(0, size - TAIL_READ_BYTES);
123572
+ input = size === 0 ? Readable.from([]) : file.createReadStream({
123573
+ encoding: "utf8",
123574
+ autoClose: false,
123575
+ start,
123576
+ end: size - 1
123577
+ });
123578
+ const lines = createInterface({
123579
+ input,
123580
+ crlfDelay: Infinity
123581
+ });
123582
+ const rendered = [];
123583
+ for await (const line of lines) {
123584
+ const record = toRenderable(line);
123585
+ if (record === void 0) continue;
123586
+ rendered.push(record);
123587
+ if (rendered.length > tailLines) rendered.shift();
123588
+ }
123589
+ return rendered;
123590
+ } finally {
123591
+ input?.destroy();
123592
+ if (input !== void 0) await finished(input, { cleanup: true }).catch(() => {});
123593
+ await file.close();
123594
+ }
123595
+ }
123596
+ };
123597
+ AgentOutputTool = __decorate$1([__decorateParam(0, IAgentLifecycleService), __decorateParam(1, ISessionContext)], AgentOutputTool);
123598
+ registerAgentToolService(IAgentOutputTool, AgentOutputTool, {
123599
+ name: "AgentOutput",
123600
+ domain: "subagent"
123601
+ });
123602
+ }));
123603
+ //#endregion
123163
123604
  //#region ../../packages/agent-core-v2/src/agent/tools/all-done/all-done.md?raw
123164
123605
  var all_done_default;
123165
123606
  var init_all_done = __esmMin((() => {
@@ -139187,13 +139628,6 @@ var init_subagent_task = __esmMin((() => {
139187
139628
  };
139188
139629
  }));
139189
139630
  //#endregion
139190
- //#region ../../packages/agent-core-v2/src/agent/contextSize/contextSize.ts
139191
- var IAgentContextSizeService;
139192
- var init_contextSize = __esmMin((() => {
139193
- init_instantiation();
139194
- IAgentContextSizeService = createDecorator("agentContextSizeService");
139195
- }));
139196
- //#endregion
139197
139631
  //#region ../../packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts
139198
139632
  function emitAgentRunSpawned(requester, targetAgentId, meta) {
139199
139633
  requester.accessor.get(IEventBus)?.publish({
@@ -208443,7 +208877,7 @@ function turnPromptText(input) {
208443
208877
  const text = input.filter((part) => part.type === "text").map((part) => part.text).join("");
208444
208878
  return text.length > 0 ? text : void 0;
208445
208879
  }
208446
- function isDisplayablePromptOrigin(origin) {
208880
+ function isDisplayablePromptOrigin$1(origin) {
208447
208881
  if (origin.kind === "user") return true;
208448
208882
  return (origin.kind === "skill_activation" || origin.kind === "plugin_command") && origin.trigger === "user-slash";
208449
208883
  }
@@ -208905,7 +209339,7 @@ var init_loopService = __esmMin((() => {
208905
209339
  type: "turn.started",
208906
209340
  turnId: job.turn.id,
208907
209341
  origin,
208908
- prompt: isDisplayablePromptOrigin(origin) ? turnPromptText(job.seed.input) : void 0
209342
+ prompt: isDisplayablePromptOrigin$1(origin) ? turnPromptText(job.seed.input) : void 0
208909
209343
  });
208910
209344
  this.runTurn(job.turn, job.ready).then(job.result.resolve, job.result.reject);
208911
209345
  }
@@ -213499,6 +213933,1504 @@ var init_rpc$2 = __esmMin((() => {
213499
213933
  createDecorator("agentSessionRPCService");
213500
213934
  }));
213501
213935
  //#endregion
213936
+ //#region ../../packages/agent-core-v2/src/agent/loop/engineTaskAdapter.ts
213937
+ /** Extract a human-readable stop reason from an AbortSignal's abort reason:
213938
+ * TaskStop aborts with the normalized reason string; `stopByUser` aborts
213939
+ * with a `UserCancellationError` (its `.message` is the user-facing reason);
213940
+ * anything else (undefined / session close) yields no reason — the engine
213941
+ * falls back to "Stopped by TaskStop". An empty reason (e.g. an Error with
213942
+ * an empty message) counts as "no reason" too, so the wire settle carries
213943
+ * the documented fallback instead of an empty `error` string. */
213944
+ function abortReasonString(reason) {
213945
+ if (typeof reason === "string") return reason === "" ? void 0 : reason;
213946
+ if (reason instanceof Error) return reason.message === "" ? void 0 : reason.message;
213947
+ }
213948
+ var EngineTaskAdapter;
213949
+ var init_engineTaskAdapter = __esmMin((() => {
213950
+ EngineTaskAdapter = class {
213951
+ options;
213952
+ idPrefix;
213953
+ kind;
213954
+ description;
213955
+ sink;
213956
+ pendingOutput = "";
213957
+ pendingSettlement;
213958
+ pendingExitCode;
213959
+ exitCode;
213960
+ settleResolve;
213961
+ settled;
213962
+ removeAbortListener;
213963
+ /** UTF-8 bytes already pushed to the sink (or buffered pre-start). The
213964
+ * engine guarantees its `task.output` deltas concatenate byte-for-byte to
213965
+ * the settle's full output — the bash poller seeds a backgrounded command
213966
+ * with its pre-timeout foreground output as the FIRST delta, and the
213967
+ * subagent worker streams the nested turn's assistant deltas verbatim —
213968
+ * so `complete` appends only the not-yet-streamed tail. */
213969
+ streamedBytes = 0;
213970
+ constructor(options) {
213971
+ this.options = options;
213972
+ this.idPrefix = options.kind === "agent" ? "agent" : "bash";
213973
+ this.kind = options.kind;
213974
+ this.description = options.description;
213975
+ this.settled = new Promise((resolve) => {
213976
+ this.settleResolve = resolve;
213977
+ });
213978
+ }
213979
+ async start(sink) {
213980
+ this.sink = sink;
213981
+ const requestStop = () => {
213982
+ this.options.forceStop(abortReasonString(sink.signal.reason));
213983
+ };
213984
+ if (sink.signal.aborted) requestStop();
213985
+ else {
213986
+ sink.signal.addEventListener("abort", requestStop, { once: true });
213987
+ this.removeAbortListener = () => {
213988
+ sink.signal.removeEventListener("abort", requestStop);
213989
+ };
213990
+ }
213991
+ this.flush();
213992
+ await this.settled;
213993
+ }
213994
+ async forceStop() {
213995
+ this.options.forceStop();
213996
+ }
213997
+ /** Runner-side live output (engine `task.output` events): stream the delta
213998
+ * straight into the service sink so TaskOutput shows partial output while
213999
+ * the task runs. Deltas racing `start()` (settle/stream before the
214000
+ * service invoked start) are buffered and flushed with the settle. */
214001
+ appendOutput(delta) {
214002
+ this.streamedBytes += Buffer.byteLength(delta, "utf-8");
214003
+ if (this.sink !== void 0) this.sink.appendOutput(delta);
214004
+ else this.pendingOutput += delta;
214005
+ }
214006
+ /** Runner-side settle: record the final output/exit code and settle the
214007
+ * registry entry (the runner dispatches the wire `task.terminated` and
214008
+ * delivers the notification itself — the service's `recorded: false`
214009
+ * entry only drives TaskList/TaskStop). The settle output is the engine's
214010
+ * full accumulated output; the deltas streamed during the run already
214011
+ * landed in the sink, so only the not-yet-streamed tail is appended (the
214012
+ * concatenated deltas equal the settle output byte-for-byte). */
214013
+ complete(output, settlement, exitCode) {
214014
+ this.pendingSettlement = settlement;
214015
+ this.pendingExitCode = exitCode ?? null;
214016
+ const fullBytes = Buffer.byteLength(output, "utf-8");
214017
+ const streamed = this.streamedBytes;
214018
+ if (streamed < fullBytes) {
214019
+ const tail = Buffer.from(output, "utf-8").subarray(streamed).toString("utf-8");
214020
+ if (this.sink !== void 0) this.sink.appendOutput(tail);
214021
+ else this.pendingOutput += tail;
214022
+ }
214023
+ this.flush();
214024
+ }
214025
+ toInfo(base) {
214026
+ if (this.kind === "agent") return {
214027
+ ...base,
214028
+ kind: "agent",
214029
+ agentId: this.options.agentId ?? "",
214030
+ subagentType: this.options.subagentType ?? this.options.description,
214031
+ detached: true
214032
+ };
214033
+ return {
214034
+ ...base,
214035
+ kind: "process",
214036
+ command: this.description,
214037
+ pid: this.options.pid ?? 0,
214038
+ exitCode: this.exitCode ?? null,
214039
+ detached: true
214040
+ };
214041
+ }
214042
+ flush() {
214043
+ const sink = this.sink;
214044
+ const settlement = this.pendingSettlement;
214045
+ if (sink === void 0 || settlement === void 0) return;
214046
+ this.exitCode = this.pendingExitCode ?? null;
214047
+ if (this.pendingOutput.length > 0) sink.appendOutput(this.pendingOutput);
214048
+ this.pendingOutput = "";
214049
+ this.pendingSettlement = void 0;
214050
+ this.removeAbortListener?.();
214051
+ this.removeAbortListener = void 0;
214052
+ sink.settle(settlement);
214053
+ this.settleResolve?.();
214054
+ }
214055
+ };
214056
+ }));
214057
+ //#endregion
214058
+ //#region ../../packages/agent-core-v2/src/agent/toolResultTruncation/toolResultTruncation.ts
214059
+ var IAgentToolResultTruncationService;
214060
+ var init_toolResultTruncation = __esmMin((() => {
214061
+ init_instantiation();
214062
+ IAgentToolResultTruncationService = createDecorator("agentToolResultTruncationService");
214063
+ }));
214064
+ //#endregion
214065
+ //#region ../../packages/agent-core-v2/src/agent/loop/rustEngineTurnRunner.ts
214066
+ /**
214067
+ * `rustEngineTurnRunner` — the default turn runner: one turn through the Rust
214068
+ * engine (`RustTurnSession` napi socket) instead of the TS loop.
214069
+ *
214070
+ * Enabled by default; the CLI `--legacy` flag sets `DIMI_LEGACY=1` to keep
214071
+ * the TS loop. The runner:
214072
+ * 1. records `turn.prompt` (turn clock) and appends the user message;
214073
+ * 2. assembles the LLM messages from the context (context assembly stays
214074
+ * on the TS side until slice 3);
214075
+ * 3. runs `RustTurnSession` (aimux-backed LLM in production, scripted
214076
+ * segments under test);
214077
+ * 4. streams the engine events onto the event bus as they happen (the
214078
+ * bridge pushes each event through a per-event callback while the turn
214079
+ * is in flight) — the transcript projection layer (`coreEventMap`)
214080
+ * folds them into wire ops exactly as it does for the TS loop — and
214081
+ * mirrors them into the context (`step.begin` / `content.part` /
214082
+ * `tool.call` / `tool.result` / `step.end` + the assistant message) so
214083
+ * the next turn's history is intact.
214084
+ *
214085
+ * Slice-1 scope: single turn, no queue/cancellation/undo (those land with
214086
+ * later slices). The runner is a parallel path — `loopService` is untouched.
214087
+ */
214088
+ function wrapSystemReminder(text) {
214089
+ const trimmed = text.trim();
214090
+ if (trimmed.startsWith("<system-reminder>") && trimmed.endsWith("</system-reminder>")) return trimmed;
214091
+ return `<system-reminder>\n${trimmed}\n</system-reminder>`;
214092
+ }
214093
+ /**
214094
+ * The Rust engine is the default runtime; the CLI `--legacy` flag sets
214095
+ * `DIMI_LEGACY=1` to keep the TS loop (and the node-local OS backends).
214096
+ */
214097
+ function rustEngineEnabled() {
214098
+ return process.env["DIMI_LEGACY"] !== "1";
214099
+ }
214100
+ /** Render an engine event/tool value as text (strings pass through). */
214101
+ function toText(value) {
214102
+ return typeof value === "string" ? value : JSON.stringify(value) ?? "";
214103
+ }
214104
+ /**
214105
+ * Serialize a TS `PromptOrigin` into the wire `TurnOrigin` JSON shape the
214106
+ * engine deserializes (`{ kind: 'user' }` / `{ kind: 'task', taskId }` /
214107
+ * … — see `dimi-wire` `model.rs`). Unknown kinds fall back to a plain user
214108
+ * origin (the wire default).
214109
+ */
214110
+ function toEngineTurnOrigin(origin) {
214111
+ switch (origin.kind) {
214112
+ case "task": return {
214113
+ kind: "task",
214114
+ taskId: origin.taskId
214115
+ };
214116
+ case "cron_job": return {
214117
+ kind: "cron",
214118
+ taskId: origin.jobId
214119
+ };
214120
+ case "cron_missed": return { kind: "cron" };
214121
+ case "hook_result": return { kind: "hook" };
214122
+ case "compaction_summary": return { kind: "compaction" };
214123
+ default: return { kind: "user" };
214124
+ }
214125
+ }
214126
+ /**
214127
+ * TS `isDisplayablePromptOrigin` parity: only user-origin (and user-slash
214128
+ * skill/plugin) turns expose their prompt on `turn.started`; task/cron/hook
214129
+ * steering text must never leak into the transcript.
214130
+ */
214131
+ function isDisplayablePromptOrigin(origin) {
214132
+ if (origin.kind === "user") return true;
214133
+ return (origin.kind === "skill_activation" || origin.kind === "plugin_command") && origin.trigger === "user-slash";
214134
+ }
214135
+ /**
214136
+ * Build a DimiErrorPayload-shaped error from the engine's `{ message, code }`
214137
+ * payload (TS `toDimiErrorPayload` parity): always carries `name` (the error
214138
+ * class name, mapped from the code when the engine omitted it), `retryable`
214139
+ * (mapped from the code per the TS registry) and `message`.
214140
+ */
214141
+ function buildErrorPayload(rawError) {
214142
+ const error = { ...rawError };
214143
+ if (error["name"] === void 0 || error["name"] === null) error["name"] = error["code"] === "PROVIDER_FILTERED" ? "ProviderFilteredError" : "Error";
214144
+ if (error["retryable"] === void 0) error["retryable"] = typeof error["code"] === "string" && RETRYABLE_ERROR_CODES.has(error["code"]);
214145
+ return error;
214146
+ }
214147
+ /** Optional numeric engine field (pid / exitCode). */
214148
+ function toOptionalNumber(value) {
214149
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
214150
+ }
214151
+ /** Mirrors the task domain's `buildAgentTaskNotificationBody` (minus the
214152
+ * output-file block — the engine carries the tail inline). */
214153
+ function buildTaskNotificationBody(task) {
214154
+ const baseLine = task.status === "timed_out" ? `${task.description} timed out.` : task.status === "killed" && task.error !== void 0 ? `${task.description} was stopped. Reason: ${task.error}` : task.status === "failed" && task.error !== void 0 ? `${task.description} failed. Reason: ${task.error}` : `${task.description} ${task.status}.`;
214155
+ if (task.kind !== "agent" || task.status === "completed") return baseLine;
214156
+ return `${baseLine}${[
214157
+ "",
214158
+ `To recover or continue this subagent, call Agent(resume="${task.agentId}", prompt="Pick up where you left off; redo the last tool call if its result was never observed.").`,
214159
+ `Use agent_id ("${task.agentId}"), NOT source_id / task_id ("${task.taskId}") — the two look alike but only agent_id is accepted by the resume parameter.`,
214160
+ "Add run_in_background=true to keep it backgrounded, or omit it to take the result inline in the current turn.",
214161
+ "The subagent retains its full prior context across the restart, but any in-flight tool call lost its result and may need to be redone."
214162
+ ].join("\n")}`;
214163
+ }
214164
+ /** Mirrors the task domain's `renderOutputPreviewBlock` (tail inline): the
214165
+ * engine's accumulated output is the "currently buffered" output, so the
214166
+ * preview is the last `NOTIFICATION_OUTPUT_PREVIEW_BYTES` bytes with the
214167
+ * same heading / `truncated` / `bytes` semantics the TS service renders. */
214168
+ function renderOutputPreviewBlock(output) {
214169
+ const fullBytes = Buffer.byteLength(output, "utf-8");
214170
+ const previewBytes = Math.min(NOTIFICATION_OUTPUT_PREVIEW_BYTES, fullBytes);
214171
+ const truncated = fullBytes > previewBytes;
214172
+ const preview = previewBytes > 0 ? Buffer.from(output, "utf-8").subarray(fullBytes - previewBytes).toString("utf-8") : "";
214173
+ return [
214174
+ `<output-preview bytes="${String(previewBytes)}" total_bytes="${String(fullBytes)}" truncated="${String(truncated)}">`,
214175
+ truncated ? `Showing the last ${String(previewBytes)} bytes. No persisted full output is available.` : "No persisted full output is available; this preview is the currently buffered task output.",
214176
+ escapeXml$1(preview),
214177
+ "</output-preview>"
214178
+ ].join("\n");
214179
+ }
214180
+ /** Resolve once the signal aborts (used to race an approval wait). */
214181
+ function abortOnSignal(signal) {
214182
+ return new Promise((resolve) => {
214183
+ if (signal.aborted) {
214184
+ resolve();
214185
+ return;
214186
+ }
214187
+ signal.addEventListener("abort", () => {
214188
+ resolve();
214189
+ }, { once: true });
214190
+ });
214191
+ }
214192
+ var WAIT_FOR_NATIVE_DESCRIPTION, ENGINE_NATIVE_TOOLS, WAIT_FOR_NATIVE_PARAMETERS, IRustEngineTurnRunner, RETRYABLE_ERROR_CODES, TERMINAL_OUTPUT_TAIL_CHARS, NOTIFICATION_OUTPUT_PREVIEW_BYTES, RustEngineTurnRunner;
214193
+ var init_rustEngineTurnRunner = __esmMin((() => {
214194
+ init_src$7();
214195
+ init_xml_escape();
214196
+ init_contextMemory();
214197
+ init_permissionMode();
214198
+ init_toolRegistry();
214199
+ init_toolPolicy();
214200
+ init_permissionRules();
214201
+ init_engineTaskAdapter();
214202
+ init_notificationXml();
214203
+ init_taskOps();
214204
+ init_task$5();
214205
+ init_configSection$12();
214206
+ init_turnOps();
214207
+ init_eventBus();
214208
+ init_config$5();
214209
+ init_log();
214210
+ init_canonical_args();
214211
+ init_errors$11();
214212
+ init_externalHooksRunner();
214213
+ init_toolResultTruncation();
214214
+ init_approval$4();
214215
+ init_usage();
214216
+ init_profile();
214217
+ init_scopeContext();
214218
+ init_catalog$1();
214219
+ init_providerRuntime();
214220
+ init_sessionContext();
214221
+ init_sessionMetadata();
214222
+ init_usage$1();
214223
+ init_wire();
214224
+ init_compactionHandoff();
214225
+ init_compactionOps();
214226
+ init_completion();
214227
+ init_decorateParam();
214228
+ init_decorate();
214229
+ init_instantiation();
214230
+ init_scope();
214231
+ WAIT_FOR_NATIVE_DESCRIPTION = [
214232
+ "Wait for a background subagent task to finish (or time out).",
214233
+ "Pass the `agent_id` of a subagent launched by the Agent tool (from its launch output).",
214234
+ "The call blocks until that subagent completes, fails, or the timeout expires, then returns its final status and output.",
214235
+ "This waits on a specific subagent task, NOT on the user: user-wait (waking on user notifications) is not implemented.",
214236
+ "Prefer AgentOutput for a quick status check; use WaitFor when the turn should pause until the subagent finishes."
214237
+ ].join(" ");
214238
+ ENGINE_NATIVE_TOOLS = new Set([
214239
+ "Bash",
214240
+ "Agent",
214241
+ "AgentOutput",
214242
+ "WaitFor"
214243
+ ]);
214244
+ WAIT_FOR_NATIVE_PARAMETERS = {
214245
+ type: "object",
214246
+ properties: {
214247
+ agent_id: {
214248
+ type: "string",
214249
+ description: "Agent id of the running subagent task to wait for, as returned by the Agent tool launch output."
214250
+ },
214251
+ timeout_seconds: {
214252
+ type: "integer",
214253
+ minimum: 1,
214254
+ maximum: 1800,
214255
+ description: "How long to wait for the subagent before giving up. Defaults to 60; maximum 1800."
214256
+ }
214257
+ },
214258
+ required: ["agent_id"],
214259
+ additionalProperties: false
214260
+ };
214261
+ IRustEngineTurnRunner = createDecorator("rustEngineTurnRunner");
214262
+ RETRYABLE_ERROR_CODES = new Set([
214263
+ "provider.rate_limit",
214264
+ "provider.connection_error",
214265
+ "provider.overloaded",
214266
+ "context.overflow"
214267
+ ]);
214268
+ TERMINAL_OUTPUT_TAIL_CHARS = 4 * 1024;
214269
+ NOTIFICATION_OUTPUT_PREVIEW_BYTES = 3e3;
214270
+ RustEngineTurnRunner = class RustEngineTurnRunner {
214271
+ context;
214272
+ eventBus;
214273
+ wire;
214274
+ config;
214275
+ modeService;
214276
+ rulesService;
214277
+ toolRegistry;
214278
+ usageService;
214279
+ profile;
214280
+ scopeContext;
214281
+ sessionContext;
214282
+ sessionMetadata;
214283
+ tasks;
214284
+ modelCatalog;
214285
+ providerRuntime;
214286
+ instantiation;
214287
+ toolPolicy;
214288
+ log;
214289
+ /** The in-flight Rust session, while a turn is running (steer/cancel target). */
214290
+ activeSession;
214291
+ /** FIFO queue of turns waiting behind the running one (TS loop queue
214292
+ * parity): prompts during an active turn wait for it to finish. */
214293
+ queued = [];
214294
+ /** Whether a turn is currently executing (queue gate). */
214295
+ turnRunning = false;
214296
+ /** turnId of the executing turn (cancel validation). */
214297
+ executingTurnId;
214298
+ /** Aborts the in-flight approval wait on cancel. */
214299
+ approvalAbort;
214300
+ /**
214301
+ * Rust-side task registry mirror: taskId → launch facts (description /
214302
+ * startedAt / pid) recorded from `task.started`, used to build the
214303
+ * terminal `task.terminated` info from `task.settled`. Entries are removed
214304
+ * once the settle is handled (a task settles exactly once), so the map
214305
+ * stays bounded by the number of tasks in flight.
214306
+ */
214307
+ taskInfos = /* @__PURE__ */ new Map();
214308
+ /** Notification dedupe (TS `deliveredNotificationKeys` parity): guards a
214309
+ * single settle from being delivered twice. Keys are removed after the
214310
+ * delivery completes — a settled task never settles again — so the set
214311
+ * stays bounded; the mid-turn+idle double-delivery guard is the
214312
+ * single-call structure of `deliverTaskNotification`, not this set. */
214313
+ deliveredNotificationKeys = /* @__PURE__ */ new Set();
214314
+ /** Registry entries for engine background tasks (TaskList/TaskStop parity):
214315
+ * taskId → adapter registered with `IAgentTaskService`. Removed once the
214316
+ * task settles (a task settles exactly once), so the map stays bounded. */
214317
+ engineTaskAdapters = /* @__PURE__ */ new Map();
214318
+ /** Owning RustTurnSession per engine task id (F1): recorded at
214319
+ * `task.started` from the session whose EventSink delivered the event.
214320
+ * Tasks launched from background workers (a subagent spawning a
214321
+ * sub-subagent, or a subagent launching a backgrounded bash) emit their
214322
+ * start AFTER the launching turn ended, when `activeSession` is undefined
214323
+ * or a newer turn's session — and the engine's per-task cancel signal
214324
+ * lives on the OWNING session's task map (`RustTurnSession::new` creates
214325
+ * a fresh map per turn), so TaskStop must cancel through this session.
214326
+ * Removed once the task settles, so the map stays bounded. */
214327
+ taskSessions = /* @__PURE__ */ new Map();
214328
+ /** Every subagent agent id this runner has handed out (the engine's
214329
+ * `agent-<n>` ids across turns): the next-turn seed must continue past
214330
+ * them so ids stay monotonic within the session (TS
214331
+ * `nextAvailableAgentId` parity). */
214332
+ engineAgentIds = /* @__PURE__ */ new Set();
214333
+ /** Every RustTurnSession this runner created. Held until the agent scope
214334
+ * is disposed so a session's EventSink stays open while its background
214335
+ * tasks are still settling (a subagent launched in turn 1 must be able to
214336
+ * notify after turn 1 ends), and so teardown can close them all at once
214337
+ * (TS `taskService.dispose` parity). */
214338
+ sessions = /* @__PURE__ */ new Set();
214339
+ /** Whether the agent scope was disposed: late engine events (background
214340
+ * task settles racing the teardown) are ignored instead of dispatching
214341
+ * wire ops on a disposed wire / appending to a disposed context. */
214342
+ disposed = false;
214343
+ constructor(context, eventBus, wire, config, modeService, rulesService, toolRegistry, usageService, profile, scopeContext, sessionContext, sessionMetadata, tasks, modelCatalog, providerRuntime, instantiation, toolPolicy, log) {
214344
+ this.context = context;
214345
+ this.eventBus = eventBus;
214346
+ this.wire = wire;
214347
+ this.config = config;
214348
+ this.modeService = modeService;
214349
+ this.rulesService = rulesService;
214350
+ this.toolRegistry = toolRegistry;
214351
+ this.usageService = usageService;
214352
+ this.profile = profile;
214353
+ this.scopeContext = scopeContext;
214354
+ this.sessionContext = sessionContext;
214355
+ this.sessionMetadata = sessionMetadata;
214356
+ this.tasks = tasks;
214357
+ this.modelCatalog = modelCatalog;
214358
+ this.providerRuntime = providerRuntime;
214359
+ this.instantiation = instantiation;
214360
+ this.toolPolicy = toolPolicy;
214361
+ this.log = log;
214362
+ this.registryId = randomUUID();
214363
+ }
214364
+ /** Agent-scoped Rust subagent registry id (see constructor). */
214365
+ registryId;
214366
+ /** TS `stopHookContinuationUsed` parity: the Stop hook fires at most once
214367
+ * after a continuation was delivered. */
214368
+ stopHookContinuationUsed = false;
214369
+ static isEnabled() {
214370
+ return rustEngineEnabled();
214371
+ }
214372
+ /**
214373
+ * Steer the running turn. Mirrors the TS steer path (`steerTurn` op +
214374
+ * user message in the context) and forwards the text into the engine's
214375
+ * steer queue, where it is drained into the next LLM request. Returns
214376
+ * `false` when there is no steerable turn: either no session is active
214377
+ * (the caller starts a new turn) or the engine's turn already finished
214378
+ * (a steer racing the teardown — between the engine's final steer check
214379
+ * and this runner clearing `activeSession` — would land in a queue that
214380
+ * is never drained again; the caller's `runTurn` fallback queues it as
214381
+ * the next turn instead, so it is never lost).
214382
+ */
214383
+ steer(payload) {
214384
+ if (this.disposed) return false;
214385
+ const session = this.activeSession;
214386
+ if (session === void 0) return false;
214387
+ const text = payload.input.filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
214388
+ if (text.length === 0) return false;
214389
+ if (!session.steer(text)) return false;
214390
+ this.wire.dispatch(steerTurn({
214391
+ input: [...payload.input],
214392
+ origin: payload.origin
214393
+ }));
214394
+ this.context.append({
214395
+ role: "user",
214396
+ content: [...payload.input],
214397
+ toolCalls: [],
214398
+ origin: payload.origin,
214399
+ id: randomUUID()
214400
+ });
214401
+ return true;
214402
+ }
214403
+ /**
214404
+ * Run one turn through the Rust engine. The turn clock advances at enqueue
214405
+ * time (TS parity); the turn itself either starts immediately (resolves
214406
+ * with its id) or waits behind the running turn (resolves `undefined`,
214407
+ * like TS `state === 'pending'`).
214408
+ */
214409
+ async runTurn(payload) {
214410
+ if (this.disposed) return void 0;
214411
+ this.wire.dispatch(promptTurn({
214412
+ input: [...payload.input],
214413
+ origin: payload.origin
214414
+ }));
214415
+ const turnId = this.wire.getModel(TurnModel).nextTurnId - 1;
214416
+ const userMessage = {
214417
+ role: "user",
214418
+ content: [...payload.input],
214419
+ toolCalls: [],
214420
+ origin: payload.origin,
214421
+ id: randomUUID()
214422
+ };
214423
+ this.context.append(userMessage);
214424
+ if (this.turnRunning) {
214425
+ this.queued.push({
214426
+ turnId,
214427
+ payload,
214428
+ cancelled: false
214429
+ });
214430
+ return;
214431
+ }
214432
+ this.startQueuedTurn({
214433
+ turnId,
214434
+ payload,
214435
+ cancelled: false
214436
+ });
214437
+ return { turnId };
214438
+ }
214439
+ /** Cancel the active turn; with `turnId`, also cancels a queued turn. */
214440
+ cancel(turnId) {
214441
+ const session = this.activeSession;
214442
+ if (session !== void 0 && (turnId === void 0 || turnId === this.executingTurnId)) {
214443
+ this.approvalAbort?.abort();
214444
+ session.cancel();
214445
+ return true;
214446
+ }
214447
+ let cancelled = false;
214448
+ for (const entry of this.queued) if (entry.turnId === turnId) {
214449
+ entry.cancelled = true;
214450
+ this.wire.dispatch(cancelTurn({ turnId }));
214451
+ cancelled = true;
214452
+ }
214453
+ return cancelled;
214454
+ }
214455
+ /**
214456
+ * Agent-scope teardown (TS `taskService.dispose` parity): every Rust
214457
+ * session is closed — its EventSink stops forwarding, the in-flight turn
214458
+ * is cancelled, and background workers/pollers observe the closed flag and
214459
+ * kill their processes — and late engine events are ignored instead of
214460
+ * dispatching wire ops on a disposed wire. The DI container invokes this
214461
+ * when the agent scope is disposed.
214462
+ */
214463
+ dispose() {
214464
+ this.disposed = true;
214465
+ this.approvalAbort?.abort();
214466
+ for (const session of this.sessions) session.close();
214467
+ this.sessions.clear();
214468
+ this.taskSessions.clear();
214469
+ this.engineTaskAdapters.clear();
214470
+ RustTurnSession.dropTaskRegistry(this.registryId);
214471
+ }
214472
+ startQueuedTurn(entry) {
214473
+ this.turnRunning = true;
214474
+ this.executingTurnId = entry.turnId;
214475
+ this.runTurnNow(entry.turnId, entry.payload.origin).catch(() => void 0).finally(() => {
214476
+ this.turnRunning = false;
214477
+ this.executingTurnId = void 0;
214478
+ if (this.disposed) {
214479
+ this.queued.length = 0;
214480
+ return;
214481
+ }
214482
+ const next = this.queued.find((queued) => !queued.cancelled);
214483
+ if (next === void 0) return;
214484
+ this.queued.splice(this.queued.indexOf(next), 1);
214485
+ this.startQueuedTurn(next);
214486
+ });
214487
+ }
214488
+ async runTurnNow(turnId, origin) {
214489
+ if (this.disposed) return { turnId };
214490
+ const messages = [{
214491
+ role: "system",
214492
+ content: this.profile.getSystemPrompt()
214493
+ }, ...this.context.get().map((message) => this.toLlmMessage(message))];
214494
+ const provider = await this.providerConfig();
214495
+ const inputJson = JSON.stringify({
214496
+ turnId,
214497
+ origin: toEngineTurnOrigin(origin),
214498
+ usesWorkerRejectionGuidance: this.scopeContext.agentId !== "main",
214499
+ activeTools: this.effectiveActiveTools() ?? null,
214500
+ messages,
214501
+ tools: [],
214502
+ provider,
214503
+ maxStepsPerTurn: this.maxStepsPerTurn() ?? null,
214504
+ maxRetriesPerStep: this.maxRetriesPerStep() ?? null,
214505
+ maxContextTokens: this.maxContextTokens() ?? null,
214506
+ nextAgentId: await this.computeNextAgentId(),
214507
+ killGraceMs: this.killGracePeriodMs(),
214508
+ completionReview: {
214509
+ minSteps: 10,
214510
+ reminder: completion_review_default
214511
+ },
214512
+ cwd: this.profile.data().cwd ?? process.cwd()
214513
+ });
214514
+ const policyJson = JSON.stringify({
214515
+ mode: this.modeService.mode,
214516
+ rules: this.rulesService.rules,
214517
+ sessionApprovedPatterns: this.rulesService.sessionApprovalRulePatterns
214518
+ });
214519
+ const scripted = process.env["DIMI_RUST_ENGINE_SCRIPTED"];
214520
+ const session = new RustTurnSession(inputJson, policyJson, scripted ?? void 0, this.registryId);
214521
+ this.sessions.add(session);
214522
+ this.activeSession = session;
214523
+ session.setToolGate((payloadJson) => {
214524
+ (async () => {
214525
+ const payload = JSON.parse(payloadJson);
214526
+ let verdict = { decision: "allow" };
214527
+ try {
214528
+ const hooksRunner = this.instantiation.invokeFunction((accessor) => accessor.get(IExternalHooksRunnerService));
214529
+ if (hooksRunner !== void 0 && ENGINE_NATIVE_TOOLS.has(payload.toolName)) {
214530
+ const block = await hooksRunner.triggerBlock("PreToolUse", {
214531
+ matcherValue: payload.toolName,
214532
+ signal: new AbortController().signal,
214533
+ sessionId: this.sessionContext.sessionId,
214534
+ inputData: {
214535
+ toolName: payload.toolName,
214536
+ toolInput: isPlainRecord(payload.arguments) ? payload.arguments : {},
214537
+ toolCallId: `native-${payload.toolName}`
214538
+ }
214539
+ });
214540
+ if (block !== void 0) verdict = {
214541
+ decision: "block",
214542
+ reason: block.reason
214543
+ };
214544
+ }
214545
+ } catch {}
214546
+ session.completeToolGate(payload.requestId, JSON.stringify(verdict));
214547
+ })();
214548
+ });
214549
+ try {
214550
+ await this.runEngineSession(session, turnId, provider["model"], origin);
214551
+ } finally {
214552
+ this.activeSession = void 0;
214553
+ }
214554
+ return { turnId };
214555
+ }
214556
+ /**
214557
+ * Run one engine session to completion: register the TS tool ecosystem,
214558
+ * drive the approval loop, mirror the engine events into the context.
214559
+ */
214560
+ async runEngineSession(session, turnId, providerModel, origin) {
214561
+ const engineNativeTools = ENGINE_NATIVE_TOOLS;
214562
+ const activeSet = this.effectiveActiveTools();
214563
+ const isToolActiveForRunner = (name) => activeSet === void 0 || activeSet.includes(name);
214564
+ for (const info of this.toolRegistry.list()) {
214565
+ if (!isToolActiveForRunner(info.name)) continue;
214566
+ try {
214567
+ if (engineNativeTools.has(info.name)) {
214568
+ if (info.name !== "Bash") if (info.name === "WaitFor") session.registerNativeToolDef(info.name, WAIT_FOR_NATIVE_DESCRIPTION, JSON.stringify(WAIT_FOR_NATIVE_PARAMETERS));
214569
+ else session.registerNativeToolDef(info.name, info.description, JSON.stringify(info.parameters ?? {
214570
+ type: "object",
214571
+ properties: {}
214572
+ }));
214573
+ continue;
214574
+ }
214575
+ const tool = this.toolRegistry.resolve(info.name);
214576
+ if (tool === void 0) continue;
214577
+ session.registerExternalTool(info.name, info.description, JSON.stringify(info.parameters ?? {
214578
+ type: "object",
214579
+ properties: {}
214580
+ }), (payloadJson) => {
214581
+ (async () => {
214582
+ const payload = JSON.parse(payloadJson);
214583
+ try {
214584
+ const toolCalls = (payload.toolCalls ?? []).map((call) => ({
214585
+ type: "function",
214586
+ id: call.id ?? payload.toolCallId ?? payload.requestId,
214587
+ name: call.name,
214588
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments) ?? "null"
214589
+ }));
214590
+ const execution = await tool.resolveExecution(payload.arguments, { toolCalls });
214591
+ if (execution.isError === true) {
214592
+ session.completeToolCall(payload.requestId, JSON.stringify({
214593
+ toolCallId: payload.toolCallId ?? payload.requestId,
214594
+ toolName: payload.name,
214595
+ output: toText(execution.output),
214596
+ isError: true,
214597
+ stopTurn: execution.stopTurn === true,
214598
+ updates: []
214599
+ }));
214600
+ return;
214601
+ }
214602
+ const signal = new AbortController().signal;
214603
+ const hooksRunner = this.instantiation.invokeFunction((accessor) => accessor.get(IExternalHooksRunnerService));
214604
+ const toolInput = isPlainRecord(payload.arguments) ? payload.arguments : {};
214605
+ const toolCallId = payload.toolCallId ?? payload.requestId;
214606
+ if (hooksRunner !== void 0) {
214607
+ const block = await hooksRunner.triggerBlock("PreToolUse", {
214608
+ matcherValue: payload.name,
214609
+ signal,
214610
+ sessionId: this.sessionContext.sessionId,
214611
+ inputData: {
214612
+ toolName: payload.name,
214613
+ toolInput,
214614
+ toolCallId
214615
+ }
214616
+ });
214617
+ if (block !== void 0) {
214618
+ session.completeToolCall(payload.requestId, JSON.stringify({
214619
+ toolCallId,
214620
+ toolName: payload.name,
214621
+ output: block.reason,
214622
+ isError: true,
214623
+ stopTurn: false,
214624
+ updates: []
214625
+ }));
214626
+ return;
214627
+ }
214628
+ }
214629
+ const result = await execution.execute({
214630
+ turnId: 0,
214631
+ toolCallId,
214632
+ signal,
214633
+ onUpdate: (update) => {
214634
+ if (signal.aborted) return;
214635
+ this.eventBus.publish({
214636
+ type: "tool.progress",
214637
+ turnId,
214638
+ toolCallId,
214639
+ update
214640
+ });
214641
+ }
214642
+ });
214643
+ let finalResult = result;
214644
+ const truncation = this.instantiation.invokeFunction((accessor) => accessor.get(IAgentToolResultTruncationService));
214645
+ if (truncation !== void 0) try {
214646
+ finalResult = await truncation.truncateForModel({
214647
+ toolName: payload.name,
214648
+ toolCallId,
214649
+ result
214650
+ });
214651
+ } catch {}
214652
+ session.completeToolCall(payload.requestId, JSON.stringify({
214653
+ toolCallId,
214654
+ toolName: payload.name,
214655
+ output: toText(finalResult.output),
214656
+ isError: finalResult.isError === true,
214657
+ stopTurn: finalResult.stopTurn === true,
214658
+ updates: []
214659
+ }));
214660
+ if (hooksRunner !== void 0) {
214661
+ const outputText = toText(finalResult.output);
214662
+ const isError = finalResult.isError === true;
214663
+ hooksRunner.fireAndForgetTrigger(isError ? "PostToolUseFailure" : "PostToolUse", {
214664
+ matcherValue: payload.name,
214665
+ signal,
214666
+ sessionId: this.sessionContext.sessionId,
214667
+ inputData: {
214668
+ toolName: payload.name,
214669
+ toolInput,
214670
+ toolCallId,
214671
+ error: isError ? toDimiErrorPayload(outputText) : void 0,
214672
+ toolOutput: isError ? void 0 : outputText.slice(0, 2e3)
214673
+ }
214674
+ });
214675
+ }
214676
+ } catch (error) {
214677
+ session.completeToolCall(payload.requestId, JSON.stringify({
214678
+ toolCallId: payload.toolCallId ?? payload.requestId,
214679
+ toolName: payload.name,
214680
+ output: `Tool "${payload.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
214681
+ isError: true,
214682
+ stopTurn: false,
214683
+ updates: []
214684
+ }));
214685
+ }
214686
+ })();
214687
+ });
214688
+ } catch (error) {
214689
+ if (engineNativeTools.has(info.name) && info.name !== "Bash") {
214690
+ this.log.error("[rustEngineTurnRunner] failed to register native tool def", {
214691
+ name: info.name,
214692
+ error
214693
+ });
214694
+ this.eventBus.publish({
214695
+ type: "turn.ended",
214696
+ turnId,
214697
+ reason: "failed",
214698
+ error: {
214699
+ name: "ToolRegistrationError",
214700
+ message: `Failed to register native tool def "${info.name}"`
214701
+ }
214702
+ });
214703
+ this.eventBus.publish({
214704
+ type: "error",
214705
+ name: "ToolRegistrationError",
214706
+ message: `Failed to register native tool def "${info.name}"`
214707
+ });
214708
+ throw error;
214709
+ }
214710
+ this.log.error("[rustEngineTurnRunner] failed to register external tool", {
214711
+ name: info.name,
214712
+ error
214713
+ });
214714
+ }
214715
+ }
214716
+ let stepUuid;
214717
+ const segments = [];
214718
+ let stepSawToolCall = false;
214719
+ const nativeToolCalls = /* @__PURE__ */ new Map();
214720
+ let usage = emptyUsage();
214721
+ const publish = (event) => {
214722
+ const { type, ...rest } = event;
214723
+ this.eventBus.publish({
214724
+ type,
214725
+ ...rest
214726
+ });
214727
+ };
214728
+ const flushParts = (turnId, step) => {
214729
+ for (const segment of segments.splice(0)) {
214730
+ const part = segment.type === "think" ? {
214731
+ type: "think",
214732
+ think: segment.text
214733
+ } : {
214734
+ type: "text",
214735
+ text: segment.text
214736
+ };
214737
+ this.context.appendLoopEvent({
214738
+ type: "content.part",
214739
+ stepUuid,
214740
+ part,
214741
+ uuid: randomUUID(),
214742
+ turnId: String(turnId),
214743
+ step
214744
+ });
214745
+ }
214746
+ };
214747
+ const handleEngineEvent = (event) => {
214748
+ if (this.disposed) return;
214749
+ if (event["type"] === "task.started") {
214750
+ this.taskSessions.set(toText(event["taskId"]), session);
214751
+ this.handleTaskStarted(event);
214752
+ return;
214753
+ }
214754
+ if (event["type"] === "task.output") {
214755
+ this.handleTaskOutput(event);
214756
+ return;
214757
+ }
214758
+ if (event["type"] === "task.settled") {
214759
+ this.handleTaskSettled(event);
214760
+ return;
214761
+ }
214762
+ if (event["type"] === "completion.review.injected") {
214763
+ this.context.append({
214764
+ role: "user",
214765
+ content: [{
214766
+ type: "text",
214767
+ text: wrapSystemReminder(toText(event["reminder"]))
214768
+ }],
214769
+ toolCalls: [],
214770
+ origin: {
214771
+ kind: "system_trigger",
214772
+ name: "completion_review"
214773
+ },
214774
+ id: randomUUID()
214775
+ });
214776
+ return;
214777
+ }
214778
+ const busEvent = { ...event };
214779
+ if (event["type"] === "turn.step.completed") {
214780
+ const engineUsage = event["usage"];
214781
+ if (engineUsage !== void 0) {
214782
+ const inputCacheRead = engineUsage.cachedTokens ?? 0;
214783
+ busEvent["usage"] = {
214784
+ inputOther: Math.max((engineUsage.inputTokens ?? 0) - inputCacheRead, 0),
214785
+ output: engineUsage.outputTokens ?? 0,
214786
+ inputCacheRead,
214787
+ inputCacheCreation: 0
214788
+ };
214789
+ } else busEvent["usage"] = emptyUsage();
214790
+ } else if (event["type"] === "turn.ended" && event["reason"] === "failed") {
214791
+ const rawError = event["error"];
214792
+ if (rawError !== void 0) busEvent["error"] = buildErrorPayload(rawError);
214793
+ } else if (event["type"] === "turn.started" && !isDisplayablePromptOrigin(origin)) delete busEvent["prompt"];
214794
+ publish(busEvent);
214795
+ switch (event["type"]) {
214796
+ case "turn.step.started": {
214797
+ const stepNumber = Number(event["step"] ?? 1);
214798
+ stepUuid = randomUUID();
214799
+ stepSawToolCall = false;
214800
+ this.context.appendLoopEvent({
214801
+ type: "step.begin",
214802
+ uuid: stepUuid,
214803
+ turnId: String(turnId),
214804
+ step: stepNumber
214805
+ });
214806
+ break;
214807
+ }
214808
+ case "thinking.delta": {
214809
+ const last = segments[segments.length - 1];
214810
+ if (last?.type === "think") last.text += toText(event["delta"]);
214811
+ else segments.push({
214812
+ type: "think",
214813
+ text: toText(event["delta"])
214814
+ });
214815
+ break;
214816
+ }
214817
+ case "assistant.delta": {
214818
+ const last = segments[segments.length - 1];
214819
+ if (last?.type === "text") last.text += toText(event["delta"]);
214820
+ else segments.push({
214821
+ type: "text",
214822
+ text: toText(event["delta"])
214823
+ });
214824
+ break;
214825
+ }
214826
+ case "tool.call.delta": break;
214827
+ case "tool.call.started": {
214828
+ const id = toText(event["toolCallId"]);
214829
+ const name = toText(event["name"]);
214830
+ stepSawToolCall = true;
214831
+ if (ENGINE_NATIVE_TOOLS.has(name)) nativeToolCalls.set(id, {
214832
+ name,
214833
+ input: event["args"]
214834
+ });
214835
+ this.context.appendLoopEvent({
214836
+ type: "tool.call",
214837
+ stepUuid,
214838
+ toolCallId: id,
214839
+ name,
214840
+ args: event["args"],
214841
+ uuid: randomUUID(),
214842
+ turnId: String(turnId),
214843
+ step: Number(event["step"] ?? 1)
214844
+ });
214845
+ break;
214846
+ }
214847
+ case "tool.result": {
214848
+ const id = toText(event["toolCallId"]);
214849
+ const output = toText(event["output"]);
214850
+ const isError = event["isError"] === true;
214851
+ const native = nativeToolCalls.get(id);
214852
+ if (native !== void 0) {
214853
+ nativeToolCalls.delete(id);
214854
+ const hooksRunner = this.instantiation.invokeFunction((accessor) => accessor.get(IExternalHooksRunnerService));
214855
+ if (hooksRunner !== void 0) hooksRunner.fireAndForgetTrigger(isError ? "PostToolUseFailure" : "PostToolUse", {
214856
+ matcherValue: native.name,
214857
+ signal: new AbortController().signal,
214858
+ sessionId: this.sessionContext.sessionId,
214859
+ inputData: {
214860
+ toolName: native.name,
214861
+ toolInput: isPlainRecord(native.input) ? native.input : {},
214862
+ toolCallId: id,
214863
+ error: isError ? toDimiErrorPayload(output) : void 0,
214864
+ toolOutput: isError ? void 0 : output.slice(0, 2e3)
214865
+ }
214866
+ });
214867
+ }
214868
+ this.context.appendLoopEvent({
214869
+ type: "tool.result",
214870
+ toolCallId: id,
214871
+ result: {
214872
+ output,
214873
+ isError
214874
+ },
214875
+ parentUuid: stepUuid
214876
+ });
214877
+ break;
214878
+ }
214879
+ case "context.compacted": {
214880
+ const summary = toText(event["summary"]);
214881
+ const tokensBefore = Number(event["tokensBefore"] ?? 0);
214882
+ const compactedCount = Number(event["compactedCount"] ?? 0);
214883
+ this.wire.dispatch(fullCompactionBegin({ source: "auto" }));
214884
+ const result = this.context.applyCompaction({
214885
+ summary,
214886
+ contextSummary: buildCompactionSummaryText(summary),
214887
+ compactedCount,
214888
+ tokensBefore
214889
+ });
214890
+ this.wire.dispatch(fullCompactionComplete({}));
214891
+ this.eventBus.publish({
214892
+ type: "compaction.completed",
214893
+ result
214894
+ });
214895
+ break;
214896
+ }
214897
+ case "turn.step.interrupted":
214898
+ if (stepSawToolCall) flushParts(turnId, Number(event["step"] ?? 1));
214899
+ break;
214900
+ case "turn.step.completed": {
214901
+ const stepFinish = toText(event["finishReason"] ?? "end_turn");
214902
+ const stepNumber = Number(event["step"] ?? 1);
214903
+ flushParts(turnId, stepNumber);
214904
+ const engineUsage = event["usage"];
214905
+ if (engineUsage !== void 0) {
214906
+ const inputCacheRead = engineUsage.cachedTokens ?? 0;
214907
+ usage = {
214908
+ inputOther: Math.max((engineUsage.inputTokens ?? 0) - inputCacheRead, 0),
214909
+ output: engineUsage.outputTokens ?? 0,
214910
+ inputCacheRead,
214911
+ inputCacheCreation: 0
214912
+ };
214913
+ } else usage = emptyUsage();
214914
+ this.usageService.record(providerModel, usage, {
214915
+ kind: "loop",
214916
+ turnId: String(turnId),
214917
+ step: stepNumber
214918
+ });
214919
+ this.context.appendLoopEvent({
214920
+ type: "step.end",
214921
+ uuid: stepUuid,
214922
+ turnId: String(turnId),
214923
+ step: stepNumber,
214924
+ finishReason: stepFinish,
214925
+ usage
214926
+ });
214927
+ if (stepFinish !== "tool_use" && stepFinish !== "filtered") this.runStopHook(turnId);
214928
+ break;
214929
+ }
214930
+ case "turn.ended":
214931
+ this.stopHookContinuationUsed = false;
214932
+ if (event["reason"] === "failed" && event["error"] !== void 0) this.eventBus.publish({
214933
+ type: "error",
214934
+ ...buildErrorPayload(event["error"])
214935
+ });
214936
+ break;
214937
+ default: break;
214938
+ }
214939
+ };
214940
+ session.setOnEvent((eventJson) => {
214941
+ try {
214942
+ handleEngineEvent(JSON.parse(eventJson));
214943
+ } catch (error) {
214944
+ this.log.error("[rustEngineTurnRunner] failed to process engine event", { error });
214945
+ }
214946
+ });
214947
+ let progress = JSON.parse(await session.run());
214948
+ while (progress.progress.status === "needsApproval") {
214949
+ const approval = progress.progress.approval;
214950
+ const approvalRequest = {
214951
+ sessionId: this.sessionContext.sessionId,
214952
+ agentId: this.scopeContext.agentId,
214953
+ turnId,
214954
+ toolCallId: approval.toolCallId,
214955
+ toolName: approval.toolName,
214956
+ action: `Approve ${approval.toolName}`,
214957
+ display: {
214958
+ kind: "generic",
214959
+ summary: `Approve ${approval.toolName}`,
214960
+ detail: approval.toolInput
214961
+ }
214962
+ };
214963
+ this.eventBus.publish({
214964
+ type: "permission.approval.requested",
214965
+ ...approvalRequest
214966
+ });
214967
+ let response = { decision: "approved" };
214968
+ const approvalController = new AbortController();
214969
+ this.approvalAbort = approvalController;
214970
+ try {
214971
+ const approvalService = this.instantiation.invokeFunction((accessor) => accessor.get(ISessionApprovalService));
214972
+ response = approvalService !== void 0 ? await Promise.race([approvalService.request(approvalRequest), abortOnSignal(approvalController.signal).then(() => ({ decision: "cancelled" }))]) : { decision: "approved" };
214973
+ } catch {
214974
+ response = { decision: "rejected" };
214975
+ } finally {
214976
+ this.approvalAbort = void 0;
214977
+ }
214978
+ this.eventBus.publish({
214979
+ type: "permission.approval.resolved",
214980
+ ...approvalRequest,
214981
+ decision: response.decision
214982
+ });
214983
+ this.rulesService.recordApprovalResult({
214984
+ turnId,
214985
+ toolCallId: approvalRequest.toolCallId,
214986
+ toolName: approvalRequest.toolName,
214987
+ action: approvalRequest.action,
214988
+ sessionApprovalRule: response.decision === "approved" && response.scope === "session" ? approvalRequest.toolName : void 0,
214989
+ result: response
214990
+ });
214991
+ if (response.decision === "approved" && response.scope === "session") session.addSessionApproval(approvalRequest.toolName);
214992
+ progress = JSON.parse(await session.resume(JSON.stringify(response)));
214993
+ }
214994
+ }
214995
+ /**
214996
+ * TS `externalHooksService.runStop` parity (P1-3): after a non-tool step,
214997
+ * fire the Stop hook; a returned reason becomes a continuation message
214998
+ * (origin `system_trigger`/`stop_hook`) that keeps the turn alive —
214999
+ * steered into the running turn, or launched as a fresh turn when the
215000
+ * engine already finished (the task-notification fallback). Fires at most
215001
+ * once after a continuation was used, mirroring `stopHookContinuationUsed`.
215002
+ */
215003
+ runStopHook(turnId) {
215004
+ if (this.stopHookContinuationUsed) return;
215005
+ this.stopHookContinuationUsed = true;
215006
+ const hooksRunner = this.instantiation.invokeFunction((accessor) => accessor.get(IExternalHooksRunnerService));
215007
+ if (hooksRunner === void 0) return;
215008
+ (async () => {
215009
+ try {
215010
+ const reason = (await hooksRunner.triggerBlock("Stop", {
215011
+ signal: new AbortController().signal,
215012
+ sessionId: this.sessionContext.sessionId,
215013
+ inputData: { stopHookActive: false }
215014
+ }))?.reason;
215015
+ if (reason === void 0 || reason.length === 0) return;
215016
+ const origin = {
215017
+ kind: "system_trigger",
215018
+ name: "stop_hook"
215019
+ };
215020
+ if (this.turnRunning && this.activeSession !== void 0 && this.activeSession.steer(reason)) {
215021
+ this.context.append({
215022
+ role: "user",
215023
+ content: [{
215024
+ type: "text",
215025
+ text: reason
215026
+ }],
215027
+ toolCalls: [],
215028
+ origin,
215029
+ id: randomUUID()
215030
+ });
215031
+ this.wire.dispatch(steerTurn({
215032
+ input: [{
215033
+ type: "text",
215034
+ text: reason
215035
+ }],
215036
+ origin
215037
+ }));
215038
+ } else this.runTurn({
215039
+ input: [{
215040
+ type: "text",
215041
+ text: reason
215042
+ }],
215043
+ origin
215044
+ }).catch(() => void 0);
215045
+ } catch {}
215046
+ })();
215047
+ }
215048
+ /**
215049
+ * `task.started` (engine transport) → TS records: the persisted
215050
+ * `task.started` wire op (TaskModel / transcript / TaskList) plus the
215051
+ * `subagent.spawned` session event for subagent launches.
215052
+ */
215053
+ handleTaskStarted(event) {
215054
+ const taskId = toText(event["taskId"]);
215055
+ const agentId = toText(event["agentId"]);
215056
+ const kind = toText(event["kind"]);
215057
+ const description = toText(event["description"]);
215058
+ const startedAt = Date.now();
215059
+ const pid = toOptionalNumber(event["pid"]);
215060
+ this.taskInfos.set(taskId, {
215061
+ description,
215062
+ startedAt,
215063
+ pid
215064
+ });
215065
+ if (kind === "agent" && agentId !== "") this.engineAgentIds.add(agentId);
215066
+ const info = kind === "agent" ? {
215067
+ taskId,
215068
+ kind: "agent",
215069
+ agentId,
215070
+ subagentType: description,
215071
+ description,
215072
+ status: "running",
215073
+ detached: true,
215074
+ startedAt,
215075
+ endedAt: null
215076
+ } : {
215077
+ taskId,
215078
+ kind: "process",
215079
+ command: description,
215080
+ pid: pid ?? 0,
215081
+ exitCode: null,
215082
+ description,
215083
+ status: "running",
215084
+ detached: true,
215085
+ startedAt,
215086
+ endedAt: null
215087
+ };
215088
+ this.wire.dispatch(taskStarted({ info }));
215089
+ this.registerEngineTask(taskId, agentId, kind, description, pid);
215090
+ if (kind === "agent") {
215091
+ this.eventBus.publish({
215092
+ type: "subagent.spawned",
215093
+ subagentId: agentId,
215094
+ subagentName: description,
215095
+ parentToolCallId: toText(event["parentToolCallId"]),
215096
+ parentAgentId: this.scopeContext.agentId,
215097
+ callerAgentId: this.scopeContext.agentId,
215098
+ description,
215099
+ runInBackground: false
215100
+ });
215101
+ this.eventBus.publish({
215102
+ type: "subagent.started",
215103
+ subagentId: agentId
215104
+ });
215105
+ }
215106
+ }
215107
+ /**
215108
+ * `task.output` (engine transport) → the task-service entry's live sink:
215109
+ * append the streamed delta so TaskOutput shows partial output while the
215110
+ * engine task is still running (TS ProcessTask parity — TS streams chunks
215111
+ * as they arrive; the adapter's settle also appends only the not-yet-
215112
+ * streamed tail, so nothing is duplicated). No wire op / bus event: the
215113
+ * live output is the service's retained buffer, the wire `task.terminated`
215114
+ * tail comes from the settle's full output.
215115
+ */
215116
+ handleTaskOutput(event) {
215117
+ const taskId = toText(event["taskId"]);
215118
+ const delta = toText(event["delta"]);
215119
+ if (delta.length === 0) return;
215120
+ const adapter = this.engineTaskAdapters.get(taskId);
215121
+ if (adapter !== void 0) {
215122
+ adapter.appendOutput(delta);
215123
+ return;
215124
+ }
215125
+ this.log.debug("[rustEngineTurnRunner] task.output for unregistered task", { taskId });
215126
+ }
215127
+ /**
215128
+ * Register an engine background task with `IAgentTaskService` (TaskList /
215129
+ * TaskStop / TaskOutput parity). The adapter bridges TaskStop into the
215130
+ * engine's per-task cancel (`session.cancelTask`) — the launching session's
215131
+ * worker/poller kills its work and settles "killed" — and its `start`
215132
+ * streams the settle output into the service's sink so TaskOutput and the
215133
+ * terminal info see it.
215134
+ */
215135
+ registerEngineTask(taskId, agentId, kind, description, pid) {
215136
+ const session = this.taskSessions.get(taskId) ?? this.activeSession;
215137
+ const adapter = new EngineTaskAdapter({
215138
+ taskId,
215139
+ agentId,
215140
+ kind: kind === "agent" ? "agent" : "process",
215141
+ description,
215142
+ pid,
215143
+ subagentType: kind === "agent" ? description : void 0,
215144
+ forceStop: (reason) => {
215145
+ try {
215146
+ session?.cancelTask(taskId, reason);
215147
+ } catch (error) {
215148
+ this.log.error("[rustEngineTurnRunner] failed to cancel engine task", {
215149
+ taskId,
215150
+ error
215151
+ });
215152
+ }
215153
+ }
215154
+ });
215155
+ this.engineTaskAdapters.set(taskId, adapter);
215156
+ try {
215157
+ this.tasks.registerTask(adapter, {
215158
+ taskId,
215159
+ detached: false
215160
+ });
215161
+ } catch (error) {
215162
+ this.log.error("[rustEngineTurnRunner] failed to register engine task", {
215163
+ taskId,
215164
+ error
215165
+ });
215166
+ this.engineTaskAdapters.delete(taskId);
215167
+ }
215168
+ }
215169
+ /**
215170
+ * `task.settled` (engine transport) → TS records: the persisted
215171
+ * `task.terminated` wire op (bounded output tail), the `subagent.completed`
215172
+ * / `subagent.failed` session events, and the completion notification
215173
+ * delivered to the model (context message + `task.notified` + steer/new
215174
+ * turn — TS `activeOrNewTurn` parity).
215175
+ */
215176
+ handleTaskSettled(event) {
215177
+ const taskId = toText(event["taskId"]);
215178
+ const agentId = toText(event["agentId"]);
215179
+ const kind = toText(event["kind"]);
215180
+ const status = toText(event["status"]);
215181
+ const output = toText(event["output"]);
215182
+ const error = event["error"] === void 0 || event["error"] === null ? void 0 : toText(event["error"]);
215183
+ const launch = this.taskInfos.get(taskId);
215184
+ const endedAt = Date.now();
215185
+ const stopReason = status === "failed" || status === "timed_out" || status === "killed" ? error : void 0;
215186
+ const base = {
215187
+ taskId,
215188
+ description: launch?.description ?? "",
215189
+ status,
215190
+ detached: true,
215191
+ startedAt: launch?.startedAt ?? endedAt,
215192
+ endedAt,
215193
+ stopReason
215194
+ };
215195
+ const info = kind === "agent" ? {
215196
+ ...base,
215197
+ kind: "agent",
215198
+ agentId,
215199
+ subagentType: launch?.description ?? ""
215200
+ } : {
215201
+ ...base,
215202
+ kind: "process",
215203
+ command: launch?.description ?? "",
215204
+ pid: launch?.pid ?? 0,
215205
+ exitCode: toOptionalNumber(event["exitCode"]) ?? null
215206
+ };
215207
+ this.wire.dispatch(taskTerminated({
215208
+ info,
215209
+ outputTail: output.slice(-TERMINAL_OUTPUT_TAIL_CHARS)
215210
+ }));
215211
+ if (kind === "agent") {
215212
+ if (status === "completed") this.eventBus.publish({
215213
+ type: "subagent.completed",
215214
+ subagentId: agentId,
215215
+ resultSummary: output
215216
+ });
215217
+ else if (status === "failed") this.eventBus.publish({
215218
+ type: "subagent.failed",
215219
+ subagentId: agentId,
215220
+ error: error ?? status
215221
+ });
215222
+ }
215223
+ const adapter = this.engineTaskAdapters.get(taskId);
215224
+ if (adapter !== void 0) {
215225
+ adapter.complete(output, {
215226
+ status,
215227
+ stopReason
215228
+ }, toOptionalNumber(event["exitCode"]));
215229
+ this.engineTaskAdapters.delete(taskId);
215230
+ }
215231
+ this.taskInfos.delete(taskId);
215232
+ this.taskSessions.delete(taskId);
215233
+ if (this.tasks.getTask(taskId)?.terminalNotificationSuppressed === true) return;
215234
+ this.deliverTaskNotification({
215235
+ taskId,
215236
+ agentId,
215237
+ kind,
215238
+ status,
215239
+ description: launch?.description ?? "",
215240
+ output,
215241
+ error
215242
+ });
215243
+ }
215244
+ /**
215245
+ * Deliver a task completion notification to the model — the Rust-engine
215246
+ * equivalent of the TS task domain's detached-task notification
215247
+ * (`TaskNotificationStepRequest`, `activeOrNewTurn` admission): append the
215248
+ * `<notification>` XML as a task-origin user message, fire the
215249
+ * `task.notified` hook event, then fold it into the running turn's next
215250
+ * step (steer) or launch a notification turn when idle.
215251
+ */
215252
+ deliverTaskNotification(task) {
215253
+ const key = `task:${task.taskId}:${task.status}`;
215254
+ if (this.deliveredNotificationKeys.has(key)) return;
215255
+ this.deliveredNotificationKeys.add(key);
215256
+ const kindLabel = task.kind === "agent" ? "agent" : "process";
215257
+ const notification = {
215258
+ id: key,
215259
+ category: "task",
215260
+ type: `task.${task.status}`,
215261
+ source_kind: "background_task",
215262
+ source_id: task.taskId,
215263
+ agent_id: task.kind === "agent" ? task.agentId : void 0,
215264
+ title: `Background ${kindLabel} ${task.status}`,
215265
+ severity: task.status === "completed" ? "info" : "warning",
215266
+ body: buildTaskNotificationBody(task),
215267
+ children: task.output.length > 0 ? [renderOutputPreviewBlock(task.output)] : void 0
215268
+ };
215269
+ const xml = renderNotificationXml(notification);
215270
+ const origin = {
215271
+ kind: "task",
215272
+ taskId: task.taskId,
215273
+ status: task.status,
215274
+ notificationId: key
215275
+ };
215276
+ try {
215277
+ if (this.turnRunning && this.activeSession !== void 0 && this.activeSession.steer(xml)) {
215278
+ this.context.append({
215279
+ role: "user",
215280
+ content: [{
215281
+ type: "text",
215282
+ text: xml
215283
+ }],
215284
+ toolCalls: [],
215285
+ origin,
215286
+ id: randomUUID()
215287
+ });
215288
+ this.wire.dispatch(steerTurn({
215289
+ input: [{
215290
+ type: "text",
215291
+ text: xml
215292
+ }],
215293
+ origin
215294
+ }));
215295
+ } else this.runTurn({
215296
+ input: [{
215297
+ type: "text",
215298
+ text: xml
215299
+ }],
215300
+ origin
215301
+ }).catch(() => void 0);
215302
+ this.eventBus.publish({
215303
+ type: "task.notified",
215304
+ notificationType: `task.${task.status}`,
215305
+ title: notification.title,
215306
+ body: notification.body,
215307
+ severity: notification.severity,
215308
+ sourceKind: notification.source_kind,
215309
+ sourceId: notification.source_id
215310
+ });
215311
+ } finally {
215312
+ this.deliveredNotificationKeys.delete(key);
215313
+ }
215314
+ }
215315
+ toLlmMessage(message) {
215316
+ const text = message.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
215317
+ const media = message.content.filter((part) => part.type === "image_url" || part.type === "audio_url" || part.type === "video_url").map((part) => {
215318
+ return {
215319
+ type: "media_url",
215320
+ url: part.imageUrl?.url ?? part.audioUrl?.url ?? part.videoUrl?.url
215321
+ };
215322
+ }).filter((part) => part.url !== void 0);
215323
+ const toolCalls = message.toolCalls.map((call) => ({
215324
+ id: call.id,
215325
+ type: "function",
215326
+ function: {
215327
+ name: call.name,
215328
+ arguments: call.arguments
215329
+ }
215330
+ }));
215331
+ return {
215332
+ role: message.role,
215333
+ content: media.length > 0 ? [...media, ...text.length > 0 ? [{
215334
+ type: "text",
215335
+ text
215336
+ }] : []] : text,
215337
+ ...message.role === "assistant" && toolCalls.length > 0 ? { toolCalls } : {},
215338
+ ...message.role === "tool" && message.toolCallId !== void 0 ? { toolCallId: message.toolCallId } : {}
215339
+ };
215340
+ }
215341
+ /**
215342
+ * The next subagent agent-id number to hand to the engine for this turn
215343
+ * (TS `nextAvailableAgentId` parity): the max known `agent-<n>` suffix
215344
+ * among (a) the ids this runner's engine has already handed out across
215345
+ * turns and (b) the session's persisted agents, plus one. The engine seeds
215346
+ * its per-session counter from it, so ids stay monotonic across turns and
215347
+ * server restarts and never collide with TS-assigned ids.
215348
+ */
215349
+ async computeNextAgentId() {
215350
+ let maxSuffix = -1;
215351
+ const consider = (id) => {
215352
+ const match = /^agent-(\d+)$/.exec(id);
215353
+ if (match !== null) maxSuffix = Math.max(maxSuffix, Number(match[1]));
215354
+ };
215355
+ for (const id of this.engineAgentIds) consider(id);
215356
+ try {
215357
+ const persisted = (await this.sessionMetadata.read()).agents ?? {};
215358
+ for (const id of Object.keys(persisted)) consider(id);
215359
+ } catch {
215360
+ this.log.warn("[rustEngineTurnRunner] failed to read session metadata; seeding next agent id from in-session engine ids only");
215361
+ }
215362
+ return maxSuffix + 1;
215363
+ }
215364
+ maxStepsPerTurn() {
215365
+ return this.config.get("loop_control")?.maxStepsPerTurn;
215366
+ }
215367
+ /**
215368
+ * The effective engine-side tool allowlist (TS `isToolActiveComposed`
215369
+ * parity): the runner's `IAgentToolPolicyService.isToolActive` composes
215370
+ * the profile allowlist/denylist, the global `[tools]` config, and the
215371
+ * session denylist — with MCP glob semantics for `mcp__*` tools. AllDone
215372
+ * stays active regardless (the completion-review protocol needs it).
215373
+ * `undefined` = unconstrained (all tools).
215374
+ */
215375
+ effectiveActiveTools() {
215376
+ const all = this.toolRegistry.list().map((info) => info.name);
215377
+ const active = all.filter((name) => {
215378
+ if (name === "AllDone") return true;
215379
+ return this.toolPolicy.isToolActive(name);
215380
+ });
215381
+ return active.length === all.length ? void 0 : active;
215382
+ }
215383
+ maxRetriesPerStep() {
215384
+ return this.config.get("loop_control")?.maxRetriesPerStep;
215385
+ }
215386
+ killGracePeriodMs() {
215387
+ return resolveAgentTaskConfig(this.config)?.killGracePeriodMs ?? 5e3;
215388
+ }
215389
+ maxContextTokens() {
215390
+ const capability = this.profile.data().modelCapabilities;
215391
+ const max = capability.max_input_tokens ?? capability.max_context_tokens;
215392
+ return max > 0 ? max : void 0;
215393
+ }
215394
+ async providerConfig() {
215395
+ const modelAlias = this.profile.data().modelAlias ?? this.profile.getModel();
215396
+ let baseUrl = "";
215397
+ let apiKey = "";
215398
+ try {
215399
+ const model = this.modelCatalog.get(modelAlias);
215400
+ baseUrl = model.baseUrl;
215401
+ apiKey = (await this.providerRuntime.getAuth(model))?.auth.apiKey ?? "";
215402
+ } catch {}
215403
+ return {
215404
+ baseUrl: baseUrl || "https://api.openai.com/v1",
215405
+ apiKey,
215406
+ model: modelAlias || "gpt-4o",
215407
+ thinkingEffort: this.profile.getEffectiveThinkingLevel()
215408
+ };
215409
+ }
215410
+ };
215411
+ RustEngineTurnRunner = __decorate$1([
215412
+ __decorateParam(0, IAgentContextMemoryService),
215413
+ __decorateParam(1, IEventBus),
215414
+ __decorateParam(2, IWireService),
215415
+ __decorateParam(3, IConfigService),
215416
+ __decorateParam(4, IAgentPermissionModeService),
215417
+ __decorateParam(5, IAgentPermissionRulesService),
215418
+ __decorateParam(6, IAgentToolRegistryService),
215419
+ __decorateParam(7, IAgentUsageService),
215420
+ __decorateParam(8, IAgentProfileService),
215421
+ __decorateParam(9, IAgentScopeContext),
215422
+ __decorateParam(10, ISessionContext),
215423
+ __decorateParam(11, ISessionMetadata),
215424
+ __decorateParam(12, IAgentTaskService),
215425
+ __decorateParam(13, IModelCatalog),
215426
+ __decorateParam(14, IProviderRuntime),
215427
+ __decorateParam(15, IInstantiationService),
215428
+ __decorateParam(16, IAgentToolPolicyService),
215429
+ __decorateParam(17, ILogService)
215430
+ ], RustEngineTurnRunner);
215431
+ registerScopedService(2, IRustEngineTurnRunner, RustEngineTurnRunner, 0, "rustEngineTurnRunner");
215432
+ }));
215433
+ //#endregion
213502
215434
  //#region ../../packages/agent-core-v2/src/agent/rpc/prompt-metadata.ts
213503
215435
  function promptMetadataTextFromPayload(payload) {
213504
215436
  return promptMetadataTextFromContentParts(payload.input);
@@ -213567,6 +215499,7 @@ var init_rpcService = __esmMin((() => {
213567
215499
  init_telemetry$2();
213568
215500
  init_toolRegistry();
213569
215501
  init_loop();
215502
+ init_rustEngineTurnRunner();
213570
215503
  init_rpc$2();
213571
215504
  init_prompt_metadata();
213572
215505
  init_decorateParam();
@@ -213590,7 +215523,8 @@ var init_rpcService = __esmMin((() => {
213590
215523
  sessionContext;
213591
215524
  scopeContext;
213592
215525
  agentLifecycle;
213593
- constructor(promptService, conversationUndo, loop, toolPolicy, permissionMode, fullCompaction, toolRegistry, context, contextSize, skills, telemetry, eventBus, eventService, plugins, metadata, sessionContext, scopeContext, agentLifecycle) {
215526
+ rustEngineTurnRunner;
215527
+ constructor(promptService, conversationUndo, loop, toolPolicy, permissionMode, fullCompaction, toolRegistry, context, contextSize, skills, telemetry, eventBus, eventService, plugins, metadata, sessionContext, scopeContext, agentLifecycle, rustEngineTurnRunner) {
213594
215528
  this.promptService = promptService;
213595
215529
  this.conversationUndo = conversationUndo;
213596
215530
  this.loop = loop;
@@ -213609,6 +215543,7 @@ var init_rpcService = __esmMin((() => {
213609
215543
  this.sessionContext = sessionContext;
213610
215544
  this.scopeContext = scopeContext;
213611
215545
  this.agentLifecycle = agentLifecycle;
215546
+ this.rustEngineTurnRunner = rustEngineTurnRunner;
213612
215547
  }
213613
215548
  async prompt(payload) {
213614
215549
  if (payload.disabledTools !== void 0) try {
@@ -213618,6 +215553,13 @@ var init_rpcService = __esmMin((() => {
213618
215553
  throw error;
213619
215554
  }
213620
215555
  await this.updatePromptMetadata(promptMetadataTextFromPayload(payload));
215556
+ if (RustEngineTurnRunner.isEnabled()) {
215557
+ const launched = await this.rustEngineTurnRunner.runTurn({
215558
+ input: [...payload.input],
215559
+ origin: { kind: "user" }
215560
+ });
215561
+ return launched === void 0 ? void 0 : { turn_id: launched.turnId };
215562
+ }
213621
215563
  const handle = await this.promptService.enqueue({ message: {
213622
215564
  role: "user",
213623
215565
  content: [...payload.input],
@@ -213630,6 +215572,17 @@ var init_rpcService = __esmMin((() => {
213630
215572
  }
213631
215573
  async steer(payload) {
213632
215574
  this.telemetry.track2("input_steer", { parts: payload.input.length });
215575
+ if (RustEngineTurnRunner.isEnabled()) {
215576
+ if (this.rustEngineTurnRunner.steer({
215577
+ input: [...payload.input],
215578
+ origin: { kind: "user" }
215579
+ })) return { turn_id: 0 };
215580
+ const launched = await this.rustEngineTurnRunner.runTurn({
215581
+ input: [...payload.input],
215582
+ origin: { kind: "user" }
215583
+ });
215584
+ return launched === void 0 ? void 0 : { turn_id: launched.turnId };
215585
+ }
213633
215586
  const submitted = await this.promptService.enqueueOrSteer({ message: {
213634
215587
  role: "user",
213635
215588
  content: [...payload.input],
@@ -213640,6 +215593,10 @@ var init_rpcService = __esmMin((() => {
213640
215593
  return turn === void 0 ? void 0 : { turn_id: turn.id };
213641
215594
  }
213642
215595
  cancel({ turnId }) {
215596
+ if (RustEngineTurnRunner.isEnabled()) {
215597
+ this.rustEngineTurnRunner.cancel(turnId);
215598
+ return;
215599
+ }
213643
215600
  if (this.loop.status().state === "running") this.telemetry.track2("cancel", {
213644
215601
  from: "streaming",
213645
215602
  trace_id: this.loop.status().activeTraceId
@@ -213744,7 +215701,8 @@ var init_rpcService = __esmMin((() => {
213744
215701
  __decorateParam(14, ISessionMetadata),
213745
215702
  __decorateParam(15, ISessionContext),
213746
215703
  __decorateParam(16, IAgentScopeContext),
213747
- __decorateParam(17, IAgentLifecycleService)
215704
+ __decorateParam(17, IAgentLifecycleService),
215705
+ __decorateParam(18, IRustEngineTurnRunner)
213748
215706
  ], AgentRPCService);
213749
215707
  registerScopedService(2, IAgentRPCService, AgentRPCService, 0, "rpc");
213750
215708
  }));
@@ -216321,13 +218279,6 @@ var init_args_validator = __esmMin((() => {
216321
218279
  ]);
216322
218280
  }));
216323
218281
  //#endregion
216324
- //#region ../../packages/agent-core-v2/src/agent/toolResultTruncation/toolResultTruncation.ts
216325
- var IAgentToolResultTruncationService;
216326
- var init_toolResultTruncation = __esmMin((() => {
216327
- init_instantiation();
216328
- IAgentToolResultTruncationService = createDecorator("agentToolResultTruncationService");
216329
- }));
216330
- //#endregion
216331
218282
  //#region ../../packages/agent-core-v2/src/agent/toolExecutor/toolScheduler.ts
216332
218283
  function createControlledPromise() {
216333
218284
  let resolve;
@@ -217391,7 +219342,7 @@ var init_src$6 = __esmMin((() => {
217391
219342
  init_migration();
217392
219343
  init_sessionLogService();
217393
219344
  init_telemetry$2();
217394
- init_events$7();
219345
+ init_events$8();
217395
219346
  init_telemetryService();
217396
219347
  init_agentTelemetryContext();
217397
219348
  init_agentTelemetryContextService();
@@ -217586,6 +219537,8 @@ var init_src$6 = __esmMin((() => {
217586
219537
  init_wait();
217587
219538
  init_waitService();
217588
219539
  init_waitForTool();
219540
+ init_agent_output$1();
219541
+ init_agentOutputTool();
217589
219542
  init_completion();
217590
219543
  init_allDoneTool();
217591
219544
  init_configSection$4();
@@ -218476,7 +220429,7 @@ var modelCatalogItemSchema$1, providerCatalogStatusSchema$1, providerCatalogItem
218476
220429
  provider: string$2().min(1),
218477
220430
  reason: string$2().min(1)
218478
220431
  });
218479
- })), tokenUsageSchema$2, finishReasonSchema$1, usageStatusSchema$2, permissionModeSchema$2, skillSourceSchema$2, userPromptOriginSchema$1, skillActivationOriginSchema$1, pluginCommandOriginSchema$1, injectionOriginSchema$1, shellCommandOriginSchema$1, compactionSummaryOriginSchema$1, systemTriggerOriginSchema$1, taskLifecycleStatusSchema$2, taskOriginSchema$1, cronJobOriginSchema$1, cronMissedOriginSchema$1, hookResultOriginSchema$1, retryOriginSchema$1, promptOriginSchema$1, dimiErrorCodeSchema$1, dimiErrorPayloadSchema$1, dimiErrorPayloadObjectSchema$1, taskInfoBaseSchema$1, processTaskInfoSchema$1, agentTaskInfoSchema$2, questionTaskInfoSchema$1, toolTaskInfoSchema$1, taskInfoSchema$1, compactionResultSchema$1, toolUpdateSchema$1, turnEndReasonSchema$2, agentPhaseSchema$1, agentStatusUpdatedEventSchema$2, sessionMetaUpdatedEventSchema$1, sessionCreatedEventSchema$1, workspaceCreatedEventSchema$1, workspaceUpdatedEventSchema$1, workspaceDeletedEventSchema$1, sessionWorkChangedEventSchema$1, sessionStatusChangedEventSchema$1, modelCatalogChangedEventSchema, skillActivatedEventSchema$1, pluginCommandActivatedEventSchema$1, errorEventSchema$2, warningEventSchema$2, turnStartedEventSchema$2, turnEndedEventSchema$2, turnStepStartedEventSchema$1, turnStepCompletedEventSchema$1, turnStepRetryingEventSchema$1, turnStepInterruptedEventSchema$1, assistantDeltaEventSchema$2, hookResultEventSchema$1, thinkingDeltaEventSchema$2, toolCallDeltaEventSchema$1, toolCallStartedEventSchema$2, toolProgressEventSchema$1, shellOutputEventSchema$1, shellStartedEventSchema$1, shellCompletedEventSchema$1, toolResultEventSchema$2, subagentSpawnedEventSchema$1, subagentStartedEventSchema$1, subagentSuspendedEventSchema$1, subagentCompletedEventSchema$1, subagentFailedEventSchema$1, compactionStartedEventSchema$1, compactionBlockedEventSchema$1, compactionCancelledEventSchema$1, compactionCompletedEventSchema$1, taskStartedEventSchema$1, taskTerminatedEventSchema$1, cronFiredEventSchema$1, promptSubmittedEventSchema$1, promptCompletedEventSchema$2, promptAbortedEventSchema$2, promptSteeredEventSchema$1, toolListUpdatedReasonSchema$1, toolListUpdatedEventSchema$1, mcpServerStatusPayloadSchema$1, mcpServerStatusEventSchema$1, agentEventSchema$1, eventSchema$1, VOLATILE_EVENT_TYPES$1, init_events$6 = __esmMin((() => {
220432
+ })), tokenUsageSchema$2, finishReasonSchema$1, usageStatusSchema$2, permissionModeSchema$2, skillSourceSchema$2, userPromptOriginSchema$1, skillActivationOriginSchema$1, pluginCommandOriginSchema$1, injectionOriginSchema$1, shellCommandOriginSchema$1, compactionSummaryOriginSchema$1, systemTriggerOriginSchema$1, taskLifecycleStatusSchema$2, taskOriginSchema$1, cronJobOriginSchema$1, cronMissedOriginSchema$1, hookResultOriginSchema$1, retryOriginSchema$1, promptOriginSchema$1, dimiErrorCodeSchema$1, dimiErrorPayloadSchema$1, dimiErrorPayloadObjectSchema$1, taskInfoBaseSchema$1, processTaskInfoSchema$1, agentTaskInfoSchema$2, questionTaskInfoSchema$1, toolTaskInfoSchema$1, taskInfoSchema$1, compactionResultSchema$1, toolUpdateSchema$1, turnEndReasonSchema$2, agentPhaseSchema$1, agentStatusUpdatedEventSchema$2, sessionMetaUpdatedEventSchema$1, sessionCreatedEventSchema$1, workspaceCreatedEventSchema$1, workspaceUpdatedEventSchema$1, workspaceDeletedEventSchema$1, sessionWorkChangedEventSchema$1, sessionStatusChangedEventSchema$1, modelCatalogChangedEventSchema, skillActivatedEventSchema$1, pluginCommandActivatedEventSchema$1, errorEventSchema$2, warningEventSchema$2, turnStartedEventSchema$2, turnEndedEventSchema$2, turnStepStartedEventSchema$1, turnStepCompletedEventSchema$1, turnStepRetryingEventSchema$1, turnStepInterruptedEventSchema$1, assistantDeltaEventSchema$2, hookResultEventSchema$1, thinkingDeltaEventSchema$2, toolCallDeltaEventSchema$1, toolCallStartedEventSchema$2, toolProgressEventSchema$1, shellOutputEventSchema$1, shellStartedEventSchema$1, shellCompletedEventSchema$1, toolResultEventSchema$2, subagentSpawnedEventSchema$1, subagentStartedEventSchema$1, subagentSuspendedEventSchema$1, subagentCompletedEventSchema$1, subagentFailedEventSchema$1, compactionStartedEventSchema$1, compactionBlockedEventSchema$1, compactionCancelledEventSchema$1, compactionCompletedEventSchema$1, taskStartedEventSchema$1, taskTerminatedEventSchema$1, cronFiredEventSchema$1, promptSubmittedEventSchema$1, promptCompletedEventSchema$2, promptAbortedEventSchema$2, promptSteeredEventSchema$1, toolListUpdatedReasonSchema$1, toolListUpdatedEventSchema$1, mcpServerStatusPayloadSchema$1, mcpServerStatusEventSchema$1, agentEventSchema$1, eventSchema$1, VOLATILE_EVENT_TYPES$1, init_events$7 = __esmMin((() => {
218480
220433
  init_zod$1();
218481
220434
  init_display$1();
218482
220435
  init_message$1();
@@ -219250,7 +221203,7 @@ var modelCatalogItemSchema$1, providerCatalogStatusSchema$1, providerCatalogItem
219250
221203
  })), sessionCursorSchema$1, cursorsBySessionSchema$1, wsEventEnvelopeSchema$1, wsAckEnvelopeSchema$1, serverHelloPayloadSchema$1, serverHelloMessageSchema$1, agentFilterSchema$1, clientHelloPayloadSchema$1, clientHelloMessageSchema$1, clientHelloAckPayloadSchema$1, clientHelloAckMessageSchema$1, watchFsConfigSchema$1, subscribePayloadSchema$1, subscribeMessageSchema$1, subscribeAckPayloadSchema$1, subscribeAckMessageSchema$1, unsubscribePayloadSchema$1, unsubscribeMessageSchema$1, unsubscribeAckPayloadSchema$1, unsubscribeAckMessageSchema$1, watchFsAddPayloadSchema$1, watchFsAddMessageSchema$1, watchFsRemovePayloadSchema$1, watchFsRemoveMessageSchema$1, watchFsAckPayloadSchema$1, watchFsAckMessageSchema$1, abortPayloadSchema$1, abortMessageSchema$1, abortAckPayloadSchema$1, abortAckMessageSchema$1, terminalAttachPayloadSchema$1, terminalAttachMessageSchema$1, terminalAttachAckPayloadSchema$1, terminalAttachAckMessageSchema$1, terminalDetachPayloadSchema$1, terminalDetachMessageSchema$1, terminalDetachAckPayloadSchema$1, terminalDetachAckMessageSchema$1, terminalInputPayloadSchema$1, terminalInputMessageSchema$1, terminalInputAckPayloadSchema$1, terminalInputAckMessageSchema$1, terminalResizePayloadSchema$1, terminalResizeMessageSchema$1, terminalResizeAckPayloadSchema$1, terminalResizeAckMessageSchema$1, terminalClosePayloadSchema$1, terminalCloseMessageSchema$1, terminalCloseAckPayloadSchema$1, terminalCloseAckMessageSchema$1, pingPayloadSchema$1, pingMessageSchema$1, pongPayloadSchema$1, pongMessageSchema$1, resyncRequiredPayloadSchema$1, resyncRequiredMessageSchema$1, wsErrorPayloadSchema$1, wsErrorMessageSchema$1, terminalOutputPayloadSchema$1, terminalExitPayloadSchema$1, clientControlOperations$1, serverSystemOperations$1;
219251
221204
  var init_ws_control$1 = __esmMin((() => {
219252
221205
  init_zod$1();
219253
- init_events$6();
221206
+ init_events$7();
219254
221207
  init_time();
219255
221208
  sessionCursorSchema$1 = object({
219256
221209
  seq: number$2().int().nonnegative(),
@@ -219795,7 +221748,7 @@ var init_tool$2 = __esmMin((() => {
219795
221748
  var skillDescriptorSchema$1;
219796
221749
  var init_skill$2 = __esmMin((() => {
219797
221750
  init_zod$1();
219798
- init_events$6();
221751
+ init_events$7();
219799
221752
  skillDescriptorSchema$1 = object({
219800
221753
  name: string$2().min(1),
219801
221754
  description: string$2(),
@@ -220528,7 +222481,7 @@ var init_src$5 = __esmMin((() => {
220528
222481
  init_pagination$1();
220529
222482
  init_time();
220530
222483
  init_request_id$2();
220531
- init_events$6();
222484
+ init_events$7();
220532
222485
  init_display$1();
220533
222486
  init_ws_control$1();
220534
222487
  init_asyncapi$1();
@@ -220567,7 +222520,7 @@ var init_src$5 = __esmMin((() => {
220567
222520
  }));
220568
222521
  //#endregion
220569
222522
  //#region ../../packages/node-sdk/src/events.ts
220570
- var init_events$5 = __esmMin((() => {
222523
+ var init_events$6 = __esmMin((() => {
220571
222524
  init_src$5();
220572
222525
  }));
220573
222526
  //#endregion
@@ -220622,7 +222575,7 @@ var MAIN_AGENT_ID$6, Session;
220622
222575
  var init_session$3 = __esmMin((() => {
220623
222576
  init_src$6();
220624
222577
  init_errors$3();
220625
- init_events$5();
222578
+ init_events$6();
220626
222579
  MAIN_AGENT_ID$6 = "main";
220627
222580
  Session = class {
220628
222581
  id;
@@ -221651,7 +223604,7 @@ var init_helpers = __esmMin((() => {
221651
223604
  removed: array$2(string$2()),
221652
223605
  changed: array$2(string$2())
221653
223606
  });
221654
- })), textPartSchema, imageUrlPartSchema, videoUrlPartSchema, promptPartSchema, emptyPayloadSchema, promptPayloadSchema, steerPayloadSchema, promptLaunchResultSchema, cancelPayloadSchema, runShellCommandPayloadSchema, shellCommandResultSchema, setModelResultSchema, permissionModeSchema$1, setPermissionPayloadSchema, tokenUsageSchema$1, usageStatusSchema$1, agentContextDataSchema, planDataSchema, taskLifecycleStatusSchema$1, taskInfoBaseFields, agentTaskInfoSchema$1, agentRpcContract;
223607
+ })), textPartSchema, imageUrlPartSchema, videoUrlPartSchema, promptPartSchema, emptyPayloadSchema, promptPayloadSchema, steerPayloadSchema, promptLaunchResultSchema, cancelPayloadSchema, runShellCommandPayloadSchema, shellCommandResultSchema$1, setModelResultSchema, permissionModeSchema$1, setPermissionPayloadSchema, tokenUsageSchema$1, usageStatusSchema$1, agentContextDataSchema, planDataSchema, taskLifecycleStatusSchema$1, taskInfoBaseFields, agentTaskInfoSchema$1, agentRpcContract;
221655
223608
  var init_rpc$1 = __esmMin((() => {
221656
223609
  init_zod$1();
221657
223610
  init_helpers();
@@ -221690,7 +223643,7 @@ var init_rpc$1 = __esmMin((() => {
221690
223643
  command: string$2(),
221691
223644
  commandId: string$2().optional()
221692
223645
  });
221693
- shellCommandResultSchema = object({
223646
+ shellCommandResultSchema$1 = object({
221694
223647
  stdout: string$2(),
221695
223648
  stderr: string$2(),
221696
223649
  isError: boolean$2().optional(),
@@ -221822,7 +223775,7 @@ var init_services = __esmMin((() => {
221822
223775
  agentShellCommandContract = {
221823
223776
  run: {
221824
223777
  input: tuple([runShellCommandPayloadSchema]),
221825
- output: shellCommandResultSchema
223778
+ output: shellCommandResultSchema$1
221826
223779
  },
221827
223780
  cancel: {
221828
223781
  input: tuple([string$2()]),
@@ -222766,7 +224719,7 @@ var init_contract$1 = __esmMin((() => {
222766
224719
  //#endregion
222767
224720
  //#region ../../packages/klient/src/contract/global/events.ts
222768
224721
  var configChangedSchema, reloadSummarySchema, sessionMetaUpdatedSchema, globalEvents;
222769
- var init_events$4 = __esmMin((() => {
224722
+ var init_events$5 = __esmMin((() => {
222770
224723
  init_zod$1();
222771
224724
  configChangedSchema = object({
222772
224725
  domain: string$2(),
@@ -222830,7 +224783,7 @@ var init_events$4 = __esmMin((() => {
222830
224783
  //#endregion
222831
224784
  //#region ../../packages/klient/src/contract/session/events.ts
222832
224785
  var sessionEvents;
222833
- var init_events$3 = __esmMin((() => {
224786
+ var init_events$4 = __esmMin((() => {
222834
224787
  init_zod$1();
222835
224788
  init_interaction$1();
222836
224789
  init_metadata();
@@ -222856,7 +224809,7 @@ var init_events$3 = __esmMin((() => {
222856
224809
  //#endregion
222857
224810
  //#region ../../packages/klient/src/contract/agent/events.ts
222858
224811
  var turnStartedEventSchema$1, turnEndedEventSchema$1, assistantDeltaEventSchema$1, thinkingDeltaEventSchema$1, toolCallStartedEventSchema$1, toolResultEventSchema$1, promptCompletedEventSchema$1, promptAbortedEventSchema$1, permissionApprovalRequestedEventSchema, permissionApprovalResolvedEventSchema, errorEventSchema$1, warningEventSchema$1, agentStatusUpdatedEventSchema$1, agentEvents;
222859
- var init_events$2 = __esmMin((() => {
224812
+ var init_events$3 = __esmMin((() => {
222860
224813
  init_zod$1();
222861
224814
  turnStartedEventSchema$1 = object({
222862
224815
  type: literal("turn.started"),
@@ -223490,9 +225443,9 @@ function createKlientFromChannel(channel, options = {}) {
223490
225443
  }
223491
225444
  var init_klient = __esmMin((() => {
223492
225445
  init_contract$1();
225446
+ init_events$5();
223493
225447
  init_events$4();
223494
225448
  init_events$3();
223495
- init_events$2();
223496
225449
  init_hub();
223497
225450
  init_global();
223498
225451
  init_session$2();
@@ -225918,7 +227871,7 @@ var init_src$4 = __esmMin((() => {
225918
227871
  init_errors$3();
225919
227872
  init_logging();
225920
227873
  init_src$6();
225921
- init_events$5();
227874
+ init_events$6();
225922
227875
  }));
225923
227876
  //#endregion
225924
227877
  //#region ../../packages/telemetry/src/index.ts
@@ -232170,7 +234123,7 @@ var init_schema$1 = __esmMin((() => {
232170
234123
  seq: transcriptSeqSchema.optional()
232171
234124
  });
232172
234125
  })), transcriptResetEventSchema, transcriptOpsEventSchema;
232173
- var init_events$1 = __esmMin((() => {
234126
+ var init_events$2 = __esmMin((() => {
232174
234127
  init_zod$1();
232175
234128
  init_schema$1();
232176
234129
  transcriptResetEventSchema = transcriptResetPayloadSchema.extend({ type: literal("transcript.reset") });
@@ -232201,7 +234154,7 @@ var init_src$2 = __esmMin((() => {
232201
234154
  init_groupTurns();
232202
234155
  init_foldFacts();
232203
234156
  init_schema$1();
232204
- init_events$1();
234157
+ init_events$2();
232205
234158
  }));
232206
234159
  //#endregion
232207
234160
  //#region ../../packages/agent-core-v2/src/agent/contextMemory/protocolMessage.ts
@@ -259004,7 +260957,7 @@ function isVolatileEventType(type) {
259004
260957
  return volatileEventTypeSet.has(type);
259005
260958
  }
259006
260959
  var VOLATILE_EVENT_TYPES, volatileEventTypeSet;
259007
- var init_events = __esmMin((() => {
260960
+ var init_events$1 = __esmMin((() => {
259008
260961
  VOLATILE_EVENT_TYPES = [
259009
260962
  "assistant.delta",
259010
260963
  "thinking.delta",
@@ -259242,11 +261195,11 @@ function registerApprovalsRoutes(app, core) {
259242
261195
  const listRoute = defineRoute({
259243
261196
  method: "GET",
259244
261197
  path: "/sessions/{session_id}/approvals",
259245
- params: sessionIdParamSchema$9,
261198
+ params: sessionIdParamSchema$10,
259246
261199
  querystring: listPendingApprovalsQuerySchema,
259247
261200
  success: { data: listPendingApprovalsResponseSchema },
259248
261201
  errors: {
259249
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$7 },
261202
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$8 },
259250
261203
  [ErrorCode.SESSION_NOT_FOUND]: {}
259251
261204
  },
259252
261205
  description: "List pending approval requests for a session",
@@ -259269,7 +261222,7 @@ function registerApprovalsRoutes(app, core) {
259269
261222
  body: approvalResolveRequestSchema,
259270
261223
  success: { data: approvalResolveResultSchema },
259271
261224
  errors: {
259272
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$7 },
261225
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$8 },
259273
261226
  [ErrorCode.SESSION_NOT_FOUND]: {},
259274
261227
  [ErrorCode.APPROVAL_NOT_FOUND]: {},
259275
261228
  [ErrorCode.APPROVAL_ALREADY_RESOLVED]: { dataSchema: approvalAlreadyResolvedDataSchema }
@@ -259332,7 +261285,7 @@ function toWireApproval(interaction, sessionId) {
259332
261285
  expires_at: new Date(interaction.createdAt + APPROVAL_EXPIRY_MS).toISOString()
259333
261286
  };
259334
261287
  }
259335
- var sessionIdParamSchema$9, approvalParamsSchema, detailsSchema$7, APPROVAL_EXPIRY_MS;
261288
+ var sessionIdParamSchema$10, approvalParamsSchema, detailsSchema$8, APPROVAL_EXPIRY_MS;
259336
261289
  var init_approvals = __esmMin((() => {
259337
261290
  init_src$6();
259338
261291
  init_error_codes();
@@ -259341,12 +261294,12 @@ var init_approvals = __esmMin((() => {
259341
261294
  init_envelope();
259342
261295
  init_requestLog();
259343
261296
  init_defineRoute();
259344
- sessionIdParamSchema$9 = object({ session_id: string$2().min(1) });
261297
+ sessionIdParamSchema$10 = object({ session_id: string$2().min(1) });
259345
261298
  approvalParamsSchema = object({
259346
261299
  session_id: string$2().min(1),
259347
261300
  approval_id: string$2().min(1)
259348
261301
  });
259349
- detailsSchema$7 = array$2(object({
261302
+ detailsSchema$8 = array$2(object({
259350
261303
  path: string$2(),
259351
261304
  message: string$2()
259352
261305
  }));
@@ -259405,11 +261358,11 @@ function registerQuestionsRoutes(app, core) {
259405
261358
  const listRoute = defineRoute({
259406
261359
  method: "GET",
259407
261360
  path: "/sessions/{session_id}/questions",
259408
- params: sessionIdParamSchema$8,
261361
+ params: sessionIdParamSchema$9,
259409
261362
  querystring: listPendingQuestionsQuerySchema,
259410
261363
  success: { data: listPendingQuestionsResponseSchema },
259411
261364
  errors: {
259412
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$6 },
261365
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$7 },
259413
261366
  [ErrorCode.SESSION_NOT_FOUND]: {}
259414
261367
  },
259415
261368
  description: "List pending question requests for a session",
@@ -259431,7 +261384,7 @@ function registerQuestionsRoutes(app, core) {
259431
261384
  params: tailParamsSchema,
259432
261385
  success: { data: questionResolveResultSchema },
259433
261386
  errors: {
259434
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$6 },
261387
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$7 },
259435
261388
  [ErrorCode.SESSION_NOT_FOUND]: {},
259436
261389
  [ErrorCode.QUESTION_NOT_FOUND]: {},
259437
261390
  [ErrorCode.APPROVAL_ALREADY_RESOLVED]: { dataSchema: questionAlreadyResolvedDataSchema },
@@ -259615,7 +261568,7 @@ function toInProcessResponse(resp, request) {
259615
261568
  if (resp.method !== void 0 && resp.method !== "click") out.method = resp.method;
259616
261569
  return out;
259617
261570
  }
259618
- var sessionIdParamSchema$8, tailParamsSchema, detailsSchema$6;
261571
+ var sessionIdParamSchema$9, tailParamsSchema, detailsSchema$7;
259619
261572
  var init_questions = __esmMin((() => {
259620
261573
  init_src$6();
259621
261574
  init_error_codes();
@@ -259626,12 +261579,12 @@ var init_questions = __esmMin((() => {
259626
261579
  init_requestLog();
259627
261580
  init_defineRoute();
259628
261581
  init_action_suffix();
259629
- sessionIdParamSchema$8 = object({ session_id: string$2().min(1) });
261582
+ sessionIdParamSchema$9 = object({ session_id: string$2().min(1) });
259630
261583
  tailParamsSchema = object({
259631
261584
  session_id: string$2().min(1),
259632
261585
  tail: string$2().min(1)
259633
261586
  });
259634
- detailsSchema$6 = array$2(object({
261587
+ detailsSchema$7 = array$2(object({
259635
261588
  path: string$2(),
259636
261589
  message: string$2()
259637
261590
  }));
@@ -260309,7 +262262,7 @@ function sessionCreatedPayload(payload) {
260309
262262
  var GLOBAL_SESSION_ID, TRANSCRIPT_RESET_TAIL_TURNS, SessionEventBroadcaster, volatileSignalTypeSet, TRANSCRIPT_PROJECTED_EVENT_TYPES;
260310
262263
  var init_sessionEventBroadcaster = __esmMin((() => {
260311
262264
  init_src$6();
260312
- init_events();
262265
+ init_events$1();
260313
262266
  init_src$2();
260314
262267
  init_approvals();
260315
262268
  init_questions();
@@ -264859,11 +266812,11 @@ function registerMessagesRoutes(app, core) {
264859
266812
  const listRoute = defineRoute({
264860
266813
  method: "GET",
264861
266814
  path: "/sessions/{session_id}/messages",
264862
- params: sessionIdParamSchema$7,
266815
+ params: sessionIdParamSchema$8,
264863
266816
  querystring: messagesListQueryCoercion,
264864
266817
  success: { data: listMessagesResponseSchema },
264865
266818
  errors: {
264866
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266819
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$6 },
264867
266820
  [ErrorCode.SESSION_NOT_FOUND]: {}
264868
266821
  },
264869
266822
  description: "List messages for a session",
@@ -264884,7 +266837,7 @@ function registerMessagesRoutes(app, core) {
264884
266837
  params: messageIdParamSchema,
264885
266838
  success: { data: getMessageResponseSchema },
264886
266839
  errors: {
264887
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266840
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$6 },
264888
266841
  [ErrorCode.SESSION_NOT_FOUND]: {},
264889
266842
  [ErrorCode.MESSAGE_NOT_FOUND]: {}
264890
266843
  },
@@ -264921,7 +266874,7 @@ function sendMappedError$7(reply, req, err) {
264921
266874
  log?.error({ err }, "message request failed");
264922
266875
  reply.send(errEnvelope(ErrorCode.INTERNAL_ERROR, err instanceof Error ? err.message : String(err), requestId, err instanceof Error ? err.stack : void 0));
264923
266876
  }
264924
- var messagesListQueryCoercion, sessionIdParamSchema$7, messageIdParamSchema, detailsSchema$5;
266877
+ var messagesListQueryCoercion, sessionIdParamSchema$8, messageIdParamSchema, detailsSchema$6;
264925
266878
  var init_messages = __esmMin((() => {
264926
266879
  init_src$6();
264927
266880
  init_protocolMessage();
@@ -264945,12 +266898,12 @@ var init_messages = __esmMin((() => {
264945
266898
  params: { code: ErrorCode.VALIDATION_FAILED }
264946
266899
  });
264947
266900
  });
264948
- sessionIdParamSchema$7 = object({ session_id: string$2().min(1) });
266901
+ sessionIdParamSchema$8 = object({ session_id: string$2().min(1) });
264949
266902
  messageIdParamSchema = object({
264950
266903
  session_id: string$2().min(1),
264951
266904
  message_id: string$2().min(1)
264952
266905
  });
264953
- detailsSchema$5 = array$2(object({
266906
+ detailsSchema$6 = array$2(object({
264954
266907
  path: string$2(),
264955
266908
  message: string$2()
264956
266909
  }));
@@ -265303,6 +267256,107 @@ var init_registerDebugRoutes = __esmMin((() => {
265303
267256
  init_serviceDispatcherRoutes();
265304
267257
  }));
265305
267258
  //#endregion
267259
+ //#region ../../packages/kap-server/src/routes/events.ts
267260
+ function registerEventsRoute(app, opts) {
267261
+ app.get("/sessions/:session_id/events", { schema: {
267262
+ params: {
267263
+ type: "object",
267264
+ properties: { session_id: { type: "string" } },
267265
+ required: ["session_id"]
267266
+ },
267267
+ querystring: {
267268
+ type: "object",
267269
+ properties: { event_seq: { type: "string" } },
267270
+ required: []
267271
+ }
267272
+ } }, async (req, reply) => {
267273
+ const { session_id } = req.params;
267274
+ if (!session_id) {
267275
+ await reply.code(400).send({
267276
+ code: 4e4,
267277
+ msg: "session_id required",
267278
+ data: null,
267279
+ request_id: req.id
267280
+ });
267281
+ return;
267282
+ }
267283
+ const eventSeq = parseEventSeq(req.query.event_seq);
267284
+ if (eventSeq !== void 0 && (!Number.isInteger(eventSeq) || eventSeq < 0)) {
267285
+ await reply.code(400).send({
267286
+ code: 4e4,
267287
+ msg: "event_seq must be a non-negative integer",
267288
+ data: null,
267289
+ request_id: req.id
267290
+ });
267291
+ return;
267292
+ }
267293
+ reply.hijack();
267294
+ const raw = reply.raw;
267295
+ raw.writeHead(200, SSE_HEADERS);
267296
+ raw.write(": connected\n\n");
267297
+ let closed = false;
267298
+ const target = { send(envelope) {
267299
+ if (closed) return;
267300
+ raw.write(`event: ${envelope.type}\ndata: ${JSON.stringify(envelope)}\n\n`);
267301
+ } };
267302
+ if (!await opts.broadcaster.subscribe(session_id, target)) {
267303
+ if (await opts.core.accessor.get(ISessionLifecycleService).resume(session_id) === void 0) {
267304
+ closed = true;
267305
+ raw.write("event: error\ndata: {\"code\":40401,\"msg\":\"session not found\"}\n\n");
267306
+ raw.end();
267307
+ return;
267308
+ }
267309
+ if (!await opts.broadcaster.subscribe(session_id, target)) {
267310
+ closed = true;
267311
+ raw.write("event: error\ndata: {\"code\":40401,\"msg\":\"session not found\"}\n\n");
267312
+ raw.end();
267313
+ return;
267314
+ }
267315
+ }
267316
+ if (eventSeq !== void 0) {
267317
+ const result = await opts.broadcaster.getBufferedSince(session_id, { seq: eventSeq });
267318
+ if (result.resyncRequired !== false) raw.write(`event: resync_required\ndata: ${JSON.stringify({
267319
+ type: "resync_required",
267320
+ session_id,
267321
+ reason: result.resyncRequired,
267322
+ current_seq: result.currentSeq,
267323
+ epoch: result.epoch
267324
+ })}\n\n`);
267325
+ else for (const { envelope } of result.events) target.send(envelope);
267326
+ }
267327
+ const heartbeat = setInterval(() => {
267328
+ if (!closed) raw.write(": ping\n\n");
267329
+ }, HEARTBEAT_INTERVAL_MS);
267330
+ req.raw.on("close", () => {
267331
+ closed = true;
267332
+ clearInterval(heartbeat);
267333
+ opts.broadcaster.unsubscribe(session_id, target);
267334
+ });
267335
+ });
267336
+ }
267337
+ /**
267338
+ * Parse the optional `?event_seq=` replay cursor. `undefined` = absent
267339
+ * (live-only stream); `NaN` = present but malformed — the caller rejects it
267340
+ * with a 400. A valid cursor is a non-negative integer (the journal seq
267341
+ * domain, matching `sessionCursorSchema.seq`).
267342
+ */
267343
+ function parseEventSeq(raw) {
267344
+ if (raw === void 0) return void 0;
267345
+ if (typeof raw !== "string" || !/^\d+$/.test(raw)) return NaN;
267346
+ return Number(raw);
267347
+ }
267348
+ var SSE_HEADERS, HEARTBEAT_INTERVAL_MS;
267349
+ var init_events = __esmMin((() => {
267350
+ init_src$6();
267351
+ SSE_HEADERS = {
267352
+ "Content-Type": "text/event-stream",
267353
+ "Cache-Control": "no-cache",
267354
+ Connection: "keep-alive",
267355
+ "X-Accel-Buffering": "no"
267356
+ };
267357
+ HEARTBEAT_INTERVAL_MS = 15e3;
267358
+ }));
267359
+ //#endregion
265306
267360
  //#region ../../packages/kap-server/src/protocol/rest-meta.ts
265307
267361
  var metaCapabilitiesSchema, metaResponseSchema;
265308
267362
  var init_rest_meta = __esmMin((() => {
@@ -265908,7 +267962,7 @@ function registerPromptsRoutes(app, core) {
265908
267962
  const listRoute = defineRoute({
265909
267963
  method: "GET",
265910
267964
  path: "/sessions/{session_id}/prompts",
265911
- params: sessionIdParamSchema$6,
267965
+ params: sessionIdParamSchema$7,
265912
267966
  success: { data: promptListResponseSchema },
265913
267967
  errors: { [ErrorCode.SESSION_NOT_FOUND]: {} },
265914
267968
  description: "List the active prompt and queued prompts for a session",
@@ -265928,7 +267982,7 @@ function registerPromptsRoutes(app, core) {
265928
267982
  method: "POST",
265929
267983
  path: "/sessions/{session_id}/prompts",
265930
267984
  body: promptSubmissionSchema,
265931
- params: sessionIdParamSchema$6,
267985
+ params: sessionIdParamSchema$7,
265932
267986
  success: { data: promptSubmitResultSchema },
265933
267987
  errors: {
265934
267988
  [ErrorCode.VALIDATION_FAILED]: { detailsSchema: validationDetailsSchema },
@@ -265994,7 +268048,7 @@ function registerPromptsRoutes(app, core) {
265994
268048
  method: "POST",
265995
268049
  path: "/sessions/{session_id}/prompts::steer",
265996
268050
  body: promptSteerRequestSchema,
265997
- params: sessionIdParamSchema$6,
268051
+ params: sessionIdParamSchema$7,
265998
268052
  success: { data: promptSteerResultSchema },
265999
268053
  errors: {
266000
268054
  [ErrorCode.VALIDATION_FAILED]: {},
@@ -266486,7 +268540,7 @@ function authModelDetails(err) {
266486
268540
  if (typeof providerId === "string") details.provider_id = providerId;
266487
268541
  return Object.keys(details).length === 0 ? null : details;
266488
268542
  }
266489
- var sessionIdParamSchema$6, validationDetailsSchema, authProviderDetailsSchema, authModelDetailsSchema, VIDEO_EXT_BY_MIME, ATTACHMENT_NAME_MAX;
268543
+ var sessionIdParamSchema$7, validationDetailsSchema, authProviderDetailsSchema, authModelDetailsSchema, VIDEO_EXT_BY_MIME, ATTACHMENT_NAME_MAX;
266490
268544
  var init_prompts$1 = __esmMin((() => {
266491
268545
  init_src$6();
266492
268546
  init_error_codes();
@@ -266497,7 +268551,7 @@ var init_prompts$1 = __esmMin((() => {
266497
268551
  init_defineRoute();
266498
268552
  init_mainAgent();
266499
268553
  init_action_suffix();
266500
- sessionIdParamSchema$6 = object({ session_id: string$2().min(1) });
268554
+ sessionIdParamSchema$7 = object({ session_id: string$2().min(1) });
266501
268555
  validationDetailsSchema = array$2(object({
266502
268556
  path: string$2(),
266503
268557
  message: string$2()
@@ -266779,7 +268833,7 @@ function registerSessionsRoutes(app, core) {
266779
268833
  body: createSessionRequestSchema,
266780
268834
  success: { data: sessionSchema },
266781
268835
  errors: {
266782
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
268836
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266783
268837
  [ErrorCode.WORKSPACE_NOT_FOUND]: {},
266784
268838
  [ErrorCode.FS_PATH_NOT_FOUND]: {}
266785
268839
  },
@@ -266817,6 +268871,7 @@ function registerSessionsRoutes(app, core) {
266817
268871
  const touched = await registry.createOrTouch(workDir);
266818
268872
  const handle = await core.accessor.get(ISessionLifecycleService).create({ workDir });
266819
268873
  if (typeof body.title === "string") await handle.accessor.get(ISessionMetadata).setTitle(body.title);
268874
+ if (body.metadata !== void 0 && Object.keys(body.metadata).length > 0) await handle.accessor.get(ISessionMetadata).update({ custom: { ...body.metadata } });
266820
268875
  const session = toWireSession({
266821
268876
  ...await handle.accessor.get(ISessionMetadata).read(),
266822
268877
  workspaceId: touched.id
@@ -266845,7 +268900,7 @@ function registerSessionsRoutes(app, core) {
266845
268900
  querystring: sessionsListQueryCoercion,
266846
268901
  success: { data: pageResponseSchema(sessionSchema) },
266847
268902
  errors: {
266848
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
268903
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266849
268904
  [ErrorCode.WORKSPACE_NOT_FOUND]: {}
266850
268905
  },
266851
268906
  description: "List sessions",
@@ -266913,10 +268968,10 @@ function registerSessionsRoutes(app, core) {
266913
268968
  const getRoute = defineRoute({
266914
268969
  method: "GET",
266915
268970
  path: "/sessions/{session_id}",
266916
- params: sessionIdParamSchema$5,
268971
+ params: sessionIdParamSchema$6,
266917
268972
  success: { data: sessionSchema },
266918
268973
  errors: {
266919
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
268974
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266920
268975
  [ErrorCode.SESSION_NOT_FOUND]: {}
266921
268976
  },
266922
268977
  description: "Get a session by ID",
@@ -266939,10 +268994,10 @@ function registerSessionsRoutes(app, core) {
266939
268994
  const getProfileRoute = defineRoute({
266940
268995
  method: "GET",
266941
268996
  path: "/sessions/{session_id}/profile",
266942
- params: sessionIdParamSchema$5,
268997
+ params: sessionIdParamSchema$6,
266943
268998
  success: { data: sessionSchema },
266944
268999
  errors: {
266945
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269000
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266946
269001
  [ErrorCode.SESSION_NOT_FOUND]: {}
266947
269002
  },
266948
269003
  description: "Get session profile",
@@ -266965,11 +269020,11 @@ function registerSessionsRoutes(app, core) {
266965
269020
  const updateProfileRoute = defineRoute({
266966
269021
  method: "POST",
266967
269022
  path: "/sessions/{session_id}/profile",
266968
- params: sessionIdParamSchema$5,
269023
+ params: sessionIdParamSchema$6,
266969
269024
  body: updateSessionProfileRequestSchema,
266970
269025
  success: { data: sessionSchema },
266971
269026
  errors: {
266972
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269027
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
266973
269028
  [ErrorCode.SESSION_NOT_FOUND]: {}
266974
269029
  },
266975
269030
  description: "Update session profile (title, metadata, agent_config)",
@@ -267011,7 +269066,7 @@ function registerSessionsRoutes(app, core) {
267011
269066
  archiveSessionResponseSchema
267012
269067
  ]) },
267013
269068
  errors: {
267014
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269069
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
267015
269070
  [ErrorCode.SESSION_NOT_FOUND]: {},
267016
269071
  [ErrorCode.SESSION_BUSY]: {},
267017
269072
  [ErrorCode.COMPACTION_UNABLE]: {},
@@ -267150,11 +269205,11 @@ function registerSessionsRoutes(app, core) {
267150
269205
  const listChildrenRoute = defineRoute({
267151
269206
  method: "GET",
267152
269207
  path: "/sessions/{session_id}/children",
267153
- params: sessionIdParamSchema$5,
269208
+ params: sessionIdParamSchema$6,
267154
269209
  querystring: sessionChildrenListQueryCoercion,
267155
269210
  success: { data: listSessionChildrenResponseSchema },
267156
269211
  errors: {
267157
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269212
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
267158
269213
  [ErrorCode.SESSION_NOT_FOUND]: {}
267159
269214
  },
267160
269215
  description: "List child sessions",
@@ -267188,11 +269243,11 @@ function registerSessionsRoutes(app, core) {
267188
269243
  const createChildRoute = defineRoute({
267189
269244
  method: "POST",
267190
269245
  path: "/sessions/{session_id}/children",
267191
- params: sessionIdParamSchema$5,
269246
+ params: sessionIdParamSchema$6,
267192
269247
  body: createSessionChildRequestSchema,
267193
269248
  success: { data: sessionSchema },
267194
269249
  errors: {
267195
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269250
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
267196
269251
  [ErrorCode.SESSION_NOT_FOUND]: {},
267197
269252
  [ErrorCode.SESSION_BUSY]: {}
267198
269253
  },
@@ -267229,10 +269284,10 @@ function registerSessionsRoutes(app, core) {
267229
269284
  const statusRoute = defineRoute({
267230
269285
  method: "GET",
267231
269286
  path: "/sessions/{session_id}/status",
267232
- params: sessionIdParamSchema$5,
269287
+ params: sessionIdParamSchema$6,
267233
269288
  success: { data: sessionStatusResponseSchema },
267234
269289
  errors: {
267235
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269290
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
267236
269291
  [ErrorCode.SESSION_NOT_FOUND]: {}
267237
269292
  },
267238
269293
  description: "Get realtime session status (best-effort in this slice)",
@@ -267250,10 +269305,10 @@ function registerSessionsRoutes(app, core) {
267250
269305
  const sessionWarningsRoute = defineRoute({
267251
269306
  method: "GET",
267252
269307
  path: "/sessions/{session_id}/warnings",
267253
- params: sessionIdParamSchema$5,
269308
+ params: sessionIdParamSchema$6,
267254
269309
  success: { data: sessionWarningsResponseSchema },
267255
269310
  errors: {
267256
- [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269311
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$5 },
267257
269312
  [ErrorCode.SESSION_NOT_FOUND]: {}
267258
269313
  },
267259
269314
  description: "Get session-level warnings (e.g. oversized AGENTS.md)",
@@ -267409,7 +269464,7 @@ function sendMappedError$4(reply, req, err) {
267409
269464
  log?.error({ err }, "session request failed");
267410
269465
  reply.send(errEnvelope(ErrorCode.INTERNAL_ERROR, err instanceof Error ? err.message : String(err), requestId, err instanceof Error ? err.stack : void 0));
267411
269466
  }
267412
- var booleanQueryParam, DEFAULT_SESSION_LIST_PAGE_SIZE, sessionsListQueryCoercion, sessionIdParamSchema$5, sessionChildrenListQueryCoercion, sessionActionTailParamSchema, sessionActionRequestSchema, detailsSchema$4, DEFAULT_UNDO_MESSAGE_PAGE_SIZE, MAX_UNDO_MESSAGE_PAGE_SIZE;
269467
+ var booleanQueryParam, DEFAULT_SESSION_LIST_PAGE_SIZE, sessionsListQueryCoercion, sessionIdParamSchema$6, sessionChildrenListQueryCoercion, sessionActionTailParamSchema, sessionActionRequestSchema, detailsSchema$5, DEFAULT_UNDO_MESSAGE_PAGE_SIZE, MAX_UNDO_MESSAGE_PAGE_SIZE;
267413
269468
  var init_sessions = __esmMin((() => {
267414
269469
  init_src$6();
267415
269470
  init_error_codes();
@@ -267453,7 +269508,7 @@ var init_sessions = __esmMin((() => {
267453
269508
  params: { code: ErrorCode.VALIDATION_FAILED }
267454
269509
  });
267455
269510
  });
267456
- sessionIdParamSchema$5 = object({ session_id: string$2().min(1) });
269511
+ sessionIdParamSchema$6 = object({ session_id: string$2().min(1) });
267457
269512
  sessionChildrenListQueryCoercion = object({
267458
269513
  before_id: string$2().min(1).optional(),
267459
269514
  after_id: string$2().min(1).optional(),
@@ -267475,7 +269530,7 @@ var init_sessions = __esmMin((() => {
267475
269530
  count: number$2().int().positive().optional(),
267476
269531
  page_size: number$2().int().min(1).max(100).optional()
267477
269532
  }));
267478
- detailsSchema$4 = array$2(object({
269533
+ detailsSchema$5 = array$2(object({
267479
269534
  path: string$2(),
267480
269535
  message: string$2()
267481
269536
  }));
@@ -267483,6 +269538,60 @@ var init_sessions = __esmMin((() => {
267483
269538
  MAX_UNDO_MESSAGE_PAGE_SIZE = 100;
267484
269539
  }));
267485
269540
  //#endregion
269541
+ //#region ../../packages/kap-server/src/routes/shell.ts
269542
+ function registerShellRoute(app, core) {
269543
+ const runShellCommandRoute = defineRoute({
269544
+ method: "POST",
269545
+ path: "/sessions/{session_id}/shell",
269546
+ params: sessionIdParamSchema$5,
269547
+ body: shellCommandRequestSchema,
269548
+ success: { data: shellCommandResultSchema },
269549
+ errors: {
269550
+ [ErrorCode.VALIDATION_FAILED]: { detailsSchema: detailsSchema$4 },
269551
+ [ErrorCode.SESSION_NOT_FOUND]: {}
269552
+ },
269553
+ description: "Run a user-initiated `!` shell command in a session",
269554
+ tags: ["sessions"]
269555
+ }, async (req, reply) => {
269556
+ const { session_id } = req.params;
269557
+ const session = await core.accessor.get(ISessionLifecycleService).resume(session_id);
269558
+ if (session === void 0) {
269559
+ reply.send(errEnvelope(ErrorCode.SESSION_NOT_FOUND, `session ${session_id} does not exist`, req.id));
269560
+ return;
269561
+ }
269562
+ const result = await (await ensureMainAgent(session)).accessor.get(IAgentShellCommandService).run({
269563
+ command: req.body.command,
269564
+ commandId: req.body.commandId
269565
+ });
269566
+ reply.send(okEnvelope(result, req.id));
269567
+ });
269568
+ app.post(runShellCommandRoute.path, runShellCommandRoute.options, runShellCommandRoute.handler);
269569
+ }
269570
+ var sessionIdParamSchema$5, shellCommandRequestSchema, shellCommandResultSchema, detailsSchema$4;
269571
+ var init_shell = __esmMin((() => {
269572
+ init_src$6();
269573
+ init_zod$1();
269574
+ init_envelope();
269575
+ init_defineRoute();
269576
+ init_error_codes();
269577
+ init_mainAgent();
269578
+ sessionIdParamSchema$5 = object({ session_id: string$2().min(1) });
269579
+ shellCommandRequestSchema = object({
269580
+ command: string$2(),
269581
+ commandId: string$2().optional()
269582
+ });
269583
+ shellCommandResultSchema = object({
269584
+ stdout: string$2(),
269585
+ stderr: string$2(),
269586
+ isError: boolean$2().optional(),
269587
+ backgrounded: boolean$2().optional()
269588
+ });
269589
+ detailsSchema$4 = array$2(object({
269590
+ path: string$2(),
269591
+ message: string$2()
269592
+ }));
269593
+ }));
269594
+ //#endregion
267486
269595
  //#region ../../packages/kap-server/src/routes/shutdown.ts
267487
269596
  function registerShutdownRoutes(app, opts) {
267488
269597
  const route = defineRoute({
@@ -269718,6 +271827,11 @@ async function registerApiV1Routes(app, core, opts) {
269718
271827
  registerConfigRoutes(apiV1, core);
269719
271828
  registerModelCatalogRoutes(apiV1, core);
269720
271829
  registerSessionsRoutes(apiV1, core);
271830
+ registerShellRoute(apiV1, core);
271831
+ registerEventsRoute(apiV1, {
271832
+ broadcaster: opts.broadcaster,
271833
+ core
271834
+ });
269721
271835
  registerSessionExportRoute(apiV1, core, { serverVersion: opts.serverVersion });
269722
271836
  registerSkillsRoutes(apiV1, core);
269723
271837
  registerMessagesRoutes(apiV1, core);
@@ -269776,6 +271890,7 @@ var init_registerApiV1Routes = __esmMin((() => {
269776
271890
  init_guiStore();
269777
271891
  init_messages();
269778
271892
  init_registerDebugRoutes();
271893
+ init_events();
269779
271894
  init_meta();
269780
271895
  init_modelCatalog();
269781
271896
  init_oauth();
@@ -269783,6 +271898,7 @@ var init_registerApiV1Routes = __esmMin((() => {
269783
271898
  init_questions();
269784
271899
  init_sessionExport();
269785
271900
  init_sessions();
271901
+ init_shell();
269786
271902
  init_shutdown();
269787
271903
  init_snapshot();
269788
271904
  init_skills$1();
@@ -273744,7 +275860,9 @@ var DEFAULT_MAX_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE,
273744
275860
  var init_wsConnectionV1 = __esmMin((() => {
273745
275861
  init_ws_control();
273746
275862
  init_src$2();
275863
+ init_src$6();
273747
275864
  init_node$3();
275865
+ init_error_codes();
273748
275866
  init_protocol();
273749
275867
  init_sessionEventBroadcaster();
273750
275868
  init_fsWatchBridge();
@@ -273762,6 +275880,7 @@ var init_wsConnectionV1 = __esmMin((() => {
273762
275880
  socket;
273763
275881
  broadcaster;
273764
275882
  fsWatchBridge;
275883
+ core;
273765
275884
  validateCredential;
273766
275885
  maxBufferSize;
273767
275886
  flushIntervalMs;
@@ -273773,6 +275892,12 @@ var init_wsConnectionV1 = __esmMin((() => {
273773
275892
  /** Per-session subscription state: legacy agent allowlist + opt-in transcript grades. */
273774
275893
  subscriptions = /* @__PURE__ */ new Map();
273775
275894
  /**
275895
+ * Terminal services this connection has attached to (one per session,
275896
+ * resolved lazily). Tracked so teardown can detach every terminal sink
275897
+ * the connection owns.
275898
+ */
275899
+ terminalServices = /* @__PURE__ */ new Set();
275900
+ /**
273776
275901
  * Serializes control-frame handling in receive order. Frames arrive
273777
275902
  * back-to-back (e.g. `client_hello` immediately followed by
273778
275903
  * `subscribe_v2`), and a later handler reads subscription state the
@@ -273794,6 +275919,7 @@ var init_wsConnectionV1 = __esmMin((() => {
273794
275919
  this.socket = opts.socket;
273795
275920
  this.broadcaster = opts.broadcaster;
273796
275921
  this.fsWatchBridge = opts.fsWatchBridge;
275922
+ this.core = opts.core;
273797
275923
  this.validateCredential = opts.validateCredential;
273798
275924
  this.logger = opts.logger;
273799
275925
  this.maxBufferSize = opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
@@ -273857,6 +275983,21 @@ var init_wsConnectionV1 = __esmMin((() => {
273857
275983
  case "watch_fs_remove":
273858
275984
  this.enqueueControl(() => this.onWatchFs(frame, false));
273859
275985
  return;
275986
+ case "terminal_attach":
275987
+ this.enqueueControl(() => this.onTerminalAttach(frame));
275988
+ return;
275989
+ case "terminal_input":
275990
+ this.enqueueControl(() => this.onTerminalInput(frame));
275991
+ return;
275992
+ case "terminal_resize":
275993
+ this.enqueueControl(() => this.onTerminalResize(frame));
275994
+ return;
275995
+ case "terminal_close":
275996
+ this.enqueueControl(() => this.onTerminalClose(frame));
275997
+ return;
275998
+ case "terminal_detach":
275999
+ this.enqueueControl(() => this.onTerminalDetach(frame));
276000
+ return;
273860
276001
  default: return;
273861
276002
  }
273862
276003
  }
@@ -274002,6 +276143,120 @@ var init_wsConnectionV1 = __esmMin((() => {
274002
276143
  }));
274003
276144
  }
274004
276145
  /**
276146
+ * `terminal_attach` — attach this connection's sink to a session terminal
276147
+ * stream. The sink's frames (`terminal_output` / `terminal_exit`) are
276148
+ * delivered over the same subscription buffer as session events (coalesced
276149
+ * only when mergeable — terminal frames never merge, they just share the
276150
+ * flush window). `since_seq` replays buffered frames past the cursor, like
276151
+ * the REST/WS attach contract.
276152
+ */
276153
+ async onTerminalAttach(frame) {
276154
+ const parsed = terminalAttachMessageSchema.safeParse(frame);
276155
+ if (!parsed.success) {
276156
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 1, "invalid terminal_attach payload", {}));
276157
+ return;
276158
+ }
276159
+ const { session_id, terminal_id, since_seq } = parsed.data.payload;
276160
+ try {
276161
+ const terminals = await this.resolveTerminalService(session_id);
276162
+ const sink = {
276163
+ id: this.id,
276164
+ send: (terminalFrame) => this.sendSubscribedFrame(terminalFrame)
276165
+ };
276166
+ const { replayed } = await terminals.attach(terminal_id, sink, { sinceSeq: since_seq });
276167
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 0, "success", {
276168
+ attached: true,
276169
+ replayed
276170
+ }));
276171
+ } catch (error) {
276172
+ this.sendTerminalErrorAck(frame.id, error);
276173
+ }
276174
+ }
276175
+ /** `terminal_input` — write bytes to the attached terminal's pty. */
276176
+ async onTerminalInput(frame) {
276177
+ const parsed = terminalInputMessageSchema.safeParse(frame);
276178
+ if (!parsed.success) {
276179
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 1, "invalid terminal_input payload", {}));
276180
+ return;
276181
+ }
276182
+ const { session_id, terminal_id, data } = parsed.data.payload;
276183
+ try {
276184
+ await (await this.resolveTerminalService(session_id)).write(terminal_id, data);
276185
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 0, "success", { accepted: true }));
276186
+ } catch (error) {
276187
+ this.sendTerminalErrorAck(frame.id, error);
276188
+ }
276189
+ }
276190
+ /** `terminal_resize` — resize the attached terminal's pty. */
276191
+ async onTerminalResize(frame) {
276192
+ const parsed = terminalResizeMessageSchema.safeParse(frame);
276193
+ if (!parsed.success) {
276194
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 1, "invalid terminal_resize payload", {}));
276195
+ return;
276196
+ }
276197
+ const { session_id, terminal_id, cols, rows } = parsed.data.payload;
276198
+ try {
276199
+ await (await this.resolveTerminalService(session_id)).resize(terminal_id, cols, rows);
276200
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 0, "success", { resized: true }));
276201
+ } catch (error) {
276202
+ this.sendTerminalErrorAck(frame.id, error);
276203
+ }
276204
+ }
276205
+ /** `terminal_close` — close the terminal's pty (idempotent). */
276206
+ async onTerminalClose(frame) {
276207
+ const parsed = terminalCloseMessageSchema.safeParse(frame);
276208
+ if (!parsed.success) {
276209
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 1, "invalid terminal_close payload", {}));
276210
+ return;
276211
+ }
276212
+ const { session_id, terminal_id } = parsed.data.payload;
276213
+ try {
276214
+ await (await this.resolveTerminalService(session_id)).close(terminal_id);
276215
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 0, "success", { closed: true }));
276216
+ } catch (error) {
276217
+ this.sendTerminalErrorAck(frame.id, error);
276218
+ }
276219
+ }
276220
+ /** `terminal_detach` — detach this connection's sink from a terminal stream. */
276221
+ async onTerminalDetach(frame) {
276222
+ const parsed = terminalDetachMessageSchema.safeParse(frame);
276223
+ if (!parsed.success) {
276224
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 1, "invalid terminal_detach payload", {}));
276225
+ return;
276226
+ }
276227
+ const { session_id, terminal_id } = parsed.data.payload;
276228
+ try {
276229
+ (await this.resolveTerminalService(session_id)).detach(terminal_id, this.id);
276230
+ this.sendImmediateFrame(buildAck(frame.id ?? "", 0, "success", { detached: true }));
276231
+ } catch (error) {
276232
+ this.sendTerminalErrorAck(frame.id, error);
276233
+ }
276234
+ }
276235
+ /**
276236
+ * Resolve a session's `ISessionTerminalService` (cold-loading a
276237
+ * persisted-but-not-live session, matching the REST terminal route).
276238
+ * The resolved service is remembered so `onClose` can detach every sink
276239
+ * this connection owns.
276240
+ */
276241
+ async resolveTerminalService(sessionId) {
276242
+ const session = await this.core.accessor.get(ISessionLifecycleService).resume(sessionId);
276243
+ if (session === void 0) throw new Error2(ErrorCodes.SESSION_NOT_FOUND, `session ${sessionId} does not exist`);
276244
+ const terminals = session.accessor.get(ISessionTerminalService);
276245
+ this.terminalServices.add(terminals);
276246
+ return terminals;
276247
+ }
276248
+ /** Ack a terminal control failure with the wire error code when known. */
276249
+ sendTerminalErrorAck(id, error) {
276250
+ let code = 1;
276251
+ let msg = "internal error";
276252
+ if (isError2(error)) {
276253
+ if (error.code === ErrorCodes.SESSION_NOT_FOUND) code = ErrorCode.SESSION_NOT_FOUND;
276254
+ else if (error.code === ErrorCodes.TERMINAL_NOT_FOUND) code = ErrorCode.TERMINAL_NOT_FOUND;
276255
+ msg = error.message;
276256
+ } else if (error instanceof Error) msg = error.message;
276257
+ this.sendImmediateFrame(buildAck(id ?? "", code, msg, {}));
276258
+ }
276259
+ /**
274005
276260
  * Shared attach path behind `client_hello` (legacy inline subscriptions)
274006
276261
  * and `subscribe`. Subscribes the connection via the broadcaster, then
274007
276262
  * either replays durable events since the client's cursor (with the
@@ -274146,6 +276401,7 @@ var init_wsConnectionV1 = __esmMin((() => {
274146
276401
  this.outbound = [];
274147
276402
  this.broadcaster.removeGlobalTarget(this);
274148
276403
  for (const sid of this.subscriptions.keys()) this.broadcaster.unsubscribe(sid, this);
276404
+ for (const terminals of this.terminalServices) terminals.detachAllForSink(this.id);
274149
276405
  this.fsWatchBridge?.detachConnection(this);
274150
276406
  }
274151
276407
  };
@@ -274164,6 +276420,7 @@ function registerWsV1(core, opts) {
274164
276420
  broadcaster,
274165
276421
  fsWatchBridge: opts.fsWatchBridge,
274166
276422
  connectionRegistry: registry,
276423
+ core,
274167
276424
  validateCredential: opts.validateCredential,
274168
276425
  remoteAddress: req.socket.remoteAddress ?? null,
274169
276426
  userAgent: req.headers["user-agent"] ?? null,
@@ -296465,6 +298722,15 @@ function stringifyToolOutput(output) {
296465
298722
  if (typeof output === "string") return output;
296466
298723
  return JSON.stringify(output) ?? String(output);
296467
298724
  }
298725
+ function stringifyToolArgs(args) {
298726
+ if (typeof args === "string") return args;
298727
+ return JSON.stringify(args) ?? String(args);
298728
+ }
298729
+ function truncateChars(text, max) {
298730
+ if (text.length <= max) return text;
298731
+ const suffix = `… (${text.length - max} more chars)`;
298732
+ return `${text.slice(0, max - suffix.length)}${suffix}`;
298733
+ }
296468
298734
  function writeVersion(version, outputFormat, stdout, stderr) {
296469
298735
  if (outputFormat === "stream-json") {
296470
298736
  const message = {
@@ -296493,16 +298759,23 @@ function writeResumeHint(sessionId, outputFormat, stdout, stderr) {
296493
298759
  }
296494
298760
  stderr.write(`${content}\n`);
296495
298761
  }
296496
- var PROMPT_BLOCK_BULLET, PROMPT_BLOCK_INDENT, PromptTranscriptWriter, PromptJsonWriter, PromptBlockWriter;
298762
+ var PROMPT_BLOCK_BULLET, PROMPT_BLOCK_INDENT, TOOL_CALL_MARK, TOOL_RESULT_MARK, RETRY_MARK, MAX_TOOL_CALL_ARGS_CHARS, MAX_TOOL_RESULT_CHARS, PromptTranscriptWriter, PromptJsonWriter, PromptBlockWriter;
296497
298763
  var init_prompt_render = __esmMin((() => {
296498
298764
  PROMPT_BLOCK_BULLET = "• ";
296499
298765
  PROMPT_BLOCK_INDENT = " ";
298766
+ TOOL_CALL_MARK = "⚒ ";
298767
+ TOOL_RESULT_MARK = "⚒ result: ";
298768
+ RETRY_MARK = "↻ retry";
298769
+ MAX_TOOL_CALL_ARGS_CHARS = 500;
298770
+ MAX_TOOL_RESULT_CHARS = 2e3;
296500
298771
  PromptTranscriptWriter = class {
296501
298772
  assistantWriter;
296502
298773
  thinkingWriter;
298774
+ toolWriter;
296503
298775
  constructor(stdout, stderr) {
296504
298776
  this.assistantWriter = new PromptBlockWriter(stdout);
296505
298777
  this.thinkingWriter = new PromptBlockWriter(stderr);
298778
+ this.toolWriter = new PromptBlockWriter(stderr);
296506
298779
  }
296507
298780
  writeAssistantDelta(delta) {
296508
298781
  this.thinkingWriter.finish();
@@ -296517,10 +298790,25 @@ var init_prompt_render = __esmMin((() => {
296517
298790
  writeThinkingDelta(delta) {
296518
298791
  this.thinkingWriter.write(delta);
296519
298792
  }
296520
- writeToolCall() {}
298793
+ writeToolCall(toolCallId, name, args) {
298794
+ this.thinkingWriter.finish();
298795
+ this.assistantWriter.finish();
298796
+ this.toolWriter.write(`${TOOL_CALL_MARK}${name}(${truncateChars(stringifyToolArgs(args), MAX_TOOL_CALL_ARGS_CHARS)})`);
298797
+ this.toolWriter.finish();
298798
+ }
296521
298799
  writeToolCallDelta() {}
296522
- writeToolResult() {}
296523
- writeRetrying() {}
298800
+ writeToolResult(toolCallId, output) {
298801
+ this.toolWriter.finish();
298802
+ this.toolWriter.write(`${TOOL_RESULT_MARK}${truncateChars(stringifyToolOutput(output), MAX_TOOL_RESULT_CHARS)}`);
298803
+ this.toolWriter.finish();
298804
+ }
298805
+ writeRetrying(event) {
298806
+ this.thinkingWriter.finish();
298807
+ this.assistantWriter.finish();
298808
+ const error = [event.errorName, event.errorMessage].filter(Boolean).join(": ");
298809
+ this.toolWriter.write(`${RETRY_MARK} ${event.failedAttempt}/${event.maxAttempts} (${error}) — ${event.delayMs}ms`);
298810
+ this.toolWriter.finish();
298811
+ }
296524
298812
  flushAssistant() {
296525
298813
  this.assistantWriter.finish();
296526
298814
  }
@@ -296528,6 +298816,7 @@ var init_prompt_render = __esmMin((() => {
296528
298816
  finish() {
296529
298817
  this.thinkingWriter.finish();
296530
298818
  this.assistantWriter.finish();
298819
+ this.toolWriter.finish();
296531
298820
  }
296532
298821
  };
296533
298822
  PromptJsonWriter = class {