pi-subagents 0.31.0 → 0.32.0

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 (42) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +170 -8
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +6 -1
  5. package/src/agents/agent-management.ts +6 -1
  6. package/src/agents/agents.ts +55 -11
  7. package/src/extension/companion-suggestions.ts +359 -0
  8. package/src/extension/config.ts +27 -4
  9. package/src/extension/doctor.ts +2 -0
  10. package/src/extension/fanout-child.ts +1 -0
  11. package/src/extension/index.ts +69 -4
  12. package/src/extension/schemas.ts +2 -2
  13. package/src/intercom/intercom-bridge.ts +25 -1
  14. package/src/profiles/profiles.ts +637 -0
  15. package/src/runs/background/async-execution.ts +138 -33
  16. package/src/runs/background/async-job-tracker.ts +77 -1
  17. package/src/runs/background/async-resume.ts +11 -13
  18. package/src/runs/background/async-status.ts +41 -9
  19. package/src/runs/background/chain-root-attachment.ts +34 -4
  20. package/src/runs/background/control-channel.ts +227 -0
  21. package/src/runs/background/run-status.ts +1 -0
  22. package/src/runs/background/stale-run-reconciler.ts +28 -1
  23. package/src/runs/background/subagent-runner.ts +459 -113
  24. package/src/runs/foreground/chain-execution.ts +29 -7
  25. package/src/runs/foreground/execution.ts +24 -6
  26. package/src/runs/foreground/subagent-executor.ts +240 -44
  27. package/src/runs/shared/acceptance.ts +45 -22
  28. package/src/runs/shared/dynamic-fanout.ts +1 -1
  29. package/src/runs/shared/model-fallback.ts +4 -0
  30. package/src/runs/shared/nested-events.ts +58 -0
  31. package/src/runs/shared/parallel-utils.ts +49 -1
  32. package/src/runs/shared/pi-args.ts +5 -3
  33. package/src/runs/shared/pi-spawn.ts +52 -20
  34. package/src/runs/shared/single-output.ts +2 -0
  35. package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
  36. package/src/runs/shared/worktree.ts +28 -5
  37. package/src/shared/artifacts.ts +15 -1
  38. package/src/shared/fork-context.ts +133 -22
  39. package/src/shared/types.ts +82 -3
  40. package/src/shared/utils.ts +99 -14
  41. package/src/slash/slash-commands.ts +726 -40
  42. package/src/tui/render.ts +16 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Added
6
+
7
+ ### Fixed
8
+
9
+ ## [0.32.0] - 2026-07-01
10
+
11
+ ### Added
12
+ - Added `subagents.defaultModel` so subagents can have a global default model separate from the parent session model. Thanks to Artem Timofeev (@atimofeev) for #339.
13
+ - Added `/subagent-cost` and `totalChildUsage` run details so parent sessions can inspect aggregate subagent child usage and cost. Thanks to Aaron Ky-Riesenbach (@aaronkyriesenbach) for #343.
14
+ - Added configurable companion package recommendations for `pi-intercom` and `pi-prompt-template-model`, surfaced in session-start transcript messages, `subagent({ action: "list" })`, and `/subagents-doctor`, with `/subagents-companions` hide/show/status controls.
15
+ - Added detached async runner stdout and stderr log files. Thanks to Daniel Mateos Carballares (@danim47c) for #358.
16
+ - Added `totalCost` rollups to foreground single, parallel, and chain run details, including nested foreground subagent costs and compact progress display. Thanks to Clark Everson (@gr3enarr0w) for #345.
17
+ - Added `globalConcurrencyLimit` to cap simultaneously running subagent tasks across parallel groups in a single run. Thanks to Clark Everson (@gr3enarr0w) for #349.
18
+ - Added stable v1 async lifecycle artifact metadata in `status.json`, `events.jsonl`, and result JSON so observability and workflow gates can correlate subagent runs without scraping terminal output. Thanks to Clark Everson (@gr3enarr0w) for #350.
19
+ - Added `PI_SUBAGENT_PI_BINARY` to let wrappers launch child agents through an explicit Pi binary instead of resolving `pi` from `PATH`. Thanks to David Barroso (@dbarrosop) for #341.
20
+ - Added `worktreeBaseDir` and `PI_SUBAGENTS_WORKTREE_DIR` so worktree isolation can use a stable trusted base directory. Thanks to Matt Robenolt (@mattrobenolt) for #185.
21
+ - Added `singleRunOutputBaseDir` so single-agent relative outputs can be routed to a configured artifact directory. Thanks to Oleksii Nikiforov (@NikiforovAll) for #173.
22
+ - Added `maxSubagentSpawnsPerSession` and `PI_SUBAGENT_MAX_SPAWNS_PER_SESSION` to cap total subagent launches in one session. Thanks to @eightHundreds for #239.
23
+ - Enforce `timeoutMs` and `maxRuntimeMs` on async and background subagent runs. The per-launch deadline drives an AbortController that cancels acceptance verification, imported async roots, and fallback retries; direct children get SIGTERM with SIGKILL escalation on a bounded timer; nested descendants get timeout requests distinct from manual interrupt. `timedOut`, `deadlineAt`, and `error` propagate across status, results, and nested summaries. Thanks to @pkese for #361.
24
+
25
+ ### Fixed
26
+ - Keep generated subagent markdown outputs, progress files, and run artifacts under the project-local `.pi-subagents/` directory by default. Thanks to Carolina (@carolitascl) for #326.
27
+ - Detach foreground subagent runs immediately when a child starts a blocking `contact_supervisor` or `intercom.ask` call, avoiding parent/child intercom deadlocks. Thanks to huarkiou (@huarkiou) for #335.
28
+ - Made child boundary prompt editing instructions tool-agnostic so Codex-style adapters are not told to call unavailable `edit`/`write` tools. Thanks to Artem Timofeev (@atimofeev) for #338.
29
+ - Recursively interrupt active async parallel children and nested async descendants when pausing a background run. Thanks to Vicary (@vicary) for #355.
30
+ - Avoid runtime peer imports from detached async runners while still forwarding the Pi package root when available. Thanks to @aurbina83 for #352 and @huangkun3251 for #342.
31
+ - Fall back to PATH `node` for async runners when the current Node executable path is stale or deleted. Thanks to Richard Hao (@0xRichardH) for #347.
32
+ - Retry fallback models when a zero-exit subagent attempt produces no output, including background async runs, preserve structured-output-only completions, and pre-warm forked session files for parallel children. Thanks to Clark Everson (@gr3enarr0w) for #344.
33
+ - Preserve explicit empty companion suggestion surfaces and keep global companion suggestions disabled when writing package dismissal state.
34
+ - Include bounded async runner stderr tails when stale-run reconciliation marks a startup crash failed. Thanks to Salem Sayed (@salemsayed) for #340.
35
+ - Persist forked child session files when Pi returns a branch path before writing it to disk. Thanks to @trisforrestcam for #174.
36
+ - Pass explicit `thinking: off` through to child model arguments as a `:off` suffix. Thanks to Thomas Dietert (@tdietert) for #147.
37
+ - Sanitize Anthropic signed `thinking` / `redacted_thinking` blocks out of forked child sessions and force child thinking off so fork-context subagents survive signed-thinking transcripts after branching or compaction. Thanks to Thomas Dietert (@tdietert) for #147.
38
+ - Restore queued and running detached async jobs into the widget after restarting Pi. Thanks to Vicary (@vicary) for #362.
39
+ - Fix session-start freeze where restoring active async jobs did O(runs × nested-route-dirs) directory scans over stale terminal runs; `listAsyncRuns` now builds a single nested-route index and filters by state before lookup.
40
+
41
+ ## [0.31.1] - 2026-06-25
42
+
43
+ ### Added
44
+ - Added `/chain` inline parallel groups with per-step metadata, group options, and tab completion. Thanks to loss-and-quick (@loss-and-quick) for #312.
45
+ - Added subagent profile commands and provider model catalog generation for quota and quality model profiles. Thanks to tencnivel (@tencnivel) for #333.
46
+
47
+ ### Fixed
48
+ - Discover `pi-intercom` installations created by `--extension npm:pi-intercom` under Pi's temporary npm extension cache. Thanks to loss-and-quick (@loss-and-quick) for #336.
49
+ - Made async subagent interrupt, steer, and stop requests portable across platforms that do not support Unix signals. Thanks to AeonDave (@AeonDave) for #332.
50
+ - Hardened profile commands by probing models without tools, rejecting unsafe profile/provider path tokens, and resolving short model IDs and thinking suffixes against the current registry.
51
+ - Limited inline `/chain` acceptance values to levels expressible in slash syntax and kept completion disabled inside shared `--` tasks with literal parentheses.
52
+
5
53
  ## [0.31.0] - 2026-06-24
