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
@@ -1,13 +1,11 @@
1
1
  /**
2
- * Registers ask_user and approve_plan in subagent sessions, delegating UI to the parent harness session.
2
+ * Registers ask_user in subagent sessions, delegating UI to the parent harness session.
3
3
  */
4
4
 
5
5
  import type {
6
6
  ExtensionAPI,
7
7
  ExtensionContext,
8
8
  } from "@earendil-works/pi-coding-agent";
9
- import { Text } from "@earendil-works/pi-tui";
10
- import { Type } from "@sinclair/typebox";
11
9
  import type {
12
10
  PlanPacketLike,
13
11
  PlanUserApproval,
@@ -27,32 +25,8 @@ import {
27
25
  toToolDetails,
28
26
  validateAskParams,
29
27
  } from "../ask-user/validate.js";
30
- import {
31
- CREATE_PLAN_GUIDELINES,
32
- CREATE_PLAN_SNIPPET,
33
- executeCreatePlan,
34
- formatCreatePlanResultText,
35
- } from "../plan-approval/create-plan.js";
36
- import { runPlanApprovalDialog } from "../plan-approval/dialog.js";
37
- import { runPlanApprovalFallback } from "../plan-approval/fallback.js";
38
- import {
39
- renderApprovePlanCall,
40
- renderApprovePlanResult,
41
- } from "../plan-approval/render.js";
42
- import {
43
- ApprovePlanParamsSchema,
44
- PROMPT_GUIDELINES as PLAN_PROMPT_GUIDELINES,
45
- PROMPT_SNIPPET as PLAN_PROMPT_SNIPPET,
46
- } from "../plan-approval/schema.js";
47
- import type { ApprovePlanParams } from "../plan-approval/types.js";
48
- import {
49
- formatApprovePlanResultText,
50
- toApprovePlanToolDetails,
51
- validateApprovePlanParams,
52
- } from "../plan-approval/validate.js";
53
28
 
54
29
  const HARNESS_UI_AGENT_TYPES = new Set([
55
- "harness/planner",
56
30
  "harness/evaluator",
57
31
  "harness/adversary",
58
32
  "harness/tie-breaker",
@@ -76,18 +50,6 @@ export interface ParentHarnessUiHooks {
76
50
  ) => void;
77
51
  }
78
52
 
79
- const CreatePlanParamsSchema = Type.Object({
80
- plan_packet: Type.Object(
81
- {},
82
- {
83
- description:
84
- "Approved PlanPacket to persist (same object as approve_plan).",
85
- },
86
- ),
87
- });
88
-
89
- const PLANNER_ONLY_AGENT = "harness/planner";
90
-
91
53
  export function agentTypeAllowsParentHarnessUi(agentType: string): boolean {
92
54
  return HARNESS_UI_AGENT_TYPES.has(agentType);
93
55
  }
@@ -120,7 +82,7 @@ export function createParentHarnessUiBridgeFactory(
120
82
  name: "ask_user",
121
83
  label: "Ask User",
122
84
  description:
123
- "Ask the user a structured question (parent session UI). Use for clarification not final plan approval (use approve_plan).",
85
+ "Ask the user a structured question (parent session UI). Plan approval uses approve_plan on the parent orchestrator only.",
124
86
  promptSnippet: ASK_PROMPT_SNIPPET,
125
87
  promptGuidelines: ASK_PROMPT_GUIDELINES,
126
88
  parameters: AskUserParamsSchema,
@@ -162,137 +124,6 @@ export function createParentHarnessUiBridgeFactory(
162
124
  return renderAskResult(result, options, theme);
163
125
  },
164
126
  });
165
-
166
- if (agentType !== PLANNER_ONLY_AGENT) {
167
- return;
168
- }
169
-
170
- pi.registerTool({
171
- name: "approve_plan",
172
- label: "Approve Plan",
173
- description:
174
- "Present the full PlanPacket for user approval in the parent TUI (scrollable overlay).",
175
- promptSnippet: PLAN_PROMPT_SNIPPET,
176
- promptGuidelines: PLAN_PROMPT_GUIDELINES,
177
- parameters: ApprovePlanParamsSchema,
178
- async execute(_toolCallId, params, _signal, _onUpdate) {
179
- const validated = validateApprovePlanParams(
180
- params as ApprovePlanParams,
181
- );
182
- if (typeof validated === "string") {
183
- return {
184
- content: [{ type: "text", text: validated }],
185
- details: {
186
- plan_packet: (params as ApprovePlanParams).plan_packet ?? {},
187
- options: [],
188
- response: null,
189
- cancelled: true,
190
- },
191
- };
192
- }
193
-
194
- hooks?.appendPlanDraft?.({
195
- plan_packet: validated.plan_packet,
196
- human_summary: validated.human_summary,
197
- });
198
-
199
- let outcome: DialogResult;
200
- if (parentCtx.hasUI) {
201
- outcome = await runPlanApprovalDialog(parentCtx.ui, validated);
202
- } else {
203
- outcome = await runPlanApprovalFallback(parentCtx.ui, validated);
204
- }
205
- const details = toApprovePlanToolDetails(
206
- validated,
207
- outcome.response,
208
- outcome.cancelled,
209
- );
210
- notifyPlanApproval(hooks, details, "approve_plan");
211
- const text = formatApprovePlanResultText(
212
- outcome.response,
213
- outcome.cancelled,
214
- );
215
- return {
216
- content: [{ type: "text", text }],
217
- details,
218
- };
219
- },
220
- renderCall(args, theme) {
221
- return renderApprovePlanCall(args, theme);
222
- },
223
- renderResult(result, options, theme) {
224
- return renderApprovePlanResult(result, options, theme);
225
- },
226
- });
227
-
228
- pi.registerTool({
229
- name: "create_plan",
230
- label: "Create Plan",
231
- description:
232
- "Write the approved PlanPacket to the canonical plan-packet.json for this harness run. Requires approve_plan Approve first. Do not use write/edit.",
233
- promptSnippet: CREATE_PLAN_SNIPPET,
234
- promptGuidelines: CREATE_PLAN_GUIDELINES,
235
- parameters: CreatePlanParamsSchema,
236
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
237
- const validated = validateApprovePlanParams(
238
- params as ApprovePlanParams,
239
- );
240
- if (typeof validated === "string") {
241
- return {
242
- content: [{ type: "text", text: validated }],
243
- details: { error: validated },
244
- };
245
- }
246
- const projectRoot = hooks?.projectRoot ?? parentCtx.cwd;
247
- const parentEntries = hooks?.getParentEntries?.() ?? [];
248
- const subEntries = ctx.sessionManager.getEntries();
249
- const result = await executeCreatePlan(validated.plan_packet, {
250
- projectRoot,
251
- getParentEntries: () => parentEntries,
252
- getSubagentEntries: () => subEntries,
253
- getParentRunContext: () => hooks?.getParentRunContext?.() ?? null,
254
- onCommitted: (runCtx, packet, planPath) => {
255
- hooks?.onPlanCommitted?.(runCtx, packet, planPath);
256
- },
257
- });
258
- const text = formatCreatePlanResultText(result);
259
- return {
260
- content: [{ type: "text", text }],
261
- details: result.ok
262
- ? {
263
- plan_path: result.planPath,
264
- plan_id: result.planId,
265
- }
266
- : { error: result.error },
267
- isError: !result.ok,
268
- };
269
- },
270
- renderCall(args, theme) {
271
- const packet = (args as { plan_packet?: PlanPacketLike }).plan_packet;
272
- const id = packet?.plan_id ?? "?";
273
- return new Text(theme.fg("accent", `create_plan: ${id}`), 0, 0);
274
- },
275
- renderResult(result, _options, theme) {
276
- const details = result.details as
277
- | { plan_path?: string; error?: string }
278
- | undefined;
279
- if (details?.error) {
280
- return new Text(
281
- theme.fg("error", details.error ?? "create_plan failed"),
282
- 0,
283
- 0,
284
- );
285
- }
286
- return new Text(
287
- theme.fg(
288
- "success",
289
- `Wrote ${details?.plan_path ?? "plan-packet.json"}`,
290
- ),
291
- 0,
292
- 0,
293
- );
294
- },
295
- });
296
127
  };
297
128
  }
298
129
 
@@ -7,6 +7,7 @@ import {
7
7
  type PlanUserApproval,
8
8
  planPacketSummary,
9
9
  } from "../../../lib/harness-run-context.js";
10
+ import { writePlanReviewMarkdown } from "../plan-approval/plan-review.js";
10
11
  import type { ParentHarnessUiHooks } from "./parent-harness-ui-bridge.js";
11
12
 
12
13
  function persistRunContext(pi: ExtensionAPI, runCtx: HarnessRunContext): void {
@@ -26,6 +27,23 @@ export function createParentHarnessUiHooks(
26
27
  const planId = String(draft.plan_packet.plan_id ?? "plan");
27
28
  const summary =
28
29
  draft.human_summary?.trim() || `Plan ${planId} — pending your approval`;
30
+ const runCtx = getLatestRunContext(getParentEntries());
31
+ void writePlanReviewMarkdown(projectRoot, runCtx, draft.plan_packet, {
32
+ human_summary: draft.human_summary,
33
+ status: "draft",
34
+ }).then((reviewPath) => {
35
+ if (!reviewPath) return;
36
+ pi.sendMessage({
37
+ customType: "harness-plan-review-path",
38
+ content: `Editor review: ${reviewPath}`,
39
+ display: true,
40
+ details: {
41
+ schema_version: "1.0.0",
42
+ plan_review_path: reviewPath,
43
+ plan_id: planId,
44
+ },
45
+ });
46
+ });
29
47
  pi.sendMessage({
30
48
  customType: "harness-plan-draft",
31
49
  content: summary,
@@ -10,7 +10,6 @@ export const SUBAGENT_BLOCKED_TOOLS = new Set([
10
10
  ]);
11
11
 
12
12
  const ASK_USER_ALLOWED_AGENT_TYPES = new Set([
13
- "harness/planner",
14
13
  "harness/evaluator",
15
14
  "harness/adversary",
16
15
  "harness/tie-breaker",
@@ -42,12 +41,9 @@ export function evaluateSubagentToolCall(
42
41
  };
43
42
  }
44
43
  if (toolName === "approve_plan" || toolName === "create_plan") {
45
- if (agentType === "harness/planner") {
46
- return { action: "allow" };
47
- }
48
44
  return {
49
45
  action: "block",
50
- reason: `Tool "${toolName}" is only available for harness/planner.`,
46
+ reason: `Tool "${toolName}" is only available in the parent harness orchestrator session.`,
51
47
  };
52
48
  }
53
49
  return { action: "allow" };
@@ -420,13 +420,6 @@ export async function runAgent(
420
420
  names.filter((t) => {
421
421
  if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
422
422
  if (t === "ask_user" && harnessUiBridge) return true;
423
- if (
424
- (t === "approve_plan" || t === "create_plan") &&
425
- harnessUiBridge &&
426
- type === "harness/planner"
427
- ) {
428
- return true;
429
- }
430
423
  if (disallowedSet?.has(t)) return false;
431
424
  if (builtinToolNameSet.has(t)) return true;
432
425
  if (extensions === false) return false;
@@ -442,13 +435,6 @@ export async function runAgent(
442
435
  } else {
443
436
  const fallback = toolNames.filter((t) => {
444
437
  if (t === "ask_user" && harnessUiBridge) return true;
445
- if (
446
- (t === "approve_plan" || t === "create_plan") &&
447
- harnessUiBridge &&
448
- type === "harness/planner"
449
- ) {
450
- return true;
451
- }
452
438
  return !disallowedSet?.has(t);
453
439
  });
454
440
  session.setActiveToolsByName(fallback);
@@ -470,10 +456,6 @@ export async function runAgent(
470
456
  if (harnessUiBridge) {
471
457
  const withHarnessUi = new Set(session.getActiveToolNames());
472
458
  withHarnessUi.add("ask_user");
473
- if (type === "harness/planner") {
474
- withHarnessUi.add("approve_plan");
475
- withHarnessUi.add("create_plan");
476
- }
477
459
  session.setActiveToolsByName([...withHarnessUi]);
478
460
  }
479
461
 
@@ -17,10 +17,7 @@ import {
17
17
  } from "@earendil-works/pi-coding-agent";
18
18
  import { Text } from "@earendil-works/pi-tui";
19
19
  import { Type } from "@sinclair/typebox";
20
- import {
21
- getLatestRunContext,
22
- syncPlannerApprovalsToParent,
23
- } from "../../../../lib/harness-run-context.js";
20
+ import { getLatestRunContext } from "../../../../lib/harness-run-context.js";
24
21
  import { getDriftReport } from "../agent-manifest.js";
25
22
  import { Blackboard } from "../blackboard.js";
26
23
  import {
@@ -759,7 +756,9 @@ export function createHarnessSubagentsExtension(packageRoot: string) {
759
756
  });
760
757
 
761
758
  // Live widget: show running agents above editor
762
- const widget = new AgentWidget(manager, agentActivity);
759
+ const widget = new AgentWidget(manager, agentActivity, () => {
760
+ pi.events.emit("subagents:agents-widget-mounted", {});
761
+ });
763
762
 
764
763
  // ---- Join mode configuration ----
765
764
  let defaultJoinMode: JoinMode = "smart";
@@ -1501,24 +1500,6 @@ Guidelines:
1501
1500
 
1502
1501
  clearInterval(spinnerInterval);
1503
1502
 
1504
- if (
1505
- subagentType === "harness/planner" &&
1506
- record.session &&
1507
- record.status !== "running" &&
1508
- record.status !== "queued"
1509
- ) {
1510
- const parentEntries = ctx.sessionManager.getEntries();
1511
- const runCtx = getLatestRunContext(parentEntries);
1512
- if (runCtx) {
1513
- syncPlannerApprovalsToParent(
1514
- (type, data) => pi.appendEntry(type, data),
1515
- parentEntries,
1516
- record.session.sessionManager.getEntries(),
1517
- runCtx,
1518
- );
1519
- }
1520
- }
1521
-
1522
1503
  // Clean up foreground agent from widget
1523
1504
  if (fgId) {
1524
1505
  agentActivity.delete(fgId);
@@ -1631,19 +1612,6 @@ Guidelines:
1631
1612
  cancelNudge(params.agent_id);
1632
1613
  }
1633
1614
 
1634
- if (record.session && record.status !== "running") {
1635
- const parentEntries = _ctx.sessionManager.getEntries();
1636
- const runCtx = getLatestRunContext(parentEntries);
1637
- if (runCtx) {
1638
- syncPlannerApprovalsToParent(
1639
- (type, data) => pi.appendEntry(type, data),
1640
- parentEntries,
1641
- record.session.sessionManager.getEntries(),
1642
- runCtx,
1643
- );
1644
- }
1645
- }
1646
-
1647
1615
  // Verbose: include full conversation
1648
1616
  if (params.verbose && record.session) {
1649
1617
  const conversation = getAgentConversation(record.session);
@@ -264,6 +264,7 @@ export class AgentWidget {
264
264
  constructor(
265
265
  private manager: AgentManager,
266
266
  private agentActivity: Map<string, AgentActivity>,
267
+ private onWidgetRegistered?: () => void,
267
268
  ) {}
268
269
 
269
270
  /** Set the UI context (grabbed from first tool execution). */
@@ -615,6 +616,7 @@ export class AgentWidget {
615
616
  { placement: "aboveEditor" },
616
617
  );
617
618
  this.widgetRegistered = true;
619
+ this.onWidgetRegistered?.();
618
620
  } else {
619
621
  // Widget already registered — just request a re-render of existing components.
620
622
  this.tui?.requestRender();
@@ -9,6 +9,7 @@ import {
9
9
  saveRunContextToDisk,
10
10
  validatePlanPacket,
11
11
  } from "../../../lib/harness-run-context.js";
12
+ import { writePlanReviewMarkdown } from "./plan-review.js";
12
13
 
13
14
  export const CREATE_PLAN_SNIPPET =
14
15
  "create_plan({ plan_packet: { ...approved PlanPacket } })";
@@ -116,6 +117,10 @@ export async function executeCreatePlan(
116
117
  /* disk mirror best-effort */
117
118
  }
118
119
 
120
+ await writePlanReviewMarkdown(deps.projectRoot, updated, planPacket, {
121
+ status: "committed",
122
+ });
123
+
119
124
  deps.onCommitted(updated, planPacket, planPath);
120
125
 
121
126
  return {