ultimate-pi 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +3 -3
  2. package/.agents/skills/harness-orchestration/SKILL.md +19 -11
  3. package/.agents/skills/harness-plan/SKILL.md +15 -9
  4. package/.pi/agents/harness/planner.md +6 -47
  5. package/.pi/agents/harness/planning/decompose.md +84 -0
  6. package/.pi/agents/harness/planning/hypothesis-eval.md +59 -0
  7. package/.pi/agents/harness/planning/hypothesis.md +90 -0
  8. package/.pi/agents/harness/planning/plan-adversary.md +50 -0
  9. package/.pi/agents/harness/planning/planner.md +20 -0
  10. package/.pi/agents/harness/planning/scout-graphify.md +48 -0
  11. package/.pi/agents/harness/planning/scout-semantic.md +42 -0
  12. package/.pi/agents/harness/planning/scout-structure.md +44 -0
  13. package/.pi/extensions/harness-ask-user.ts +5 -0
  14. package/.pi/extensions/harness-live-widget.ts +48 -28
  15. package/.pi/extensions/harness-plan-approval.ts +192 -24
  16. package/.pi/extensions/harness-run-context.ts +24 -15
  17. package/.pi/extensions/harness-subagents.ts +8 -3
  18. package/.pi/extensions/harness-web-tools.ts +2 -0
  19. package/.pi/extensions/lib/extension-load-guard.ts +39 -0
  20. package/.pi/extensions/lib/harness-subagents/harness-subagent-policy.ts +33 -5
  21. package/.pi/extensions/lib/harness-subagents/parent-harness-ui-bridge.ts +2 -171
  22. package/.pi/extensions/lib/harness-subagents/parent-harness-ui-hooks.ts +18 -0
  23. package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +1 -5
  24. package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +0 -18
  25. package/.pi/extensions/lib/harness-subagents/vendored/index.ts +4 -36
  26. package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +2 -0
  27. package/.pi/extensions/lib/plan-approval/create-plan.ts +5 -0
  28. package/.pi/extensions/lib/plan-approval/dialog.ts +231 -147
  29. package/.pi/extensions/lib/plan-approval/plan-review.ts +393 -0
  30. package/.pi/extensions/lib/plan-approval/schema.ts +16 -1
  31. package/.pi/extensions/lib/plan-approval/types.ts +10 -0
  32. package/.pi/extensions/lib/plan-approval/validate.ts +2 -0
  33. package/.pi/extensions/policy-gate.ts +1 -1
  34. package/.pi/extensions/ultimate-pi-vcc.ts +5 -0
  35. package/.pi/harness/agents.manifest.json +114 -82
  36. package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +3 -3
  37. package/.pi/harness/docs/adrs/0033-parent-orchestrated-planning.md +34 -0
  38. package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +41 -0
  39. package/.pi/harness/docs/adrs/README.md +2 -0
  40. package/.pi/harness/specs/README.md +1 -1
  41. package/.pi/harness/specs/harness-spawn-context.schema.json +2 -1
  42. package/.pi/harness/specs/plan-adversary-brief.schema.json +45 -0
  43. package/.pi/harness/specs/plan-decomposition-brief.schema.json +108 -0
  44. package/.pi/harness/specs/plan-hypothesis-brief.schema.json +96 -0
  45. package/.pi/harness/specs/plan-hypothesis-eval.schema.json +61 -0
  46. package/.pi/lib/harness-run-context.ts +12 -0
  47. package/.pi/prompts/harness-auto.md +1 -1
  48. package/.pi/prompts/harness-plan.md +116 -20
  49. package/.pi/prompts/harness-setup.md +1 -1
  50. package/.pi/scripts/harness-resolve-up-pkg.mjs +13 -0
  51. package/CHANGELOG.md +18 -0
  52. package/biome.json +4 -1
  53. package/package.json +2 -2
@@ -12,6 +12,51 @@ interface CustomAnswer {
12
12
  response: { kind: "selection"; selections: string[] };
13
13
  }
14
14
 