6
54
 
7
55
  ### Added
package/README.md CHANGED
@@ -117,7 +117,21 @@ A simple rule of thumb: use `scout` before you understand the code, `researcher`
117
117
 
118
118
  ## Changing an agent's model
119
119
 
120
- Builtin agents inherit your current Pi default model by default. This keeps new installs from depending on a provider you may not have configured. If you want a role to use a specific model, set an override instead of copying the bundled agent file.
120
+ Builtin agents inherit your current Pi default model by default. This keeps new installs from depending on a provider you may not have configured. If you want every subagent without its own model to use a different default, set `subagents.defaultModel`. If you want a role to use a specific model, set an override instead of copying the bundled agent file.
121
+
122
+ ```json
123
+ {
124
+ "defaultModel": "deepseek-v4-pro",
125
+ "subagents": {
126
+ "defaultModel": "deepseek-v4-flash",
127
+ "agentOverrides": {
128
+ "oracle": {
129
+ "model": "deepseek-v4-pro"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ ```
121
135
 
122
136
  For one run, put the override in the command:
123
137
 
@@ -141,7 +155,7 @@ For a persistent override, edit settings. This example pins the reviewer everywh
141
155
  }
142
156
  ```
143
157
 
144
- Use `~/.pi/agent/settings.json` for a user override or the project config settings file (`.pi/settings.json` in standard Pi) for a project override. The same `agentOverrides` block can change `tools`, `skills`, inherited context, prompt text, or disable a builtin. Matching user and project agents also receive override fields that their frontmatter leaves unset, so a shared project config agent can keep the persona while local settings choose the model. Explicit frontmatter still wins.
158
+ Use `~/.pi/agent/settings.json` for a user override or the project config settings file (`.pi/settings.json` in standard Pi) for a project override. `subagents.defaultModel` applies to builtin, package, user, and project agents that do not set `model` in frontmatter. Per-run model overrides and `agentOverrides.<name>.model` still win, and explicit agent frontmatter still wins over the global default. The same `agentOverrides` block can change `tools`, `skills`, inherited context, prompt text, or disable a builtin. Matching user and project agents also receive override fields that their frontmatter leaves unset, so a shared project config agent can keep the persona while local settings choose the model.
145
159
 
146
160
  If your provider rejects model IDs with thinking suffixes, set `subagents.disableThinking: true` in user or project settings. That clears bundled builtin thinking defaults in one place; an explicit higher-precedence `agentOverrides.<name>.thinking` value can opt a role back in.
147
161
 
@@ -168,6 +182,10 @@ You can also ask naturally:
168
182
  Show me the current async runs.
169
183
  ```
170
184
 
