ultimate-pi 0.7.0 → 0.8.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 (111) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +20 -1
  2. package/.agents/skills/harness-eval/SKILL.md +11 -13
  3. package/.agents/skills/harness-orchestration/SKILL.md +36 -30
  4. package/.agents/skills/harness-plan/SKILL.md +13 -18
  5. package/.pi/PACKAGING.md +1 -1
  6. package/.pi/agents/harness/adversary.md +20 -12
  7. package/.pi/agents/harness/evaluator.md +25 -14
  8. package/.pi/agents/harness/executor.md +27 -16
  9. package/.pi/agents/harness/incident-recorder.md +37 -0
  10. package/.pi/agents/harness/meta-optimizer.md +18 -15
  11. package/.pi/agents/harness/planner.md +27 -30
  12. package/.pi/agents/harness/tie-breaker.md +4 -2
  13. package/.pi/agents/harness/trace-librarian.md +18 -11
  14. package/.pi/agents/pi-pi/ext-expert.md +1 -1
  15. package/.pi/agents/pi-pi/keybinding-expert.md +1 -1
  16. package/.pi/agents/pi-pi/tui-expert.md +3 -3
  17. package/.pi/extensions/00-ultimate-pi-system-prompt.ts +2 -2
  18. package/.pi/extensions/budget-guard.ts +1 -1
  19. package/.pi/extensions/custom-footer.ts +8 -3
  20. package/.pi/extensions/custom-header.ts +2 -2
  21. package/.pi/extensions/debate-orchestrator.ts +1 -1
  22. package/.pi/extensions/dotenv-loader.ts +1 -1
  23. package/.pi/extensions/drift-monitor.ts +1 -1
  24. package/.pi/extensions/harness-ask-user.ts +1 -1
  25. package/.pi/extensions/harness-live-widget.ts +1 -1
  26. package/.pi/extensions/harness-run-context.ts +52 -10
  27. package/.pi/extensions/harness-telemetry.ts +1 -1
  28. package/.pi/extensions/harness-web-guard.ts +1 -1
  29. package/.pi/extensions/harness-web-tools.ts +1 -1
  30. package/.pi/extensions/lib/ask-user/dialog.ts +2 -2
  31. package/.pi/extensions/lib/ask-user/fallback.ts +1 -1
  32. package/.pi/extensions/lib/ask-user/render.ts +3 -3
  33. package/.pi/extensions/lib/harness-subagents/agent-loader.ts +1 -1
  34. package/.pi/extensions/lib/harness-subagents/agent-parser.ts +1 -1
  35. package/.pi/extensions/lib/harness-subagents/blackboard-tool.ts +1 -1
  36. package/.pi/extensions/lib/harness-subagents/harness-subagent-policy.ts +134 -0
  37. package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +2 -2
  38. package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +9 -5
  39. package/.pi/extensions/lib/harness-subagents/vendored/context.ts +1 -1
  40. package/.pi/extensions/lib/harness-subagents/vendored/env.ts +1 -1
  41. package/.pi/extensions/lib/harness-subagents/vendored/index.ts +2 -2
  42. package/.pi/extensions/lib/harness-subagents/vendored/output-file.ts +1 -1
  43. package/.pi/extensions/lib/harness-subagents/vendored/schedule.ts +1 -1
  44. package/.pi/extensions/lib/harness-subagents/vendored/settings.ts +1 -1
  45. package/.pi/extensions/lib/harness-subagents/vendored/skill-loader.ts +1 -1
  46. package/.pi/extensions/lib/harness-subagents/vendored/types.ts +2 -2
  47. package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +1 -1
  48. package/.pi/extensions/lib/harness-subagents/vendored/ui/conversation-viewer.ts +2 -2
  49. package/.pi/extensions/lib/harness-subagents/vendored/ui/schedule-menu.ts +1 -1
  50. package/.pi/extensions/observation-bus.ts +1 -1
  51. package/.pi/extensions/pi-model-router-harness.ts +1 -1
  52. package/.pi/extensions/policy-gate.ts +86 -16
  53. package/.pi/extensions/provider-payload-sanitize.ts +1 -1
  54. package/.pi/extensions/review-integrity.ts +76 -22
  55. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  56. package/.pi/extensions/soundboard.ts +1 -1
  57. package/.pi/extensions/test-diff-integrity.ts +1 -1
  58. package/.pi/extensions/trace-recorder.ts +1 -1
  59. package/.pi/extensions/ultimate-pi-vcc.ts +1 -1
  60. package/.pi/harness/agents.manifest.json +16 -12
  61. package/.pi/harness/docs/adrs/0031-harness-run-context.md +5 -2
  62. package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +37 -0
  63. package/.pi/harness/docs/adrs/README.md +1 -0
  64. package/.pi/harness/specs/harness-spawn-context.schema.json +65 -0
  65. package/.pi/lib/harness-agent-output.ts +41 -0
  66. package/.pi/lib/harness-run-context.ts +352 -7
  67. package/.pi/lib/harness-ui-state.ts +1 -1
  68. package/.pi/prompts/harness-auto.md +36 -61
  69. package/.pi/prompts/harness-critic.md +15 -28
  70. package/.pi/prompts/harness-eval.md +19 -27
  71. package/.pi/prompts/harness-incident.md +15 -34
  72. package/.pi/prompts/harness-plan.md +31 -50
  73. package/.pi/prompts/harness-review.md +16 -30
  74. package/.pi/prompts/harness-router-tune.md +16 -38
  75. package/.pi/prompts/harness-run.md +21 -38
  76. package/.pi/prompts/harness-setup.md +2 -0
  77. package/.pi/prompts/harness-trace.md +13 -30
  78. package/.pi/scripts/harness-generate-model-router.mjs +16 -13
  79. package/.pi/scripts/harness-verify.mjs +16 -0
  80. package/.pi/scripts/vendor-sync-pi-model-router.sh +10 -10
  81. package/CHANGELOG.md +19 -1
  82. package/README.md +4 -5
  83. package/THIRD_PARTY_NOTICES.md +1 -1
  84. package/package.json +13 -8
  85. package/vendor/pi-model-router/UPSTREAM_PIN.md +1 -1
  86. package/vendor/pi-model-router/extensions/commands.ts +2 -2
  87. package/vendor/pi-model-router/extensions/config.ts +2 -2
  88. package/vendor/pi-model-router/extensions/index.ts +1 -1
  89. package/vendor/pi-model-router/extensions/provider.ts +2 -2
  90. package/vendor/pi-model-router/extensions/routing.ts +2 -2
  91. package/vendor/pi-model-router/extensions/types.ts +1 -1
  92. package/vendor/pi-model-router/extensions/ui.ts +1 -1
  93. package/vendor/pi-model-router/package.json +4 -4
  94. package/vendor/pi-vcc/index.ts +1 -1
  95. package/vendor/pi-vcc/package.json +1 -1
  96. package/vendor/pi-vcc/src/commands/pi-vcc.ts +1 -1
  97. package/vendor/pi-vcc/src/commands/vcc-recall.ts +1 -1
  98. package/vendor/pi-vcc/src/core/content.ts +1 -1
  99. package/vendor/pi-vcc/src/core/load-messages.ts +1 -1
  100. package/vendor/pi-vcc/src/core/normalize.ts +1 -1
  101. package/vendor/pi-vcc/src/core/render-entries.ts +1 -1
  102. package/vendor/pi-vcc/src/core/report.ts +1 -1
  103. package/vendor/pi-vcc/src/core/search-entries.ts +1 -1
  104. package/vendor/pi-vcc/src/core/summarize.ts +1 -1
  105. package/vendor/pi-vcc/src/hooks/before-compact.ts +2 -2
  106. package/vendor/pi-vcc/src/tools/recall.ts +1 -1
  107. package/vendor/pi-vcc/src/types.ts +1 -1
  108. package/vendor/pi-vcc/tests/fixtures.ts +1 -1
  109. package/vendor/pi-vcc/tests/render-entries.test.ts +1 -1
  110. package/vendor/pi-vcc/tests/search-entries.test.ts +1 -1
  111. package/vendor/pi-vcc/tests/support/load-session.ts +2 -2
@@ -19,7 +19,7 @@ You are an extensions expert for the Pi coding agent. You know EVERYTHING about
19
19
  - Flags via `pi.registerFlag()`
20
20
  - State management via tool result details and `pi.appendEntry()`
21
21
  - Custom rendering via renderCall/renderResult
22
- - Available imports: `@mariozechner/pi-coding-agent`, `@sinclair/typebox`, `@mariozechner/pi-ai` (StringEnum), `@mariozechner/pi-tui`
22
+ - Available imports: `@earendil-works/pi-coding-agent`, `@sinclair/typebox`, `@earendil-works/pi-ai` (StringEnum), `@earendil-works/pi-tui`
23
23
  - System prompt override via before_agent_start
24
24
  - Context manipulation via context event
25
25
  - Tool blocking and result modification
@@ -87,7 +87,7 @@ This is CRITICAL for building extensions that work on macOS:
87
87
  - When a reserved action is remapped away from a key, that key becomes available for extensions
88
88
  - The conflict check uses EFFECTIVE keybindings (after user remaps), not defaults
89
89
 
90
- ### Key Helper (from @mariozechner/pi-tui)
90
+ ### Key Helper (from @earendil-works/pi-tui)
91
91
 
92
92
  - `Key.ctrl("x")` → `"ctrl+x"`
93
93
  - `Key.shift("tab")` → `"shift+tab"`
@@ -19,7 +19,7 @@ You are a TUI (Terminal User Interface) expert for the Pi coding agent. You know
19
19
  - wantsKeyRelease? — for Kitty protocol key release events
20
20
  - invalidate() — clear cached render state
21
21
 
22
- ### Built-in Components (from @mariozechner/pi-tui)
22
+ ### Built-in Components (from @earendil-works/pi-tui)
23
23
 
24
24
  - Text: multi-line text with word wrapping, paddingX, paddingY, background function
25
25
  - Box: container with padding and background color
@@ -30,7 +30,7 @@ You are a TUI (Terminal User Interface) expert for the Pi coding agent. You know
30
30
  - SelectList: selection dialog with theme, onSelect/onCancel
31
31
  - SettingsList: toggle settings with theme
32
32
 
33
- ### From @mariozechner/pi-coding-agent
33
+ ### From @earendil-works/pi-coding-agent
34
34
 
35
35
  - DynamicBorder: border with color function — ALWAYS type the param: `(s: string) => theme.fg("accent", s)`
36
36
  - BorderedLoader: spinner with abort support
@@ -93,7 +93,7 @@ Then read the fetched file. Also search the local codebase for existing TUI comp
93
93
  ## How to Respond
94
94
 
95
95
  - Provide COMPLETE, WORKING component code
96
- - Include all imports from @mariozechner/pi-tui and @mariozechner/pi-coding-agent
96
+ - Include all imports from @earendil-works/pi-tui and @earendil-works/pi-coding-agent
97
97
  - Show the ctx.ui.custom() wrapper for interactive components
98
98
  - Handle invalidation properly for theme changes
99
99
  - Include keyboard input handling where relevant
@@ -13,8 +13,8 @@ import { join } from "node:path";
13
13
  import type {
14
14
  BuildSystemPromptOptions,
15
15
  ExtensionAPI,
16
- } from "@mariozechner/pi-coding-agent";
17
- import { formatSkillsForPrompt } from "@mariozechner/pi-coding-agent";
16
+ } from "@earendil-works/pi-coding-agent";
17
+ import { formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
18
18
  import { resolveHarnessAsset } from "./lib/harness-paths.js";
19
19
 
20
20
  // @ts-expect-error pi extensions run as ESM
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { appendFile, mkdir, readFile } from "node:fs/promises";
9
9
  import { join } from "node:path";
10
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
11
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
12
12
 
13
13
  type HarnessPhase = "plan" | "execute" | "evaluate" | "adversary" | "merge";
@@ -9,9 +9,14 @@ import type {
9
9
  ExtensionAPI,
10
10
  ExtensionContext,
11
11
  ThemeColor,
12
- } from "@mariozechner/pi-coding-agent";
13
- import type { TUI } from "@mariozechner/pi-tui";
14
- import { Box, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
12
+ } from "@earendil-works/pi-coding-agent";
13
+ import type { TUI } from "@earendil-works/pi-tui";
14
+ import {
15
+ Box,
16
+ Text,
17
+ truncateToWidth,
18
+ visibleWidth,
19
+ } from "@earendil-works/pi-tui";
15
20
 
16
21
  // ── router decision reader ──────────────────────────────────────────
17
22
 
@@ -6,8 +6,8 @@
6
6
  * doubling vertical resolution in the same terminal footprint.
7
7
  */
8
8
 
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
- import { truncateToWidth } from "@mariozechner/pi-tui";
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
+ import { truncateToWidth } from "@earendil-works/pi-tui";
11
11
  import * as JimpModule from "jimp";
12
12
  import { resolveHarnessAsset } from "./lib/harness-paths.js";
13
13
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
18
18
  import { join } from "node:path";
19
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
19
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
20
20
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
21
21
 
22
22
  type DebateParticipant =
@@ -13,7 +13,7 @@
13
13
 
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  import { resolve } from "node:path";
16
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
17
17
 
18
18
  // ── .env parser ──────────────────────────────────────────────────
19
19
 
@@ -4,7 +4,7 @@
4
4
  * Emits harness-drift-report custom entries for harness-telemetry + observation bus.
5
5
  */
6
6
 
7
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
7
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
8
8
 
9
9
  type HarnessPhase = "plan" | "execute" | "evaluate" | "adversary" | "merge";
10
10
 
@@ -3,7 +3,7 @@
3
3
  * Design references: pi-ask-user, @pi-unipi/ask-user, rpiv-ask-user-question (not vendored).
4
4
  */
5
5
 
6
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
6
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
7
  import { runAskDialog } from "./lib/ask-user/dialog.js";
8
8
  import { runAskFallback } from "./lib/ask-user/fallback.js";
9
9
  import { renderAskCall, renderAskResult } from "./lib/ask-user/render.js";
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
- } from "@mariozechner/pi-coding-agent";
4
+ } from "@earendil-works/pi-coding-agent";
5
5
  import {
6
6
  type HarnessUiState,
7
7
  HarnessUiStateStore,
@@ -5,7 +5,7 @@
5
5
  * in before_agent_start so trace-recorder reuses it on agent_start.
6
6
  */
7
7
 
8
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import {
10
10
  canonicalPlanPath,
11
11
  createFreshRunContext,
@@ -18,6 +18,7 @@ import {
18
18
  getPolicyTransitionBlock,
19
19
  type HarnessRunContext,
20
20
  hasHarnessAbortSignal,
21
+ hasPlanUserApproval,
21
22
  isAmendPlanAllowed,
22
23
  isHarnessBootstrapPrompt,
23
24
  isHarnessSlashCommand,
@@ -26,7 +27,9 @@ import {
26
27
  loadProjectActiveRun,
27
28
  loadRunContextFromDisk,
28
29
  nextStepAfterOutcome,
30
+ nowIso,
29
31
  type PlanPacketSummary,
32
+ parseAskUserApprovalFromMessage,
30
33
  parseHarnessSlashCommand,
31
34
  planPacketSummary,
32
35
  readPlanPacketFromPath,
@@ -70,16 +73,17 @@ function syncPolicyFromPlan(
70
73
  entries: unknown[],
71
74
  planId: string,
72
75
  phase: HarnessRunContext["phase"],
76
+ approvedPlan: boolean,
73
77
  ): void {
74
78
  let prior: Record<string, unknown> = {
75
79
  phase,
76
- approvedPlan: true,
80
+ approvedPlan,
77
81
  planId,
78
82
  budgetBypass: false,
79
83
  aborted: false,
80
84
  abortReason: null,
81
85
  abortedAt: null,
82
- updatedAt: new Date().toISOString(),
86
+ updatedAt: nowIso(),
83
87
  };
84
88
  for (let i = entries.length - 1; i >= 0; i--) {
85
89
  const entry = entries[i] as SessionEntryLike;
@@ -204,6 +208,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
204
208
  entries,
205
209
  activeCtx.plan_id ?? "plan-pending",
206
210
  "plan",
211
+ false,
207
212
  );
208
213
  persistContext(pi, activeCtx);
209
214
  return {
@@ -321,15 +326,18 @@ export default function harnessRunContext(pi: ExtensionAPI) {
321
326
  const task = extractTaskSummary(userPrompt);
322
327
  activeCtx = createFreshRunContext(sessionId, projectRoot, task);
323
328
  }
324
- if (activeCtx.status === "aborted") {
325
- activeCtx.plan_ready = false;
326
- }
329
+ activeCtx.plan_ready = false;
327
330
  activeCtx.phase = "plan";
328
331
  activeCtx.status = "active";
329
332
  if (command === "harness-plan") {
330
333
  const task = extractTaskSummary(userPrompt);
331
334
  if (task) activeCtx.task_summary = task;
332
335
  }
336
+ pi.appendEntry("harness-plan-attempt", {
337
+ run_id: activeCtx.run_id,
338
+ command,
339
+ started_at: nowIso(),
340
+ });
333
341
  } else if (
334
342
  activeCtx &&
335
343
  shouldReuseHarnessRunId(userPrompt, activeCtx, command)
@@ -519,15 +527,31 @@ export default function harnessRunContext(pi: ExtensionAPI) {
519
527
  ) {
520
528
  const packet = await readPlanPacketFromPath(activeCtx.plan_packet_path);
521
529
  const validation = validatePlanPacket(packet);
522
- planReady = validation.valid;
523
- if (planReady && packet?.plan_id) {
530
+ const approved = hasPlanUserApproval(entries, {
531
+ sincePlanCommand: true,
532
+ planId: packet?.plan_id ?? null,
533
+ });
534
+ planReady = validation.valid && approved;
535
+ if (validation.valid && !approved) {
536
+ activeCtx.last_outcome = "needs_clarification";
537
+ activeCtx.last_completed_step = "plan";
538
+ const msg =
539
+ "Plan file exists but user approval was not recorded. Present the full plan and call ask_user (Approve) before writing plan-packet.json.";
540
+ if (ctx.hasUI) ctx.ui.notify(msg, "warning");
541
+ else
542
+ pi.sendMessage({
543
+ customType: "harness-plan-packet",
544
+ content: msg,
545
+ display: true,
546
+ });
547
+ } else if (planReady && packet?.plan_id) {
524
548
  activeCtx.plan_id = packet.plan_id;
525
- syncPolicyFromPlan(pi, entries, packet.plan_id, "plan");
549
+ syncPolicyFromPlan(pi, entries, packet.plan_id, "plan", true);
526
550
  const summary = planPacketSummary(packet, activeCtx.plan_packet_path);
527
551
  pi.appendEntry("harness-plan-packet", summary);
528
552
  activeCtx.last_completed_step = "plan";
529
553
  activeCtx.last_outcome = summary.plan_status;
530
- } else {
554
+ } else if (!validation.valid) {
531
555
  activeCtx.last_outcome = "needs_clarification";
532
556
  activeCtx.last_completed_step = "plan";
533
557
  }
@@ -578,6 +602,24 @@ export default function harnessRunContext(pi: ExtensionAPI) {
578
602
  }
579
603
  });
580
604
 
605
+ pi.on("tool_result", async (event, ctx) => {
606
+ if (event.toolName !== "ask_user" || event.isError) return;
607
+ const approval = parseAskUserApprovalFromMessage({
608
+ toolName: "ask_user",
609
+ details: event.details,
610
+ content: event.content,
611
+ });
612
+ if (!approval) return;
613
+ const entries = getEntries(ctx);
614
+ const runCtx = getLatestRunContext(entries) ?? activeCtx;
615
+ if (!runCtx) return;
616
+ pi.appendEntry("harness-plan-approval", {
617
+ plan_id: approval.plan_id ?? runCtx.plan_id,
618
+ approved_at: approval.approved_at,
619
+ source: "ask_user",
620
+ });
621
+ });
622
+
581
623
  pi.on("tool_call", async (event) => {
582
624
  if (!activeCtx?.plan_packet_path) return undefined;
583
625
  const phase = activeCtx.phase;
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createHash } from "node:crypto";
11
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
11
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
12
12
  import {
13
13
  captureHarnessEvent,
14
14
  type HarnessPostHogEventName,
@@ -2,7 +2,7 @@
2
2
  * harness-web-guard — block bash that bypasses web_search / web_fetch tools.
3
3
  */
4
4
 
5
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
 
7
7
  const BLOCK_REASON =
8
8
  "harness-web-guard: use web_search (SERP) or web_fetch (page content) instead of raw curl/wget/firecrawl/scrapling fetch. " +
@@ -2,7 +2,7 @@
2
2
  * harness-web-tools — web_search + web_fetch pi tools wrapping harness-web.py.
3
3
  */
4
4
 
5
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { Type } from "@sinclair/typebox";
7
7
  import {
8
8
  harnessWebContextLine,
@@ -1,11 +1,11 @@
1
- import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
3
  Editor,
4
4
  type EditorTheme,
5
5
  Key,
6
6
  matchesKey,
7
7
  truncateToWidth,
8
- } from "@mariozechner/pi-tui";
8
+ } from "@earendil-works/pi-tui";
9
9
  import type { AskResponse, DialogResult, ValidatedAskParams } from "./types.js";
10
10
 
11
11
  type DisplayOption = {
@@ -1,4 +1,4 @@
1
- import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { DialogResult, ValidatedAskParams } from "./types.js";
3
3
 
4
4
  export async function runAskFallback(
@@ -1,9 +1,9 @@
1
- import type { AgentToolResult } from "@mariozechner/pi-agent-core";
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
2
  import type {
3
3
  Theme,
4
4
  ToolRenderResultOptions,
5
- } from "@mariozechner/pi-coding-agent";
6
- import { Text } from "@mariozechner/pi-tui";
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { Text } from "@earendil-works/pi-tui";
7
7
  import type { AskToolDetails } from "./types.js";
8
8
 
9
9
  export function renderAskCall(
@@ -5,7 +5,7 @@
5
5
  import { createHash } from "node:crypto";
6
6
  import { type Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
7
7
  import { join, relative } from "node:path";
8
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
8
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
9
9
  import { parseAgentMarkdown } from "./agent-parser.js";
10
10
  import type { AgentConfig } from "./vendored/types.js";
11
11
 
@@ -2,7 +2,7 @@
2
2
  * Parse harness agent .md files into AgentConfig (path id = posix relative path).
3
3
  */
4
4
 
5
- import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
5
+ import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
6
6
  import { BUILTIN_TOOL_NAMES } from "./vendored/agent-types.js";
7
7
  import type {
8
8
  AgentConfig,
@@ -2,7 +2,7 @@
2
2
  * Orchestrator blackboard tool (list/read/query/wait/delete).
3
3
  */
4
4
 
5
- import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { Type } from "@sinclair/typebox";
7
7
  import type { Blackboard } from "./blackboard.js";
8
8
  import type { BlackboardQuery } from "./types-blackboard.js";
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Per-agent tool policy for harness/* subagents (defense in depth with frontmatter).
3
+ */
4
+
5
+ import {
6
+ evaluateSubagentToolCall,
7
+ type ToolCallDecision,
8
+ } from "./spawn-policy.js";
9
+
10
+ export type HarnessAgentKind =
11
+ | "planner"
12
+ | "executor"
13
+ | "evaluator"
14
+ | "adversary"
15
+ | "tie_breaker"
16
+ | "meta"
17
+ | "trace"
18
+ | "incident"
19
+ | "other";
20
+
21
+ const MUTATING_TOOLS = new Set(["write", "edit"]);
22
+
23
+ const BASH_MUTATION_PATTERNS = [
24
+ /\brm\s+-/i,
25
+ /\bmv\s+/i,
26
+ /\bcp\s+/i,
27
+ /\btouch\s+/i,
28
+ /\bmkdir\s+/i,
29
+ /\btee\s+/i,
30
+ /\bgit\s+(add|commit|push|reset|checkout|merge|rebase|cherry-pick|apply)\b/i,
31
+ /\bnpm\s+(install|uninstall|ci)\b/i,
32
+ /\bpnpm\s+(add|install|remove)\b/i,
33
+ /\byarn\s+(add|install|remove)\b/i,
34
+ /\bsed\s+-i\b/i,
35
+ /\bperl\s+-i\b/i,
36
+ ];
37
+
38
+ const READ_ONLY_KINDS = new Set<HarnessAgentKind>([
39
+ "planner",
40
+ "evaluator",
41
+ "adversary",
42
+ "tie_breaker",
43
+ "trace",
44
+ "incident",
45
+ "meta",
46
+ ]);
47
+
48
+ export function classifyHarnessAgent(agentType: string): HarnessAgentKind {
49
+ const id = agentType.replace(/^harness\//, "");
50
+ switch (id) {
51
+ case "planner":
52
+ return "planner";
53
+ case "executor":
54
+ return "executor";
55
+ case "evaluator":
56
+ return "evaluator";
57
+ case "adversary":
58
+ return "adversary";
59
+ case "tie-breaker":
60
+ return "tie_breaker";
61
+ case "meta-optimizer":
62
+ return "meta";
63
+ case "trace-librarian":
64
+ return "trace";
65
+ case "incident-recorder":
66
+ return "incident";
67
+ default:
68
+ return agentType.startsWith("harness/") ? "other" : "other";
69
+ }
70
+ }
71
+
72
+ function isMutatingBash(command: string): boolean {
73
+ return BASH_MUTATION_PATTERNS.some((pattern) => pattern.test(command));
74
+ }
75
+
76
+ export function isHarnessPackageAgent(agentType: string): boolean {
77
+ return agentType.startsWith("harness/");
78
+ }
79
+
80
+ export function evaluateHarnessSubagentToolCall(
81
+ toolName: string,
82
+ input: Record<string, unknown> | undefined,
83
+ agentType: string,
84
+ ): ToolCallDecision {
85
+ const base = evaluateSubagentToolCall(toolName);
86
+ if (base.action === "block") {
87
+ return base;
88
+ }
89
+
90
+ if (!isHarnessPackageAgent(agentType)) {
91
+ return { action: "allow" };
92
+ }
93
+
94
+ const kind = classifyHarnessAgent(agentType);
95
+ if (!READ_ONLY_KINDS.has(kind)) {
96
+ return { action: "allow" };
97
+ }
98
+
99
+ if (MUTATING_TOOLS.has(toolName)) {
100
+ return {
101
+ action: "block",
102
+ reason: `harness-subagent-policy: ${toolName} blocked for harness/${kind} (read-only phase agent).`,
103
+ };
104
+ }
105
+
106
+ if (toolName === "bash") {
107
+ const command = String(input?.command ?? "");
108
+ if (command && isMutatingBash(command)) {
109
+ return {
110
+ action: "block",
111
+ reason: `harness-subagent-policy: mutating bash blocked for harness/${kind}.`,
112
+ };
113
+ }
114
+ }
115
+
116
+ return { action: "allow" };
117
+ }
118
+
119
+ /** Policy phase hint seeded into subagent system prompt appendix when extensions load policy-gate. */
120
+ export function harnessSubagentPhaseHint(agentType: string): string | null {
121
+ const kind = classifyHarnessAgent(agentType);
122
+ switch (kind) {
123
+ case "planner":
124
+ return "plan";
125
+ case "executor":
126
+ return "execute";
127
+ case "evaluator":
128
+ return "evaluate";
129
+ case "adversary":
130
+ return "adversary";
131
+ default:
132
+ return null;
133
+ }
134
+ }
@@ -7,12 +7,12 @@
7
7
  */
8
8
 
9
9
  import { randomUUID } from "node:crypto";
10
- import type { Model } from "@mariozechner/pi-ai";
10
+ import type { Model } from "@earendil-works/pi-ai";
11
11
  import type {
12
12
  AgentSession,
13
13
  ExtensionAPI,
14
14
  ExtensionContext,
15
- } from "@mariozechner/pi-coding-agent";
15
+ } from "@earendil-works/pi-coding-agent";
16
16
  import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
17
17
  import type {
18
18
  AgentInvocation,
@@ -5,8 +5,8 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
- import type { Model } from "@mariozechner/pi-ai";
9
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
8
+ import type { Model } from "@earendil-works/pi-ai";
9
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
10
10
  import {
11
11
  type AgentSession,
12
12
  type AgentSessionEvent,
@@ -16,8 +16,8 @@ import {
16
16
  getAgentDir,
17
17
  SessionManager,
18
18
  SettingsManager,
19
- } from "@mariozechner/pi-coding-agent";
20
- import { evaluateSubagentToolCall } from "../spawn-policy.js";
19
+ } from "@earendil-works/pi-coding-agent";
20
+ import { evaluateHarnessSubagentToolCall } from "../harness-subagent-policy.js";
21
21
  import {
22
22
  getAgentConfig,
23
23
  getConfig,
@@ -331,7 +331,11 @@ export async function runAgent(
331
331
  const extensionFactories: Array<(pi: ExtensionAPI) => void> = [
332
332
  (pi) => {
333
333
  pi.on("tool_call", (event) => {
334
- const decision = evaluateSubagentToolCall(event.toolName);
334
+ const decision = evaluateHarnessSubagentToolCall(
335
+ event.toolName,
336
+ event.input as Record<string, unknown> | undefined,
337
+ type,
338
+ );
335
339
  if (decision.action === "block") {
336
340
  return { block: true, reason: decision.reason };
337
341
  }
@@ -2,7 +2,7 @@
2
2
  * context.ts — Extract parent conversation context for subagent inheritance.
3
3
  */
4
4
 
5
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
6
 
7
7
  /** Extract text from a message content block array. */
8
8
  export function extractText(content: unknown[]): string {
@@ -2,7 +2,7 @@
2
2
  * env.ts — Detect environment info (git, platform) for subagent system prompts.
3
3
  */
4
4
 
5
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import type { EnvInfo } from "./types.js";
7
7
 
8
8
  export async function detectEnv(
@@ -14,8 +14,8 @@ import {
14
14
  type ExtensionCommandContext,
15
15
  type ExtensionContext,
16
16
  getAgentDir,
17
- } from "@mariozechner/pi-coding-agent";
18
- import { Text } from "@mariozechner/pi-tui";
17
+ } from "@earendil-works/pi-coding-agent";
18
+ import { Text } from "@earendil-works/pi-tui";
19
19
  import { Type } from "@sinclair/typebox";
20
20
  import { getDriftReport } from "../agent-manifest.js";
21
21
  import { Blackboard } from "../blackboard.js";
@@ -11,7 +11,7 @@ import { join } from "node:path";
11
11
  import type {
12
12
  AgentSession,
13
13
  AgentSessionEvent,
14
- } from "@mariozechner/pi-coding-agent";
14
+ } from "@earendil-works/pi-coding-agent";
15
15
 
16
16
  /**
17
17
  * Encode a cwd path as a filesystem-safe directory name. Handles:
@@ -18,7 +18,7 @@
18
18
  import type {
19
19
  ExtensionAPI,
20
20
  ExtensionContext,
21
- } from "@mariozechner/pi-coding-agent";
21
+ } from "@earendil-works/pi-coding-agent";
22
22
  import { Cron } from "croner";
23
23
  import { nanoid } from "nanoid";
24
24
  import type { AgentManager } from "./agent-manager.js";
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
7
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
8
8
  import type { JoinMode } from "./types.js";
9
9
 
10
10
  export interface SubagentsSettings {
@@ -22,7 +22,7 @@ import type { Dirent } from "node:fs";
22
22
  import { existsSync, readdirSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { join } from "node:path";
25
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
25
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
26
26
  import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
27
27
 
28
28
  export interface PreloadedSkill {
@@ -2,8 +2,8 @@
2
2
  * types.ts — Type definitions for the subagent system.
3
3
  */
4
4
 
5
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
6
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
5
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
6
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
7
7
  import type { LifetimeUsage } from "./usage.js";
8
8
 
9
9
  export type { ThinkingLevel };