pi-advisor-flow 0.8.1-dev.2 → 0.8.2-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
@@ -8,6 +8,22 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
8
8
 
9
9
  ### Added
10
10
 
11
+ - Added a configurable Advisor Scout response-stream timeout in `/advisor-settings` and `advisor.json`, defaulting to 30 seconds. Model and auth resolution remain outside the timeout window.
12
+
13
+ ### Fixed
14
+
15
+ - Scout timeout fallbacks now report that the original conversation was retained instead of showing a misleading zero-kept/all-omitted selection count.
16
+
17
+ ## 0.8.1 - 2026-09-23
18
+
19
+ ### Fixed
20
+
21
+ - 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`.
22
+ - Keep marked child model selections out of the persisted parent Executor configuration.
23
+ - Run Advisor Scout with the marked child's active model and thinking level instead of replacing them with the configured parent Executor.
24
+
25
+ ### Added
26
+
11
27
  - Published validated npm development previews for package changes on `main` under the `dev` dist-tag; stable installs continue to use `latest`.
12
28
 
13
29
  ## 0.8.0 - 2026-09-22
package/dist/index.js CHANGED
@@ -14,6 +14,8 @@ var DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES = PI_DEFAULT_MAX_BYTES;
14
14
  var DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS = 20000;
15
15
  var DEFAULT_JEV_MODEL = "jev-latest";
16
16
  var DEFAULT_JEV_TIMEOUT_MS = 8000;
17
+ var DEFAULT_SCOUT_TIMEOUT_MS = 30000;
18
+ var MAX_SCOUT_TIMEOUT_MS = 2147483647;
17
19
  var DEFAULT_JEV_DIGEST_MAX_CHARS = 4000;
18
20
  var DEFAULT_JEV_PRICE_PER_MTOK = 0.042;
19
21
  var DEFAULT_JEV_FILTER_SKIP_CONFIDENCE = 0.85;
@@ -82,6 +84,7 @@ var advisorOutcomeLoggingRef = false;
82
84
  var advisorUntrackedContentRef = false;
83
85
  var advisorTrackedFileContentRef = false;
84
86
  var advisorScoutEnabledRef = false;
87
+ var advisorScoutTimeoutMsRef = DEFAULT_SCOUT_TIMEOUT_MS;
85
88
  var showUsageDetailsRef = true;
86
89
  var showUsageFooterRef = false;
87
90
  var setExecutorRef = (ref) => {
@@ -218,6 +221,9 @@ var setAdvisorTrackedFileContentRef = (enabled) => {
218
221
  var setAdvisorScoutEnabledRef = (enabled) => {
219
222
  advisorScoutEnabledRef = enabled;
220
223
  };
224
+ var setAdvisorScoutTimeoutMsRef = (value) => {
225
+ advisorScoutTimeoutMsRef = value;
226
+ };
221
227
  var setShowUsageDetailsRef = (enabled) => {
222
228
  showUsageDetailsRef = enabled;
223
229
  };
@@ -256,6 +262,7 @@ var getAdvisorSettings = () => ({
256
262
  planGate: advisorPlanGateRef,
257
263
  redactSecrets: advisorRedactSecretsRef,
258
264
  scoutEnabled: advisorScoutEnabledRef,
265
+ scoutTimeoutMs: advisorScoutTimeoutMsRef,
259
266
  sessionSummary: advisorSessionSummaryRef,
260
267
  showUsageDetails: showUsageDetailsRef,
261
268
  showUsageFooter: showUsageFooterRef,
@@ -272,6 +279,16 @@ var splitRef = (ref) => {
272
279
  return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
273
280
  };
274
281
 
282
+ // src/child-session.ts
283
+ var isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
284
+ var effectiveExecutorRef = (ctx) => {
285
+ if (isMarkedSubagent() && ctx.model) {
286
+ return `${ctx.model.provider}/${ctx.model.id}`;
287
+ }
288
+ return executorRef;
289
+ };
290
+ var effectiveExecutorEffort = (ctx) => isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
291
+
275
292
  // src/content-utils.ts
276
293
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
277
294
  var isRecordOf = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -426,6 +443,7 @@ var isValidGateFailureMode = (value) => isString(value) && GATE_FAILURE_MODE_NAM
426
443
  var isValidToolResultMaxLines = (value) => nonNegativeSafeInteger(value);
427
444
  var isValidToolResultMaxBytes = (value) => nonNegativeSafeInteger(value);
428
445
  var isValidJevTimeoutMs = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
446
+ var isValidScoutTimeoutMs = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= MAX_SCOUT_TIMEOUT_MS;
429
447
  var isValidJevDigestMaxChars = (value) => nonNegativeSafeInteger(value);
430
448
  var isValidJevPricePerMtok = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
431
449
  var isValidJevSkipConfidence = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0.5 && value <= 1;
@@ -621,6 +639,13 @@ var CONFIG_SCHEMA = {
621
639
  persisted: true,
622
640
  type: "boolean"
623
641
  },
642
+ advisorScoutTimeoutMs: {
643
+ accepted: `a positive safe integer no greater than ${MAX_SCOUT_TIMEOUT_MS} milliseconds`,
644
+ current: () => advisorScoutTimeoutMsRef,
645
+ persisted: true,
646
+ type: "number",
647
+ validate: isValidScoutTimeoutMs
648
+ },
624
649
  advisorSessionSummary: {
625
650
  accepted: "true or false",
626
651
  current: () => advisorSessionSummaryRef,
@@ -859,6 +884,7 @@ var resetDefaults = () => {
859
884
  setAdvisorUntrackedContentRef(false);
860
885
  setAdvisorTrackedFileContentRef(false);
861
886
  setAdvisorScoutEnabledRef(false);
887
+ setAdvisorScoutTimeoutMsRef(DEFAULT_SCOUT_TIMEOUT_MS);
862
888
  setShowUsageDetailsRef(true);
863
889
  setShowUsageFooterRef(false);
864
890
  };
@@ -891,6 +917,7 @@ var applyConfig = (config) => {
891
917
  applyOptionalConfig(config, "advisorModelWhitelist", setAdvisorModelWhitelistRef);
892
918
  applyOptionalConfig(config, "advisorSessionSummary", setAdvisorSessionSummaryRef);
893
919
  applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
920
+ applyOptionalConfig(config, "advisorScoutTimeoutMs", setAdvisorScoutTimeoutMsRef);
894
921
  applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
895
922
  applyOptionalConfig(config, "showUsageFooter", setShowUsageFooterRef);
896
923
  applyOptionalConfig(config, "simpleMode", setSimpleModeRef);
@@ -2691,7 +2718,6 @@ var formatAdvisorUsageStatus = (totals) => {
2691
2718
  };
2692
2719
 
2693
2720
  // src/scout.ts
2694
- var SCOUT_TIMEOUT_MS = 30000;
2695
2721
  var SCOUT_SYSTEM = [
2696
2722
  "You are Scout, a context curator serving a separate engineering Advisor.",
2697
2723
  "Select only conversation groups materially relevant to the current request, unresolved decisions, attempted work, diagnostics, and validation.",
@@ -2814,17 +2840,17 @@ var setupAbortWatch = (parentSignal, timeoutMs) => {
2814
2840
  wasTimedOut: () => timedOut
2815
2841
  };
2816
2842
  };
2817
- var streamScoutResponse = async (dependencies, resolved, manifest, parentSignal, timeoutMs, publish) => {
2843
+ var streamScoutResponse = async (dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish) => {
2818
2844
  const { abortPromise, controller, teardown, wasTimedOut } = setupAbortWatch(parentSignal, timeoutMs);
2819
2845
  try {
2820
2846
  const collection = dependencies.collect(resolved, {
2821
2847
  messages: [manifestMessage(manifest)],
2822
2848
  onChunk: (thinking, text) => {
2823
2849
  if (!controller.signal.aborted) {
2824
- publish({ model: executorRef, text, thinking, type: "chunk" });
2850
+ publish({ model: executorModel, text, thinking, type: "chunk" });
2825
2851
  }
2826
2852
  },
2827
- reasoning: executorEffortRef,
2853
+ reasoning: executorEffort,
2828
2854
  signal: controller.signal,
2829
2855
  systemPrompt: SCOUT_SYSTEM
2830
2856
  });
@@ -2843,7 +2869,7 @@ var streamScoutResponse = async (dependencies, resolved, manifest, parentSignal,
2843
2869
  teardown();
2844
2870
  }
2845
2871
  };
2846
- var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = SCOUT_TIMEOUT_MS, dependencies = defaultDependencies) => {
2872
+ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = advisorScoutTimeoutMsRef, dependencies = defaultDependencies) => {
2847
2873
  const startedAt = Date.now();
2848
2874
  const publish = (event) => {
2849
2875
  onEvent?.(event);
@@ -2857,7 +2883,7 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2857
2883
  category,
2858
2884
  message,
2859
2885
  metrics: usage === undefined ? baseMetrics(manifest, startedAt) : { ...baseMetrics(manifest, startedAt), usage },
2860
- model: executorRef,
2886
+ model: effectiveExecutorRef(ctx),
2861
2887
  ok: false
2862
2888
  };
2863
2889
  publish({ outcome, type: "fallback" });
@@ -2866,9 +2892,11 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2866
2892
  if (parentSignal?.aborted) {
2867
2893
  return cancelled();
2868
2894
  }
2895
+ const executorModel = effectiveExecutorRef(ctx);
2896
+ const executorEffort = effectiveExecutorEffort(ctx);
2869
2897
  let resolved;
2870
2898
  try {
2871
- resolved = await dependencies.resolve(ctx, executorRef, "Scout");
2899
+ resolved = await dependencies.resolve(ctx, executorModel, "Scout");
2872
2900
  } catch (error) {
2873
2901
  if (parentSignal?.aborted) {
2874
2902
  return cancelled();
@@ -2879,8 +2907,8 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2879
2907
  if (parentSignal?.aborted) {
2880
2908
  return cancelled();
2881
2909
  }
2882
- publish({ model: executorRef, type: "call" });
2883
- const streamed = await streamScoutResponse(dependencies, resolved, manifest, parentSignal, timeoutMs, publish);
2910
+ publish({ model: executorModel, type: "call" });
2911
+ const streamed = await streamScoutResponse(dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish);
2884
2912
  if (!streamed.ok) {
2885
2913
  if (parentSignal?.aborted) {
2886
2914
  return cancelled();
@@ -2907,7 +2935,7 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
2907
2935
  ]).size,
2908
2936
  usage: snapshotAdvisorUsage(streamed.streamed.usage)
2909
2937
  },
2910
- model: executorRef,
2938
+ model: executorModel,
2911
2939
  ok: true,
2912
2940
  selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
2913
2941
  selection
@@ -3488,7 +3516,6 @@ var scoutDetailsFromEvent = (event, previous) => {
3488
3516
  latencyMs: outcome.metrics.latencyMs,
3489
3517
  model: outcome.model,
3490
3518
  omittedBeforeScout: outcome.metrics.omittedBeforeScout,
3491
- selectedCount: 0,
3492
3519
  status: "fallback",
3493
3520
  usage: snapshotAdvisorUsage(outcome.metrics.usage)
3494
3521
  };
@@ -3560,7 +3587,16 @@ var scoutTitle = (scout, frame) => {
3560
3587
  }
3561
3588
  return "◆ SCOUT · FALLBACK";
3562
3589
  };
3563
- var scoutSummaryLine = (scout) => ` ${scout.model}${scout.selectedCount === undefined ? "" : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
3590
+ var scoutSelectionSummary = (scout) => {
3591
+ if (scout.status === "fallback") {
3592
+ return " · original conversation retained";
3593
+ }
3594
+ if (scout.status !== "curated" || scout.selectedCount === undefined) {
3595
+ return "";
3596
+ }
3597
+ return ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`;
3598
+ };
3599
+ var scoutSummaryLine = (scout) => ` ${scout.model}${scoutSelectionSummary(scout)}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
3564
3600
  var scoutExpandedLines = (scout, expanded, theme) => {
3565
3601
  const lines = [];
3566
3602
  if (expanded && scout.selectedLabels?.length) {
@@ -4173,7 +4209,7 @@ var resolveActivationModels = async (runtime, ctx) => {
4173
4209
  if (!(advisorAuth.ok && advisorAuth.apiKey)) {
4174
4210
  return { error: `No API key for Advisor ${advisorRef}` };
4175
4211
  }
4176
- if (!await runtime.setExecutorModel(executor)) {
4212
+ if (!isMarkedSubagent() && !await runtime.setExecutorModel(executor)) {
4177
4213
  return { error: `No API key for Executor ${executorRef}` };
4178
4214
  }
4179
4215
  return {};
@@ -4219,7 +4255,7 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
4219
4255
  saveConfig(ctx, { persistAdvisor: true, persistExecutor: true });
4220
4256
  }
4221
4257
  runtime.pendingExecutorModelRef = undefined;
4222
- if (executorEffortRef) {
4258
+ if (executorEffortRef && !isMarkedSubagent()) {
4223
4259
  runtime.pi.setThinkingLevel(executorEffortRef);
4224
4260
  }
4225
4261
  if (!runtime.flowEnabled()) {
@@ -4230,9 +4266,11 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
4230
4266
  ]);
4231
4267
  }
4232
4268
  if (announce) {
4269
+ const activeExecutorRef = effectiveExecutorRef(ctx);
4270
+ const activeExecutorEffort = effectiveExecutorEffort(ctx);
4233
4271
  notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
4234
4272
 
4235
- Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
4273
+ Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
4236
4274
  }
4237
4275
  };
4238
4276
 
@@ -4251,7 +4289,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
4251
4289
  }
4252
4290
  });
