pi-delegation-policy 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.0 - 2026-08-27
6
+
7
+ ### Added
8
+
9
+ - Added a compact effective-policy preview, selector guidance, and available public model metadata to the `/delegate` panel.
10
+
11
+ ### Changed
12
+
13
+ - Require exact `provider/model` references for delegation guidance instead of relying on a launcher default.
14
+ - Clarified task-fit role selection, preference tie-breaks, dynamic advisory thinking, and current panel and status diagnostics.
15
+ - Raised the Pi peer requirement and explicitly checked baseline to `0.84.3`.
16
+
5
17
  ## 0.3.2 - 2026-08-26
6
18
 
7
19
  ### Documentation
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A local Pi extension that helps the main agent decide **when delegation is worth it** and which exact models to use for the Small, Medium, Large, and optional UI Design roles. It provides guidance; it is not a subagent runner.
4
4
 
5
- > **Status:** Version **0.3.2** is the latest published package. The package accepts Pi `>=0.84.1`; Pi `0.84.1` is the explicitly validated baseline (newer versions are not claimed as tested).
5
+ > **Status:** Version **0.4.0** is the latest published package. The package requires Pi `>=0.84.3`; Pi `0.84.3` is the explicitly checked baseline (newer versions are not claimed as tested).
6
6
  >