15
+ /** Lines reserved below overlay: harness-live widget + editor + footer. */
16
+ export const PLAN_APPROVAL_BOTTOM_RESERVE_LINES = 11;
17
+ /** Estimate agents widget height when stacking above harness live. */
18
+ export const PLAN_APPROVAL_AGENTS_TOP_RESERVE_LINES = 12;
19
+ export const PLAN_APPROVAL_MIN_VIEWPORT = 6;
20
+
21
+ export function computePlanViewport(
22
+ availableHeight: number,
23
+ chromeLines: number,
24
+ ): number {
25
+ return Math.max(PLAN_APPROVAL_MIN_VIEWPORT, availableHeight - chromeLines);
26
+ }
27
+
28
+ export function computePlanOverlayMaxHeight(termHeight: number): number {
29
+ return Math.max(
30
+ PLAN_APPROVAL_MIN_VIEWPORT + 8,
31
+ termHeight -
32
+ PLAN_APPROVAL_BOTTOM_RESERVE_LINES -
33
+ PLAN_APPROVAL_AGENTS_TOP_RESERVE_LINES,
34
+ );
35
+ }
36
+
37
+ function countPlanChromeLines(
38
+ validated: ValidatedApprovePlanParams,
39
+ displayOptions: ValidatedApprovePlanParams["options"],
40
+ useOverlay: boolean,
41
+ ): number {
42
+ let chrome = useOverlay ? 2 : 0; // borders
43
+ chrome += 1; // title
44
+ if (validated.human_summary) {
45
+ chrome += validated.human_summary.split("\n").length;
46
+ }
47
+ chrome += 1; // blank before plan
48
+ chrome += 1; // plan label
49
+ chrome += 1; // blank before options
50
+ chrome += 1; // options label
51
+ for (const opt of displayOptions) {
52
+ chrome += 1;
53
+ if (opt.description) chrome += 1;
54
+ }
55
+ chrome += 1; // blank before hints
56
+ chrome += 1; // hints
57
+ return chrome;
58
+ }
59
+
15
60
  function withTimeout<T>(
16
61
  promise: Promise<T | null>,
17
62
  ms: number | undefined,
@@ -25,177 +70,216 @@ function withTimeout<T>(
25
70
  ]);
26
71
  }
27
72
 