4253
4291
  runtime.pi.on("model_select", (event, ctx) => {
4254
- if (event.source !== "set" || runtime.suppressModelSelectionSync) {
4292
+ if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
4255
4293
  return;
4256
4294
  }
4257
4295
  const selected = `${event.model.provider}/${event.model.id}`;
@@ -6041,6 +6079,16 @@ var toggle = (id, label, description, value, defaultValue) => ({
6041
6079
  label,
6042
6080
  values: TOGGLE_VALUES
6043
6081
  });
6082
+ var scoutTimeoutItem = (settings) => {
6083
+ const timeoutMs = settings.scoutTimeoutMs ?? DEFAULT_SCOUT_TIMEOUT_MS;
6084
+ return {
6085
+ currentValue: String(timeoutMs),
6086
+ description: "Maximum time for Scout's response stream; model and auth resolution happen before this timeout starts.",
6087
+ id: "scoutTimeoutMs",
6088
+ label: "Scout timeout ms",
6089
+ values: numericValues(timeoutMs, [5000, 1e4, 15000, 30000, 45000, 60000, 90000, 120000])
6090
+ };
6091
+ };
6044
6092
  var jevItems = (settings, theme, tui) => [
6045
6093
  {
6046
6094
  currentValue: settingValue(settings.jevFilterEnabled, false),
@@ -6198,7 +6246,7 @@ var createSettingsItems = ({
6198
6246
  id: "effort",
6199
6247
  label: "Advisor reasoning",
6200
6248
  values: withCurrentValue(currentEffort(settings.effort), effortLevels)
6201
- }, modelWhitelist, toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), toggle("showUsageDetails", "Show usage and cost details", "Show token usage and cost details in Advisor responses.", settings.showUsageDetails, true), toggle("showUsageFooter", "Show usage in footer", "Show the current Advisor usage summary in the footer.", settings.showUsageFooter, false), toggle("planGate", "Plan gate", "Ask the Advisor to review implementation plans.", settings.planGate, true), toggle("failureGate", "Failure gate", "Ask the Advisor to review repeated failures.", settings.failureGate, true), toggle("completionGate", "Completion gate", "Ask the Advisor to review work before declaring success.", settings.completionGate, true), toggle("collapseResponses", "Collapse long responses", "Collapse long Advisor responses in the transcript.", settings.collapseResponses, false), {
6249
+ }, modelWhitelist, toggle("scoutEnabled", "Experimental Advisor Scout", "Enable the experimental Scout before Advisor calls.", settings.scoutEnabled, false), scoutTimeoutItem(settings), toggle("showUsageDetails", "Show usage and cost details", "Show token usage and cost details in Advisor responses.", settings.showUsageDetails, true), toggle("showUsageFooter", "Show usage in footer", "Show the current Advisor usage summary in the footer.", settings.showUsageFooter, false), toggle("planGate", "Plan gate", "Ask the Advisor to review implementation plans.", settings.planGate, true), toggle("failureGate", "Failure gate", "Ask the Advisor to review repeated failures.", settings.failureGate, true), toggle("completionGate", "Completion gate", "Ask the Advisor to review work before declaring success.", settings.completionGate, true), toggle("collapseResponses", "Collapse long responses", "Collapse long Advisor responses in the transcript.", settings.collapseResponses, false), {
6202
6250
  currentValue: settings.customRule || "None",
6203
6251
  description: "Add a rule that triggers Advisor involvement.",
6204
6252
  id: "customRule",
@@ -6496,6 +6544,10 @@ var applyJevMutation = (settings, id, value) => {
6496
6544
  settings.jevTimeoutMs = Number(value);
6497
6545
  return true;
6498
6546
  }
6547
+ case "scoutTimeoutMs": {
6548
+ settings.scoutTimeoutMs = Number(value);
6549
+ return true;
6550
+ }
6499
6551
  case "jevDigestMaxChars": {
6500
6552
  settings.jevDigestMaxChars = Number(value);
6501
6553
  return true;
@@ -6653,6 +6705,7 @@ var applySessionSettings = (settings) => {
6653
6705
  setAdvisorModelWhitelistRef(settings.modelWhitelist ?? []);
6654
6706
  setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
6655
6707
  setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
6708
+ setAdvisorScoutTimeoutMsRef(settings.scoutTimeoutMs ?? DEFAULT_SCOUT_TIMEOUT_MS);
6656
6709
  setShowUsageDetailsRef(settings.showUsageDetails ?? true);
6657
6710
  setShowUsageFooterRef(settings.showUsageFooter ?? false);
6658
6711
  setSimpleModeRef(settings.simpleMode ?? false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-advisor-flow",
3
- "version": "0.8.1-dev.2",
3
+ "version": "0.8.2-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}`;
@@ -31,6 +31,7 @@ import {
31
31
  setAdvisorPlanGateRef,
32
32
  setAdvisorRedactSecretsRef,
33
33
  setAdvisorScoutEnabledRef,
34
+ setAdvisorScoutTimeoutMsRef,
34
35
  setAdvisorSessionSummaryRef,
35
36
  setAdvisorToolPoliciesRef,
36
37
  setAdvisorToolResultMaxBytesRef,
@@ -55,6 +56,7 @@ import {
55
56
  DEFAULT_JEV_TRANSPORT,
56
57
  DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
57
58
  DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
59
+ DEFAULT_SCOUT_TIMEOUT_MS,
58
60
  } from "../config/types.ts";
59
61
  import type { AdvisorSettings } from "../ui/types.ts";
60
62
 
@@ -75,6 +77,9 @@ const applySessionSettings = (settings: AdvisorSettings) => {
75
77
  setAdvisorModelWhitelistRef(settings.modelWhitelist ?? []);
76
78
  setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
77
79
  setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
80
+ setAdvisorScoutTimeoutMsRef(
81
+ settings.scoutTimeoutMs ?? DEFAULT_SCOUT_TIMEOUT_MS
82
+ );
78
83
  setShowUsageDetailsRef(settings.showUsageDetails ?? true);
79
84
  setShowUsageFooterRef(settings.showUsageFooter ?? false);
80
85
  setSimpleModeRef(settings.simpleMode ?? false);
@@ -29,6 +29,7 @@ import {
29
29
  setAdvisorRedactSecretsRef,
30
30
  setAdvisorRef,
31
31
  setAdvisorScoutEnabledRef,
32
+ setAdvisorScoutTimeoutMsRef,
32
33
  setAdvisorSessionSummaryRef,
33
34
  setAdvisorToolPoliciesRef,
34
35
  setAdvisorToolResultMaxBytesRef,
@@ -60,6 +61,7 @@ import {
60
61
  DEFAULT_JEV_TRANSPORT,
61
62
  DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
62
63
  DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
64
+ DEFAULT_SCOUT_TIMEOUT_MS,
63
65
  } from "./types.ts";
64
66
 
65
67
  export const resetDefaults = () => {
@@ -105,6 +107,7 @@ export const resetDefaults = () => {
105
107
  setAdvisorUntrackedContentRef(false);
106
108
  setAdvisorTrackedFileContentRef(false);
107
109
  setAdvisorScoutEnabledRef(false);
110
+ setAdvisorScoutTimeoutMsRef(DEFAULT_SCOUT_TIMEOUT_MS);
108
111
  setShowUsageDetailsRef(true);
109
112
  setShowUsageFooterRef(false);
110
113
  };
@@ -179,6 +182,11 @@ export const applyConfig = (config: AdvisorConfig) => {
179
182
  setAdvisorSessionSummaryRef
180
183
  );
181
184
  applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
185
+ applyOptionalConfig(
186
+ config,
187
+ "advisorScoutTimeoutMs",
188
+ setAdvisorScoutTimeoutMsRef
189
+ );
182
190
  applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
183
191
  applyOptionalConfig(config, "showUsageFooter", setShowUsageFooterRef);
184
192
  applyOptionalConfig(config, "simpleMode", setSimpleModeRef);
@@ -32,6 +32,7 @@ import {
32
32
  advisorRedactSecretsRef,
33
33
  advisorRef,
34
34
  advisorScoutEnabledRef,
35
+ advisorScoutTimeoutMsRef,
35
36
  advisorSessionSummaryRef,
36
37
  advisorToolPoliciesRef,
37
38
  advisorToolResultMaxBytesRef,
@@ -56,6 +57,7 @@ import {
56
57
  GATE_FAILURE_MODES,
57
58
  JEV_TRANSPORTS,
58
59
  MAX_CONTEXT_MAX_CHARS,
60
+ MAX_SCOUT_TIMEOUT_MS,
59
61
  } from "./types.ts";
60
62
 
61
63
  /** An empty ref means no model has been selected yet. */
@@ -116,6 +118,12 @@ export const isValidToolResultMaxBytes = (value: unknown): value is number =>
116
118
  export const isValidJevTimeoutMs = (value: unknown): value is number =>
117
119
  typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
118
120
 
121
+ const isValidScoutTimeoutMs = (value: unknown): value is number =>
122
+ typeof value === "number" &&
123
+ Number.isSafeInteger(value) &&
124
+ value >= 1 &&
125
+ value <= MAX_SCOUT_TIMEOUT_MS;
126
+
119
127
  export const isValidJevDigestMaxChars = (value: unknown): value is number =>
120
128
  nonNegativeSafeInteger(value);
121
129
 
@@ -344,6 +352,13 @@ export const CONFIG_SCHEMA = {
344
352
  persisted: true,
345
353
  type: "boolean",
346
354
  },
355
+ advisorScoutTimeoutMs: {
356
+ accepted: `a positive safe integer no greater than ${MAX_SCOUT_TIMEOUT_MS} milliseconds`,
357
+ current: () => advisorScoutTimeoutMsRef,
358
+ persisted: true,
359
+ type: "number",
360
+ validate: isValidScoutTimeoutMs,
361
+ },
347
362
  advisorSessionSummary: {
348
363
  accepted: "true or false",
349
364
  current: () => advisorSessionSummaryRef,
@@ -14,6 +14,7 @@ import {
14
14
  DEFAULT_JEV_TRANSPORT,
15
15
  DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
16
16
  DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
17
+ DEFAULT_SCOUT_TIMEOUT_MS,
17
18
  } from "./types.ts";
18
19
  import type {
19
20
  AdvisorToolPolicies,
@@ -68,6 +69,7 @@ export let advisorOutcomeLoggingRef = false;
68
69
  export let advisorUntrackedContentRef = false;
69
70
  export let advisorTrackedFileContentRef = false;
70
71
  export let advisorScoutEnabledRef = false;
72
+ export let advisorScoutTimeoutMsRef = DEFAULT_SCOUT_TIMEOUT_MS;
71
73
  export let showUsageDetailsRef = true;
72
74
  export let showUsageFooterRef = false;
73
75
 
@@ -213,6 +215,9 @@ export const setAdvisorTrackedFileContentRef = (enabled: boolean) => {
213
215
  export const setAdvisorScoutEnabledRef = (enabled: boolean) => {
214
216
  advisorScoutEnabledRef = enabled;
215
217
  };
218
+ export const setAdvisorScoutTimeoutMsRef = (value: number) => {
219
+ advisorScoutTimeoutMsRef = value;
220
+ };
216
221
  export const setShowUsageDetailsRef = (enabled: boolean) => {
217
222
  showUsageDetailsRef = enabled;
218
223
  };
@@ -256,6 +261,7 @@ export const getAdvisorSettings = () => ({
256
261
  planGate: advisorPlanGateRef,
257
262
  redactSecrets: advisorRedactSecretsRef,
258
263
  scoutEnabled: advisorScoutEnabledRef,
264
+ scoutTimeoutMs: advisorScoutTimeoutMsRef,
259
265
  sessionSummary: advisorSessionSummaryRef,
260
266
  showUsageDetails: showUsageDetailsRef,
261
267
  showUsageFooter: showUsageFooterRef,
@@ -23,6 +23,8 @@ export const DEFAULT_JEV_MODEL = "jev-latest";
23
23
  // Provisional: the latency benchmark was dropped from scope; revisit if live
24
24
  // measurements suggest a different bound.
25
25
  export const DEFAULT_JEV_TIMEOUT_MS = 8000;
26
+ export const DEFAULT_SCOUT_TIMEOUT_MS = 30_000;
27
+ export const MAX_SCOUT_TIMEOUT_MS = 2_147_483_647;
26
28
  export const DEFAULT_JEV_DIGEST_MAX_CHARS = 4000;
27
29
  export const DEFAULT_JEV_PRICE_PER_MTOK = 0.042;
28
30
  export const DEFAULT_JEV_FILTER_SKIP_CONFIDENCE = 0.85;
@@ -86,6 +88,7 @@ export interface AdvisorConfig {
86
88
  advisorPlanGate?: boolean;
87
89
  advisorRedactSecrets?: boolean;
88
90
  advisorScoutEnabled?: boolean;
91
+ advisorScoutTimeoutMs?: number;
89
92
  advisorSessionSummary?: boolean;
90
93
  advisorToolPolicies?: AdvisorToolPolicies;
91
94
  advisorToolResultMaxBytes?: number;
package/src/scout.ts CHANGED
@@ -1,7 +1,11 @@
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";
8
+ import { advisorScoutTimeoutMsRef } from "./config/state.ts";
5
9
  import { isRecord, isString } from "./content-utils.ts";
6
10
  import { collectTextStream, resolveConfiguredModel } from "./model-stream.ts";
7
11
  import type {
@@ -18,8 +22,6 @@ import type { ScoutManifest } from "./scout-types.ts";
18
22
  import { snapshotAdvisorUsage } from "./usage.ts";
19
23
  import type { AdvisorUsageSnapshot } from "./usage.ts";
20
24
 
21
- const SCOUT_TIMEOUT_MS = 30_000;
22
-
23
25
  export const SCOUT_SYSTEM = [
24
26
  "You are Scout, a context curator serving a separate engineering Advisor.",
25
27
  "Select only conversation groups materially relevant to the current request, unresolved decisions, attempted work, diagnostics, and validation.",
@@ -250,6 +252,8 @@ type ScoutStreamResult =
250
252
  const streamScoutResponse = async (
251
253
  dependencies: ScoutDependencies,
252
254
  resolved: ResolvedConfiguredModel,
255
+ executorModel: string,
256
+ executorEffort: string | undefined,
253
257
  manifest: ScoutManifest,
254
258
  parentSignal: AbortSignal | undefined,
255
259
  timeoutMs: number,
@@ -264,10 +268,10 @@ const streamScoutResponse = async (
264
268
  messages: [manifestMessage(manifest)],
265
269
  onChunk: (thinking, text) => {
266
270
  if (!controller.signal.aborted) {
267
- publish({ model: executorRef, text, thinking, type: "chunk" });
271
+ publish({ model: executorModel, text, thinking, type: "chunk" });
268
272
  }
269
273
  },
270
- reasoning: executorEffortRef,
274
+ reasoning: executorEffort,
271
275
  signal: controller.signal,
272
276
  systemPrompt: SCOUT_SYSTEM,
273
277
  });
@@ -294,7 +298,7 @@ export const runAdvisorScout = async (
294
298
  manifest: ScoutManifest,
295
299
  parentSignal?: AbortSignal,
296
300
  onEvent?: (event: ScoutLifecycleEvent) => void,
297
- timeoutMs = SCOUT_TIMEOUT_MS,
301
+ timeoutMs = advisorScoutTimeoutMsRef,
298
302
  dependencies: ScoutDependencies = defaultDependencies
299
303
  ): Promise<ScoutOutcome> => {
300
304
  const startedAt = Date.now();
@@ -317,7 +321,7 @@ export const runAdvisorScout = async (
317
321
  usage === undefined
318
322
  ? baseMetrics(manifest, startedAt)
319
323
  : { ...baseMetrics(manifest, startedAt), usage },
320
- model: executorRef,
324
+ model: effectiveExecutorRef(ctx),
321
325
  ok: false as const,
322
326
  };
323
327
  publish({ outcome, type: "fallback" });
@@ -328,9 +332,11 @@ export const runAdvisorScout = async (
328
332
  return cancelled();
329
333
  }
330
334
 
335
+ const executorModel = effectiveExecutorRef(ctx);
336
+ const executorEffort = effectiveExecutorEffort(ctx);
331
337
  let resolved: ResolvedConfiguredModel;
332
338
  try {
333
- resolved = await dependencies.resolve(ctx, executorRef, "Scout");
339
+ resolved = await dependencies.resolve(ctx, executorModel, "Scout");
334
340
  } catch (error) {
335
341
  if (parentSignal?.aborted) {
336
342
  return cancelled();
@@ -342,11 +348,13 @@ export const runAdvisorScout = async (
342
348
  if (parentSignal?.aborted) {
343
349
  return cancelled();
344
350
  }
345
- publish({ model: executorRef, type: "call" });
351
+ publish({ model: executorModel, type: "call" });
346
352
 
347
353
  const streamed = await streamScoutResponse(
348
354
  dependencies,
349
355
  resolved,
356
+ executorModel,
357
+ executorEffort,
350
358
  manifest,
351
359
  parentSignal,
352
360
  timeoutMs,
@@ -390,7 +398,7 @@ export const runAdvisorScout = async (
390
398
  ]).size,
391
399
  usage: snapshotAdvisorUsage(streamed.streamed.usage),
392
400
  },
393
- model: executorRef,
401
+ model: executorModel,
394
402
  ok: true as const,
395
403
  selectedLabels: manifest.groups
396
404
  .filter(
@@ -53,7 +53,6 @@ export const scoutDetailsFromEvent = (
53
53
  latencyMs: outcome.metrics.latencyMs,
54
54
  model: outcome.model,
55
55
  omittedBeforeScout: outcome.metrics.omittedBeforeScout,
56
- selectedCount: 0,
57
56
  status: "fallback",
58
57
  usage: snapshotAdvisorUsage(outcome.metrics.usage),
59
58
  };
@@ -144,12 +143,18 @@ const scoutTitle = (scout: ScoutToolDetails, frame: string) => {
144
143
  return "◆ SCOUT · FALLBACK";
145
144
  };
146
145
 
146
+ const scoutSelectionSummary = (scout: ScoutToolDetails) => {
147
+ if (scout.status === "fallback") {
148
+ return " · original conversation retained";
149
+ }
150
+ if (scout.status !== "curated" || scout.selectedCount === undefined) {
151
+ return "";
152
+ }
153
+ return ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`;
154
+ };
155
+
147
156
  const scoutSummaryLine = (scout: ScoutToolDetails) =>
148
- ` ${scout.model}${
149
- scout.selectedCount === undefined
150
- ? ""
151
- : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`
152
- }${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
157
+ ` ${scout.model}${scoutSelectionSummary(scout)}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
153
158
 
154
159
  const scoutExpandedLines = (
155
160
  scout: ScoutToolDetails,
@@ -12,6 +12,7 @@ import {
12
12
  DEFAULT_JEV_TIMEOUT_MS,
13
13
  DEFAULT_JEV_TRANSPORT,
14
14
  DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
15
+ DEFAULT_SCOUT_TIMEOUT_MS,
15
16
  } from "../config/types.ts";
16
17
  import { isValidAdvisorToolPolicies } from "../config/validation.ts";
17
18
  import { JevSetupSubmenu } from "./jev-setup-submenu.ts";
@@ -56,6 +57,21 @@ const toggle = (
56
57
  values: TOGGLE_VALUES,
57
58
  });
58
59
 
60
+ const scoutTimeoutItem = (settings: AdvisorSettings): SettingItem => {
61
+ const timeoutMs = settings.scoutTimeoutMs ?? DEFAULT_SCOUT_TIMEOUT_MS;
62
+ return {
63
+ currentValue: String(timeoutMs),
64
+ description:
65
+ "Maximum time for Scout's response stream; model and auth resolution happen before this timeout starts.",
66
+ id: "scoutTimeoutMs",
67
+ label: "Scout timeout ms",
68
+ values: numericValues(
69
+ timeoutMs,
70
+ [5000, 10_000, 15_000, 30_000, 45_000, 60_000, 90_000, 120_000]
71
+ ),
72
+ };
73
+ };
74
+
59
75
  const jevItems = (
60
76
  settings: AdvisorSettings,
61
77
  theme: Theme,
@@ -288,6 +304,7 @@ export const createSettingsItems = ({
288
304
  settings.scoutEnabled,
289
305
  false
290
306
  ),
307
+ scoutTimeoutItem(settings),
291
308
  toggle(
292
309
  "showUsageDetails",
293
310
  "Show usage and cost details",
@@ -161,6 +161,10 @@ const applyJevMutation = (
161
161
  settings.jevTimeoutMs = Number(value);
162
162
  return true;
163
163
  }
164
+ case "scoutTimeoutMs": {
165
+ settings.scoutTimeoutMs = Number(value);
166
+ return true;
167
+ }
164
168
  case "jevDigestMaxChars": {
165
169
  settings.jevDigestMaxChars = Number(value);
166
170
  return true;
package/src/ui/types.ts CHANGED
@@ -98,6 +98,7 @@ export interface AdvisorSettings {
98
98
  planGate: boolean;
99
99
  redactSecrets?: boolean;
100
100
  scoutEnabled?: boolean;
101
+ scoutTimeoutMs?: number;
101
102
  sessionSummary?: boolean;
102
103
  showUsageDetails?: boolean;
103
104
  showUsageFooter?: boolean;