pi-cohort 7.1.0 → 7.1.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.1.2] - 2026-09-29
4
+
5
+ ### Fixed
6
+
7
+ - `typebox` moves from `dependencies` to an optional `peerDependencies: "*"` entry, which silences pi's "Host-provided extension packages must be declared in peerDependencies" startup warning and removes the second typebox copy from consumer installs. The detached async runner keeps working without its own copy: `spawnRunner` now passes pi's own `typebox`, `typebox/compile`, and `typebox/value` files to the bare jiti child through `JITI_ALIAS`, the same alias map pi's extension loader applies in-process, so the `Cannot find module 'typebox/compile'` crash from [#9](https://github.com/jjuraszek/pi-cohort/issues/9) stays fixed.
8
+
9
+ ## [7.1.1] - 2026-09-24
10
+
11
+ ### Changed
12
+
13
+ - A dispatch that names no model now runs the child on the parent session's current model and thinking level instead of the child's saved `defaultModel`; `thinking: off` is emitted as `:off`, and `max` is a recognized level. Precedence: per-call `model:` > agent `model` / `thinking` (after `agentOverrides`) > parent session > none. Pin `subagents.agentOverrides.<agent>.model` to keep a child on a fixed model. Agents that pin `model` but not `thinking` now also take the parent's thinking level; set `thinking` to keep a fixed level. Children now bill on the parent's model, and an agent with `extensions:` whose parent model comes from an extension provider fails as an unknown model would; give it a pinned model or `fallbackModels`.
14
+
3
15
  ## [7.1.0] - 2026-09-20
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "7.1.0",
3
+ "version": "7.1.2",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -64,7 +64,8 @@
64
64
  "@earendil-works/pi-agent-core": "*",
65
65
  "@earendil-works/pi-ai": "*",
66
66
  "@earendil-works/pi-coding-agent": ">=0.85.0",
67
- "@earendil-works/pi-tui": "*"
67
+ "@earendil-works/pi-tui": "*",
68
+ "typebox": "*"
68
69
  },
69
70
  "peerDependenciesMeta": {
70
71
  "@earendil-works/pi-agent-core": {
@@ -78,16 +79,19 @@
78
79
  },
79
80
  "@earendil-works/pi-tui": {
80
81
  "optional": true
82
+ },
83
+ "typebox": {
84
+ "optional": true
81
85
  }
82
86
  },
83
87
  "dependencies": {
84
- "jiti": "^2.7.0",
85
- "typebox": "^1.3.11"
88
+ "jiti": "^2.7.0"
86
89
  },
87
90
  "devDependencies": {
88
91
  "@earendil-works/pi-agent-core": "^0.85.0",
89
92
  "@earendil-works/pi-ai": "^0.85.0",
90
93
  "@earendil-works/pi-coding-agent": "^0.85.0",
91
- "@earendil-works/pi-tui": "^0.85.0"
94
+ "@earendil-works/pi-tui": "^0.85.0",
95
+ "typebox": "^1.3.11"
92
96
  }
93
97
  }