185
+ Async runs also write machine-readable lifecycle artifacts for observability and workflow gates. For a top-level async run, `details.asyncDir` points at a directory containing `status.json`, `events.jsonl`, `output-<index>.log`, and `subagent-log-<runId>.md`; the final summary is written to Pi's subagent results directory as `<runId>.json`. Nested async runs use the same shape under the nested async root and are discoverable through status projections that read the nested-run registry. These files are append/update artifacts only; interactive foreground behavior is unchanged.
186
+
187
+ The stable v1 status/result fields are `lifecycleArtifactVersion`, `runId`/`id`, `sessionId`, `mode`, `state`, `startedAt`, `lastUpdate`, `endedAt`, `durationMs`, `cwd`, `asyncDir`, `sessionFile`, `outputFile`, `workflowGraph`, `steps`, `results`, `totalTokens`, `totalCost`, `model`/`attemptedModels`/`modelAttempts`, `toolCount`, `turnCount`, and nested `children` when a child is allowed to launch subagents. `events.jsonl` records lifecycle transitions such as `subagent.run.started`, `subagent.step.started`, `subagent.step.completed`/`failed`/`paused`, control attention events, nested interrupt failures, and `subagent.run.completed`; run boundary events include the lifecycle artifact version. Consumers should read these JSON files instead of scraping terminal output; unknown fields and event types should be ignored for forward compatibility.
188
+
171
189
  If something feels misconfigured, run:
172
190
 
173
191
  ```text
@@ -218,6 +236,8 @@ Add `autofix` to `/parallel-review` or `/parallel-cleanup` to apply only the syn
218
236
  pi install npm:pi-intercom
219
237
  ```
220
238
 
239
+ When `pi-intercom` is not active, `pi-subagents` may recommend it at session start, in `subagent({ action: "list" })`, and in `/subagents-doctor`. The recommendation is visible to the assistant so it can offer to run the install command or hide the recommendation after you approve that action.
240
+
221
241
  Most users do not call `intercom` directly. After `pi-intercom` is installed, `pi-subagents` can automatically give child agents a private coordination channel back to the parent session. The bridge recognizes the normal `pi install npm:pi-intercom` package install as well as legacy local extension checkouts.
222
242
 
223
243
  Use it for work where the child might need a decision instead of guessing:
@@ -329,13 +349,46 @@ Skip this section until you want exact syntax.
329
349
  |---------|-------------|
330
350
  | `/run <agent> [task]` | Run one agent; omit the task for self-contained agents |
331
351
  | `/chain agent1 "task1" -> agent2 "task2"` | Run agents in sequence |
352
+ | `/chain scout "scan" -> (reviewer "A" \| reviewer "B") -> writer "fix"` | Run a chain with a static parallel group inline |
332
353
  | `/parallel agent1 "task1" -> agent2 "task2"` | Run agents in parallel |
333
354
  | `/run-chain <chainName> -- <task>` | Launch a saved `.chain.md` or `.chain.json` workflow |
355
+ | `/subagent-cost` | Show parent plus child subagent token usage and cost for this session |
334
356
  | `/subagents-doctor` | Show read-only setup diagnostics |
335
357
  | `/subagents-models [agent]` | Show the runtime-loaded builtin model mapping, optionally filtered to one builtin |
358
+ | `/subagents-profiles` | List saved subagent profiles from `~/.pi/agent/profiles/pi-subagents/` |
359
+ | `/subagents-load-profile <name>` | Replace only `settings.subagents` with a saved profile and optionally switch this session to the profile worker model |
360
+ | `/subagents-refresh-provider-models <provider> [--force]` | Create or refresh the cached provider model catalog |
361
+ | `/subagents-generate-profiles <provider>` | Generate `<provider>.quota.json` and `<provider>.quality.json` profiles |
362
+ | `/subagents-check-profile <name>` | Check a saved profile against the current registry and live model probes |
336
363
 
337
364
  Commands validate agent names locally, support tab completion, and send results back into the conversation.
338
365
 
366
+ ### Profiles and provider model catalogs
367
+
368
+ Profiles are stored under:
369
+
370
+ ```text
371
+ ~/.pi/agent/profiles/pi-subagents/
372
+ ```
373
+
374
+ Provider model catalogs are cached under:
375
+
376
+ ```text
377
+ ~/.pi/agent/profiles/pi-subagents/providers/
378
+ ```
379
+
380
+ Use the profile workflow like this:
381
+
382
+ ```text
383
+ /subagents-refresh-provider-models openai-codex
384
+ /subagents-generate-profiles openai-codex
385
+ /subagents-load-profile openai-codex.quota
386
+ ```
387
+
388
+ `/subagents-refresh-provider-models` writes a serialized provider model catalog with observed registry data, simple role-oriented classification, and live probe results from tiny one-shot `pi -p --model ... --no-tools` checks. The cache refreshes when missing or stale; use `--force` to ignore freshness and probe again immediately.
389
+
390
+ `/subagents-generate-profiles` uses the provider catalog to produce quota and quality profiles. `/subagents-check-profile` re-checks each assigned model in a saved profile against the current registry and a live probe so you can detect model removals, auth problems, or stale assignments.
391
+
339
392
  ### Per-step tasks
340
393
 
341
394
  Use `->` to separate steps and give each step its own task:
@@ -353,6 +406,37 @@ Both double and single quotes work. You can also use `--` as a delimiter:
353
406
 
354
407
  Steps without a task inherit behavior from the execution mode. Chain steps get `{previous}`, the prior step’s output. Parallel steps use the first available task as a fallback.
355
408
 