73
+ export type RunPlanApprovalDialogOptions = {
74
+ onMounted?: () => void;
75
+ };
76
+
28
77
  export async function runPlanApprovalDialog(
29
78
  ui: ExtensionUIContext,
30
79
  validated: ValidatedApprovePlanParams,
80
+ options?: RunPlanApprovalDialogOptions,
31
81
  ): Promise<PlanApprovalDialogResult> {
32
82
  const planLines = formatPlanPacketLines(validated.plan_packet, 100);
33
83
  const displayOptions = validated.options;
84
+ const useOverlay = validated.displayMode !== "inline";
85
+ let overlayTermHeight = 24;
34
86
 
35
87
  const result = await withTimeout(
36
- ui.custom<CustomAnswer | null>((tui, theme, _kb, done) => {
37
- let scrollOffset = 0;
38
- let optionIndex = 0;
39
- let focus: FocusRegion = "plan";
40
- let cachedLines: string[] | undefined;
41
-
42
- function refresh() {
43
- cachedLines = undefined;
44
- tui.requestRender();
45
- }
46
-
47
- function submitSelection() {
48
- const opt = displayOptions[optionIndex];
49
- done({
50
- response: { kind: "selection", selections: [opt.title] },
51
- });
52
- }
53
-
54
- function handleInput(data: string) {
55
- if (focus === "plan") {
56
- if (matchesKey(data, Key.up) || data === "k") {
57
- scrollOffset = Math.max(0, scrollOffset - 1);
58
- refresh();
59
- return;
60
- }
61
- if (matchesKey(data, Key.down) || data === "j") {
62
- scrollOffset += 1;
63
- refresh();
64
- return;
65
- }
66
- if (matchesKey(data, Key.pageUp)) {
67
- scrollOffset = Math.max(0, scrollOffset - 8);
68
- refresh();
69
- return;
70
- }
71
- if (matchesKey(data, Key.pageDown)) {
72
- scrollOffset += 8;
73
- refresh();
74
- return;
75
- }
76
- if (matchesKey(data, Key.tab)) {
77
- focus = "options";
78
- refresh();
79
- return;
80
- }
88
+ ui.custom<CustomAnswer | null>(
89
+ (tui, theme, _kb, done) => {
90
+ const tuiHeight = (tui as unknown as { height?: number }).height;
91
+ overlayTermHeight =
92
+ typeof tuiHeight === "number" && tuiHeight > 10 ? tuiHeight : 24;
93
+ options?.onMounted?.();
94
+
95
+ let scrollOffset = 0;
96
+ let optionIndex = 0;
97
+ let focus: FocusRegion = "plan";
98
+ let cachedLines: string[] | undefined;
99
+
100
+ function refresh() {
101
+ cachedLines = undefined;
102
+ tui.requestRender();
81
103
  }
82
104
 
83
- if (focus === "options") {
84
- if (matchesKey(data, Key.up)) {
85
- optionIndex = Math.max(0, optionIndex - 1);
86
- refresh();
87
- return;
88
- }
89
- if (matchesKey(data, Key.down)) {
90
- optionIndex = Math.min(displayOptions.length - 1, optionIndex + 1);
91
- refresh();
92
- return;
105
+ function submitSelection() {
106
+ const opt = displayOptions[optionIndex];
107
+ done({
108
+ response: { kind: "selection", selections: [opt.title] },
109
+ });
110
+ }
111
+
112
+ function handleInput(data: string) {
113
+ if (focus === "plan") {
114
+ if (matchesKey(data, Key.up) || data === "k") {
115
+ scrollOffset = Math.max(0, scrollOffset - 1);
116
+ refresh();
117
+ return;
118
+ }
119
+ if (matchesKey(data, Key.down) || data === "j") {
120
+ scrollOffset += 1;
121
+ refresh();
122
+ return;
123
+ }
124
+ if (matchesKey(data, Key.pageUp)) {
125
+ scrollOffset = Math.max(0, scrollOffset - 8);
126
+ refresh();
127
+ return;
128
+ }
129
+ if (matchesKey(data, Key.pageDown)) {
130
+ scrollOffset += 8;
131
+ refresh();
132
+ return;
133
+ }
134
+ if (matchesKey(data, Key.tab)) {
135
+ focus = "options";
136
+ refresh();
137
+ return;
138
+ }
93
139
  }
94
- if (matchesKey(data, Key.tab)) {
95
- focus = "plan";
96
- refresh();
97
- return;
140
+
141
+ if (focus === "options") {
142
+ if (matchesKey(data, Key.up)) {
143
+ optionIndex = Math.max(0, optionIndex - 1);
144
+ refresh();
145
+ return;
146
+ }
147
+ if (matchesKey(data, Key.down)) {
148
+ optionIndex = Math.min(
149
+ displayOptions.length - 1,
150
+ optionIndex + 1,
151
+ );
152
+ refresh();
153
+ return;
154
+ }
155
+ if (matchesKey(data, Key.tab)) {
156
+ focus = "plan";
157
+ refresh();
158
+ return;
159
+ }
160
+ if (matchesKey(data, Key.enter)) {
161
+ submitSelection();
162
+ return;
163
+ }
98
164
  }
99
- if (matchesKey(data, Key.enter)) {
100
- submitSelection();
101
- return;
165
+
166
+ if (matchesKey(data, Key.escape)) {
167
+ done(null);
102
168
  }
103
169
  }
104
170
 
105
- if (matchesKey(data, Key.escape)) {
106
- done(null);
107
- }
108
- }
109
-
110
- function render(width: number): string[] {
111
- if (cachedLines) return cachedLines;
112
-
113
- const lines: string[] = [];
114
- const add = (s: string) => lines.push(truncateToWidth(s, width));
115
- const useOverlay = validated.displayMode !== "inline";
116
- const dims = (tui as { height?: number }).height;
117
- const termHeight = typeof dims === "number" && dims > 10 ? dims : 24;
118
- const footerLines = displayOptions.length * 2 + 8;
119
- const planViewport = Math.max(
120
- 6,
121
- Math.floor(termHeight * 0.55) - footerLines,
122
- );
123
-
124
- if (useOverlay) {
125
- add(theme.fg("accent", "─".repeat(width)));
126
- }
171
+ function render(width: number): string[] {
172
+ if (cachedLines) return cachedLines;
127
173
 
128
- add(theme.fg("accent", " Plan approval"));
129
- if (validated.human_summary) {
130
- for (const line of validated.human_summary.split("\n")) {
131
- add(theme.fg("muted", ` ${line}`));
132
- }
133
- }
134
- lines.push("");
135
-
136
- const maxScroll = Math.max(0, planLines.length - planViewport);
137
- scrollOffset = Math.min(scrollOffset, maxScroll);
138
- const visible = planLines.slice(
139
- scrollOffset,
140
- scrollOffset + planViewport,
141
- );
142
- const planLabel =
143
- focus === "plan"
144
- ? theme.fg("accent", " [plan — ↑↓/Pg scroll, Tab → options]")
145
- : theme.fg("dim", " [plan]");
146
- add(planLabel);
147
- for (const line of visible) {
148
- add(theme.fg("text", ` ${line}`));
149
- }
150
- if (planLines.length > planViewport) {
151
- add(
152
- theme.fg(
153
- "dim",
154
- ` … ${scrollOffset + 1}-${scrollOffset + visible.length} of ${planLines.length}`,
155
- ),
174
+ const lines: string[] = [];
175
+ const add = (s: string) => lines.push(truncateToWidth(s, width));
176
+ const dims = (tui as { height?: number }).height;
177
+ const termHeight = typeof dims === "number" && dims > 10 ? dims : 24;
178
+ const chromeLines = countPlanChromeLines(
179
+ validated,
180
+ displayOptions,
181
+ useOverlay,
156
182
  );
157
- }
158
- lines.push("");
159
-
160
- const optLabel =
161
- focus === "options"
162
- ? theme.fg("accent", " Options (↑↓, Enter, Tab → plan):")
163
- : theme.fg("dim", " Options (Tab to focus):");
164
- add(optLabel);
165
- for (let i = 0; i < displayOptions.length; i++) {
166
- const opt = displayOptions[i];
167
- const focused = focus === "options" && i === optionIndex;
168
- const prefix = focused ? theme.fg("accent", "> ") : " ";
169
- const num = `${i + 1}. `;
170
- if (focused) {
171
- add(prefix + theme.fg("accent", `${num}${opt.title}`));
183
+
184
+ let availableHeight: number;
185
+ if (useOverlay) {
186
+ const overlayMax = computePlanOverlayMaxHeight(termHeight);
187
+ availableHeight = overlayMax;
172
188
  } else {
173
- add(`${prefix}${theme.fg("text", `${num}${opt.title}`)}`);
189
+ availableHeight = termHeight - PLAN_APPROVAL_BOTTOM_RESERVE_LINES;
174
190
  }
175
- if (opt.description) {
176
- add(` ${theme.fg("muted", opt.description)}`);
191
+ const planViewport = computePlanViewport(
192
+ availableHeight,
193
+ chromeLines,
194
+ );
195
+
196
+ if (useOverlay) {
197
+ add(theme.fg("accent", "─".repeat(width)));
177
198
  }
178
- }
179
199
 
180
- lines.push("");
181
- add(theme.fg("dim", " Tab: plan ↔ options • Esc: cancel"));
200
+ add(theme.fg("accent", " Plan approval"));
201
+ if (validated.human_summary) {
202
+ for (const line of validated.human_summary.split("\n")) {
203
+ add(theme.fg("muted", ` ${line}`));
204
+ }
205
+ }
206
+ lines.push("");
182
207
 
183
- if (useOverlay) {
184
- add(theme.fg("accent", "─".repeat(width)));
185
- }
208
+ const maxScroll = Math.max(0, planLines.length - planViewport);
209
+ scrollOffset = Math.min(scrollOffset, maxScroll);
210
+ const visible = planLines.slice(
211
+ scrollOffset,
212
+ scrollOffset + planViewport,
213
+ );
214
+ const planLabel =
215
+ focus === "plan"
216
+ ? theme.fg("accent", " [plan — ↑↓/Pg scroll, Tab → options]")
217
+ : theme.fg("dim", " [plan]");
218
+ add(planLabel);
219
+ for (const line of visible) {
220
+ add(theme.fg("text", ` ${line}`));
221
+ }
222
+ if (planLines.length > planViewport) {
223
+ add(
224
+ theme.fg(
225
+ "dim",
226
+ ` … ${scrollOffset + 1}-${scrollOffset + visible.length} of ${planLines.length}`,
227
+ ),
228
+ );
229
+ }
230
+ lines.push("");
186
231
 
187
- cachedLines = lines;
188
- return lines;
189
- }
232
+ const optLabel =
233
+ focus === "options"
234
+ ? theme.fg("accent", " Options (↑↓, Enter, Tab → plan):")
235
+ : theme.fg("dim", " Options (Tab to focus):");
236
+ add(optLabel);
237
+ for (let i = 0; i < displayOptions.length; i++) {
238
+ const opt = displayOptions[i];
239
+ const focused = focus === "options" && i === optionIndex;
240
+ const prefix = focused ? theme.fg("accent", "> ") : " ";
241
+ const num = `${i + 1}. `;
242
+ if (focused) {
243
+ add(prefix + theme.fg("accent", `${num}${opt.title}`));
244
+ } else {
245
+ add(`${prefix}${theme.fg("text", `${num}${opt.title}`)}`);
246
+ }
247
+ if (opt.description) {
248
+ add(` ${theme.fg("muted", opt.description)}`);
249
+ }
250
+ }
190
251
 
191
- return {
192
- render,
193
- invalidate: () => {
194
- cachedLines = undefined;
195
- },
196
- handleInput,
197
- };
198
- }),
252
+ lines.push("");
253
+ add(theme.fg("dim", " Tab: plan ↔ options • Esc: cancel"));
254
+
255
+ if (useOverlay) {
256
+ add(theme.fg("accent", "─".repeat(width)));
257
+ }
258
+
259
+ cachedLines = lines;
260
+ return lines;
261
+ }
262
+
263
+ return {
264
+ render,
265
+ invalidate: () => {
266
+ cachedLines = undefined;
267
+ },
268
+ handleInput,
269
+ };
270
+ },
271
+ useOverlay
272
+ ? {
273
+ overlay: true,
274
+ overlayOptions: () => ({
275
+ anchor: "bottom-center",
276
+ width: "100%",
277
+ margin: { bottom: PLAN_APPROVAL_BOTTOM_RESERVE_LINES },
278
+ maxHeight: computePlanOverlayMaxHeight(overlayTermHeight),
279
+ }),
280
+ }
281
+ : undefined,
282
+ ),
199
283
  undefined,
200
284
  );
201
285