@@ -42,7 +42,7 @@ export function serializeAgent(config: AgentConfig): string {
42
42
  if (config.model) lines.push(`model: ${config.model}`);
43
43
  const fallbackModelsValue = joinComma(config.fallbackModels);
44
44
  if (fallbackModelsValue) lines.push(`fallbackModels: ${fallbackModelsValue}`);
45
- if (config.thinking && config.thinking !== "off") lines.push(`thinking: ${config.thinking}`);
45
+ if (config.thinking) lines.push(`thinking: ${config.thinking}`);
46
46
  lines.push(`systemPromptMode: ${config.systemPromptMode}`);
47
47
  lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
48
48
  lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
@@ -14,7 +14,7 @@ import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolvePa
14
14
  import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
15
15
  import type { RunnerStep } from "../shared/parallel-utils.ts";
16
16
  import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
17
- import { ensureJitiCliPath } from "../shared/jiti-cli.ts";
17
+ import { ensureJitiCliPath, resolveHostModuleAliases } from "../shared/jiti-cli.ts";
18
18
  export { createJitiCliResolver } from "../shared/jiti-cli.ts";
19
19
  import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
20
20
  import { resolveChildCwd } from "../../shared/utils.ts";
@@ -51,6 +51,10 @@ interface AsyncExecutionContext {
51
51
  cwd: string;
52
52
  currentSessionId: string;
53
53
  currentModelProvider?: string;
54
+ /** Parent session model as provider/id, captured at dispatch */
55
+ parentModel?: string;
56
+ /** Parent session thinking level, captured at dispatch */
57
+ parentThinking?: string;
54
58
  executionBackend?: string;
55
59
  }
56
60
 
@@ -141,7 +145,7 @@ export function isAsyncAvailable(): boolean {
141
145
  * Falls back to ignored stdio if the log file cannot be opened; the parent always
142
146
  * closes its copy of the fd, on every exit path.
143
147
  */
144
- export function spawnDetachedWithLog(command: string, args: string[], cwd: string, asyncDir: string, spawnImpl: typeof spawn = spawn): { pid?: number; error?: string } {
148
+ export function spawnDetachedWithLog(command: string, args: string[], cwd: string, asyncDir: string, spawnImpl: typeof spawn = spawn, env?: NodeJS.ProcessEnv): { pid?: number; error?: string } {
145
149
  let fd: number | undefined;
146
150
  try {
147
151
  try {
@@ -152,6 +156,7 @@ export function spawnDetachedWithLog(command: string, args: string[], cwd: strin
152
156
  }
153
157
  const proc = spawnImpl(command, args, {
154
158
  cwd,
159
+ env,
155
160
  detached: true,
156
161
  stdio: fd === undefined ? "ignore" : ["ignore", fd, fd],
157
162
  windowsHide: true,
@@ -183,7 +188,7 @@ export function spawnRunner(
183
188
  suffix: string,
184
189
  cwd: string,
185
190
  asyncDir: string,
186
- deps: { ensureJiti?: () => string | undefined; spawnImpl?: typeof spawn } = {},
191
+ deps: { ensureJiti?: () => string | undefined; spawnImpl?: typeof spawn; hostAliases?: () => Record<string, string> } = {},
187
192
  ): { pid?: number; error?: string } {
188
193
  const ensureJiti = deps.ensureJiti ?? ensureJitiCliPath;
189
194
  const jitiCliPath = ensureJiti();
@@ -205,7 +210,12 @@ export function spawnRunner(
205
210
  fs.writeFileSync(cfgPath, JSON.stringify(cfg));
206
211
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
207
212
 
208
- return spawnDetachedWithLog(process.execPath, [jitiCliPath, runner, cfgPath], cwd, asyncDir, deps.spawnImpl ?? spawn);
213
+ const aliases = (deps.hostAliases ?? resolveHostModuleAliases)();
214
+ const env = Object.keys(aliases).length > 0
215
+ ? { ...process.env, JITI_ALIAS: JSON.stringify(aliases) }
216
+ : process.env;
217
+
218
+ return spawnDetachedWithLog(process.execPath, [jitiCliPath, runner, cfgPath], cwd, asyncDir, deps.spawnImpl ?? spawn, env);
209
219
  }
210
220
 
211
221
  /**
@@ -355,8 +365,10 @@ export function executeAsyncChain(
355
365
  taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
356
366
  const task = injectSingleOutputInstruction(`${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`, outputPath);
357
367
 
358
- const primaryModel = resolveModelCandidate(behavior.model ?? a.model, availableModels, ctx.currentModelProvider);
359
- const model = applyThinkingSuffix(primaryModel, a.thinking);
368
+ const primaryRaw = behavior.model ?? a.model ?? ctx.parentModel;
369
+ const thinking = a.thinking ?? ctx.parentThinking;
370
+ const primaryModel = resolveModelCandidate(primaryRaw, availableModels, ctx.currentModelProvider);
371
+ const model = applyThinkingSuffix(primaryModel, thinking);
360
372
  return {
361
373
  agent: s.agent,
362
374
  task,
@@ -366,9 +378,9 @@ export function executeAsyncChain(
366
378
  structured: Boolean(s.outputSchema),
367
379
  cwd: stepCwd,
368
380
  model,
369
- thinking: resolveEffectiveThinking(model, a.thinking),
370
- modelCandidates: buildModelCandidates(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
371
- applyThinkingSuffix(candidate, a.thinking),
381
+ thinking: resolveEffectiveThinking(model, thinking),
382
+ modelCandidates: buildModelCandidates(primaryRaw, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
383
+ applyThinkingSuffix(candidate, thinking),
372
384
  ),
373
385
  tools: a.tools,
374
386
  extensions: a.extensions,
@@ -670,10 +682,9 @@ export function executeAsyncSingle(
670
682
  const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
671
683
  if (validationError) return formatAsyncStartError("single", validationError);
672
684
  const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
673
- const model = applyThinkingSuffix(
674
- resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider),
675
- agentConfig.thinking,
676
- );
685
+ const primaryRaw = params.modelOverride ?? agentConfig.model ?? ctx.parentModel;
686
+ const thinking = agentConfig.thinking ?? ctx.parentThinking;
687
+ const model = applyThinkingSuffix(resolveModelCandidate(primaryRaw, availableModels, ctx.currentModelProvider), thinking);
677
688
  let spawnResult: { pid?: number; error?: string } = {};
678
689
  try {
679
690
  spawnResult = spawnRunner(
@@ -685,9 +696,9 @@ export function executeAsyncSingle(
685
696
  task: taskWithOutputInstruction,
686
697
  cwd: runnerCwd,
687
698
  model,
688
- thinking: resolveEffectiveThinking(model, agentConfig.thinking),
689
- modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
690
- applyThinkingSuffix(candidate, agentConfig.thinking),
699
+ thinking: resolveEffectiveThinking(model, thinking),
700
+ modelCandidates: buildModelCandidates(primaryRaw, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
701
+ applyThinkingSuffix(candidate, thinking),
691
702
  ),
692
703
  tools: agentConfig.tools,
693
704
  extensions: agentConfig.extensions,
@@ -11,7 +11,7 @@ import { matchesKey, visibleWidth, truncateToWidth } from "@earendil-works/pi-tu
11
11
  import type { AgentConfig } from "../../agents/agents.ts";
12
12
  import type { ResolvedStepBehavior } from "../../shared/settings.ts";
13
13
  import { resolveModelCandidate, splitThinkingSuffix } from "../shared/model-fallback.ts";
14
- import { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from "../../shared/model-info.ts";
14
+ import { findModelInfo, getSupportedThinkingLevels, splitKnownThinkingSuffix, type ModelInfo, type ThinkingLevel } from "../../shared/model-info.ts";
15
15
 
16
16
  type ClarifyMode = 'single' | 'parallel' | 'chain';
17
17
 
@@ -696,9 +696,8 @@ export class ChainClarifyComponent implements Component {
696
696
  const currentModel = this.getEffectiveBehavior(stepIndex).model;
697
697
  if (!currentModel) return;
698
698
 
699
- const { baseModel } = splitThinkingSuffix(currentModel);
700
- const newModel = level === "off" ? baseModel : `${baseModel}:${level}`;
701
- this.updateBehavior(stepIndex, "model", newModel);
699
+ const { baseModel } = splitKnownThinkingSuffix(currentModel);
700
+ this.updateBehavior(stepIndex, "model", `${baseModel}:${level}`);
702
701
  }
703
702
 
704
703
  private filterSkills(): void {
@@ -1009,7 +1008,8 @@ export class ChainClarifyComponent implements Component {
1009
1008
  "low": "Light reasoning",
1010
1009
  "medium": "Moderate reasoning",
1011
1010
  "high": "Deep reasoning",
1012
- "xhigh": "Maximum reasoning (ultrathink)",
1011
+ "xhigh": "Extra-deep reasoning (ultrathink)",
1012
+ "max": "Provider maximum reasoning",
1013
1013
  };
1014
1014
 
1015
1015
  const levels = this.getAvailableThinkingLevels(this.editingStep!);
@@ -8,7 +8,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
8
8
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
9
9
  import type { AgentConfig } from "../../agents/agents.ts";
10
10
  import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride } from "./chain-clarify.ts";
11
- import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
11
+ import { parentModelFullId, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
12
12
  import {
13
13
  resolveChainTemplates,
14
14
  createChainDir,
@@ -58,6 +58,7 @@ import {
58
58
  resolveChildMaxSubagentDepth,
59
59
  } from "../../shared/types.ts";
60
60
  import { resolveModelCandidate } from "../shared/model-fallback.ts";
61
+ import { applyThinkingSuffix } from "../shared/pi-args.ts";
61
62
  import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
62
63
  import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
63
64
  import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
@@ -278,6 +279,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
278
279
  modelOverride: effectiveModel,
279
280
  availableModels: input.availableModels,
280
281
  preferredModelProvider: input.ctx.model?.provider,
282
+ parentModel: parentModelFullId(input.ctx.model),
283
+ parentThinking: input.ctx.thinkingLevel,
281
284
  skills: behavior.skills === false ? [] : behavior.skills,
282
285
  structuredOutput: structuredRuntime,
283
286
  acceptance: task.acceptance,
@@ -512,6 +515,14 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
512
515
  resolveStepBehavior(config, stepOverrides[i]!, chainSkills),
513
516
  );
514
517
  const flatTemplates = templates as string[];
518
+ // Seed what dispatch will resolve so the screen shows it and the thinking selector is enabled;
519
+ // untouched steps still reach runSync with no override and inherit there.
520
+ const parentModel = parentModelFullId(ctx.model);
521
+ const seededBehaviors = resolvedBehaviors.map((behavior, i) => {
522
+ const model = behavior.model ?? parentModel;
523
+ if (!model) return behavior;
524
+ return { ...behavior, model: applyThinkingSuffix(model, agentConfigs[i]!.thinking ?? ctx.thinkingLevel) };
525
+ });
515
526
 
516
527
  const result = await ctx.ui.custom<ChainClarifyResult>(
517
528
  (tui, theme, _kb, done) =>
@@ -522,7 +533,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
522
533
  flatTemplates,
523
534
  originalTask,
524
535
  chainDir,
525
- resolvedBehaviors,
536
+ seededBehaviors,
526
537
  availableModels,
527
538
  ctx.model?.provider,
528
539
  availableSkills,
@@ -1030,6 +1041,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1030
1041
  modelOverride: effectiveModel,
1031
1042
  availableModels,
1032
1043
  preferredModelProvider: ctx.model?.provider,
1044
+ parentModel: parentModelFullId(ctx.model),
1045
+ parentThinking: ctx.thinkingLevel,
1033
1046
  skills: behavior.skills === false ? [] : behavior.skills,
1034
1047
  structuredOutput: structuredRuntime,
1035
1048
  acceptance: seqStep.acceptance,
@@ -684,11 +684,12 @@ export async function runSync(
684
684
  }
685
685
 
686
686
  const candidates = buildModelCandidates(
687
- options.modelOverride ?? agent.model,
687
+ options.modelOverride ?? agent.model ?? options.parentModel,
688
688
  agent.fallbackModels,
689
689
  options.availableModels,
690
690
  options.preferredModelProvider,
691
691
  );
692
+ const effectiveAgent: AgentConfig = { ...agent, thinking: agent.thinking ?? options.parentThinking };
692
693
  const attemptedModels: string[] = [];
693
694
  const modelAttempts: ModelAttempt[] = [];
694
695
  const aggregateUsage = emptyUsage();
@@ -751,7 +752,7 @@ export async function runSync(
751
752
  owner: externalOwner,
752
753
  attemptId: `${childId}-attempt-${i}`,
753
754
  runtimeCwd,
754
- agent,
755
+ agent: effectiveAgent,
755
756
  model: candidate,
756
757
  task: taskWithAcceptance,
757
758
  options,
@@ -762,7 +763,7 @@ export async function runSync(
762
763
  }
763
764
  } else {
764
765
  result = await (dependencies.runNativeAttempt ?? runSingleAttempt)(
765
- runtimeCwd, agent, taskWithAcceptance, candidate, options, shared,
766
+ runtimeCwd, effectiveAgent, taskWithAcceptance, candidate, options, shared,
766
767
  );
767
768
  }
768
769
  lastResult = result;
@@ -6,7 +6,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
6
6
  import type { AgentConfig, AgentScope } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir } from "../../shared/artifacts.ts";
8
8
  import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
- import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
9
+ import { parentModelFullId, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
10
10
  import { executeChain } from "./chain-execution.ts";
11
11
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
12
12
  import { handleManagementAction } from "../../agents/agent-management.ts";
@@ -210,6 +210,18 @@ function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResoluti
210
210
  };
211
211
  }
212
212
 
213
+ function buildAsyncContext(ctx: ExtensionContext, deps: ExecutorDeps, cwd: string) {
214
+ return {
215
+ pi: deps.pi,
216
+ cwd,
217
+ currentSessionId: deps.state.currentSessionId!,
218
+ currentModelProvider: ctx.model?.provider,
219
+ parentModel: parentModelFullId(ctx.model),
220
+ parentThinking: ctx.thinkingLevel,
221
+ executionBackend: deps.config.executionBackend,
222
+ };
223
+ }
224
+
213
225
  function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
214
226
  let nestedWarning: string | undefined;
215
227
  try {
@@ -591,13 +603,7 @@ async function resumeAsyncRun(input: {
591
603
  agent: target.agent,
592
604
  task: buildRevivedAsyncTask(target, followUp),
593
605
  agentConfig,
594
- ctx: {
595
- pi: input.deps.pi,
596
- cwd: input.requestCwd,
597
- currentSessionId: input.deps.state.currentSessionId,
598
- currentModelProvider: input.ctx.model?.provider,
599
- executionBackend: input.deps.config.executionBackend,
600
- },
606
+ ctx: buildAsyncContext(input.ctx, input.deps, input.requestCwd),
601
607
  cwd: effectiveCwd,
602
608
  maxOutput: input.params.maxOutput,
603
609
  artifactsDir: input.deps.tempArtifactsDir,
@@ -974,13 +980,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
974
980
  };
975
981
  }
976
982
  const id = randomUUID();
977
- const asyncCtx = {
978
- pi: deps.pi,
979
- cwd: ctx.cwd,
980
- currentSessionId: deps.state.currentSessionId!,
981
- currentModelProvider: ctx.model?.provider,
982
- executionBackend: deps.config.executionBackend,
983
- };
983
+ const asyncCtx = buildAsyncContext(ctx, deps, ctx.cwd);
984
984
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
985
985
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
986
986
  const currentProvider = ctx.model?.provider;
@@ -1164,13 +1164,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1164
1164
  };
1165
1165
  }
1166
1166
  const id = randomUUID();
1167
- const asyncCtx = {
1168
- pi: deps.pi,
1169
- cwd: ctx.cwd,
1170
- currentSessionId: deps.state.currentSessionId!,
1171
- currentModelProvider: ctx.model?.provider,
1172
- executionBackend: deps.config.executionBackend,
1173
- };
1167
+ const asyncCtx = buildAsyncContext(ctx, deps, ctx.cwd);
1174
1168
  const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
1175
1169
  return executeAsyncChain(id, {
1176
1170
  chain: asyncChain,
@@ -1432,6 +1426,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1432
1426
  modelOverride: input.modelOverrides[index],
1433
1427
  availableModels: input.availableModels,
1434
1428
  preferredModelProvider: input.ctx.model?.provider,
1429
+ parentModel: parentModelFullId(input.ctx.model),
1430
+ parentThinking: input.ctx.thinkingLevel,
1435
1431
  skills: effectiveSkills === false ? [] : effectiveSkills,
1436
1432
  acceptance: task.acceptance,
1437
1433
  acceptanceContext: { mode: "parallel" },
@@ -1605,13 +1601,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1605
1601
  };
1606
1602
  }
1607
1603
  const id = randomUUID();
1608
- const asyncCtx = {
1609
- pi: deps.pi,
1610
- cwd: ctx.cwd,
1611
- currentSessionId: deps.state.currentSessionId!,
1612
- currentModelProvider: ctx.model?.provider,
1613
- executionBackend: deps.config.executionBackend,
1614
- };
1604
+ const asyncCtx = buildAsyncContext(ctx, deps, ctx.cwd);
1615
1605
  const parallelTasks = tasks.map((t, i) => {
1616
1606
  const taskText = params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
1617
1607
  const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
@@ -1883,13 +1873,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1883
1873
  };
1884
1874
  }
1885
1875
  const id = randomUUID();
1886
- const asyncCtx = {
1887
- pi: deps.pi,
1888
- cwd: ctx.cwd,
1889
- currentSessionId: deps.state.currentSessionId!,
1890
- currentModelProvider: ctx.model?.provider,
1891
- executionBackend: deps.config.executionBackend,
1892
- };
1876
+ const asyncCtx = buildAsyncContext(ctx, deps, ctx.cwd);
1893
1877
  return executeAsyncSingle(id, {
1894
1878
  agent: params.agent!,
1895
1879
  task: params.context === "fork" ? wrapForkTask(task) : task,
@@ -1993,6 +1977,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1993
1977
  modelOverride,
1994
1978
  availableModels,
1995
1979
  preferredModelProvider: currentProvider,
1980
+ parentModel: parentModelFullId(ctx.model),
1981
+ parentThinking: ctx.thinkingLevel,
1996
1982
  skills: effectiveSkills,
1997
1983
  acceptance: params.acceptance,
1998
1984
  acceptanceContext: { mode: "single" },
@@ -24,17 +24,23 @@ function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefi
24
24
  return undefined;
25
25
  }
26
26
 
27
- function resolveJitiCliPath(): string | undefined {
28
- const candidates: Array<() => string | undefined> = [
29
- () => require.resolve("jiti/package.json"),
27
+ function hostResolveCandidates(specifier: string): Array<() => string | undefined> {
28
+ return [
29
+ () => require.resolve(specifier),
30
30
  () => piPackageRoot
31
- ? createRequire(path.join(piPackageRoot, "package.json")).resolve("jiti/package.json")
31
+ ? createRequire(path.join(piPackageRoot, "package.json")).resolve(specifier)
32
32
  : undefined,
33
33
  () => {
34
34
  if (!process.argv[1]) return undefined;
35
35
  const piEntry = fs.realpathSync(process.argv[1]);
36
- return createRequire(piEntry).resolve("jiti/package.json");
36
+ return createRequire(piEntry).resolve(specifier);
37
37
  },
38
+ ];
39
+ }
40
+
41
+ function resolveJitiCliPath(): string | undefined {
42
+ const candidates = [
43
+ ...hostResolveCandidates("jiti/package.json"),
38
44
  () => piPackageRoot ? path.join(piPackageRoot, "node_modules", "jiti", "package.json") : undefined,
39
45
  ];
40
46
  for (const candidate of candidates) {
@@ -50,6 +56,31 @@ function resolveJitiCliPath(): string | undefined {
50
56
  return undefined;
51
57
  }
52
58
 
59
+ function resolveFromHost(specifier: string): string | undefined {
60
+ for (const candidate of hostResolveCandidates(specifier)) {
61
+ try {
62
+ const resolved = candidate();
63
+ if (resolved) return resolved;
64
+ } catch {
65
+ // Candidate not available in this install, continue probing.
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ // Pi's extension loader aliases these to its own copies; the detached runner boots
72
+ // under the bare jiti CLI, so it gets the same map through JITI_ALIAS.
73
+ const HOST_ALIASED_SPECIFIERS = ["typebox", "typebox/compile", "typebox/value"];
74
+
75
+ export function resolveHostModuleAliases(): Record<string, string> {
76
+ const aliases: Record<string, string> = {};
77
+ for (const specifier of HOST_ALIASED_SPECIFIERS) {
78
+ const resolved = resolveFromHost(specifier);
79
+ if (resolved) aliases[specifier] = resolved;
80
+ }
81
+ return aliases;
82
+ }
83
+
53
84
  export function createJitiCliResolver(deps: { resolve?: () => string | undefined; exists?: (p: string) => boolean } = {}): () => string | undefined {
54
85
  const resolve = deps.resolve ?? resolveJitiCliPath;
55
86
  const exists = deps.exists ?? ((p: string) => fs.existsSync(p));
@@ -6,7 +6,7 @@ import { encodeNestedPathEnv, parseNestedPathEnv, type NestedPathEntry } from ".
6
6
  import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, STRUCTURED_OUTPUT_TOOL_NAME } from "./structured-output.ts";
7
7
  import type { JsonSchemaObject } from "../../shared/types.ts";
8
8
 
9
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
9
+ const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
10
10
  const TASK_ARG_LIMIT = 8000;
11
11
  const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
12
12
  const FANOUT_CHILD_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", "fanout-child.ts");
@@ -73,7 +73,7 @@ export function runDirEnv(asyncDir: string): Record<string, string> {
73
73
  }
74
74
 
75
75
  export function applyThinkingSuffix(model: string | undefined, thinking: string | undefined): string | undefined {
76
- if (!model || !thinking || thinking === "off") return model;
76
+ if (!model || !thinking) return model;
77
77
  const colonIdx = model.lastIndexOf(":");
78
78
  if (colonIdx !== -1 && THINKING_LEVELS.includes(model.substring(colonIdx + 1))) return model;
79
79
  return `${model}:${thinking}`;
@@ -1,4 +1,4 @@
1
- export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
1
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
2
2
  export type ThinkingLevel = typeof THINKING_LEVELS[number];
3
3
  export type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;
4
4
 
@@ -63,16 +63,21 @@ export function findModelInfo(model: string | undefined, availableModels: ModelI
63
63
  }
64
64
 
65
65
  export function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {
66
- if (!model) return [...THINKING_LEVELS];
66
+ if (!model) return THINKING_LEVELS.filter((level) => level !== "max");
67
67
  if (model.reasoning === false) return ["off"];
68
68
 
69
- if (!model.thinkingLevelMap) return [...THINKING_LEVELS];
69
+ if (!model.thinkingLevelMap) return THINKING_LEVELS.filter((level) => level !== "max");
70
70
 
71
71
  const levels = THINKING_LEVELS.filter((level) => {
72
72
  const mapped = model.thinkingLevelMap?.[level];
73
73
  if (mapped === null) return false;
74
- if (level === "xhigh") return mapped !== undefined;
74
+ if (level === "xhigh" || level === "max") return mapped !== undefined;
75
75
  return true;
76
76
  });
77
77
  return levels;
78
78
  }
79
+
80
+ /** Provider-qualified id of the parent session model; undefined when either part is missing (SDK hosts, minimal test contexts). */
81
+ export function parentModelFullId(model: { provider?: string; id?: string } | undefined): string | undefined {
82
+ return model?.provider && model.id ? `${model.provider}/${model.id}` : undefined;
83
+ }
@@ -776,6 +776,10 @@ export interface RunSyncOptions {
776
776
  availableModels?: Array<{ provider: string; id: string; fullId: string }>;
777
777
  /** Current parent-session provider to prefer for ambiguous bare model ids */
778
778
  preferredModelProvider?: string;
779
+ /** Parent session model as provider/id; used when neither the call nor the agent names a model */
780
+ parentModel?: string;
781
+ /** Parent session thinking level; used when the agent sets no `thinking` */
782
+ parentThinking?: string;
779
783
  /** Skills to inject (overrides agent default if provided) */
780
784
  skills?: string[];
781
785
  structuredOutput?: {