409
+ ### Inline parallel groups in `/chain`
410
+
411
+ Wrap a group of agents in parentheses and separate them with `|` to fan them out within a single chain step. The group runs all of its tasks concurrently, then the next `->` step continues once they finish:
412
+
413
+ ```text
414
+ /chain scout "scan" -> (reviewer "review A" | reviewer "review B") -> writer "fix"
415
+ ```
416
+
417
+ Notes:
418
+
419
+ - Groups must contain at least two tasks separated by ` | `, each with its own task.
420
+ - Group syntax is only valid between ` -> ` separators, and the group must appear as a complete step.
421
+ - Only a step that *opens* with `(` is a group. Parentheses inside a shared `--` task (e.g. `/chain scout -- inspect auth (backend)`) stay literal text and keep the legacy single-agent behavior.
422
+ - A group is treated as the prior step’s output for the next sequential step.
423
+ - Tab completion suggests agents inside groups — after `(`, after `|`, and on each new `->` step.
424
+
425
+ Add a `[...]` suffix right after the closing `)` to set step-level options on the group:
426
+
427
+ ```text
428
+ /chain scout "scan" -> (reviewer "A" | reviewer "B")[concurrency=2,failFast,worktree] -> writer "fix"
429
+ ```
430
+
431
+ | Group option | Description |
432
+ |--------------|-------------|
433
+ | `concurrency=N` | Max tasks running at once within the group. |
434
+ | `failFast` | Stop the group as soon as one task fails. |
435
+ | `worktree` | Run each group task in its own git worktree. |
436
+
437
+ Dynamic fanout (`expand` / `collect`) is intentionally not available inline — use the
438
+ `subagent({ chain: [...] })` tool API or a saved `.chain.json` for data-driven fan-out.
439
+
356
440
  ```text
357
441
  /chain scout "analyze auth" -> planner -> worker
358
442
  # scout gets "analyze auth"; planner gets scout output; worker gets planner output
@@ -367,7 +451,7 @@ For a shared task, list agents and place one `--` before the task:
367
451
 
368
452
  ### Inline per-step config
369
453
 
370
- Append `[key=value,...]` to an agent name to override defaults for that step:
454
+ Append `[key=value,...]` to an agent name to override defaults. `/chain` applies every key below; `/run` and `/parallel` use the execution-behavior keys (`output`, `outputMode`, `reads`, `model`, `skills`, `progress`) and ignore chain-only metadata such as `as`, `label`, `phase`, `count`, `outputSchema`, and `acceptance`.
371
455
 
372
456
  ```text
373
457
  /chain scout[output=context.md] "scan code" -> planner[reads=context.md] "analyze auth"
@@ -377,15 +461,24 @@ Append `[key=value,...]` to an agent name to override defaults for that step:
377
461
 
378
462
  | Key | Example | Description |
379
463
  |-----|---------|-------------|
380
- | `output` | `output=context.md` | Write results to a file. For `/chain` and `/parallel`, relative paths live under the chain directory; for `/run`, relative paths resolve against cwd. |
464
+ | `output` | `output=context.md` | Write results to a file. Absolute paths are used as-is. Relative paths in `/run` resolve under `singleRunOutputBaseDir` when configured, otherwise under the run's output artifact directory. Relative paths in `/chain` and `/parallel` live under the chain or parallel run directory. |
381
465
  | `outputMode` | `outputMode=file-only` | Return only a concise file reference for saved output instead of the full saved content. Requires `output`; default is `inline`. |
382
466
  | `reads` | `reads=a.md+b.md` | Read files before executing. `+` separates multiple paths. |
383
467
  | `model` | `model=anthropic/claude-sonnet-4` | Override model for this step. |
384
468
  | `skills` | `skills=planning+review` | Override available skills. `+` separates multiple skills. |
385
469
  | `progress` | `progress` | Enable progress tracking. |
470
+ | `as` | `as=context` | Name this step’s output so later steps can reference it. |
471
+ | `label` | `label=Recon` | Human-readable label for the step. |
472
+ | `phase` | `phase=analysis` | Group steps into a named phase. |
473
+ | `cwd` | `cwd=packages/api` | Run the step in a subdirectory. |
474
+ | `count` | `count=3` | Fan a group task into N copies (only inside a `( ... )` group). |
475
+ | `outputSchema` | `outputSchema=schema.json` | Validate structured output against a JSON Schema file (path resolved against the session cwd, not an inline step `cwd`). |
476
+ | `acceptance` | `acceptance=checked` | Inline acceptance level: `auto`, `attested`, or `checked`. Use the tool API or saved `.chain.json` for object contracts such as `none`, `verified`, or `reviewed`. |
386
477
 
387
478
  Set `output=false`, `reads=false`, or `skills=false` to disable that behavior explicitly. Do not use `output=false` for file-only returns; use `outputMode=file-only` with an `output` path.
388
479
 
480
+ Inline `[...]` values must not contain spaces or commas — keep `label`/`phase` to single tokens.
481
+
389
482
  ### Background and forked runs
390
483
 
391
484
  Add `--bg` to run in the background:
@@ -449,7 +542,7 @@ Agent locations, lowest to highest priority:
449
542
 
450
543
  Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files do not define agents. Installed Pi packages can expose agent directories from either `{"pi-subagents":{"agents":["./agents"]}}` or `{"pi":{"subagents":{"agents":["./agents"]}}}` in their package manifest. Package agents load above builtins and below user/project agents. If both `.agents/` and the project config agents directory define the same parsed runtime agent name, the project config directory wins. Use `agentScope: "user" | "project" | "both"` to control discovery; `both` is the default and project definitions win runtime-name collisions.
451
544
 
452
- Builtin agents load at the lowest priority, so a user or project agent with the same name overrides them. They do not pin a provider model; they inherit your current Pi default model unless you set `subagents.agentOverrides.<name>.model`. `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `worker` is the implementation agent for normal tasks and approved oracle handoffs.
545
+ Builtin agents load at the lowest priority, so a user or project agent with the same name overrides them. They do not pin a provider model; they inherit your current Pi default model unless you set `subagents.defaultModel` or `subagents.agentOverrides.<name>.model`. `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `worker` is the implementation agent for normal tasks and approved oracle handoffs.
453
546
 
