pi-advisor-flow 0.8.0 → 0.8.1-dev.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project are documented here.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/).
6
6
 
7
+ ## 0.8.1-dev.1 - 2026-09-23
8
+
9
+ ### Fixed
10
+
11
+ - Preserve a native subagent's host-selected model and thinking level when `alwaysOn` or explicit `/advisor` activation runs in a process marked with `PI_SUBAGENT_CHILD=1`.
12
+ - Keep marked child model selections out of the persisted parent Executor configuration.
13
+ - Run Advisor Scout with the marked child's active model and thinking level instead of replacing them with the configured parent Executor.
14
+
7
15
  ## 0.8.0 - 2026-09-22
8
16
 
9
17
  ### Added
package/dist/index.js CHANGED
@@ -272,6 +272,16 @@ var splitRef = (ref) => {
272
272
  return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
273
273
  };
274
274
 
275
+ // src/child-session.ts
276
+ var isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
277
+ var effectiveExecutorRef = (ctx) => {
278
+ if (isMarkedSubagent() && ctx.model) {
279
+ return `${ctx.model.provider}/${ctx.model.id}`;
280
+ }
281
+ return executorRef;
282
+ };
283
+ var effectiveExecutorEffort = (ctx) => isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
284
+
275
285
  // src/content-utils.ts
276
286
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
277
287
  var isRecordOf = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -2067,8 +2077,8 @@ var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies
2067
2077
  return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
2068
2078
  }
2069
2079
  if (policy === "summary") {
2070
- const capped = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
2071
- return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped.totalLines} lines, ${capped.totalBytes} bytes; source output was${capped.truncated ? "" : " not"} truncated)`;
2080
+ const capped2 = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
2081
+ return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped2.totalLines} lines, ${capped2.totalBytes} bytes; source output was${capped2.truncated ? "" : " not"} truncated)`;
2072
2082
  }
2073
2083
  const disclosed = redact ? redactSecrets(source) : source;
2074
2084
  const capped = capToolResult(disclosed, toolResultMaxLines, toolResultMaxBytes);
@@ -2108,17 +2118,17 @@ var selectRecentEntries = (entries, maxChars) => {
2108
2118
  }
2109
2119
  const newestTruncated = "[Newest entry truncated]";
2110
2120
  if (entries.length === 1) {
2111
- const prefix = `${newestTruncated}${separator}`;
2112
- return `${prefix}${entries[0].slice(0, Math.max(0, maxChars - prefix.length))}`.slice(0, maxChars);
2121
+ const prefix2 = `${newestTruncated}${separator}`;
2122
+ return `${prefix2}${entries[0].slice(0, Math.max(0, maxChars - prefix2.length))}`.slice(0, maxChars);
2113
2123
  }
2114
2124
  const selected = [];
2115
2125
  let selectedLength = 0;
