@bastani/atomic 0.9.58-alpha.1 → 0.9.59-alpha.1

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 (36) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +12 -0
  3. package/dist/builtin/intercom/broker/spawn.ts +22 -2
  4. package/dist/builtin/intercom/index.bundle.mjs +11 -2
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  8. package/dist/builtin/subagents/README.md +3 -1
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/src/extension/index.bundle.mjs +163 -33
  11. package/dist/builtin/web-access/package.json +1 -1
  12. package/dist/builtin/workflows/package.json +1 -1
  13. package/dist/builtin/workflows/src/extension/index.bundle.mjs +11 -2
  14. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +11 -2
  15. package/dist/builtin/workflows/src/index.js +11 -2
  16. package/dist/builtin/workflows/src/local-worker-entry.mjs +11 -2
  17. package/dist/core/child-session-options.d.ts.map +1 -1
  18. package/dist/core/child-session-options.js +3 -1
  19. package/dist/core/child-session-options.js.map +1 -1
  20. package/dist/core/model-runtime-providers.d.ts +2 -2
  21. package/dist/core/model-runtime-providers.d.ts.map +1 -1
  22. package/dist/core/model-runtime-providers.js +8 -4
  23. package/dist/core/model-runtime-providers.js.map +1 -1
  24. package/dist/core/model-runtime.d.ts.map +1 -1
  25. package/dist/core/model-runtime.js +1 -1
  26. package/dist/core/model-runtime.js.map +1 -1
  27. package/dist/core/sdk-types.d.ts +6 -1
  28. package/dist/core/sdk-types.d.ts.map +1 -1
  29. package/dist/core/sdk-types.js.map +1 -1
  30. package/dist/core/sdk.js +4 -2
  31. package/dist/core/sdk.js.map +1 -1
  32. package/docs/providers.md +6 -6
  33. package/docs/sdk.md +3 -1
  34. package/docs/subagents/reference.md +2 -0
  35. package/npm-shrinkwrap.json +32 -32
  36. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.59] - 2026-11-01