454
547
  The `researcher` builtin uses `web_search`, `fetch_content`, and `get_search_content`; those require [pi-web-access](https://github.com/nicobailon/pi-web-access):
455
548
 
@@ -480,6 +573,8 @@ Example:
480
573
 
481
574
  Supported override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `defaultContext`, `disabled`, `skills`, `tools`, and `systemPrompt`. Use `defaultContext: false` in builtin overrides to clear an inherited context default. Project overrides beat user overrides.
482
575
 
576
+ Set `subagents.defaultModel` to give all subagents without an explicit model their own default model, separate from the parent session model. Per-agent model overrides and agent frontmatter still win.
577
+
483
578
  Set `disabled: true` to hide a builtin from runtime discovery and agent-facing `subagent({ action: "list" })` output. For bulk control, set `subagents.disableBuiltins: true` in settings.
484
579
 
485
580
  Set `subagents.disableThinking: true` to clear bundled builtin thinking defaults globally for providers that do not support `:low`, `:medium`, `:high`, or similar model suffixes. A higher-precedence per-agent `thinking` override can opt one builtin back in.
@@ -905,6 +1000,7 @@ Agent definitions are not loaded into context by default. Management actions let
905
1000
  | `clarify` | boolean | true for chains | Show TUI preview/edit flow. |
906
1001
  | `agentScope` | `user \| project \| both` | `both` | Agent discovery scope. Project wins on collisions. |
907
1002
  | `async` | boolean | false | Background execution. For chains, `clarify: true` explicitly keeps the run foreground for the clarify UI. |
1003
+ | `timeoutMs` / `maxRuntimeMs` | number | none | Optional run-level max runtime in milliseconds for foreground and async/background runs. |
908
1004
  | `cwd` | string | runtime cwd | Override working directory. |
909
1005
  | `maxOutput` | object | 200KB, 5000 lines | Final output truncation limits. |
910
1006
  | `artifacts` | boolean | true | Write debug artifacts. |
@@ -913,7 +1009,7 @@ Agent definitions are not loaded into context by default. Management actions let
913
1009
  | `sessionDir` | string | derived | Override session log directory. |
914
1010
  | `acceptance` | string/object/false | inferred | Override the run's inferred acceptance gates. Use `"auto"`, `"attested"`, `"checked"`, `"verified"`, `"reviewed"`, or `{ level: "none", reason: "..." }`. |
915
1011
 
916
- `context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs that omit `context`, each agent/task/step follows its own `defaultContext`, so a fresh-default scout can run fresh beside a fork-default worker. Pass explicit `context: "fork"` or `context: "fresh"` when you intentionally want one context for every child.
1012
+ `context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. When the inherited transcript contains signed Anthropic `thinking` / `redacted_thinking` blocks, `pi-subagents` strips those provider-private blocks from the forked child session and forces the child run's thinking level to `off` so Anthropic does not reject modified signatures after branching or compaction. Forking never silently downgrades to `fresh`. In multi-agent runs that omit `context`, each agent/task/step follows its own `defaultContext`, so a fresh-default scout can run fresh beside a fork-default worker. Pass explicit `context: "fork"` or `context: "fresh"` when you intentionally want one context for every child.
917
1013
 
918
1014
  Use `outputMode: "file-only"` when a saved output may be large and the parent only needs a pointer. The returned text is a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` Failed runs and save errors still return normal inline output for debugging. In chains, later `{previous}` steps receive the same compact reference when the prior step used file-only mode.
919
1015
 
@@ -968,6 +1064,8 @@ Requirements:
968
1064
  - task-level `cwd` overrides must be omitted or match the shared cwd
969
1065
  - configured `worktreeSetupHook` must return valid JSON before timeout
970
1066
 
1067
+ By default, worktrees are created under the system temp directory. Set `worktreeBaseDir` in config, or `PI_SUBAGENTS_WORKTREE_DIR` when config is unset, to put them under a stable trusted directory. Missing base directories are created automatically.
1068
+
971
1069
  After a worktree parallel step completes, per-agent diff stats are appended to the output and full patch files are written to artifacts. Worktrees and temp branches are cleaned up in `finally` blocks.
972
1070
 
973
1071
  ## Configuration
@@ -990,6 +1088,22 @@ Makes top-level calls use background execution when the request does not explici
990
1088
 
991
1089
  Forces depth-0 single, parallel, and chain runs into background mode and bypasses clarify UI by forcing `clarify: false`. Nested calls keep their own inherited settings.
992
1090
 
1091
+ ### `globalConcurrencyLimit`
1092
+
1093
+ ```json
1094
+ { "globalConcurrencyLimit": 20 }
1095
+ ```
1096
+
1097
+ Caps simultaneously running subagent tasks within a single run across top-level parallel tasks, inline chain parallel groups, and dynamic fanout groups. The default is `20`; invalid values are clamped to `1`. Per-step `concurrency` and `parallel.concurrency` still apply, so effective concurrency is the lower of the local cap and the available global slots.
1098
+
1099
+ ### `maxSubagentSpawnsPerSession`
1100
+
1101
+ ```json
1102
+ { "maxSubagentSpawnsPerSession": 40 }
1103
+ ```
1104
+
1105
+ Caps the total number of child subagent launches allowed during one parent session, including single runs, parallel task counts, static chain steps, and bounded dynamic fanout children. Set `PI_SUBAGENT_MAX_SPAWNS_PER_SESSION` to override the config for a process. The default is `40`; `0` blocks new subagent launches for that session.
1106
+
993
1107
  ### `parallel`
994
1108
 
995
1109
  ```json
@@ -1011,6 +1125,14 @@ Forces depth-0 single, parallel, and chain runs into background mode and bypasse
1011
1125
 
1012
1126
  Session directory precedence is: `params.sessionDir`, then `config.defaultSessionDir`, then a directory derived from the parent session. Sessions are always enabled.
1013
1127
 
1128
+ ### `singleRunOutputBaseDir`
1129
+
1130
+ ```json
1131
+ { "singleRunOutputBaseDir": "~/.pi/subagent-outputs" }
1132
+ ```
1133
+
1134
+ Routes relative `output` paths for single-agent `/run` calls under this directory. Absolute per-call or agent output paths are still used as-is. When unset, relative single-run outputs go under the run's output artifact directory instead of the project root.
1135
+
1014
1136
  ### `maxSubagentDepth`
1015
1137
 
1016
1138
  ```json
@@ -1019,6 +1141,14 @@ Session directory precedence is: `params.sessionDir`, then `config.defaultSessio
1019
1141
 
1020
1142
  Controls nested delegation when no inherited `PI_SUBAGENT_MAX_DEPTH` is already in effect. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit. This applies even to children that explicitly declare `tools: subagent`; at the cap, execution fanout is blocked instead of silently hiding nested work.
1021
1143
 
1144
+ ### `PI_SUBAGENT_PI_BINARY`
1145
+
1146
+ ```bash
1147
+ export PI_SUBAGENT_PI_BINARY=/path/to/pi-or-wrapper
1148
+ ```
1149
+
1150
+ Overrides the command used to launch child Pi processes. Package wrappers can set this to their own `pi`/agent binary so subagents inherit wrapper flags, environment setup, and bundled resources without relying on `PATH` ordering. Empty or whitespace-only values are ignored.
1151
+
1022
1152
  ### `intercomBridge`
1023
1153
 
1024
1154
  ```json
@@ -1041,6 +1171,36 @@ Bridge activation also requires `pi-intercom` to be installed and enabled throug
1041
1171
 
1042
1172
  The default injected guidance tells children to use `contact_supervisor` with `reason: "need_decision"` when blocked or needing a decision, `reason: "progress_update"` only for meaningful blocked/progress updates, generic `intercom` as fallback plumbing, and avoid routine completion handoffs.
1043
1173
 
1174
+ ### `companionSuggestions`
1175
+
1176
+ ```json
1177
+ {
1178
+ "companionSuggestions": {
1179
+ "enabled": true,
1180
+ "packages": {
1181
+ "pi-intercom": {
1182
+ "surfaces": ["session_start", "list", "doctor"]
1183
+ },
1184
+ "pi-prompt-template-model": {
1185
+ "surfaces": ["session_start", "list", "doctor"]
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ ```
1191
+
1192
+ Controls recommendations for optional companion packages. `pi-intercom` enables live supervisor decisions, progress updates, and grouped result delivery. `pi-prompt-template-model` makes subagent workflows reusable as prompt templates with model/thinking/skill/subagent frontmatter.
1193
+
1194
+ Use `/subagents-companions status` to inspect recommendation status. Use `/subagents-companions hide pi-intercom workspace` or `/subagents-companions hide pi-prompt-template-model user` to hide a recommendation. Use `/subagents-companions show <package>` to show a workspace recommendation again. Set `companionSuggestions` to `false` to disable all companion recommendations.
1195
+
1196
+ ### `worktreeBaseDir`
1197
+
1198
+ ```json
1199
+ { "worktreeBaseDir": "/Users/matt/code/.worktrees/pi-subagents" }
1200
+ ```
1201
+
1202
+ Sets the base directory for `worktree: true` runs. Relative paths resolve from the repository root, `~/...` expands to your home directory, and `PI_SUBAGENTS_WORKTREE_DIR` is used when config is unset. The default remains the system temp directory.
1203
+
1044
1204
  ### `worktreeSetupHook`
1045
1205
 
1046
1206
  ```json
@@ -1070,7 +1230,7 @@ Each chain run creates a user-scoped temp directory like:
1070
1230
 
1071
1231
  It may contain files such as `context.md`, `plan.md`, `progress.md`, and `parallel-{stepIndex}/.../output.md`. Directories older than 24 hours are cleaned up on extension startup.
1072
1232
 
1073
- Debug artifacts live under `{sessionDir}/subagent-artifacts/` or a user-scoped temp artifact directory. Per task you may see:
1233
+ Debug artifacts live under `{sessionDir}/subagent-artifacts/`, `.pi-subagents/artifacts/` for project-scoped runs, or a user-scoped temp artifact directory. Single-run relative `output` files are saved under `{artifactsDir}/outputs/{runId}/` unless `singleRunOutputBaseDir` is configured. Per task you may see:
1074
1234
 
1075
1235
  - `{runId}_{agent}_input.md`
1076
1236
  - `{runId}_{agent}_output.md`
@@ -1127,7 +1287,7 @@ For `attested` or stricter levels, the child prompt includes a standardized acce
1127
1287
 
1128
1288
  ## Live progress
1129
1289
 
1130
- Foreground runs show compact live progress for single, chain, and parallel modes: current tool, recent output, token counts, duration, activity freshness, current-tool duration, and chain graph metadata when available.
1290
+ Foreground runs show compact live progress for single, chain, and parallel modes: current tool, recent output, token counts, aggregate cost, duration, activity freshness, current-tool duration, and chain graph metadata when available.
1131
1291
 
1132
1292
  Press `Ctrl+O` to expand the full streaming view with complete output per step.
1133
1293
 
@@ -1197,6 +1357,8 @@ Then `/take-screenshot https://example.com` switches to Sonnet, delegates to `br
1197
1357
 
1198
1358
  For more reusable workflows on top of subagents, including `/chain-prompts` and compare-style prompts such as `/best-of-n`, install `pi-prompt-template-model` separately and copy the examples you want into `~/.pi/agent/prompts/`.
1199
1359
 
1360
+ When `pi-prompt-template-model` is not active, `pi-subagents` may recommend it at session start, in `subagent({ action: "list" })`, and in `/subagents-doctor`. The recommendation is only a prompt; package installation still requires an explicit user-approved command.
1361
+
1200
1362
  ## Runtime files
1201
1363
 
1202
1364
  The main runtime files are:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@ Use this skill when the parent orchestrator needs to launch a specialized subage
21
21
  - **Recon and planning**: use `scout` or `context-builder`, then `planner`
22
22
  - **Parallel exploration**: run multiple non-conflicting tasks concurrently
23
23
  - **Regular skill specialists**: when discovery shows proactive skill subagent suggestions and the current work is broad enough, launch a small fresh-context fanout that asks one subagent per relevant regularly used skill to apply that skill's perspective to the task
24
- - **Long-running work**: launch async/background runs and inspect them later
24
+ - **Long-running work**: launch async/background runs and inspect them later; use `timeoutMs` or `maxRuntimeMs` when a foreground or async run needs a hard max runtime
25
25
  - **Subagent control**: watch needs-attention signals and soft-interrupt only when a delegated run is genuinely blocked
26
26
  - **Agent authoring**: create, update, or override agents and chains for a project
27
27
 
@@ -632,6 +632,11 @@ If `pi-prompt-template-model` is installed, additional user prompt templates can
632
632
  `pi-subagents`. This is useful when a slash command should always run through a
633
633
  particular agent or with forked context.
634
634
 
635
+ If `subagent({ action: "list" })`, `/subagents-doctor`, or a startup message recommends
636
+ `pi-intercom` or `pi-prompt-template-model`, offer to run the shown `pi install npm:<package>`
637
+ command only after user approval. To hide future recommendations, use
638
+ `/subagents-companions hide <package> workspace` or `... user`.
639
+
635
640
  ## Important Constraints
636
641
 
637
642
  - **Forking requires a persisted parent session.** If the current session does not
@@ -31,7 +31,7 @@ import { getProjectConfigDir } from "../shared/utils.ts";
31
31
 
32
32
  type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete";
33
33
  type ManagementScope = "user" | "project";
34
- type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig };
34
+ type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig; companionSuggestionLines?: () => string[] };
35
35
 
36
36
  interface ManagementParams {
37
37
  action?: string;
@@ -549,6 +549,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
549
549
  config: ctx.config?.proactiveSkillSubagents,
550
550
  discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
551
551
  });
552
+ const companionSuggestions = ctx.companionSuggestionLines?.() ?? [];
552
553
  const lines = [
553
554
  "Executable agents:",
554
555
  ...(agents.length
@@ -558,6 +559,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
558
559
  "Chains:",
559
560
  ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
560
561
  ...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
562
+ ...(companionSuggestions.length ? ["", ...companionSuggestions] : []),
561
563
  ...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
562
564
  ];
563
565
  return result(lines.join("\n"));
@@ -567,6 +569,9 @@ function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undef
567
569
  if (agent.override && agent.model !== agent.override.base.model) {
568
570
  return `${agent.override.scope} override`;
569
571
  }
572
+ if (agent.modelSource?.type === "subagents.defaultModel" && agent.model === agent.modelSource.model) {
573
+ return `${agent.modelSource.scope} defaultModel`;
574
+ }
570
575
  if (agent.model) return "builtin agent config";
571
576
  if (currentModel) return "inherits current session model";
572
577
  return "inherit requested, but no current session model is available";
@@ -84,6 +84,13 @@ interface BuiltinAgentOverrideInfo {
84
84
  base: BuiltinAgentOverrideBase;
85
85
  }
86
86
 
87
+ export interface AgentModelSourceInfo {
88
+ type: "subagents.defaultModel";
89
+ scope: "user" | "project";
90
+ path: string;
91
+ model: string;
92
+ }
93
+
87
94
  export interface AgentConfig {
88
95
  name: string;
89
96
  localName?: string;
@@ -113,10 +120,12 @@ export interface AgentConfig {
113
120
  disabled?: boolean;
114
121
  extraFields?: Record<string, string>;
115
122
  override?: BuiltinAgentOverrideInfo;
123
+ modelSource?: AgentModelSourceInfo;
116
124
  }
117
125
 
118
126
  interface SubagentSettings {
119
127
  overrides: Record<string, BuiltinAgentOverrideConfig>;
128
+ defaultModel?: string;
120
129
  disableBuiltins?: boolean;
121
130
  disableThinking?: boolean;
122
131
  }
@@ -683,17 +692,50 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
683
692
  throw new Error(`Subagent settings in '${filePath}' have invalid 'disableThinking'; expected a boolean.`);
684
693
  }
685
694
  }
695
+ let defaultModel: string | undefined;
696
+ if ("defaultModel" in subagentsObject) {
697
+ if (typeof subagentsObject.defaultModel === "string" && subagentsObject.defaultModel.trim()) {
698
+ defaultModel = subagentsObject.defaultModel.trim();
699
+ } else {
700
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultModel'; expected a non-empty string.`);
701
+ }
702
+ }
686
703
 
687
704
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
688
705
  const agentOverrides = subagentsObject.agentOverrides;
689
706
  if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
690
- return { overrides: parsed, disableBuiltins, disableThinking };
707
+ return { overrides: parsed, defaultModel, disableBuiltins, disableThinking };
691
708
  }
692
709
  for (const [name, value] of Object.entries(agentOverrides)) {
693
710
  const override = parseBuiltinOverrideEntry(name, value, filePath);
694
711
  if (override) parsed[name] = override;
695
712
  }
696
- return { overrides: parsed, disableBuiltins, disableThinking };
713
+ return { overrides: parsed, defaultModel, disableBuiltins, disableThinking };
714
+ }
715
+
716
+ function resolveSubagentDefaultModel(
717
+ userSettings: SubagentSettings,
718
+ projectSettings: SubagentSettings,
719
+ userSettingsPath: string,
720
+ projectSettingsPath: string | null,
721
+ ): AgentModelSourceInfo | undefined {
722
+ if (projectSettingsPath && projectSettings.defaultModel !== undefined) {
723
+ return { type: "subagents.defaultModel", scope: "project", path: projectSettingsPath, model: projectSettings.defaultModel };
724
+ }
725
+ return userSettings.defaultModel !== undefined
726
+ ? { type: "subagents.defaultModel", scope: "user", path: userSettingsPath, model: userSettings.defaultModel }
727
+ : undefined;
728
+ }
729
+
730
+ function applySubagentDefaultModel(agents: AgentConfig[], defaultModel: AgentModelSourceInfo | undefined): AgentConfig[] {
731
+ if (!defaultModel) return agents;
732
+ return agents.map((agent) => {
733
+ if (agent.model !== undefined) return agent;
734
+ const next = { ...agent, model: defaultModel.model, modelSource: defaultModel };
735
+ const frontmatterFields = agentFrontmatterFields.get(agent);
736
+ if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
737
+ return next;
738
+ });
697
739
  }
698
740
 
699
741
  function applyBuiltinOverride(
@@ -1241,13 +1283,14 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1241
1283
  const projectSettingsPath = getProjectAgentSettingsPath(cwd);
1242
1284
  const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
1243
1285
  const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
1286
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
1244
1287
  const packageSubagentPaths = collectPackageSubagentPaths(cwd, {
1245
1288
  includeUser: scope !== "project",
1246
1289
  includeProject: scope !== "user",
1247
1290
  });
1248
1291
 
1249
1292
  const builtinAgents = applyBuiltinOverrides(
1250
- loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
1293
+ applySubagentDefaultModel(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel),
1251
1294
  userSettings,
1252
1295
  projectSettings,
1253
1296
  userSettingsPath,
@@ -1258,7 +1301,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1258
1301
  const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
1259
1302
  const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
1260
1303
  const userAgents = applyCustomAgentOverrides(
1261
- [...userAgentsExtra, ...userAgentsOld, ...userAgentsNew],
1304
+ applySubagentDefaultModel([...userAgentsExtra, ...userAgentsOld, ...userAgentsNew], defaultModel),
1262
1305
  userSettings,
1263
1306
  projectSettings,
1264
1307
  userSettingsPath,
@@ -1266,13 +1309,13 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1266
1309
  );
1267
1310
 
1268
1311
  const projectAgents = applyCustomAgentOverrides(
1269
- scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project")),
1312
+ applySubagentDefaultModel(scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project")), defaultModel),
1270
1313
  userSettings,
1271
1314
  projectSettings,
1272
1315
  userSettingsPath,
1273
1316
  projectSettingsPath,
1274
1317
  );
1275
- const packageAgents = packageSubagentPaths.agents.flatMap((dir) => loadAgentsFromDir(dir, "package"));
1318
+ const packageAgents = applySubagentDefaultModel(packageSubagentPaths.agents.flatMap((dir) => loadAgentsFromDir(dir, "package")), defaultModel);
1276
1319
  const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
1277
1320
  .filter((agent) => agent.disabled !== true);
1278
1321
 
@@ -1302,21 +1345,22 @@ export function discoverAgentsAll(cwd: string): {
1302
1345
  const projectSettingsPath = getProjectAgentSettingsPath(cwd);
1303
1346
  const userSettings = readSubagentSettings(userSettingsPath);
1304
1347
  const projectSettings = readSubagentSettings(projectSettingsPath);
1348
+ const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
1305
1349
  const packageSubagentPaths = collectPackageSubagentPaths(cwd);
1306
1350
 
1307
1351
  const builtin = applyBuiltinOverrides(
1308
- loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
1352
+ applySubagentDefaultModel(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel),
1309
1353
  userSettings,
1310
1354
  projectSettings,
1311
1355
  userSettingsPath,
1312
1356
  projectSettingsPath,
1313
1357
  );
1314
1358
  const user = applyCustomAgentOverrides(
1315
- [
1359
+ applySubagentDefaultModel([
1316
1360
  ...extraUserAgentDirs().flatMap((dir) => loadAgentsFromDir(dir, "user")),
1317
1361
  ...loadAgentsFromDir(userDirOld, "user"),
1318
1362
  ...loadAgentsFromDir(userDirNew, "user"),
1319
- ],
1363
+ ], defaultModel),
1320
1364
  userSettings,
1321
1365
  projectSettings,
1322
1366
  userSettingsPath,
@@ -1328,7 +1372,7 @@ export function discoverAgentsAll(cwd: string): {
1328
1372
  if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
1329
1373
  }
1330
1374
  }
1331
- const packageAgents = Array.from(packageMap.values());
1375
+ const packageAgents = applySubagentDefaultModel(Array.from(packageMap.values()), defaultModel);
1332
1376
  const projectMap = new Map<string, AgentConfig>();
1333
1377
  for (const dir of projectDirs) {
1334
1378
  for (const agent of loadAgentsFromDir(dir, "project")) {
@@ -1336,7 +1380,7 @@ export function discoverAgentsAll(cwd: string): {
1336
1380
  }
1337
1381
  }
1338
1382
  const project = applyCustomAgentOverrides(
1339
- Array.from(projectMap.values()),
1383
+ applySubagentDefaultModel(Array.from(projectMap.values()), defaultModel),
1340
1384
  userSettings,
1341
1385
  projectSettings,
1342
1386
  userSettingsPath,