2116
2126
  for (let index = entries.length - 1;index >= 0; index -= 1) {
2117
2127
  const entry = entries[index];
2118
2128
  const candidateCount = selected.length + 1;
2119
- const omitted = entries.length - candidateCount;
2129
+ const omitted2 = entries.length - candidateCount;
2120
2130
  const candidateLength = selectedLength + entry.length + (selected.length > 0 ? separator.length : 0);
2121
- if (omissionMarker(omitted).length + separator.length + candidateLength > maxChars) {
2131
+ if (omissionMarker(omitted2).length + separator.length + candidateLength > maxChars) {
2122
2132
  break;
2123
2133
  }
2124
2134
  selected.unshift(entry);
@@ -2814,17 +2824,17 @@ var setupAbortWatch = (parentSignal, timeoutMs) => {
2814
2824
  wasTimedOut: () => timedOut
2815
2825
  };
2816
2826
  };
2817
- var streamScoutResponse = async (dependencies, resolved, manifest, parentSignal, timeoutMs, publish) => {
2827
+ var streamScoutResponse = async (dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish) => {
2818
2828
  const { abortPromise, controller, teardown, wasTimedOut } = setupAbortWatch(parentSignal, timeoutMs);
2819
2829
  try {
2820
2830
  const collection = dependencies.collect(resolved, {
2821
2831
  messages: [manifestMessage(manifest)],
2822
2832
  onChunk: (thinking, text) => {
2823
2833
  if (!controller.signal.aborted) {
2824
- publish({ model: executorRef, text, thinking, type: "chunk" });
2834
+ publish({ model: executorModel, text, thinking, type: "chunk" });
2825
2835
  }
2826
2836
  },
2827
- reasoning: executorEffortRef,
2837
+ reasoning: executorEffort,
2828
2838
  signal: controller.signal,
2829
2839
  systemPrompt: SCOUT_SYSTEM
2830
2840
  });
@@ -2853,22 +2863,24 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2853
2863
  return { cancelled: true, ok: false };
2854
2864
  };
2855
2865
  const fallback = (category, message, usage) => {
2856
- const outcome = {
2866
+ const outcome2 = {
2857
2867
  category,
2858
2868
  message,
2859
2869
  metrics: usage === undefined ? baseMetrics(manifest, startedAt) : { ...baseMetrics(manifest, startedAt), usage },
2860
- model: executorRef,
2870
+ model: effectiveExecutorRef(ctx),
2861
2871
  ok: false
2862
2872
  };
2863
- publish({ outcome, type: "fallback" });
2864
- return outcome;
2873
+ publish({ outcome: outcome2, type: "fallback" });
2874
+ return outcome2;
2865
2875
  };
2866
2876
  if (parentSignal?.aborted) {
2867
2877
  return cancelled();
2868
2878
  }
2879
+ const executorModel = effectiveExecutorRef(ctx);
2880
+ const executorEffort = effectiveExecutorEffort(ctx);
2869
2881
  let resolved;
2870
2882
  try {
2871
- resolved = await dependencies.resolve(ctx, executorRef, "Scout");
2883
+ resolved = await dependencies.resolve(ctx, executorModel, "Scout");
2872
2884
  } catch (error) {
2873
2885
  if (parentSignal?.aborted) {
2874
2886
  return cancelled();
@@ -2879,8 +2891,8 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2879
2891
  if (parentSignal?.aborted) {
2880
2892
  return cancelled();
2881
2893
  }
2882
- publish({ model: executorRef, type: "call" });
2883
- const streamed = await streamScoutResponse(dependencies, resolved, manifest, parentSignal, timeoutMs, publish);
2894
+ publish({ model: executorModel, type: "call" });
2895
+ const streamed = await streamScoutResponse(dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish);
2884
2896
  if (!streamed.ok) {
2885
2897
  if (parentSignal?.aborted) {
2886
2898
  return cancelled();
@@ -2907,7 +2919,7 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2907
2919
  ]).size,
2908
2920
  usage: snapshotAdvisorUsage(streamed.streamed.usage)
2909
2921
  },
2910
- model: executorRef,
2922
+ model: executorModel,
2911
2923
  ok: true,
2912
2924
  selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
2913
2925
  selection
@@ -2958,12 +2970,12 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
2958
2970
  };
2959
2971
 
2960
2972
  // src/tools/prompts.ts
2961
- var advisorMessageText = (conversation, question, changes, draft, preferences, untracked, tracked) => {
2973
+ var advisorMessageText = (conversation, question, changes, draft, preferences, untracked2, tracked2) => {
2962
2974
  const safeConversation = escapeRepositoryText(conversation);
2963
2975
  const safeDraft = draft ? escapeRepositoryText(draft) : undefined;
2964
2976
  const safePreferences = preferences ? escapeRepositoryText(preferences) : undefined;
2965
- const safeUntracked = (untracked ?? []).map(escapeRepositoryText);
2966
- const safeTracked = (tracked ?? []).map(escapeRepositoryText);
2977
+ const safeUntracked = (untracked2 ?? []).map(escapeRepositoryText);
2978
+ const safeTracked = (tracked2 ?? []).map(escapeRepositoryText);
2967
2979
  const text = `${safeConversation ? `<conversation>
2968
2980
  ${safeConversation}
2969
2981
  </conversation>` : ""}${changes ? `
@@ -3076,16 +3088,16 @@ var assembleConsultationContext = async (options) => {
3076
3088
  const curated = await curateAdvisorConversation(ctx, legacyConversation, options.signal, options.onScout, advisorScoutEnabledRef, runAdvisorScout, options.currentInvocationId, conversationBudget);
3077
3089
  const preferences = await readProjectPreferences(ctx, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef);
3078
3090
  const draftText = options.draft ? redactAndCapText(options.draft, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef) : undefined;
3079
- const untracked = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
3080
- const tracked = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked.reduce((sum, item) => sum + item.bytes, 0)));
3091
+ const untracked2 = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
3092
+ const tracked2 = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked2.reduce((sum, item) => sum + item.bytes, 0)));
3081
3093
  return {
3082
3094
  changeText,
3083
3095
  conversation: curated.conversation,
3084
3096
  draftText,
3085
3097
  preferences,
3086
3098
  scout: curated.scout,
3087
- tracked,
3088
- untracked
3099
+ tracked: tracked2,
3100
+ untracked: untracked2
3089
3101
  };
3090
3102
  };
3091
3103
 
@@ -3149,11 +3161,11 @@ var parseAutomaticDecision = (text) => {
3149
3161
  if (!subsequent) {
3150
3162
  continue;
3151
3163
  }
3152
- const repeated = namedGroups(subsequent).decision.trim().toLowerCase();
3164
+ const repeated2 = namedGroups(subsequent).decision.trim().toLowerCase();
3153
3165
  if (openingFence) {
3154
- pendingFencedDecisions.push(repeated);
3166
+ pendingFencedDecisions.push(repeated2);
3155
3167
  } else {
3156
- decisions.push(repeated);
3168
+ decisions.push(repeated2);
3157
3169
  }
3158
3170
  }
3159
3171
  if (openingFence) {
@@ -4173,7 +4185,7 @@ var resolveActivationModels = async (runtime, ctx) => {
4173
4185
  if (!(advisorAuth.ok && advisorAuth.apiKey)) {
4174
4186
  return { error: `No API key for Advisor ${advisorRef}` };
4175
4187
  }
4176
- if (!await runtime.setExecutorModel(executor)) {
4188
+ if (!isMarkedSubagent() && !await runtime.setExecutorModel(executor)) {
4177
4189
  return { error: `No API key for Executor ${executorRef}` };
4178
4190
  }
4179
4191
  return {};
@@ -4219,7 +4231,7 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
4219
4231
  saveConfig(ctx, { persistAdvisor: true, persistExecutor: true });
4220
4232
  }
4221
4233
  runtime.pendingExecutorModelRef = undefined;
4222
- if (executorEffortRef) {
4234
+ if (executorEffortRef && !isMarkedSubagent()) {
4223
4235
  runtime.pi.setThinkingLevel(executorEffortRef);
4224
4236
  }
4225
4237
  if (!runtime.flowEnabled()) {
@@ -4230,20 +4242,22 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
4230
4242
  ]);
4231
4243
  }
4232
4244
  if (announce) {
4245
+ const activeExecutorRef = effectiveExecutorRef(ctx);
4246
+ const activeExecutorEffort = effectiveExecutorEffort(ctx);
4233
4247
  notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
4234
4248
 
4235
- Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
4249
+ Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
4236
4250
  }
4237
4251
  };
4238
4252
 
4239
4253
  // src/commands/lifecycle.ts
4240
- var registerCommandLifecycle = (runtime, activateAdvisor) => {
4254
+ var registerCommandLifecycle = (runtime, activateAdvisor2) => {
4241
4255
  runtime.pi.on("session_start", async (_event, ctx) => {
4242
4256
  runtime.pendingExecutorModelRef = undefined;
4243
4257
  try {
4244
4258
  loadConfig(ctx);
4245
4259
  if (alwaysOnRef) {
4246
- await activateAdvisor("", ctx, false);
4260
+ await activateAdvisor2("", ctx, false);
4247
4261
  }
4248
4262
  } catch (error) {
4249
4263
  const message = error instanceof Error ? error.message : String(error);
@@ -4251,7 +4265,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
4251
4265
  }
4252
4266
  });
4253
4267
  runtime.pi.on("model_select", (event, ctx) => {
4254
- if (event.source !== "set" || runtime.suppressModelSelectionSync) {
4268
+ if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
4255
4269
  return;
4256
4270
  }
4257
4271
  const selected = `${event.model.provider}/${event.model.id}`;
@@ -4638,12 +4652,12 @@ var connectionFailure = (error) => {
4638
4652
  failure: new JevFailureError("network", `Jev connection failed: ${message}`)
4639
4653
  };
4640
4654
  };
4641
- var sleepWithAbort = (signal, ms) => new Promise((resolve) => {
4642
- const timer = setTimeout(resolve, ms);
4655
+ var sleepWithAbort = (signal, ms) => new Promise((resolve2) => {
4656
+ const timer = setTimeout(resolve2, ms);
4643
4657
  timer.unref?.();
4644
4658
  signal.addEventListener("abort", () => {
4645
4659
  clearTimeout(timer);
4646
- resolve();
4660
+ resolve2();
4647
4661
  }, { once: true });
4648
4662
  });
4649
4663
 
@@ -5630,6 +5644,56 @@ import { getKeybindings } from "@earendil-works/pi-tui";
5630
5644
  import { Key as Key2, matchesKey as matchesKey2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
5631
5645
 
5632
5646
  // node_modules/@typesafe-ai/sdk/dist/index.mjs
5647
+ var requestIdFrom = (headers) => headers.get("x-typesafe-request-id") ?? undefined;
5648
+ var APIPromise = class APIPromise2 extends Promise {
5649
+ #responsePromise;
5650
+ #parseResponse;
5651
+ #parsed;
5652
+ constructor(responsePromise, parseResponse) {
5653
+ super((resolve2) => resolve2(undefined));
5654
+ this.#responsePromise = responsePromise;
5655
+ this.#parseResponse = parseResponse;
5656
+ }
5657
+ asResponse() {
5658
+ return this.#responsePromise;
5659
+ }
5660
+ async withResponse() {
5661
+ const [data, response] = await Promise.all([this.#parse(), this.#responsePromise]);
5662
+ return {
5663
+ data,
5664
+ response,
5665
+ requestId: requestIdFrom(response.headers)
5666
+ };
5667
+ }
5668
+ map(fn) {
5669
+ return new APIPromise2(this.#responsePromise, () => this.#parse().then(fn));
5670
+ }
5671
+ #parse() {
5672
+ this.#parsed ??= this.#responsePromise.then(this.#parseResponse);
5673
+ return this.#parsed;
5674
+ }
5675
+ then(onfulfilled, onrejected) {
5676
+ return this.#parse().then(onfulfilled, onrejected);
5677
+ }
5678
+ catch(onrejected) {
5679
+ return this.#parse().catch(onrejected);
5680
+ }
5681
+ finally(onfinally) {
5682
+ return this.#parse().finally(onfinally);
5683
+ }
5684
+ };
5685
+ var ENV = {
5686
+ apiKey: "TYPESAFE_API_KEY",
5687
+ baseURL: "TYPESAFE_BASE_URL",
5688
+ defaultModel: "TYPESAFE_DEFAULT_MODEL",
5689
+ logLevel: "TYPESAFE_LOG_LEVEL"
5690
+ };
5691
+ var readEnv = (name) => {
5692
+ if (typeof process === "undefined" || !process.env)
5693
+ return;
5694
+ return process.env[name]?.trim() || undefined;
5695
+ };
5696
+ var fromCodeOrEnv = (fromCode, envVar) => fromCode ?? readEnv(envVar);
5633
5697
  var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
5634
5698
  var DEFAULT_RETRY_POLICY = {
5635
5699
  maxRetries: 2,
@@ -5647,11 +5711,239 @@ var DEFAULT_RETRY_POLICY = {
5647
5711
  apiTimeoutError: true
5648
5712
  };
5649
5713
  DEFAULT_RETRY_POLICY.maxRetries;
5714
+ var isRetryableStatus = (status, policy = DEFAULT_RETRY_POLICY) => policy.httpStatuses.has(status);
5715
+ var parseRetryAfter = (headers, now = Date.now()) => {
5716
+ const ms = Number(headers.get("retry-after-ms"));
5717
+ if (headers.has("retry-after-ms") && Number.isFinite(ms) && ms >= 0)
5718
+ return ms;
5719
+ const raw = headers.get("retry-after");
5720
+ if (raw === null)
5721
+ return;
5722
+ const seconds = Number(raw);
5723
+ if (Number.isFinite(seconds))
5724
+ return seconds >= 0 ? seconds * 1000 : undefined;
5725
+ const date = Date.parse(raw);
5726
+ if (!Number.isNaN(date))
5727
+ return Math.max(0, date - now);
5728
+ };
5729
+ var retryDelayMs = (attempt, headers, policy = DEFAULT_RETRY_POLICY, random = Math.random) => {
5730
+ if (policy.respectRetryAfter && headers !== undefined) {
5731
+ const retryAfter = parseRetryAfter(headers);
5732
+ if (retryAfter !== undefined && retryAfter <= policy.maxRetryAfterMs)
5733
+ return retryAfter;
5734
+ }
5735
+ const exponential = Math.min(policy.backoffInitialMs * 2 ** attempt, policy.backoffMaxMs);
5736
+ return Math.round(exponential * (1 - random() * policy.backoffJitter));
5737
+ };
5738
+ var sleep = (ms, signal) => new Promise((resolve2, reject) => {
5739
+ if (signal?.aborted)
5740
+ return reject(signal.reason);
5741
+ const onAbort = () => {
5742
+ clearTimeout(timer);
5743
+ reject(signal?.reason);
5744
+ };
5745
+ const timer = setTimeout(() => {
5746
+ signal?.removeEventListener("abort", onAbort);
5747
+ resolve2();
5748
+ }, ms);
5749
+ signal?.addEventListener("abort", onAbort, { once: true });
5750
+ });
5751
+ var TypeSafeError = class extends Error {
5752
+ constructor(message, options) {
5753
+ super(message, options);
5754
+ this.name = new.target.name;
5755
+ }
5756
+ };
5757
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
5758
+ var extractMessage = (body) => {
5759
+ if (typeof body === "string")
5760
+ return body || undefined;
5761
+ if (!isRecord2(body))
5762
+ return;
5763
+ const { error, message, detail } = body;
5764
+ if (typeof error === "string")
5765
+ return error;
5766
+ if (isRecord2(error) && typeof error.message === "string")
5767
+ return error.message;
5768
+ if (typeof message === "string")
5769
+ return message;
5770
+ if (typeof detail === "string")
5771
+ return detail;
5772
+ if (isRecord2(detail) && typeof detail.message === "string")
5773
+ return detail.message;
5774
+ if (Array.isArray(detail))
5775
+ return describeValidationErrors(detail);
5776
+ };
5777
+ var describeValidationErrors = (errors) => {
5778
+ const parts = errors.flatMap((e) => {
5779
+ if (!isRecord2(e) || typeof e.msg !== "string")
5780
+ return [];
5781
+ const loc = Array.isArray(e.loc) ? e.loc.filter((x) => x !== "body").join(".") : "";
5782
+ return [loc ? `${loc}: ${e.msg}` : e.msg];
5783
+ });
5784
+ return parts.length > 0 ? parts.join("; ") : undefined;
5785
+ };
5786
+ var MAX_RAW_BODY_IN_MESSAGE = 200;
5787
+ var APIError = class APIError2 extends TypeSafeError {
5788
+ status;
5789
+ headers;
5790
+ body;
5791
+ requestId;
5792
+ constructor(status, body, headers, message) {
5793
+ super(message ?? APIError2.describe(status, body));
5794
+ this.status = status;
5795
+ this.body = body;
5796
+ this.headers = headers;
5797
+ this.requestId = requestIdFrom(headers);
5798
+ }
5799
+ static describe(status, body) {
5800
+ const detail = extractMessage(body);
5801
+ if (detail)
5802
+ return `${status} ${detail}`;
5803
+ if (body === undefined)
5804
+ return `${status} status code (no body)`;
5805
+ const raw = typeof body === "string" ? body : JSON.stringify(body);
5806
+ return `${status} ${raw.length > MAX_RAW_BODY_IN_MESSAGE ? `${raw.slice(0, MAX_RAW_BODY_IN_MESSAGE)}…` : raw}`;
5807
+ }
5808
+ static fromResponse(status, body, headers) {
5809
+ if (status === 400)
5810
+ return new BadRequestError(status, body, headers);
5811
+ if (status === 401)
5812
+ return new AuthenticationError(status, body, headers);
5813
+ if (status === 403)
5814
+ return new PermissionDeniedError(status, body, headers);
5815
+ if (status === 404)
5816
+ return new NotFoundError(status, body, headers);
5817
+ if (status === 422)
5818
+ return new UnprocessableEntityError(status, body, headers);
5819
+ if (status === 429)
5820
+ return new RateLimitError(status, body, headers);
5821
+ if (status >= 500)
5822
+ return new InternalServerError(status, body, headers);
5823
+ return new APIError2(status, body, headers);
5824
+ }
5825
+ };
5826
+ var BadRequestError = class extends APIError {
5827
+ };
5828
+ var AuthenticationError = class extends APIError {
5829
+ };
5830
+ var PermissionDeniedError = class extends APIError {
5831
+ };
5832
+ var NotFoundError = class extends APIError {
5833
+ };
5834
+ var UnprocessableEntityError = class extends APIError {
5835
+ };
5836
+ var RateLimitError = class extends APIError {
5837
+ retryAfterMs = parseRetryAfter(this.headers);
5838
+ };
5839
+ var InternalServerError = class extends APIError {
5840
+ };
5841
+ var APIConnectionError = class extends TypeSafeError {
5842
+ constructor(message = "Connection error.", options) {
5843
+ super(message, options);
5844
+ }
5845
+ };
5846
+ var APITimeoutError = class extends APIConnectionError {
5847
+ timeoutMs;
5848
+ constructor(timeoutMs, options) {
5849
+ super(`Request timed out after ${timeoutMs}ms.`, options);
5850
+ this.timeoutMs = timeoutMs;
5851
+ }
5852
+ };
5853
+ var APIUserAbortError = class extends TypeSafeError {
5854
+ constructor(message = "Request was aborted.", options) {
5855
+ super(message, options);
5856
+ }
5857
+ };
5858
+ var LOG_LEVELS = [
5859
+ "debug",
5860
+ "info",
5861
+ "warn",
5862
+ "error",
5863
+ "off"
5864
+ ];
5865
+ var DEFAULT_LOG_LEVEL = "warn";
5866
+ var isLogLevel = (value) => LOG_LEVELS.includes(value);
5867
+ var parseLogLevel = (value, source) => {
5868
+ if (isLogLevel(value))
5869
+ return value;
5870
+ throw new TypeSafeError(`Invalid log level "${value}" from ${source}. Expected one of: ${LOG_LEVELS.join(", ")}.`);
5871
+ };
5872
+ var PREFIX = "[typesafe-sdk]";
5873
+ var consoleLogger = {
5874
+ debug: (message, ...args) => console.debug(`${PREFIX} ${message}`, ...args),
5875
+ info: (message, ...args) => console.info(`${PREFIX} ${message}`, ...args),
5876
+ warn: (message, ...args) => console.warn(`${PREFIX} ${message}`, ...args),
5877
+ error: (message, ...args) => console.error(`${PREFIX} ${message}`, ...args)
5878
+ };
5879
+ var RANK = {
5880
+ debug: 0,
5881
+ info: 1,
5882
+ warn: 2,
5883
+ error: 3,
5884
+ off: 4
5885
+ };
5886
+ var drop = () => {};
5887
+ var withLevel = (sink, level) => {
5888
+ const enabled = (at) => RANK[at] >= RANK[level];
5889
+ return {
5890
+ debug: enabled("debug") ? (message, ...args) => sink.debug(message, ...args) : drop,
5891
+ info: enabled("info") ? (message, ...args) => sink.info(message, ...args) : drop,
5892
+ warn: enabled("warn") ? (message, ...args) => sink.warn(message, ...args) : drop,
5893
+ error: enabled("error") ? (message, ...args) => sink.error(message, ...args) : drop
5894
+ };
5895
+ };
5896
+ var KEY_HEADERS = /* @__PURE__ */ new Set([
5897
+ "authorization",
5898
+ "proxy-authorization",
5899
+ "x-api-key"
5900
+ ]);
5901
+ var OPAQUE_HEADERS = /* @__PURE__ */ new Set(["cookie", "set-cookie"]);
5902
+ var redactKey = (value) => {
5903
+ const [scheme, secret] = value.includes(" ") ? value.split(/\s+/, 2) : [undefined, value];
5904
+ const tail = secret && secret.length > 8 ? secret.slice(-4) : "";
5905
+ return `${scheme ? `${scheme} ` : ""}***${tail}`;
5906
+ };
5907
+ var redact = (name, value) => {
5908
+ const lower = name.toLowerCase();
5909
+ if (KEY_HEADERS.has(lower))
5910
+ return redactKey(value);
5911
+ if (OPAQUE_HEADERS.has(lower))
5912
+ return "***";
5913
+ return value;
5914
+ };
5915
+ var redactHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, redact(name, value)]));
5650
5916
  var noul = (instructions = null, criteria) => ({
5651
5917
  type: "noul",
5652
5918
  instructions,
5653
5919
  criteria
5654
5920
  });
5921
+ var validateQuestions = (questions) => {
5922
+ if (Object.keys(questions).length === 0)
5923
+ throw new TypeSafeError("At least one question is required.");
5924
+ for (const [name, question] of Object.entries(questions)) {
5925
+ if (question.type !== "score")
5926
+ continue;
5927
+ if (!Array.isArray(question.criteria))
5928
+ throw new TypeSafeError(`Score question "${name}" has criteria that are not a list; score criteria must be a list of descriptions indexed by score from zero.`);
5929
+ if (question.criteria.length < 2)
5930
+ throw new TypeSafeError(`Score question "${name}" has ${question.criteria.length} criteria; at least two scores are required.`);
5931
+ }
5932
+ };
5933
+ var Models = class {
5934
+ #transport;
5935
+ constructor(transport) {
5936
+ this.#transport = transport;
5937
+ }
5938
+ list(options = {}) {
5939
+ return this.#transport.request("GET", "/v1/models", options).map(unwrapModels);
5940
+ }
5941
+ };
5942
+ var unwrapModels = (wire) => {
5943
+ if (Array.isArray(wire?.models))
5944
+ return wire.models;
5945
+ throw new TypeSafeError("Unexpected response shape from GET /v1/models; expected { models: [...] }.");
5946
+ };
5655
5947
  var g = globalThis;
5656
5948
  var isBrowser = () => typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
5657
5949
  var describeRuntime = () => {
@@ -5670,7 +5962,280 @@ var describeRuntime = () => {
5670
5962
  return "browser";
5671
5963
  return "unknown";
5672
5964
  };
5965
+ var VERSION = "0.6.0";
5966
+ var missingApiKey = () => {
5967
+ throw new TypeSafeError(`No API key was provided. Pass \`apiKey\` to the TypeSafeClient constructor or set the ${ENV.apiKey} environment variable.`);
5968
+ };
5969
+ var missingFetch = () => {
5970
+ throw new TypeSafeError("No global `fetch` is available in this runtime. Pass a `fetch` implementation to the TypeSafeClient constructor.");
5971
+ };
5972
+ var refuseBrowser = () => {
5973
+ throw new TypeSafeError("TypeSafeClient is running in a browser, which would expose your API key to anyone using the page. Call the API from a server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
5974
+ };
5975
+ var defaultFetch = (input, init) => globalThis.fetch(input, init);
5976
+ var assertNonNegativeInteger = (name, value) => {
5977
+ if (!Number.isInteger(value) || value < 0)
5978
+ throw new TypeSafeError(`\`${name}\` must be a non-negative integer, got ${String(value)}.`);
5979
+ return value;
5980
+ };
5981
+ var assertPositiveMs = (name, value) => {
5982
+ if (!Number.isFinite(value) || value <= 0)
5983
+ throw new TypeSafeError(`\`${name}\` must be a positive number of milliseconds, got ${String(value)}.`);
5984
+ return value;
5985
+ };
5986
+ var assertNonNegativeMs = (name, value) => {
5987
+ if (!Number.isFinite(value) || value < 0)
5988
+ throw new TypeSafeError(`\`${name}\` must be a non-negative number of milliseconds, got ${String(value)}.`);
5989
+ return value;
5990
+ };
5991
+ var assertFraction = (name, value) => {
5992
+ if (!Number.isFinite(value) || value < 0 || value > 1)
5993
+ throw new TypeSafeError(`\`${name}\` must be between 0 and 1, got ${String(value)}.`);
5994
+ return value;
5995
+ };
5996
+ var assertStatusSet = (name, statuses) => {
5997
+ for (const status of statuses)
5998
+ if (!Number.isInteger(status) || status < 100 || status > 999)
5999
+ throw new TypeSafeError(`\`${name}\` must contain HTTP status codes, got ${String(status)}.`);
6000
+ return statuses;
6001
+ };
6002
+ var resolveRetryPolicy = (base, overrides) => {
6003
+ const o = overrides ?? {};
6004
+ return {
6005
+ maxRetries: o.maxRetries === undefined ? base.maxRetries : assertNonNegativeInteger("retry.maxRetries", o.maxRetries),
6006
+ backoffInitialMs: o.backoffInitialMs === undefined ? base.backoffInitialMs : assertNonNegativeMs("retry.backoffInitialMs", o.backoffInitialMs),
6007
+ backoffMaxMs: o.backoffMaxMs === undefined ? base.backoffMaxMs : assertNonNegativeMs("retry.backoffMaxMs", o.backoffMaxMs),
6008
+ backoffJitter: o.backoffJitter === undefined ? base.backoffJitter : assertFraction("retry.backoffJitter", o.backoffJitter),
6009
+ httpStatuses: new Set(o.httpStatuses === undefined ? base.httpStatuses : assertStatusSet("retry.httpStatuses", o.httpStatuses)),
6010
+ respectRetryAfter: o.respectRetryAfter ?? base.respectRetryAfter,
6011
+ maxRetryAfterMs: o.maxRetryAfterMs === undefined ? base.maxRetryAfterMs : assertNonNegativeMs("retry.maxRetryAfterMs", o.maxRetryAfterMs),
6012
+ apiConnectionError: o.apiConnectionError ?? base.apiConnectionError,
6013
+ apiTimeoutError: o.apiTimeoutError ?? base.apiTimeoutError
6014
+ };
6015
+ };
6016
+ var isRetryableError = (err, policy) => {
6017
+ if (err instanceof APITimeoutError)
6018
+ return policy.apiTimeoutError;
6019
+ if (err instanceof APIConnectionError)
6020
+ return policy.apiConnectionError;
6021
+ return false;
6022
+ };
6023
+ var resolveLogLevel = (fromCode) => {
6024
+ if (fromCode !== undefined)
6025
+ return parseLogLevel(fromCode, "the `logLevel` option");
6026
+ const fromEnv = readEnv(ENV.logLevel);
6027
+ if (fromEnv !== undefined)
6028
+ return parseLogLevel(fromEnv, ENV.logLevel);
6029
+ return DEFAULT_LOG_LEVEL;
6030
+ };
6031
+ var stripTrailingSlashes = (url) => url.replace(/\/+$/, "");
6032
+ var mergeHeaders = (...sources) => {
6033
+ const entries = /* @__PURE__ */ new Map;
6034
+ for (const source of sources)
6035
+ for (const [name, value] of Object.entries(source))
6036
+ if (value === undefined)
6037
+ entries.delete(name.toLowerCase());
6038
+ else
6039
+ entries.set(name.toLowerCase(), [name, value]);
6040
+ return Object.fromEntries(entries.values());
6041
+ };
6042
+ var bufferResponse = async (response, signal) => {
6043
+ const reader = response.clone().body?.getReader();
6044
+ if (!reader)
6045
+ return;
6046
+ const cancel = () => {
6047
+ reader.cancel(signal.reason).catch(() => {});
6048
+ response.body?.cancel(signal.reason).catch(() => {});
6049
+ };
6050
+ signal.addEventListener("abort", cancel, { once: true });
6051
+ try {
6052
+ if (signal.aborted)
6053
+ cancel();
6054
+ signal.throwIfAborted();
6055
+ while (!(await reader.read()).done)
6056
+ signal.throwIfAborted();
6057
+ signal.throwIfAborted();
6058
+ } finally {
6059
+ signal.removeEventListener("abort", cancel);
6060
+ reader.releaseLock();
6061
+ }
6062
+ };
5673
6063
  var RUNTIME = describeRuntime();
6064
+ var TypeSafeClient = class {
6065
+ #apiKey;
6066
+ baseURL;
6067
+ defaultModel;
6068
+ logLevel;
6069
+ logger;
6070
+ retry;
6071
+ timeout;
6072
+ defaultHeaders;
6073
+ fetch;
6074
+ models;
6075
+ #requestCount = 0;
6076
+ constructor(config = {}) {
6077
+ if (isBrowser() && !config.dangerouslyAllowBrowser)
6078
+ refuseBrowser();
6079
+ this.#apiKey = fromCodeOrEnv(config.apiKey, ENV.apiKey) ?? missingApiKey();
6080
+ this.baseURL = stripTrailingSlashes(fromCodeOrEnv(config.baseURL, ENV.baseURL) ?? "https://api.typesafe.ai");
6081
+ this.defaultModel = fromCodeOrEnv(config.defaultModel, ENV.defaultModel) ?? "jev-latest";
6082
+ this.logLevel = resolveLogLevel(config.logLevel);
6083
+ this.logger = withLevel(config.logger ?? consoleLogger, this.logLevel);
6084
+ this.retry = resolveRetryPolicy(DEFAULT_RETRY_POLICY, config.retry);
6085
+ this.timeout = assertPositiveMs("timeout", config.timeout ?? 1e4);
6086
+ this.defaultHeaders = { ...config.defaultHeaders };
6087
+ if (config.fetch === undefined && typeof globalThis.fetch !== "function")
6088
+ missingFetch();
6089
+ this.fetch = config.fetch ?? defaultFetch;
6090
+ const transport = {
6091
+ request: (method, path, options) => this.#request(method, path, options),
6092
+ defaultModel: this.defaultModel
6093
+ };
6094
+ this.models = new Models(transport);
6095
+ }
6096
+ systemOne(request, options = {}) {
6097
+ validateQuestions(request.questions);
6098
+ const body = {
6099
+ ...request,
6100
+ model: request.model ?? this.defaultModel
6101
+ };
6102
+ return this.#request("POST", "/v1/systemone", {
6103
+ ...options,
6104
+ body
6105
+ });
6106
+ }
6107
+ #request(method, path, options = {}) {
6108
+ const resolved = {
6109
+ method,
6110
+ path,
6111
+ body: options.body,
6112
+ headers: mergeHeaders(this.defaultHeaders, options.headers ?? {}),
6113
+ signal: options.signal,
6114
+ timeout: options.timeout === undefined ? this.timeout : assertPositiveMs("timeout", options.timeout),
6115
+ retry: resolveRetryPolicy(this.retry, options.retry)
6116
+ };
6117
+ const tag = `#${++this.#requestCount} ${method} ${path}`;
6118
+ return new APIPromise(this.fetchWithRetries(tag, resolved), async (res) => {
6119
+ const parsed = await parseBody(res);
6120
+ this.logger.debug(`${tag} <- body`, parsed);
6121
+ return parsed;
6122
+ });
6123
+ }
6124
+ async fetchWithRetries(tag, req) {
6125
+ const url = `${this.baseURL}${req.path}`;
6126
+ const headers = mergeHeaders(req.headers, {
6127
+ Authorization: `Bearer ${this.#apiKey}`,
6128
+ Accept: "application/json",
6129
+ "User-Agent": `typesafe-sdk/${VERSION}`,
6130
+ "X-TypeSafe-SDK": `typesafe-sdk/${VERSION}`,
6131
+ "X-TypeSafe-Runtime": RUNTIME,
6132
+ "Content-Type": req.body === undefined ? undefined : "application/json",
6133
+ "X-TypeSafe-Retry-Count": undefined
6134
+ });
6135
+ const body = req.body === undefined ? undefined : JSON.stringify(req.body);
6136
+ for (let attempt = 0;; attempt++) {
6137
+ const retriesLeft = req.retry.maxRetries - attempt;
6138
+ const attemptHeaders = attempt === 0 ? headers : {
6139
+ ...headers,
6140
+ "X-TypeSafe-Retry-Count": String(attempt)
6141
+ };
6142
+ this.logger.debug(`${tag} -> ${url}`, {
6143
+ headers: redactHeaders(attemptHeaders),
6144
+ body: req.body
6145
+ });
6146
+ const started = Date.now();
6147
+ let res;
6148
+ try {
6149
+ res = await this.attempt(tag, url, {
6150
+ method: req.method,
6151
+ headers: attemptHeaders,
6152
+ body
6153
+ }, req);
6154
+ } catch (err) {
6155
+ if (err instanceof APIUserAbortError || retriesLeft <= 0)
6156
+ throw err;
6157
+ if (!isRetryableError(err, req.retry))
6158
+ throw err;
6159
+ await this.backOff(tag, attempt, retriesLeft, err.message, undefined, req);
6160
+ continue;
6161
+ }
6162
+ const requestId = requestIdFrom(res.headers);
6163
+ this.logger.info(`${tag} <- ${res.status} in ${Date.now() - started}ms${requestId ? ` (request ${requestId})` : ""}`);
6164
+ if (res.ok)
6165
+ return res;
6166
+ const errorBody = await parseBody(res);
6167
+ this.logger.debug(`${tag} <- error body`, errorBody);
6168
+ const error = APIError.fromResponse(res.status, errorBody, res.headers);
6169
+ if (retriesLeft <= 0 || !isRetryableStatus(res.status, req.retry))
6170
+ throw error;
6171
+ await this.backOff(tag, attempt, retriesLeft, `${res.status}`, res.headers, req);
6172
+ }
6173
+ }
6174
+ async attempt(tag, url, init, { signal, timeout }) {
6175
+ const controller = new AbortController;
6176
+ const abortFromCaller = () => controller.abort(signal?.reason);
6177
+ if (signal?.aborted)
6178
+ abortFromCaller();
6179
+ signal?.addEventListener("abort", abortFromCaller, { once: true });
6180
+ let timedOut = false;
6181
+ const timer = setTimeout(() => {
6182
+ timedOut = true;
6183
+ controller.abort();
6184
+ }, timeout);
6185
+ const started = Date.now();
6186
+ const elapsed = () => `${Date.now() - started}ms`;
6187
+ try {
6188
+ const response = await this.fetch(url, {
6189
+ ...init,
6190
+ signal: controller.signal
6191
+ });
6192
+ await bufferResponse(response, controller.signal);
6193
+ return response;
6194
+ } catch (err) {
6195
+ if (signal?.aborted) {
6196
+ this.logger.info(`${tag} aborted by caller after ${elapsed()}`);
6197
+ throw new APIUserAbortError(undefined, { cause: err });
6198
+ }
6199
+ if (timedOut) {
6200
+ this.logger.info(`${tag} timed out after ${elapsed()}`);
6201
+ throw new APITimeoutError(timeout, { cause: err });
6202
+ }
6203
+ this.logger.info(`${tag} connection error after ${elapsed()}`, err);
6204
+ throw new APIConnectionError(err instanceof Error ? `Connection error: ${err.message}` : undefined, { cause: err });
6205
+ } finally {
6206
+ clearTimeout(timer);
6207
+ signal?.removeEventListener("abort", abortFromCaller);
6208
+ }
6209
+ }
6210
+ async backOff(tag, attempt, retriesLeft, reason, headers, { retry, signal }) {
6211
+ const delay = retryDelayMs(attempt, headers, retry);
6212
+ const nth = attempt + 1;
6213
+ const total = attempt + retriesLeft;
6214
+ this.logger.info(`${tag} retrying in ${delay}ms (retry ${nth}/${total}) after ${reason}`);
6215
+ try {
6216
+ await sleep(delay, signal);
6217
+ } catch (err) {
6218
+ this.logger.info(`${tag} aborted by caller while waiting to retry`);
6219
+ throw new APIUserAbortError(undefined, { cause: err });
6220
+ }
6221
+ }
6222
+ };
6223
+ var parseBody = async (res) => {
6224
+ const text = await res.text();
6225
+ if (text.length === 0)
6226
+ return;
6227
+ if ((res.headers.get("content-type") ?? "").includes("application/json"))
6228
+ try {
6229
+ return JSON.parse(text);
6230
+ } catch {
6231
+ return text;
6232
+ }
6233
+ try {
6234
+ return JSON.parse(text);
6235
+ } catch {
6236
+ return text;
6237
+ }
6238
+ };
5674
6239
 
5675
6240
  // src/ui/masked-input.ts
5676
6241
  import { Input as Input2, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
@@ -6773,7 +7338,7 @@ import {
6773
7338
  writeFile
6774
7339
  } from "node:fs/promises";
6775
7340
  import { join as join5 } from "node:path";
6776
- import { setTimeout as sleep } from "node:timers/promises";
7341
+ import { setTimeout as sleep2 } from "node:timers/promises";
6777
7342
  import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
6778
7343
  var ADOPTIONS = [
6779
7344
  "followed",
@@ -6861,7 +7426,7 @@ var withOutcomeLock = async (run) => {
6861
7426
  }
6862
7427
  continue;
6863
7428
  }
6864
- await sleep(5);
7429
+ await sleep2(5);
6865
7430
  }
6866
7431
  }
6867
7432
  throw new Error("Timed out waiting to append an Advisor outcome.");
@@ -7763,8 +8328,8 @@ function registerPiAdvisor(pi) {
7763
8328
  });
7764
8329
  }
7765
8330
  export {
7766
- consultAdvisor2 as consultAdvisor,
7767
- registerPiAdvisor as default,
8331
+ runAdvisorGate2 as runAdvisorGate,
7768
8332
  parseAutomaticDecision2 as parseAutomaticDecision,
7769
- runAdvisorGate2 as runAdvisorGate
8333
+ registerPiAdvisor as default,
8334
+ consultAdvisor2 as consultAdvisor
7770
8335
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-advisor-flow",
3
- "version": "0.8.0",
3
+ "version": "0.8.1-dev.1",
4
4
  "description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
5
5
  "keywords": [
6
6
  "advisor",
@@ -0,0 +1,15 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ import { executorEffortRef, executorRef } from "./config/state.ts";
4
+
5
+ export const isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
6
+
7
+ export const effectiveExecutorRef = (ctx: ExtensionContext) => {
8
+ if (isMarkedSubagent() && ctx.model) {
9
+ return `${ctx.model.provider}/${ctx.model.id}`;
10
+ }
11
+ return executorRef;
12
+ };
13
+
14
+ export const effectiveExecutorEffort = (ctx: ExtensionContext) =>
15
+ isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
@@ -1,5 +1,10 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
+ import {
4
+ effectiveExecutorEffort,
5
+ effectiveExecutorRef,
6
+ isMarkedSubagent,
7
+ } from "../child-session.ts";
3
8
  import { parseArgs } from "../config/args.ts";
4
9
  import {
5
10
  advisorEffortRef,
@@ -51,7 +56,7 @@ const resolveActivationModels = async (
51
56
  if (!(advisorAuth.ok && advisorAuth.apiKey)) {
52
57
  return { error: `No API key for Advisor ${advisorRef}` };
53
58
  }
54
- if (!(await runtime.setExecutorModel(executor))) {
59
+ if (!isMarkedSubagent() && !(await runtime.setExecutorModel(executor))) {
55
60
  return { error: `No API key for Executor ${executorRef}` };
56
61
  }
57
62
  return {};
@@ -116,7 +121,7 @@ export const activateAdvisor = async (
116
121
  // an older inactive selection override an explicit activation argument on a
117
122
  // later attempt.
118
123
  runtime.pendingExecutorModelRef = undefined;
119
- if (executorEffortRef) {
124
+ if (executorEffortRef && !isMarkedSubagent()) {
120
125
  // SAFETY: executorEffortRef is operator-configured and trusted to name a ThinkingLevel.
121
126
  runtime.pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
122
127
  }
@@ -128,9 +133,11 @@ export const activateAdvisor = async (
128
133
  ]);
129
134
  }
130
135
  if (announce) {
136
+ const activeExecutorRef = effectiveExecutorRef(ctx);
137
+ const activeExecutorEffort = effectiveExecutorEffort(ctx);
131
138
  notify(
132
139
  ctx,
133
- `${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
140
+ `${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
134
141
  "info"
135
142
  );
136
143
  }
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
+ import { isMarkedSubagent } from "../child-session.ts";
3
4
  import {
4
5
  alwaysOnRef,
5
6
  executorRef,
@@ -40,7 +41,11 @@ export const registerCommandLifecycle = (
40
41
  // "restore" replays a stored session model and "cycle" changes the active
41
42
  // model without an explicit `/model` choice. Neither should redefine the
42
43
  // configured Executor.
43
- if (event.source !== "set" || runtime.suppressModelSelectionSync) {
44
+ if (
45
+ event.source !== "set" ||
46
+ runtime.suppressModelSelectionSync ||
47
+ isMarkedSubagent()
48
+ ) {
44
49
  return;
45
50
  }
46
51
  const selected = `${event.model.provider}/${event.model.id}`;
package/src/scout.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import type { Message } from "@earendil-works/pi-ai/compat";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
 
4
- import { executorEffortRef, executorRef } from "./config/state.ts";
4
+ import {
5
+ effectiveExecutorEffort,
6
+ effectiveExecutorRef,
7
+ } from "./child-session.ts";
5
8
  import { isRecord, isString } from "./content-utils.ts";
6
9
  import { collectTextStream, resolveConfiguredModel } from "./model-stream.ts";
7
10
  import type {
@@ -250,6 +253,8 @@ type ScoutStreamResult =
250
253
  const streamScoutResponse = async (
251
254
  dependencies: ScoutDependencies,
252
255
  resolved: ResolvedConfiguredModel,
256
+ executorModel: string,
257
+ executorEffort: string | undefined,
253
258
  manifest: ScoutManifest,
254
259
  parentSignal: AbortSignal | undefined,
255
260
  timeoutMs: number,
@@ -264,10 +269,10 @@ const streamScoutResponse = async (
264
269
  messages: [manifestMessage(manifest)],
265
270
  onChunk: (thinking, text) => {
266
271
  if (!controller.signal.aborted) {
267
- publish({ model: executorRef, text, thinking, type: "chunk" });
272
+ publish({ model: executorModel, text, thinking, type: "chunk" });
268
273
  }
269
274
  },
270
- reasoning: executorEffortRef,
275
+ reasoning: executorEffort,
271
276
  signal: controller.signal,
272
277
  systemPrompt: SCOUT_SYSTEM,
273
278
  });
@@ -317,7 +322,7 @@ export const runAdvisorScout = async (
317
322
  usage === undefined
318
323
  ? baseMetrics(manifest, startedAt)
319
324
  : { ...baseMetrics(manifest, startedAt), usage },
320
- model: executorRef,
325
+ model: effectiveExecutorRef(ctx),
321
326
  ok: false as const,
322
327
  };
323
328
  publish({ outcome, type: "fallback" });
@@ -328,9 +333,11 @@ export const runAdvisorScout = async (
328
333
  return cancelled();
329
334
  }
330
335
 
336
+ const executorModel = effectiveExecutorRef(ctx);
337
+ const executorEffort = effectiveExecutorEffort(ctx);
331
338
  let resolved: ResolvedConfiguredModel;
332
339
  try {
333
- resolved = await dependencies.resolve(ctx, executorRef, "Scout");
340
+ resolved = await dependencies.resolve(ctx, executorModel, "Scout");
334
341
  } catch (error) {
335
342
  if (parentSignal?.aborted) {
336
343
  return cancelled();
@@ -342,11 +349,13 @@ export const runAdvisorScout = async (
342
349
  if (parentSignal?.aborted) {
343
350
  return cancelled();
344
351
  }
345
- publish({ model: executorRef, type: "call" });
352
+ publish({ model: executorModel, type: "call" });
346
353
 
347
354
  const streamed = await streamScoutResponse(
348
355
  dependencies,
349
356
  resolved,
357
+ executorModel,
358
+ executorEffort,
350
359
  manifest,
351
360
  parentSignal,
352
361
  timeoutMs,
@@ -390,7 +399,7 @@ export const runAdvisorScout = async (
390
399
  ]).size,
391
400
  usage: snapshotAdvisorUsage(streamed.streamed.usage),
392
401
  },
393
- model: executorRef,
402
+ model: executorModel,
394
403
  ok: true as const,
395
404
  selectedLabels: manifest.groups
396
405
  .filter(