7
7
  > **Docs:** [Read the documentation site](https://yivas.github.io/pi-delegation-policy/).
8
8
 
@@ -27,7 +27,7 @@ pi install npm:pi-delegation-policy
27
27
  1. Open `/delegate` (or press `Alt+G` in Pi's TUI).
28
28
  2. Configure exact, authenticated Small, Medium, and Large provider/model references. UI Design is optional.
29
29
  3. Select `normal` or `aggressive`, then choose **Apply changes**.
30
- 4. Run `/delegate status` and inspect the footer for `D:NORM` or `D:AGG`. `D:ERR` means an active required role is invalid; no policy is injected.
30
+ 4. Run `/delegate status` and inspect the exact role references and their sources. `D:ERR` means an active required role is invalid; no policy is injected.
31
31
  5. The applied state affects the **next** agent run. An agent already running is not rewritten.
32
32
 
33
33
  See the [end-to-end getting-started guide](https://yivas.github.io/pi-delegation-policy/getting-started/) and [configuration reference](https://yivas.github.io/pi-delegation-policy/configuration/) for details.
@@ -43,11 +43,11 @@ See the [end-to-end getting-started guide](https://yivas.github.io/pi-delegation
43
43
  /delegate reset Reset this branch to off and other fields to global defaults
44
44
  ```
45
45
 
46
- The editor is a bounded, keyboard-first panel. It shows model ID first and `[provider]` last, fuzzy-searches provider, model ID, and display name, and shows at most 10 model rows. Inheritance actions stay pinned while searching. Changes are drafts until **Apply changes**; saving effective configuration as defaults updates the global file without applying the draft, and closing a modified draft requires explicit discard.
46
+ The editor is a bounded, keyboard-first panel. It shows model ID first and `[provider]` last, fuzzy-searches provider, model ID, and display name, and shows at most 10 model rows. It also shows a compact effective-policy preview, field explanations, and public model metadata when Pi supplies it. Changes are drafts until **Apply changes**; saving effective configuration as defaults updates the global file without applying the draft, and closing a modified draft requires explicit discard.
47
47
 
48
48
  ## Configuration and safety
49
49
 
50
- Global defaults are stored at `~/.pi/agent/delegation-policy.json` (schema version 2). The file stores optional intensity, preference, exact role references, and an optional UI Design reference; it never stores thinking. A valid active mode requires exact, available, in-scope, authenticated Small, Medium, and Large references. Invalid active configuration fails closed as `D:ERR` with no policy injection; `off` is always `D:OFF`.
50
+ Global defaults are stored at `~/.pi/agent/delegation-policy.json` (schema version 2). The file stores optional intensity, preference, exact role references, and an optional UI Design reference; it never stores thinking. A valid active mode requires exact, available, in-scope, authenticated Small, Medium, and Large references. Before every delegated launch, the policy requires the selected role's exact combined reference as `model: "provider/model"`; it does not rely on an ambient launcher default. Invalid active configuration fails closed as `D:ERR` with no policy injection; `off` is always `D:OFF`.
51
51
 
52
52
  The extension stores only policy settings and provider/model identifiers in local defaults and session entries. Review local configuration before sharing diagnostics, and remove credentials, prompts, personal paths, session files, and unredacted logs from reports. See the [limits and privacy reference](https://yivas.github.io/pi-delegation-policy/limits-and-privacy/) and [security policy](https://github.com/Yivas/pi-delegation-policy/blob/main/SECURITY.md).
53
53
 
package/SECURITY.md CHANGED
@@ -14,4 +14,4 @@ Include the affected version or commit, operating system, Pi version, reproducti
14
14
 
15
15
  ## Supported versions
16
16
 
17
- Only the latest published version is supported. Version 0.3.2 is the current supported release.
17
+ Only the latest published version is supported. Version 0.4.0 is the current supported release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-delegation-policy",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "A Pi extension for configurable delegation intensity and exact subagent role model references.",
6
6
  "type": "module",
@@ -36,12 +36,14 @@
36
36
  "pack:check": "npm pack --dry-run"
37
37
  },
38
38
  "peerDependencies": {
39
- "@earendil-works/pi-coding-agent": ">=0.84.1"
39
+ "@earendil-works/pi-ai": ">=0.84.3",
40
+ "@earendil-works/pi-coding-agent": ">=0.84.3",
41
+ "@earendil-works/pi-tui": ">=0.84.3"
40
42
  },
41
43
  "devDependencies": {
42
- "@earendil-works/pi-ai": "0.84.1",
43
- "@earendil-works/pi-coding-agent": "0.84.1",
44
- "@earendil-works/pi-tui": "0.84.1",
44
+ "@earendil-works/pi-ai": "0.84.3",
45
+ "@earendil-works/pi-coding-agent": "0.84.3",
46
+ "@earendil-works/pi-tui": "0.84.3",
45
47
  "@eslint/js": "^9.39.5",
46
48
  "@types/node": "^22.0.0",
47
49
  "ajv": "^8.20.0",
@@ -12,6 +12,7 @@ import {
12
12
  type TUI,
13
13
  } from "@earendil-works/pi-tui";
14
14
  import { resolveDelegateState } from "./config.ts";
15
+ import { buildPolicyPreview } from "./prompt.ts";
15
16
  import {
16
17
  INTENSITIES,
17
18
  PREFERENCES,
@@ -52,6 +53,7 @@ export interface DelegatePanelOptions {
52
53
  session: SessionDelegateState;
53
54
  candidates: Model<Api>[];
54
55
  diagnostics: string[];
56
+ hasRuntimeError: boolean;
55
57
  onApply: (draft: SessionDelegateState) => Promise<boolean>;
56
58
  onSaveDefaults: (draft: SessionDelegateState) => Promise<GlobalDefaults | undefined>;
57
59
  onDone: (result: DelegatePanelResult) => void;
@@ -74,6 +76,15 @@ const FIELD_LABELS: Record<DelegateField, string> = {
74
76
  uiDesign: "UI Design",
75
77
  };
76
78
 
79
+ const FIELD_DESCRIPTIONS: Record<DelegateField, string> = {
80
+ intensity: "When delegation is worth considering.",
81
+ preference: "Tie-break only; task fit decides the role first.",
82
+ small: "Bounded, planned, and verifiable execution.",
83
+ medium: "Planning, ambiguity, synthesis, and coordination.",
84
+ large: "Exceptional unblocker for persistent hard problems.",
85
+ uiDesign: "Visual direction and review only; never implementation.",
86
+ };
87
+
77
88
  const ACTION_LABELS: Record<PanelAction, string> = {
78
89
  apply: "Apply changes",
79
90
  "save-defaults": "Save effective configuration as defaults",
@@ -132,6 +143,17 @@ function sortedModels(models: readonly Model<Api>[]): Model<Api>[] {
132
143
  });
133
144
  }
134
145
 
146
+ function modelMetadata(model: Model<Api> | undefined): string[] {
147
+ if (!model) return [];
148
+ const details: string[] = [];
149
+ if (model.name !== undefined) details.push(`Model Name: ${model.name}`);
150
+ if (model.api !== undefined) details.push(`API: ${model.api}`);
151
+ if (model.reasoning !== undefined) details.push(`Reasoning: ${model.reasoning ? "yes" : "no"}`);
152
+ if (model.contextWindow !== undefined) details.push(`Context: ${model.contextWindow}`);
153
+ if (model.maxTokens !== undefined) details.push(`Max output: ${model.maxTokens}`);
154
+ return details;
155
+ }
156
+
135
157
  function visibleBlockRange(blocks: string[][], selected: number, budget: number): [number, number] {
136
158
  if (blocks.length === 0) return [0, 0];
137
159
  const safeSelected = Math.max(0, Math.min(selected, blocks.length - 1));
@@ -169,6 +191,7 @@ export class DelegatePanel implements Component, Focusable {
169
191
  private readonly theme: Theme;
170
192
  private readonly candidates: Model<Api>[];
171
193
  private readonly diagnostics: string[];
194
+ private readonly hasRuntimeError: boolean;
172
195
  private readonly onApply: DelegatePanelOptions["onApply"];
173
196
  private readonly onSaveDefaults: DelegatePanelOptions["onSaveDefaults"];
174
197
  private readonly onDone: DelegatePanelOptions["onDone"];
@@ -191,6 +214,7 @@ export class DelegatePanel implements Component, Focusable {
191
214
  this.draft = cloneSession(options.session);
192
215
  this.candidates = sortedModels(options.candidates);
193
216
  this.diagnostics = [...options.diagnostics];
217
+ this.hasRuntimeError = options.hasRuntimeError;
194
218
  this.onApply = options.onApply;
195
219
  this.onSaveDefaults = options.onSaveDefaults;
196
220
  this.onDone = options.onDone;
@@ -332,9 +356,7 @@ export class DelegatePanel implements Component, Focusable {
332
356
  ];
333
357
  const notice =
334
358
  this.message ??
335
- (this.diagnostics.length > 0
336
- ? { kind: "error" as const, text: "Warning: global defaults are invalid." }
337
- : undefined);
359
+ (this.diagnostics[0] ? { kind: "error" as const, text: this.diagnostics[0] } : undefined);
338
360
  if (notice) {
339
361
  lines.push(
340
362
  truncateToWidth(
@@ -365,6 +387,8 @@ export class DelegatePanel implements Component, Focusable {
365
387
 
366
388
  private renderSettings(width: number, budget: number): string[] {
367
389
  const effective = resolveDelegateState(this.global, this.draft);
390
+ const preview = this.renderPolicyPreview(width, budget, effective);
391
+ const settingsBudget = Math.max(1, budget - preview.length);
368
392
  const blocks = SETTINGS_ITEMS.map((item, index) => {
369
393
  const selected = index === this.settingsIndex;
370
394
  if (FIELD_IDS.includes(item as DelegateField)) {
@@ -381,6 +405,7 @@ export class DelegatePanel implements Component, Focusable {
381
405
  if (width < 48) {
382
406
  return [
383
407
  selectedLine(this.theme, FIELD_LABELS[field], width, selected),
408
+ ...wrapTextWithAnsi(` ${FIELD_DESCRIPTIONS[field]}`, width),
384
409
  ...wrapTextWithAnsi(` ${value}`, width),
385
410
  ...details.flatMap((line) =>
386
411
  wrapTextWithAnsi(this.theme.fg("dim", ` ${line}`), width),
@@ -391,7 +416,10 @@ export class DelegatePanel implements Component, Focusable {
391
416
  const first = `${FIELD_LABELS[field].padEnd(labelWidth)}${value}`;
392
417
  return [
393
418
  selectedLine(this.theme, first, width, selected),
394
- ...wrapTextWithAnsi(this.theme.fg("dim", ` ${details.join(" · ")}`), width),
419
+ ...wrapTextWithAnsi(
420
+ this.theme.fg("dim", ` ${FIELD_DESCRIPTIONS[field]} · ${details.join(" · ")}`),
421
+ width,
422
+ ),
395
423
  ];
396
424
  }
397
425
  const action = item as PanelAction;
@@ -401,7 +429,25 @@ export class DelegatePanel implements Component, Focusable {
401
429
  return [disabled ? this.theme.fg("dim", line) : line];
402
430
  });
403
431
 
404
- return this.renderBlockViewport(blocks, this.settingsIndex, width, budget);
432
+ return [
433
+ ...preview,
434
+ ...this.renderBlockViewport(blocks, this.settingsIndex, width, settingsBudget),
435
+ ];
436
+ }
437
+
438
+ private renderPolicyPreview(
439
+ width: number,
440
+ budget: number,
441
+ effective: ReturnType<typeof resolveDelegateState>,
442
+ ): string[] {
443
+ const lines =
444
+ effective.intensity !== "off" && this.hasRuntimeError
445
+ ? ["D:ERR · policy unavailable; fix the reported role diagnostics"]
446
+ : buildPolicyPreview(effective);
447
+ const maximum = width >= 60 && budget >= 8 ? 4 : 2;
448
+ return ["Effective policy preview", ...lines]
449
+ .slice(0, maximum)
450
+ .map((line) => truncateToWidth(this.theme.fg("dim", line), width, ""));
405
451
  }
406
452
 
407
453
  private sourceDetails(field: DelegateField): string[] {
@@ -439,12 +485,30 @@ export class DelegatePanel implements Component, Focusable {
439
485
  mode: Extract<PanelMode, { kind: "enum" }>,
440
486
  ): string[] {
441
487
  const values = mode.field === "intensity" ? INTENSITIES : PREFERENCES;
488
+ const descriptions =
489
+ mode.field === "intensity"
490
+ ? {
491
+ off: "No policy is injected.",
492
+ normal: "Delegate when the expected benefit clearly outweighs overhead.",
493
+ aggressive: "Delegate suitable substantial work by default.",
494
+ }
495
+ : {
496
+ efficient: "Tie-break comparable fits toward Small.",
497
+ standard: "No extra Small or Medium bias.",
498
+ intensive: "Tie-break comparable fits toward Medium.",
499
+ };
500
+ const globalValue =
501
+ this.global[mode.field] ?? (mode.field === "intensity" ? "off" : "standard");
442
502
  const options = [
443
- `${USE_GLOBAL_DEFAULT} (${this.global[mode.field] ?? (mode.field === "intensity" ? "off" : "standard")})`,
444
- ...values,
503
+ {
504
+ label: `${USE_GLOBAL_DEFAULT} (${globalValue})`,
505
+ description: "Use the current global value.",
506
+ },
507
+ ...values.map((value) => ({ label: value, description: descriptions[value] })),
445
508
  ];
446
509
  const blocks = options.map((option, index) => [
447
- selectedLine(this.theme, option, width, index === mode.selected),
510
+ selectedLine(this.theme, option.label, width, index === mode.selected),
511
+ ...wrapTextWithAnsi(this.theme.fg("dim", ` ${option.description}`), width),
448
512
  ]);
449
513
  return this.renderBlockViewport(blocks, mode.selected, width, budget);
450
514
  }
@@ -471,15 +535,20 @@ export class DelegatePanel implements Component, Focusable {
471
535
  );
472
536
  const dividerRows = budget > pinnedLines.length + 3 ? 1 : 0;
473
537
  const selectedChoice = choices[mode.selected];
474
- const selectedName =
475
- selectedChoice?.kind === "model" && selectedChoice.description
476
- ? [` Model Name: ${selectedChoice.description}`]
538
+ const metadata =
539
+ selectedChoice?.kind === "model"
540
+ ? modelMetadata(
541
+ this.candidates.find(
542
+ (model) =>
543
+ modelKey({ provider: model.provider, model: model.id }) === selectedChoice.key,
544
+ ),
545
+ )
477
546
  : [];
478
- const detailRows = selectedName.length > 0 && budget > pinnedLines.length + 6 ? 2 : 0;
479
- const availableListRows = Math.max(
480
- 0,
481
- budget - 2 - pinnedLines.length - dividerRows - detailRows,
482
- );
547
+ const fixedRows = 2 + pinnedLines.length + dividerRows;
548
+ const availableDetailRows = Math.max(0, budget - fixedRows - 1);
549
+ const details = metadata.slice(0, availableDetailRows);
550
+ const detailRows = details.length > 0 ? details.length + 1 : 0;
551
+ const availableListRows = Math.max(0, budget - fixedRows - detailRows);
483
552
  const listBudget = Math.min(11, availableListRows);
484
553
  const modelBlocks = models.map((choice, index) => {
485
554
  const combinedIndex = index + pinnedCount;
@@ -512,7 +581,7 @@ export class DelegatePanel implements Component, Focusable {
512
581
  ...pinnedLines,
513
582
  ...(dividerRows ? [this.theme.fg("borderMuted", "─".repeat(width))] : []),
514
583
  ...modelLines,
515
- ...(detailRows ? ["", ...selectedName.map((line) => this.theme.fg("muted", line))] : []),
584
+ ...(detailRows ? ["", ...details.map((line) => this.theme.fg("muted", ` ${line}`))] : []),
516
585
  ].slice(0, budget);
517
586
  }
518
587
 
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  import type { AutocompleteItem } from "@earendil-works/pi-tui";
7
7
  import { buildDelegationPolicy } from "./prompt.ts";
8
8
  import {
9
+ formatModelRef,
9
10
  hasRuntimeError,
10
11
  loadRuntime,
11
12
  sessionEntry,
@@ -54,19 +55,15 @@ async function openEditor(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void
54
55
  updateStatus(ctx, await refresh(ctx));
55
56
  }
56
57
 
57
- function configuredLabel(value: unknown): string {
58
- return value ? "configured" : "unset";
59
- }
60
-
61
58
  export function statusText(state: RuntimeState): string {
62
59
  const { effective } = state;
63
60
  const details = [
64
61
  `${statusLabel(state)} intensity=${effective.intensity} (${effective.source.intensity})`,
65
62
  `preference=${effective.preference} (${effective.source.preference})`,
66
- `small=${configuredLabel(effective.small)} (${effective.source.small})`,
67
- `medium=${configuredLabel(effective.medium)} (${effective.source.medium})`,
68
- `large=${configuredLabel(effective.large)} (${effective.source.large})`,
69
- `ui-design=${configuredLabel(effective.uiDesign)} (${effective.source.uiDesign})`,
63
+ `small=${formatModelRef(effective.small)} (${effective.source.small})`,
64
+ `medium=${formatModelRef(effective.medium)} (${effective.source.medium})`,
65
+ `large=${formatModelRef(effective.large)} (${effective.source.large})`,
66
+ `ui-design=${formatModelRef(effective.uiDesign)} (${effective.source.uiDesign})`,
70
67
  ];
71
68
 
72
69
  if (effective.intensity !== "off" && state.runtimeErrors.length > 0) {
package/src/prompt.ts CHANGED
@@ -1,35 +1,42 @@
1
1
  import { hasRuntimeError, type RuntimeState } from "./runtime.ts";
2
- import type { ModelRef, Preference } from "./types.ts";
2
+ import type { EffectiveDelegateState, ModelRef, Preference } from "./types.ts";
3
3
 
4
4
  const NORMAL_POLICY =
5
5
  "Delegate substantial, separable work only when the expected benefit clearly outweighs briefing, supervision, review, and integration cost. Count parallelism as a benefit only when valuable work can advance now or elapsed time matters. A merely possible fresh perspective is not enough by itself. Keep borderline work with the main agent.";
6
6
  const AGGRESSIVE_POLICY =
7
7
  "Default to delegating substantial, separable, independently checkable work with a clear objective and acceptance criteria. Delegate when the benefit is plausible even if not proven, including a useful independent perspective. Keep work with the main agent when it is poorly bounded, tightly coupled, dominated by integration or final accountability, or has clearly prohibitive delegation overhead.";
8
8
 
9
- const ROLE_SELECTION_POLICY = `Choose the role and thinking together from the combination of:
10
- - task demand: execute, search, plan, decide, or unblock;
11
- - difficulty: clarity, ambiguity, dependencies, risk, and competing hypotheses;
12
- - quantity: files, modules, systems, sources, and context volume.
13
- No single factor decides the role.
9
+ const ROLE_SELECTION_POLICY = `Choose the role by task fit before considering model preference:
10
+ - demand: execute, search, plan, decide, coordinate, or unblock;
11
+ - difficulty: clarity, ambiguity, dependencies, competing hypotheses, and risk;
12
+ - quantity: files, modules, systems, sources, and context volume;
13
+ - error and review cost: what can go wrong, how costly it is to detect, and what evidence is needed.
14
+ No single factor decides the role. Select the smallest role that can satisfy the acceptance criteria and evidence requirements.
14
15
 
15
- Use Small habitually for bounded, planned, and verifiable work: concrete searches, scoped exploration, defined implementation, focused documentation, tests, reviews, mechanical changes, evident bugs, and bounded UI implementation whose design and stack are decided. Difficult but well-defined execution can remain Small with higher thinking.
16
+ Use Small for bounded, planned, and verifiable execution: concrete searches, scoped exploration, defined implementation, focused documentation, tests, reviews, mechanical changes, evident bugs, and bounded UI implementation whose design and stack are decided. Difficult but well-defined execution can remain Small with higher thinking.
16
17
 
17
- Use Medium directly when the combined demands materially require defining a plan, reducing meaningful ambiguity, broad synthesis, tracing several modules, comparing sources or options, coordinating substantial context, or making difficult decisions. These are evidence, not automatic triggers. Small does not need to fail first.
18
+ Use Medium directly when the combined task fit materially requires planning, reducing meaningful ambiguity, broad synthesis, tracing several modules, comparing sources or options, coordinating substantial context, or making difficult decisions. Small does not need to fail first.
18
19
 
19
20
  Use Large only to unblock genuinely stuck work: persistent failures, severe framework conflicts, contradictory hypotheses, or reliable prior evidence that ordinary roles have not produced a trustworthy answer. Do not require ceremonial failed attempts. Large remains exceptional.
20
21
 
21
- Large quantities of repetitive, independent work favor multiple Small delegations; volume alone does not justify Medium or Large. Agent type does not determine the model role. A clearly better task fit overrides preference; preference only shifts credible Small/Medium choices.
22
+ Large quantities of repetitive, independent work favor multiple Small delegations; volume alone does not justify Medium or Large. Agent type does not determine the model role. Apply preference only when Small and Medium are comparably credible fits.
22
23
 
23
24
  In every intensity, keep global strategy, coordination, integration, final review, and work whose essential context is too costly or risky to transfer with the main agent.`;
24
25
 
25
26
  function preferenceGuidance(preference: Preference): string {
26
27
  if (preference === "efficient") {
27
- return "Favor Small more strongly than standard. When Small can safely satisfy the acceptance criteria, choose it unless Medium provides a material advantage.";
28
+ return "Use efficient only as a Small tie-break when Small and Medium are comparably credible. Do not choose Small when Medium is a materially better task fit.";
28
29
  }
29
30
  if (preference === "intensive") {
30
- return "When Small and Medium are both credible, normally prefer Medium. Keep Small for work that is clearly narrow, routine, mechanical, or an especially clear Small fit.";
31
+ return "Use intensive only as a Medium tie-break when Small and Medium are comparably credible. Do not choose Medium when Small is the clearly better task fit.";
31
32
  }
32
- return "Choose Small on a genuine Small/Medium tie; otherwise follow the role-selection policy above.";
33
+ return "Standard adds no Small or Medium bias; follow task fit.";
34
+ }
35
+
36
+ function preferencePreview(preference: Preference): string {
37
+ if (preference === "efficient") return "efficient breaks comparable fits toward Small";
38
+ if (preference === "intensive") return "intensive breaks comparable fits toward Medium";
39
+ return "standard has no extra bias";
33
40
  }
34
41
 
35
42
  function promptString(value: string): string {
@@ -43,6 +50,25 @@ function formatReference(reference: ModelRef): string {
43
50
  return `provider=${promptString(reference.provider)} model=${promptString(reference.model)}`;
44
51
  }
45
52
 
53
+ function formatLaunchModel(reference: ModelRef): string {
54
+ return promptString(`${reference.provider}/${reference.model}`);
55
+ }
56
+
57
+ export function buildPolicyPreview(effective: EffectiveDelegateState): string[] {
58
+ if (effective.intensity === "off") return ["off · no policy injected"];
59
+
60
+ const { small, medium, large } = effective;
61
+ if (!small) return ["active · Small not configured · no policy can be injected"];
62
+ if (!medium) return ["active · Medium not configured · no policy can be injected"];
63
+ if (!large) return ["active · Large not configured · no policy can be injected"];
64
+
65
+ return [
66
+ `${effective.intensity} · task fit first · ${preferencePreview(effective.preference)}`,
67
+ `Small ${formatLaunchModel(small)} · Medium ${formatLaunchModel(medium)} · Large ${formatLaunchModel(large)}`,
68
+ "Every launch must include the selected exact model; thinking stays dynamic.",
69
+ ];
70
+ }
71
+
46
72
  export function buildDelegationPolicy(state: RuntimeState): string | undefined {
47
73
  if (state.effective.intensity === "off" || hasRuntimeError(state)) return undefined;
48
74
 
@@ -51,7 +77,7 @@ export function buildDelegationPolicy(state: RuntimeState): string | undefined {
51
77
 
52
78
  const intensityPolicy = effective.intensity === "normal" ? NORMAL_POLICY : AGGRESSIVE_POLICY;
53
79
  const uiDesign = effective.uiDesign
54
- ? `\n- UI Design: ${formatReference(effective.uiDesign)}. Use this role only for visual design direction, exploration, or review. Never use it to implement an interface, write code, or run tests.`
80
+ ? `\n- UI Design: ${formatReference(effective.uiDesign)}; launch with model: ${formatLaunchModel(effective.uiDesign)}. Use this role only for visual design direction, exploration, or review. Never use it to implement an interface, write code, or run tests.`
55
81
  : "";
56
82
 
57
83
  return `<delegation_policy>
@@ -62,12 +88,12 @@ ${ROLE_SELECTION_POLICY}
62
88
 
63
89
  Model preference: ${effective.preference}. ${preferenceGuidance(effective.preference)}
64
90
 
65
- Use the exact provider and model for the selected role. The references below use JSON string syntax; interpret escaped characters as JSON before use. Do not invent a fallback model or role. Choose thinking dynamically for each delegation from task demand, difficulty, quantity, and the selected model's capabilities. Do not treat thinking as persisted configuration.
91
+ Before every delegated launch, name the selected role, take its exact combined provider/model reference below, and include it in the call as model: "provider/model" using the JSON-escaped value shown for that role. Do not omit model, inherit an ambient launcher default, substitute another model, or invent a fallback model or role. Choose thinking dynamically for each delegation from task demand, difficulty, quantity, risk, review cost, and the selected model's capabilities. Thinking is advisory and is not persisted configuration.
66
92
 
67
93
  Roles:
68
- - Small: ${formatReference(effective.small)}
69
- - Medium: ${formatReference(effective.medium)}
70
- - Large: ${formatReference(effective.large)}${uiDesign}
94
+ - Small: ${formatReference(effective.small)}; launch with model: ${formatLaunchModel(effective.small)}
95
+ - Medium: ${formatReference(effective.medium)}; launch with model: ${formatLaunchModel(effective.medium)}
96
+ - Large: ${formatReference(effective.large)}; launch with model: ${formatLaunchModel(effective.large)}${uiDesign}
71
97
 
72
98
  This is guidance for the main agent. It does not create, execute, route, supervise, or enforce delegated work.
73
99
  </delegation_policy>`;
package/src/ui.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  writeConfig,
7
7
  } from "./config.ts";
8
8
  import { DelegatePanel, type DelegatePanelResult } from "./delegate-panel.ts";
9
- import { loadRuntime, modelCandidates, sessionEntry } from "./runtime.ts";
9
+ import { hasRuntimeError, loadRuntime, modelCandidates, sessionEntry } from "./runtime.ts";
10
10
 
11
11
  export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI): Promise<void> {
12
12
  if (!ctx.hasUI) return;
@@ -29,7 +29,8 @@ export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI
29
29
  global: state.global,
30
30
  session: state.session,
31
31
  candidates,
32
- diagnostics: state.diagnostics.map((diagnostic) => diagnostic.message),
32
+ diagnostics: state.runtimeErrors,
33
+ hasRuntimeError: hasRuntimeError(state),
33
34
  onApply: async (draft) => {
34
35
  const session = structuredClone(draft);
35
36
  sessionEntry(pi, { ...state, session });