6
+
7
+ GitHub Copilot tokens that work from `/login`, and intercom that recovers on Windows.
8
+
9
+ - **Copilot tokens saved through `/login`:** a GitHub Copilot personal access token saved with `/login` → **Use an API key** now works for business and enterprise accounts, so you no longer need `COPILOT_GITHUB_TOKEN` to avoid `421 Misdirected Request` ([#4151](https://github.com/bastani-inc/atomic/pull/4151))
10
+ - **Intercom recovers on Windows:** when the intercom broker dies, sessions start a new one right away instead of waiting out the start-up timeout and failing with `Broker failed to start within timeout` ([#4152](https://github.com/bastani-inc/atomic/pull/4152))
11
+
12
+ ### Fixed
13
+
14
+ - Fixed GitHub Copilot personal access tokens saved through `/login` → **Use an API key** being sent to the individual Copilot host, where business and enterprise accounts fail with `421 Misdirected Request`. Saved tokens now get the same endpoint routing as `COPILOT_GITHUB_TOKEN`, so the environment variable is no longer required. A `models.json` `baseUrl` still takes precedence, and OAuth logins are unchanged. ([#4151](https://github.com/bastani-inc/atomic/pull/4151))
15
+
16
+ ## [0.9.59-alpha.1] - 2026-11-01
17
+
18
+ GitHub Copilot tokens that work from `/login`, and intercom that recovers on Windows.
19
+
20
+ - **Copilot tokens saved through `/login`:** a GitHub Copilot personal access token saved with `/login` → **Use an API key** now works for business and enterprise accounts, so you no longer need `COPILOT_GITHUB_TOKEN` to avoid `421 Misdirected Request` ([#4151](https://github.com/bastani-inc/atomic/pull/4151))
21
+ - **Intercom recovers on Windows:** when the intercom broker dies, sessions start a new one right away instead of waiting out the start-up timeout and failing with `Broker failed to start within timeout` ([#4152](https://github.com/bastani-inc/atomic/pull/4152))
22
+
23
+ ### Fixed
24
+
25
+ - Fixed GitHub Copilot personal access tokens saved through `/login` → **Use an API key** being sent to the individual Copilot host, where business and enterprise accounts fail with `421 Misdirected Request`. Saved tokens now get the same endpoint routing as `COPILOT_GITHUB_TOKEN`, so the environment variable is no longer required. A `models.json` `baseUrl` still takes precedence, and OAuth logins are unchanged. ([#4151](https://github.com/bastani-inc/atomic/pull/4151))
26
+
5
27
  ## [0.9.58] - 2026-11-01
6
28
 
7
29
  Task heartbeats, stage chats that work on local workers, and a local worker pool that holds up under load.
@@ -11,6 +33,7 @@ Task heartbeats, stage chats that work on local workers, and a local worker pool
11
33
  - **A local worker pool that holds up under load:** reloading workflows or starting other runs no longer fails a running root's children with `Stale lease for run <id>`, a busy machine no longer fails stages on delivery or lease errors, `/workflow pause` pauses a root's stages even while its own steps run, and an overloaded machine starts stages one at a time, tunable with `workflows.localWorkers.maxLoadPerCpu` ([#4138](https://github.com/bastani-inc/atomic/pull/4138), [#4145](https://github.com/bastani-inc/atomic/pull/4145), [#4146](https://github.com/bastani-inc/atomic/pull/4146))
12
34
  - **Resuming pooled runs:** a root that runs children on local workers resumes from a new process, including one paused or failed by 0.9.57, and runs again the children that failed on a pool fault instead of replaying the failure ([#4146](https://github.com/bastani-inc/atomic/pull/4146))
13
35
  - **Stopped tasks finish:** a shell stopped with `kill`, from `/tasks` or with Esc no longer stays "stopping" in the Tasks indicator forever; it ends as failed with the resource that could not be cleaned up ([#4142](https://github.com/bastani-inc/atomic/pull/4142))
36
+ - **Subagents that start and run as asked:** a subagent launched with an effort such as `model: "openai/gpt-5.5:xhigh"`, or from a chat turn that a workflow started, no longer stays `running` with no session, and one whose session still does not start within 5 minutes fails with the cause. An effort applies exactly where the model supports it, including on a fallback that starts in place of the requested model, and `status` lists only the children your session launched ([#4148](https://github.com/bastani-inc/atomic/pull/4148), [#4150](https://github.com/bastani-inc/atomic/pull/4150))
14
37
  - **Quieter PDF fetches:** pdf.js font warnings such as `Warning: TT: undefined function: 32` no longer print over the terminal UI when a PDF is fetched ([#4136](https://github.com/bastani-inc/atomic/pull/4136))
15
38
 
16
39
  ### Added
@@ -27,6 +50,10 @@ Task heartbeats, stage chats that work on local workers, and a local worker pool
27
50
  - `/workflow pause` pauses a root's stages on local workers even while the root's own steps run, instead of reporting `No active stages to pause`, and pause or resume no longer fails with `Worker control acknowledgement unresolved` on a busy machine; the result says how many stages have not confirmed the pause yet. ([#4145](https://github.com/bastani-inc/atomic/pull/4145))
28
51
  - Resuming a workflow root that runs children on local workers, including one paused or failed by 0.9.57, runs again the children an earlier process failed on a fault of the pool itself instead of replaying their failure, and a paused root with durable progress says it can be resumed from a new process. ([#4146](https://github.com/bastani-inc/atomic/pull/4146))
29
52
  - On an overloaded machine, stages of workflow children on local workers start one at a time, at most one every 5 seconds, instead of all at once while earlier ones are still starting. Tune or turn off with `workflows.localWorkers.maxLoadPerCpu` (default `3`, `0` off). ([#4146](https://github.com/bastani-inc/atomic/pull/4146))
53
+ - A subagent launched from a chat turn that a running workflow started, for example with its completion notice, no longer stays `running` for good with no session, transcript or Intercom name. Its session waited for a permit from that workflow's resource admission (such as `admission.host.minFreeMemoryBytes` on a busy host) that it never received. A child session built from `getChildSessionOptions()` now takes its parent's admission or none, never one a `withResourceAdmission` scope the parent's turn happened to run in; SDK callers can pass `resourceAdmission: null` for a session that takes no permit inside such a scope. ([#4148](https://github.com/bastani-inc/atomic/pull/4148))
54
+ - A subagent whose session does not start within 5 minutes now fails with `Subagent session did not start within 5 min (still <step>)` instead of staying `running`, and `subagent({ action: "status" })` shows it failed with that cause, in its text and its expanded card. ([#4148](https://github.com/bastani-inc/atomic/pull/4148), [#4150](https://github.com/bastani-inc/atomic/pull/4150))
55
+ - A subagent launched with an effort, such as `model: "openai/gpt-5.5:xhigh"`, runs at exactly that effort on a model that supports it, and at that model's default level (its `modelThinkingLevels` entry, else `defaultThinkingLevel`) on one that does not, instead of the nearest level. A model its fallback chain switches to follows the same rule, so a fallback no longer drops the child to the default level silently, and a fallback the child starts on because the requested model's provider has no auth runs at its own effort. ([#4148](https://github.com/bastani-inc/atomic/pull/4148), [#4150](https://github.com/bastani-inc/atomic/pull/4150))
56
+ - `subagent({ action: "status" })` lists only the children this session launched, each with the child id `pause`, `resume` and `quit` take (`· id <run-id>:<n>`), and those actions also take the listed path. It no longer lists children of other sessions in the same process, such as a workflow stage's, that `quit` then refused with `this session launched no such child`, and `status` with a child id finds the child. ([#4148](https://github.com/bastani-inc/atomic/pull/4148))
30
57
 
31
58
  ## [0.9.58-alpha.1] - 2026-11-01
32
59
 
@@ -4,6 +4,18 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.59] - 2026-11-01
8
+
9
+ ### Fixed
10
+
11
+ - On Windows, sessions reconnect to a new intercom broker right after the running one dies, instead of waiting out the whole broker start-up timeout and failing with `Broker failed to start within timeout`. The dead broker's hidden `cmd.exe` launcher holds `broker.log` for a moment after the broker exits, and the respawn mistook that for another broker still starting; it now waits up to a second for the log to be released first. ([#4152](https://github.com/bastani-inc/atomic/pull/4152))
12
+
13
+ ## [0.9.59-alpha.1] - 2026-11-01
14
+
15
+ ### Fixed
16
+
17
+ - On Windows, sessions reconnect to a new intercom broker right after the running one dies, instead of waiting out the whole broker start-up timeout and failing with `Broker failed to start within timeout`. The dead broker's hidden `cmd.exe` launcher holds `broker.log` for a moment after the broker exits, and the respawn mistook that for another broker still starting; it now waits up to a second for the log to be released first. ([#4152](https://github.com/bastani-inc/atomic/pull/4152))
18
+
7
19
  ## [0.9.56] - 2026-10-30
8
20
 
9
21
  ### Fixed
@@ -291,9 +291,15 @@ export function getBrokerSpawnOptions(
291
291
  };
292
292
  }
293
293
 
294
+ /**
295
+ * How long a spawn waits for the Windows launcher of a broker that just exited to let go of the log. Its `cmd.exe`
296
+ * outlives the broker by a moment, and a broker that is really still starting keeps the log for as long as it runs.
297
+ */
298
+ export const BROKER_LOG_RELEASE_WAIT_MS = 1_000;
299
+
294
300
  /**
295
301
  * Preserve a bounded previous log across truncation. Returns false when a
296
- * Windows broker that is still starting holds the log through its launcher's stderr redirect.
302
+ * Windows launcher holds the log through its stderr redirect.
297
303
  */
298
304
  function resetBrokerLog(logPath: string = BROKER_LOG): boolean {
299
305
  ensurePrivateDirectory(dirname(logPath));
@@ -315,6 +321,20 @@ function resetBrokerLog(logPath: string = BROKER_LOG): boolean {
315
321
  }
316
322
  }
317
323
 
324
+ /**
325
+ * Reset the log, waiting up to {@link BROKER_LOG_RELEASE_WAIT_MS} for the launcher of a broker that just exited, as
326
+ * one does when a running broker dies and its clients reconnect at once. Returns false only when the log stays held,
327
+ * which means a broker is still starting.
328
+ */
329
+ async function resetBrokerLogOnceReleased(): Promise<boolean> {
330
+ const deadline = Date.now() + BROKER_LOG_RELEASE_WAIT_MS;
331
+ while (!resetBrokerLog()) {
332
+ if (Date.now() >= deadline) return false;
333
+ await sleep(50);
334
+ }
335
+ return true;
336
+ }
337
+
318
338
  /** Read at most {@link BROKER_LOG_TAIL_BYTES} trailing bytes of the broker log. */
319
339
  export function readBrokerLogTail(logPath: string = BROKER_LOG, maxBytes: number = BROKER_LOG_TAIL_BYTES): string {
320
340
  let fd: number | undefined;
@@ -392,7 +412,7 @@ export async function spawnBrokerIfNeeded(
392
412
  writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
393
413
  }
394
414
  // Reset before spawning either way: the Windows launcher appends to this same path.
395
- if (!resetBrokerLog()) {
415
+ if (!(await resetBrokerLogOnceReleased())) {
396
416
  await waitForBroker(readyTimeoutMs);
397
417
  return;
398
418
  }
@@ -2487,6 +2487,15 @@ function resetBrokerLog(logPath = BROKER_LOG) {
2487
2487
  throw error;
2488
2488
  }
2489
2489
  }
2490
+ async function resetBrokerLogOnceReleased() {
2491
+ const deadline = Date.now() + BROKER_LOG_RELEASE_WAIT_MS;
2492
+ while (!resetBrokerLog()) {
2493
+ if (Date.now() >= deadline)
2494
+ return false;
2495
+ await sleep(50);
2496
+ }
2497
+ return true;
2498
+ }
2490
2499
  function readBrokerLogTail(logPath = BROKER_LOG, maxBytes = BROKER_LOG_TAIL_BYTES) {
2491
2500
  let fd;
2492
2501
  try {
@@ -2545,7 +2554,7 @@ async function spawnBrokerIfNeeded(brokerCommand, brokerArgs, readyTimeoutMs = B
2545
2554
  if (launch.kind === "windows-launcher") {
2546
2555
  writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
2547
2556
  }
2548
- if (!resetBrokerLog()) {
2557
+ if (!await resetBrokerLogOnceReleased()) {
2549
2558
  await waitForBroker(readyTimeoutMs);
2550
2559
  return;
2551
2560
  }
@@ -2718,7 +2727,7 @@ ${describeBrokerLog()}`);
2718
2727
  ` + "The broker did not answer on its socket in time; it may still be starting, which a busy machine or an empty jiti " + "cache slows. Try again, or raise `brokerStartupTimeoutMs` in the Intercom config.json (default " + `${BROKER_READY_TIMEOUT_MS} ms).
2719
2728
  ${describeBrokerLog()}`);
2720
2729
  }
2721
- var INTERCOM_DIR, EXTENSION_DIR, BROKER_SOCKET2, BROKER_PID, BROKER_SPAWN_LOCK, BROKER_LOG, BROKER_START_ANNOUNCEMENT, BROKER_LOG_TAIL_BYTES, BROKER_POLL_INITIAL_INTERVAL_MS = 10, BROKER_POLL_MAX_INTERVAL_MS = 100, BROKER_SPAWN_LOCK_MAX_AGE_MS = 60000, INTERNAL_INTERCOM_BROKER_ARG = "--atomic-internal-intercom-broker";
2730
+ var INTERCOM_DIR, EXTENSION_DIR, BROKER_SOCKET2, BROKER_PID, BROKER_SPAWN_LOCK, BROKER_LOG, BROKER_START_ANNOUNCEMENT, BROKER_LOG_TAIL_BYTES, BROKER_POLL_INITIAL_INTERVAL_MS = 10, BROKER_POLL_MAX_INTERVAL_MS = 100, BROKER_SPAWN_LOCK_MAX_AGE_MS = 60000, INTERNAL_INTERCOM_BROKER_ARG = "--atomic-internal-intercom-broker", BROKER_LOG_RELEASE_WAIT_MS = 1000;
2722
2731
  var init_spawn = __esm(() => {
2723
2732
  init_socket_liveness();
2724
2733
  init_start_announcement();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.58-alpha.1",
3
+ "version": "0.9.59-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.58-alpha.1",
3
+ "version": "0.9.59-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic's pi-compatible MCP integration for server connections, OAuth, tools, resources, and terminal management.",
6
6
  "contributors": [
@@ -8,6 +8,12 @@
8
8
 
9
9
  - Running subagents now send the parent periodic task heartbeats (every 15 minutes by default) with their current tool call, time since their last activity, and turn, tool-call and token counts. Set `heartbeatMs` on a `subagent` call, or on a `tasks` entry, to change a child's cadence; `0` turns it off.
10
10
 
11
+ ### Fixed
12
+
13
+ - A subagent launched from a chat turn that a running workflow started no longer stays `running` for good with no session: its session no longer waits on that workflow's resource admission. A subagent whose session still does not start within 5 minutes fails with `Subagent session did not start within 5 min (still <step>)`, and `status` shows the cause in its text and its expanded card. ([#4148](https://github.com/bastani-inc/atomic/pull/4148), [#4150](https://github.com/bastani-inc/atomic/pull/4150))
14
+ - A subagent launched with an effort, such as `model: "openai/gpt-5.5:xhigh"`, runs at exactly that effort on a model that supports it, and at that model's default level on one that does not; each model in its fallback chain follows the same rule instead of falling back to the default level, including a fallback the child starts on in place of a requested model whose provider has no auth. ([#4148](https://github.com/bastani-inc/atomic/pull/4148), [#4150](https://github.com/bastani-inc/atomic/pull/4150))
15
+ - `status` lists only the children this session launched, with the child id `pause`, `resume` and `quit` take, and those actions also take the listed path; `status` with a child id finds the child. ([#4148](https://github.com/bastani-inc/atomic/pull/4148))
16
+
11
17
  ## [0.9.58-alpha.1] - 2026-11-01
12
18
 
13
19
  ### Added
@@ -570,6 +570,8 @@ subagent({ action: "quit", id: "<child-id>" })
570
570
  subagent({ action: "quit", all: true })
571
571
  ```
572
572
 
573
+ `status` lists the children this session launched that are still in this process, each as `<run-id>/<agent>_<n> — <state> (loaded|cold) · id <child-id>`, with the cause of a failed or interrupted child after it; `pause`, `resume` and `quit` take the child id or that path, and `status` takes either to show one child. Children other sessions in the same process launched, such as a workflow stage's, are not listed, since this session cannot control them. A child whose session has not started within 5 minutes fails with `Subagent session did not start within 5 min (still <step>)` instead of staying running.
574
+
573
575
  `pause` stops a child and holds its queued messages until `resume`; `quit` stops it gracefully, including a child already paused by you or with its workflow stage. Both stay resumable while the session or workflow stage that launched the child can still resume it, as do children stopped because their workflow stage was paused or quit, including by Atomic exiting or a session switch. `resume` reopens the child's own session under the same id and Intercom name. A paused, quit, or interrupted child receives `message` once as its next prompt and then continues where it left off; a completed or failed child receives `message` as one fresh prompt. Without `message`, every resumable child, including a completed or failed one, is asked to continue where it left off. Children cancelled for good (owner or batch cancellation, including a `/tasks` stop, and the removed `kill` action), children whose workflow stage ended (the stage finished, or its run ended or was cancelled; this covers all of that stage's children, including ones you paused or quit), and children reported gone after a restart cannot be resumed: `resume` returns an error that says why, and follow-up work requires a fresh launch with explicit context. A parallel child waiting for a reply is not terminal and continues in its original execution. Parent cancellation of a still-running child uses the interrupted/abort state: receipts and progress present it as cancelled rather than failed, persisted metadata keeps the abort cause, and bounded partial findings remain available. Migrate old subagent calls from `action: "kill"` or `action: "interrupt"` to `action: "quit"`; the old actions are rejected with `The subagent action "kill" was removed. Use { action: "quit", id } to stop a child gracefully; it stays resumable with { action: "resume", id, message? }.` (naming the action that was used). Host interrupt APIs are unchanged.
574
576
 
575
577
  ## Worktree isolation
@@ -814,7 +816,7 @@ The main runtime files are:
814
816
 
815
817
  ### Suffix-first reasoning levels
816
818
 
817
- Reasoning levels are configured suffix-first using the `model_name:thinking_effort` syntax on `model` and each `fallbackModels` entry: `model: claude-sonnet-4:high` and `fallbackModels: claude-sonnet-4:medium, gpt-5:low, claude-haiku-4:off`. Canonical efforts are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. `xhigh` and `max` are forwarded only when the selected model supports them. The older `thinking` field is deprecated; it remains supported as a legacy default only when a model candidate has no suffix, and a suffix always wins.
819
+ Reasoning levels are configured suffix-first using the `model_name:thinking_effort` syntax on `model` and each `fallbackModels` entry: `model: claude-sonnet-4:high` and `fallbackModels: claude-sonnet-4:medium, gpt-5:low, claude-haiku-4:off`. Canonical efforts are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. A child runs at exactly the effort it was launched with on a model that supports it; a model that does not support it, `xhigh` or `max` on many models, runs at its own default level instead (its `modelThinkingLevels` setting, else `defaultThinkingLevel`). Fallback models follow the same rule, using their own suffix or, without one, the launch's effort. The older `thinking` field is deprecated; it remains supported as a legacy default only when a model candidate has no suffix, and a suffix always wins.
818
820
 
819
821
  Migrate legacy `thinking` frontmatter by folding the effort into `model` and `fallbackModels`:
820
822
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.58-alpha.1",
3
+ "version": "0.9.59-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -8939,6 +8939,13 @@ function subagentChildCardRecord(childId, taskId) {
8939
8939
  const id = childId ?? childIdsByTaskId.get(taskId);
8940
8940
  return id === undefined ? undefined : latest.get(id);
8941
8941
  }
8942
+ function observedSubagentRunIds(ownerSessionId) {
8943
+ const runIds = new Set;
8944
+ for (const record of latest.values())
8945
+ if (record.owner?.sessionId === ownerSessionId)
8946
+ runIds.add(record.runId);
8947
+ return runIds;
8948
+ }
8942
8949
  function watchSubagentChildCard(card, invalidate, key, ownerSessionId) {
8943
8950
  invalidators.set(card, invalidate);
8944
8951
  const cards = watchers.get(key) ?? new Set;
@@ -11825,7 +11832,10 @@ import {
11825
11832
  import {
11826
11833
  SubagentControl
11827
11834
  } from "@bastani/atomic-natives";
11828
- import { clampThinkingLevel } from "@bastani/pi-ai/compat";
11835
+ import {
11836
+ clampThinkingLevel,
11837
+ getSupportedThinkingLevels
11838
+ } from "@bastani/pi-ai/compat";
11829
11839
 
11830
11840
  // dist/builtin/subagents/src/agents/skills.ts
11831
11841
  import * as fs14 from "node:fs";
@@ -12566,6 +12576,23 @@ function createInProcessChildPromptBehavior(policy) {
12566
12576
  }
12567
12577
 
12568
12578
  // dist/builtin/subagents/src/runs/inprocess/runner.ts
12579
+ var SUBAGENT_SESSION_START_TIMEOUT_MS = 5 * 60000;
12580
+ function formatStartTimeout(ms) {
12581
+ return ms % 60000 === 0 ? `${ms / 60000} min` : `${Math.max(1, Math.round(ms / 1000))}s`;
12582
+ }
12583
+ var DEFAULT_THINKING_LEVEL = "medium";
12584
+ function explicitEffortFor(model, requested, settings) {
12585
+ if (getSupportedThinkingLevels(model).includes(requested))
12586
+ return requested;
12587
+ return clampThinkingLevel(model, settings.getModelThinkingLevel(model.provider, model.id) ?? settings.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);
12588
+ }
12589
+ function pinFallbackEffort(entry, requested, settings, resolve) {
12590
+ const resolved = resolve?.(entry);
12591
+ if (!resolved)
12592
+ return entry;
12593
+ const { baseModel } = splitKnownThinkingSuffix(entry.trim());
12594
+ return `${baseModel}:${explicitEffortFor(resolved.model, resolved.thinkingLevel ?? requested, settings)}`;
12595
+ }
12569
12596
  function modelIdForCandidate(candidate, fallbackModel) {
12570
12597
  return candidate.modelId ?? (candidate.model ? `${candidate.model.provider}/${candidate.model.id}` : undefined) ?? (fallbackModel ? `${fallbackModel.provider}/${fallbackModel.id}` : undefined);
12571
12598
  }
@@ -13018,12 +13045,23 @@ class SubagentControlRuntime {
13018
13045
  delivered = new Set;
13019
13046
  deliveredEnvelopes = new Map;
13020
13047
  killedChildren = new Set;
13048
+ childIds = new Map;
13021
13049
  nextAttemptId = 1;
13022
13050
  constructor(parent, sessionRoot, runningCapacity) {
13023
13051
  this.parent = { ...parent };
13024
13052
  this.sessionRoot = trustedSessionRoot(this.parent, sessionRoot);
13025
13053
  this.native = new SubagentControl(this.parent.path, runningCapacity);
13026
13054
  }
13055
+ childIdOf(pathValue) {
13056
+ return this.childIds.get(pathValue);
13057
+ }
13058
+ pathOfChildId(childId) {
13059
+ let latest;
13060
+ for (const [pathValue, id] of this.childIds)
13061
+ if (id === childId)
13062
+ latest = pathValue;
13063
+ return latest;
13064
+ }
13027
13065
  registerAgents(agents) {
13028
13066
  for (const agent of agents)
13029
13067
  this.native.registerAgent(agent.name);
@@ -13041,6 +13079,8 @@ class SubagentControlRuntime {
13041
13079
  if (!native.child)
13042
13080
  return refusal(native);
13043
13081
  const identity = native.child;
13082
+ if (spec.childId !== undefined)
13083
+ this.childIds.set(identity.path, spec.childId);
13044
13084
  const childModePolicy = resolveChildModePolicy(spec);
13045
13085
  const sessionDir = spec.sessionManager?.getSessionDir() || sessionDirectory(this.sessionRoot, identity.path);
13046
13086
  mkdirSync7(sessionDir, { recursive: true });
@@ -13183,9 +13223,34 @@ class SubagentControlRuntime {
13183
13223
  signals.interrupt.addEventListener("abort", interruptListener, {
13184
13224
  once: true
13185
13225
  });
13226
+ const startTimeoutMs = admitted.spec.sessionStartTimeoutMs ?? SUBAGENT_SESSION_START_TIMEOUT_MS;
13227
+ const startDeadline = new AbortController;
13228
+ let startPhase = "opening its session";
13229
+ const startTimer = setTimeout(() => startDeadline.abort(new Error(`Subagent session did not start within ${formatStartTimeout(startTimeoutMs)} (still ${startPhase}); it was stopped before its task ran.`)), startTimeoutMs);
13230
+ const beforeStartDeadline = (phase, work, abandon) => {
13231
+ startPhase = phase;
13232
+ return new Promise((resolve, reject) => {
13233
+ const expire = () => {
13234
+ work.then((late) => abandon?.(late), () => {
13235
+ return;
13236
+ });
13237
+ reject(startDeadline.signal.reason);
13238
+ };
13239
+ if (startDeadline.signal.aborted)
13240
+ return expire();
13241
+ startDeadline.signal.addEventListener("abort", expire, { once: true });
13242
+ work.then((value) => {
13243
+ startDeadline.signal.removeEventListener("abort", expire);
13244
+ resolve(value);
13245
+ }, (error) => {
13246
+ startDeadline.signal.removeEventListener("abort", expire);
13247
+ reject(error);
13248
+ });
13249
+ });
13250
+ };
13186
13251
  try {
13187
13252
  const workflow = workflowMetadataFromContext(admitted.spec.parent?.orchestrationContext);
13188
- const sessionManager = admitted.sessionManager ? await SessionManager3.open(admitted.sessionManager.getSessionStore(), admitted.sessionManager.getSessionId(), admitted.policy.cwd) : await SessionManager3.create(admitted.policy.cwd, admitted.sessionDir, workflow ? { internal: true, workflow } : { internal: true });
13253
+ const sessionManager = await beforeStartDeadline("opening its session", admitted.sessionManager ? SessionManager3.open(admitted.sessionManager.getSessionStore(), admitted.sessionManager.getSessionId(), admitted.policy.cwd) : SessionManager3.create(admitted.policy.cwd, admitted.sessionDir, workflow ? { internal: true, workflow } : { internal: true }));
13189
13254
  transcriptManager = sessionManager;
13190
13255
  taskHooks?.bindTranscript?.(sessionManager);
13191
13256
  const childSession = {
@@ -13195,7 +13260,7 @@ class SubagentControlRuntime {
13195
13260
  taskHooks?.bindChildSession?.(childSession);
13196
13261
  admitted.spec.onChildSession?.(childSession);
13197
13262
  if (workflow)
13198
- await sessionManager.markSessionInternal(workflow);
13263
+ await beforeStartDeadline("opening its session", sessionManager.markSessionInternal(workflow));
13199
13264
  const launchLeafId = sessionManager.getLeafId();
13200
13265
  ownMessages = () => messagesSinceLaunch(sessionManager, launchLeafId);
13201
13266
  let created;
@@ -13221,6 +13286,13 @@ class SubagentControlRuntime {
13221
13286
  } : inherited.extensionBindings;
13222
13287
  const agentDir = inherited.agentDir ?? getAgentDir2();
13223
13288
  const settingsManager = inherited.settingsManager ?? SettingsManager.create(admitted.policy.cwd, agentDir);
13289
+ const requested = admitted.spec.requestedThinking;
13290
+ const startSuffix = splitKnownThinkingSuffix(candidate.modelId ?? "").thinkingSuffix.slice(1);
13291
+ const startEffort = THINKING_LEVELS.find((level) => level === startSuffix) ?? requested;
13292
+ const explicitEffort = requested === undefined || startEffort === undefined ? {} : {
13293
+ ...inherited.model ? { thinkingLevel: explicitEffortFor(inherited.model, startEffort, settingsManager) } : {},
13294
+ fallbackModels: (inherited.fallbackModels ?? settingsManager.getFallbackModels() ?? []).map((entry) => pinFallbackEffort(entry, requested, settingsManager, admitted.spec.resolveCandidateModel))
13295
+ };
13224
13296
  const resourceLoader = new DefaultResourceLoader(inProcessChildResourceLoaderOptions({
13225
13297
  cwd: admitted.policy.cwd,
13226
13298
  agentDir,
@@ -13230,7 +13302,7 @@ class SubagentControlRuntime {
13230
13302
  resourceLoaderInheritanceSnapshot: admitted.spec.parent?.resourceLoaderInheritanceSnapshot,
13231
13303
  orchestrationContext: admitted.spec.parent?.orchestrationContext
13232
13304
  }));
13233
- await resourceLoader.reload();
13305
+ await beforeStartDeadline("loading its resources", resourceLoader.reload());
13234
13306
  const selectedSkills = resolveSkillsFromCatalog([...admitted.policy.skills], resourceLoader.getSkillCatalog(), admitted.policy.cwd);
13235
13307
  skillReport = {
13236
13308
  ...selectedSkills.resolved.length > 0 ? { skills: selectedSkills.resolved.map((skill) => skill.name) } : {},
@@ -13242,26 +13314,28 @@ class SubagentControlRuntime {
13242
13314
  const skillInjection = buildSkillInjection(selectedSkills.resolved);
13243
13315
  const promptBehavior = createInProcessChildPromptBehavior(admitted.policy);
13244
13316
  const systemPromptTransform = createInProcessChildSystemPromptTransform(admitted.policy, skillInjection);
13245
- created = {
13246
- session: (await createAgentSession({
13247
- ...inherited,
13248
- extensionBindings,
13249
- customTools: admitted.policy.customTools ?? inherited.customTools,
13250
- resourceLoader,
13251
- resourceAdmissionSignal: AbortSignal.any([signals.abort, signals.interrupt]),
13252
- subagentPolicy: {
13253
- ...admitted.policy,
13254
- executionEnded: executionEnded.signal,
13255
- ...!childAllowsIntercom(inherited) ? { intercom: undefined, intercomGroup: undefined } : {}
13256
- },
13257
- settingsManager,
13258
- orchestrationContext: admitted.spec.parent?.orchestrationContext,
13259
- sessionManager,
13260
- systemPromptTransform,
13261
- initialContextTransform: promptBehavior.initialContextTransform
13262
- })).session
13263
- };
13317
+ created = await beforeStartDeadline("creating its agent session, which includes any wait for a resource-admission permit", createAgentSession({
13318
+ ...inherited,
13319
+ ...explicitEffort,
13320
+ extensionBindings,
13321
+ customTools: admitted.policy.customTools ?? inherited.customTools,
13322
+ resourceLoader,
13323
+ resourceAdmissionSignal: AbortSignal.any([signals.abort, signals.interrupt, startDeadline.signal]),
13324
+ subagentPolicy: {
13325
+ ...admitted.policy,
13326
+ executionEnded: executionEnded.signal,
13327
+ ...!childAllowsIntercom(inherited) ? { intercom: undefined, intercomGroup: undefined } : {}
13328
+ },
13329
+ settingsManager,
13330
+ orchestrationContext: admitted.spec.parent?.orchestrationContext,
13331
+ sessionManager,
13332
+ systemPromptTransform,
13333
+ initialContextTransform: promptBehavior.initialContextTransform
13334
+ }), (late) => void late.session.dispose().catch(() => {
13335
+ return;
13336
+ }));
13264
13337
  }
13338
+ clearTimeout(startTimer);
13265
13339
  session = created.session;
13266
13340
  const transcriptSession = session;
13267
13341
  let observedStreamingMessage;
@@ -13493,6 +13567,7 @@ class SubagentControlRuntime {
13493
13567
  ...skillReport
13494
13568
  };
13495
13569
  } finally {
13570
+ clearTimeout(startTimer);
13496
13571
  executionEnded.abort();
13497
13572
  signals.abort.removeEventListener("abort", abortListener);
13498
13573
  signals.interrupt.removeEventListener("abort", interruptListener);
@@ -13680,11 +13755,24 @@ function findSubagentControl(id) {
13680
13755
  if (direct)
13681
13756
  return direct;
13682
13757
  for (const control of controls.values()) {
13758
+ if (control.pathOfChildId(id) !== undefined)
13759
+ return control;
13683
13760
  if (control.native.listChildren().some((child) => child.path === id))
13684
13761
  return control;
13685
13762
  }
13686
13763
  return;
13687
13764
  }
13765
+ function subagentChildPath(control, id) {
13766
+ return control.pathOfChildId(id) ?? id;
13767
+ }
13768
+ function subagentChildIdOfPath(id) {
13769
+ for (const control of controls.values()) {
13770
+ const childId = control.childIdOf(id);
13771
+ if (childId !== undefined)
13772
+ return childId;
13773
+ }
13774
+ return;
13775
+ }
13688
13776
  function listSubagentControls() {
13689
13777
  return [...controls.values()];
13690
13778
  }
@@ -14030,6 +14118,8 @@ async function runSingleInProcess(runtimeCwd, agent, task, options) {
14030
14118
  const resolvedCandidate = candidate ? options.resolveCandidateModel?.(candidate) : undefined;
14031
14119
  if (options.modelRoute && !resolvedCandidate)
14032
14120
  throw new Error("Subagent auto-selected model could not be resolved before execution. Retry explicitly with the current catalog.");
14121
+ const requestedSuffix = splitKnownThinkingSuffix(options.modelOverride ?? agent.model ?? "").thinkingSuffix.slice(1);
14122
+ const requestedThinking = options.modelRoute ? undefined : THINKING_LEVELS.find((level) => level === requestedSuffix);
14033
14123
  const orchestrationContext = workflowOrchestrationContext(options);
14034
14124
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
14035
14125
  const parent = {
@@ -14056,6 +14146,7 @@ ${task}`);
14056
14146
  const testSession = options.testSession ?? defaultTestSession();
14057
14147
  const spec = {
14058
14148
  taskName: agent.name,
14149
+ childId: `${options.runId}:${options.index ?? 0}`,
14059
14150
  task,
14060
14151
  agent,
14061
14152
  cwd,
@@ -14069,6 +14160,11 @@ ${task}`);
14069
14160
  ...options.progressPath ? { progressPath: options.progressPath } : {},
14070
14161
  ...options.progressArtifactPath ? { progressArtifactPath: options.progressArtifactPath } : options.progressPath && artifactsDir ? { progressArtifactPath: options.progressPath } : {},
14071
14162
  ...artifactPaths?.outputPath ? { outputArtifactPath: artifactPaths.outputPath } : {},
14163
+ ...options.sessionStartTimeoutMs === undefined ? {} : { sessionStartTimeoutMs: options.sessionStartTimeoutMs },
14164
+ ...requestedThinking === undefined ? {} : {
14165
+ requestedThinking,
14166
+ ...options.resolveCandidateModel ? { resolveCandidateModel: options.resolveCandidateModel } : {}
14167
+ },
14072
14168
  tools: agent.tools,
14073
14169
  mcpDirectTools: agent.mcpDirectTools,
14074
14170
  skills: options.skills ?? agent.skills,
@@ -15898,34 +15994,52 @@ function handleSubagentControlNotice(input) {
15898
15994
  function canonicalChildren(control) {
15899
15995
  return [...control.listChildren()].sort((left, right) => left.path.localeCompare(right.path));
15900
15996
  }
15997
+ function settledCause(control, path, status) {
15998
+ if (status !== "error" && status !== "interrupted")
15999
+ return;
16000
+ const cause = control.getDeliveredResult(path)?.cause?.replace(/\s+/g, " ").trim();
16001
+ return cause || undefined;
16002
+ }
15901
16003
  function childLines(control, id) {
15902
- if (!control)
15903
- return [];
15904
16004
  if (id) {
15905
- const child = control.findChild(id);
16005
+ const path = subagentChildPath(control, id);
16006
+ const child = control.findChild(path);
15906
16007
  if (!child)
15907
16008
  return [];
15908
- const delivered = control.getDeliveredResult(id);
16009
+ const delivered = control.getDeliveredResult(path);
16010
+ const childId = control.childIdOf(path);
16011
+ const cause = settledCause(control, path, child.status);
15909
16012
  return [
16013
+ ...childId ? [`Id: ${childId}`] : [],
15910
16014
  `Child: ${child.path}`,
15911
16015
  `Parent: ${child.parentPath}`,
15912
16016
  `Task: ${child.taskName}`,
15913
16017
  `Depth: ${child.depth}`,
15914
16018
  `Status: ${child.status}`,
16019
+ ...cause ? [`Cause: ${cause}`] : [],
15915
16020
  `Residency: ${child.loaded ? "loaded" : "cold"}`,
15916
16021
  ...delivered?.sessionFile ? [`Session: ${delivered.sessionFile}`] : []
15917
16022
  ];
15918
16023
  }
15919
- return control.listChildren().map((child) => `${child.path} — ${child.status} (${child.loaded ? "loaded" : "cold"})`);
16024
+ return control.listChildren().map((child) => {
16025
+ const childId = control.childIdOf(child.path);
16026
+ const cause = settledCause(control, child.path, child.status);
16027
+ return `${child.path} — ${child.status} (${child.loaded ? "loaded" : "cold"})${childId ? ` · id ${childId}` : ""}${cause ? ` · ${cause}` : ""}`;
16028
+ });
15920
16029
  }
15921
16030
  function statusGroup(control, id) {
16031
+ const path = id === undefined ? undefined : subagentChildPath(control, id);
15922
16032
  return {
15923
16033
  parentPath: control.parent.path,
15924
- children: canonicalChildren(control).filter((child) => !id || id === control.parent.path || child.path === id).map((child) => {
16034
+ children: canonicalChildren(control).filter((child) => !path || path === control.parent.path || child.path === path).map((child) => {
15925
16035
  const delivered = control.getDeliveredResult(child.path);
15926
16036
  const metadata = control.getChildMetadata(child.path) ?? delivered;
16037
+ const childId = control.childIdOf(child.path);
16038
+ const failure = settledCause(control, child.path, child.status);
15927
16039
  return {
15928
16040
  ...child,
16041
+ ...childId === undefined ? {} : { childId },
16042
+ ...failure === undefined ? {} : { failure },
15929
16043
  sessionFile: delivered?.sessionFile,
15930
16044
  ...metadata?.model === undefined ? {} : { model: metadata.model },
15931
16045
  ...metadata?.thinking === undefined ? {} : { thinking: metadata.thinking }
@@ -15933,10 +16047,11 @@ function statusGroup(control, id) {
15933
16047
  })
15934
16048
  };
15935
16049
  }
15936
- function inspectInProcessChildStatus(id) {
16050
+ function inspectInProcessChildStatus(id, ownsRun) {
16051
+ const owned = (control) => ownsRun?.(control.parent.path) ?? true;
15937
16052
  if (id) {
15938
16053
  const control = findSubagentControl(id);
15939
- if (!control)
16054
+ if (!control || !owned(control))
15940
16055
  return;
15941
16056
  const text = id === control.parent.path ? [`Parent: ${control.parent.path}`, ...childLines(control)].join(`
15942
16057
  `) : childLines(control, id).join(`
@@ -15948,7 +16063,7 @@ function inspectInProcessChildStatus(id) {
15948
16063
  details: { mode: "management", results: [], statusGroups: [statusGroup(control, id)] }
15949
16064
  };
15950
16065
  }
15951
- const controls = listSubagentControls();
16066
+ const controls = listSubagentControls().filter(owned);
15952
16067
  const lines = controls.flatMap((control) => [`Parent: ${control.parent.path}`, ...childLines(control)]);
15953
16068
  if (lines.length === 0)
15954
16069
  return;
@@ -17561,10 +17676,22 @@ function liveTaskIds(host) {
17561
17676
  function ownedRecords(ctx) {
17562
17677
  return readSubagentChildRecords(ctx.sessionManager.getEntries(), subagentChildOwner(ctx).sessionId);
17563
17678
  }
17679
+ async function subagentRunsLaunchedBy(ctx) {
17680
+ const owner = subagentChildOwner(ctx).sessionId;
17681
+ await subagentChildRecordsAppended(owner);
17682
+ const runIds = observedSubagentRunIds(owner);
17683
+ for (const record of ownedRecords(ctx).values())
17684
+ runIds.add(record.runId);
17685
+ return runIds;
17686
+ }
17564
17687
  function resolveRecord(records, id) {
17565
17688
  const direct = findSubagentChildRecord(records, id);
17566
17689
  if (direct)
17567
17690
  return direct;
17691
+ const listedAs = subagentChildIdOfPath(id.trim());
17692
+ const byPath = listedAs === undefined ? undefined : records.get(listedAs);
17693
+ if (byPath)
17694
+ return byPath;
17568
17695
  const inRun = [...records.values()].filter((record) => record.runId === id.trim());
17569
17696
  if (inRun.length === 1)
17570
17697
  return inRun[0];
@@ -17905,7 +18032,8 @@ async function handleManagementRequest(input) {
17905
18032
  if (action === "status") {
17906
18033
  const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
17907
18034
  const foreground = getForegroundControl(deps.state, targetRunId);
17908
- const live = inspectInProcessChildStatus(targetRunId) ?? (foreground ? foregroundStatusResult(foreground) : undefined);
18035
+ const launched = await subagentRunsLaunchedBy(ctx);
18036
+ const live = inspectInProcessChildStatus(targetRunId, (runId) => launched.has(runId)) ?? (foreground ? foregroundStatusResult(foreground) : undefined);
17909
18037
  if (awaitingResume.length > 0) {
17910
18038
  const liveText = live?.content.map((part) => part.type === "text" ? part.text : "").join(`
17911
18039
  `);
@@ -19263,11 +19391,13 @@ function childStatusLines(child, expanded, theme, width) {
19263
19391
  return [
19264
19392
  heading,
19265
19393
  ...metadata,
19394
+ ...child.childId ? [theme.fg("dim", ` Id: ${displayText(child.childId)}`)] : [],
19266
19395
  theme.fg("dim", ` Path: ${displayText(child.path)}`),
19267
19396
  theme.fg("dim", ` Parent: ${displayText(child.parentPath)} · Depth: ${child.depth}`),
19268
19397
  theme.fg("dim", ` Task: ${displayText(child.taskName)}`),
19269
19398
  theme.fg("dim", ` Residency: ${child.loaded ? "loaded" : "cold"}`),
19270
19399
  ...child.cause ? [theme.fg("muted", ` Cause: ${displayText(child.cause)}`)] : [],
19400
+ ...child.failure ? [theme.fg("error", ` Error: ${displayText(child.failure)}`)] : [],
19271
19401
  ...child.sessionFile ? [theme.fg("dim", ` Session: ${displayText(child.sessionFile)}`)] : []
19272
19402
  ];
19273
19403
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.58-alpha.1",
3
+ "version": "0.9.59-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.58-alpha.1",
3
+ "version": "0.9.59-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [