ultimate-pi 0.7.0 → 0.9.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 (115) 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 +26 -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 +47 -18
  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 +197 -33
  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/parent-ask-user-bridge.ts +89 -0
  38. package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +20 -2
  39. package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +3 -2
  40. package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +44 -24
  41. package/.pi/extensions/lib/harness-subagents/vendored/context.ts +1 -1
  42. package/.pi/extensions/lib/harness-subagents/vendored/env.ts +1 -1
  43. package/.pi/extensions/lib/harness-subagents/vendored/index.ts +23 -2
  44. package/.pi/extensions/lib/harness-subagents/vendored/output-file.ts +1 -1
  45. package/.pi/extensions/lib/harness-subagents/vendored/schedule.ts +1 -1
  46. package/.pi/extensions/lib/harness-subagents/vendored/settings.ts +1 -1
  47. package/.pi/extensions/lib/harness-subagents/vendored/skill-loader.ts +1 -1
  48. package/.pi/extensions/lib/harness-subagents/vendored/types.ts +2 -2
  49. package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +1 -1
  50. package/.pi/extensions/lib/harness-subagents/vendored/ui/conversation-viewer.ts +2 -2
  51. package/.pi/extensions/lib/harness-subagents/vendored/ui/schedule-menu.ts +1 -1
  52. package/.pi/extensions/observation-bus.ts +1 -1
  53. package/.pi/extensions/pi-model-router-harness.ts +1 -1
  54. package/.pi/extensions/policy-gate.ts +90 -20
  55. package/.pi/extensions/provider-payload-sanitize.ts +1 -1
  56. package/.pi/extensions/review-integrity.ts +76 -22
  57. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  58. package/.pi/extensions/soundboard.ts +1 -1
  59. package/.pi/extensions/test-diff-integrity.ts +1 -1
  60. package/.pi/extensions/trace-recorder.ts +1 -1
  61. package/.pi/extensions/ultimate-pi-vcc.ts +1 -1
  62. package/.pi/harness/agents.manifest.json +82 -78
  63. package/.pi/harness/docs/adrs/0031-harness-run-context.md +6 -3
  64. package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +37 -0
  65. package/.pi/harness/docs/adrs/README.md +1 -0
  66. package/.pi/harness/specs/budget-exhausted-event.schema.json +3 -1
  67. package/.pi/harness/specs/harness-spawn-context.schema.json +65 -0
  68. package/.pi/harness/specs/harness-turn.schema.json +18 -0
  69. package/.pi/lib/harness-agent-output.ts +41 -0
  70. package/.pi/lib/harness-run-context.ts +516 -37
  71. package/.pi/lib/harness-ui-state.ts +1 -1
  72. package/.pi/prompts/harness-auto.md +36 -61
  73. package/.pi/prompts/harness-critic.md +15 -28
  74. package/.pi/prompts/harness-eval.md +19 -27
  75. package/.pi/prompts/harness-incident.md +15 -34
  76. package/.pi/prompts/harness-plan.md +28 -49
  77. package/.pi/prompts/harness-review.md +16 -30
  78. package/.pi/prompts/harness-router-tune.md +16 -38
  79. package/.pi/prompts/harness-run.md +21 -38
  80. package/.pi/prompts/harness-setup.md +2 -0
  81. package/.pi/prompts/harness-trace.md +13 -30
  82. package/.pi/scripts/harness-generate-model-router.mjs +16 -13
  83. package/.pi/scripts/harness-verify.mjs +17 -0
  84. package/.pi/scripts/vendor-sync-pi-model-router.sh +10 -10
  85. package/CHANGELOG.md +25 -1
  86. package/README.md +4 -5
  87. package/THIRD_PARTY_NOTICES.md +1 -1
  88. package/package.json +13 -8
  89. package/vendor/pi-model-router/UPSTREAM_PIN.md +1 -1
  90. package/vendor/pi-model-router/extensions/commands.ts +2 -2
  91. package/vendor/pi-model-router/extensions/config.ts +2 -2
  92. package/vendor/pi-model-router/extensions/index.ts +1 -1
  93. package/vendor/pi-model-router/extensions/provider.ts +2 -2
  94. package/vendor/pi-model-router/extensions/routing.ts +2 -2
  95. package/vendor/pi-model-router/extensions/types.ts +1 -1
  96. package/vendor/pi-model-router/extensions/ui.ts +1 -1
  97. package/vendor/pi-model-router/package.json +4 -4
  98. package/vendor/pi-vcc/index.ts +1 -1
  99. package/vendor/pi-vcc/package.json +1 -1
  100. package/vendor/pi-vcc/src/commands/pi-vcc.ts +1 -1
  101. package/vendor/pi-vcc/src/commands/vcc-recall.ts +1 -1
  102. package/vendor/pi-vcc/src/core/content.ts +1 -1
  103. package/vendor/pi-vcc/src/core/load-messages.ts +1 -1
  104. package/vendor/pi-vcc/src/core/normalize.ts +1 -1
  105. package/vendor/pi-vcc/src/core/render-entries.ts +1 -1
  106. package/vendor/pi-vcc/src/core/report.ts +1 -1
  107. package/vendor/pi-vcc/src/core/search-entries.ts +1 -1
  108. package/vendor/pi-vcc/src/core/summarize.ts +1 -1
  109. package/vendor/pi-vcc/src/hooks/before-compact.ts +2 -2
  110. package/vendor/pi-vcc/src/tools/recall.ts +1 -1
  111. package/vendor/pi-vcc/src/types.ts +1 -1
  112. package/vendor/pi-vcc/tests/fixtures.ts +1 -1
  113. package/vendor/pi-vcc/tests/render-entries.test.ts +1 -1
  114. package/vendor/pi-vcc/tests/search-entries.test.ts +1 -1
  115. package/vendor/pi-vcc/tests/support/load-session.ts +2 -2
@@ -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, agentType);
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
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Registers ask_user in subagent sessions, delegating UI to the parent harness session.
3
+ */
4
+
5
+ import type {
6
+ ExtensionAPI,
7
+ ExtensionContext,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { runAskDialog } from "../ask-user/dialog.js";
10
+ import { runAskFallback } from "../ask-user/fallback.js";
11
+ import { renderAskCall, renderAskResult } from "../ask-user/render.js";
12
+ import {
13
+ AskUserParamsSchema,
14
+ PROMPT_GUIDELINES,
15
+ PROMPT_SNIPPET,
16
+ } from "../ask-user/schema.js";
17
+ import type { AskUserParams, DialogResult } from "../ask-user/types.js";
18
+ import {
19
+ formatResultText,
20
+ toToolDetails,
21
+ validateAskParams,
22
+ } from "../ask-user/validate.js";
23
+
24
+ const ASK_USER_AGENT_TYPES = new Set([
25
+ "harness/planner",
26
+ "harness/evaluator",
27
+ "harness/adversary",
28
+ "harness/tie-breaker",
29
+ ]);
30
+
31
+ export function agentTypeAllowsParentAskUser(agentType: string): boolean {
32
+ return ASK_USER_AGENT_TYPES.has(agentType);
33
+ }
34
+
35
+ export function createParentAskUserBridgeFactory(
36
+ parentCtx: ExtensionContext,
37
+ agentType: string,
38
+ ): ((pi: ExtensionAPI) => void) | null {
39
+ if (!agentTypeAllowsParentAskUser(agentType)) {
40
+ return null;
41
+ }
42
+ return (pi: ExtensionAPI) => {
43
+ pi.registerTool({
44
+ name: "ask_user",
45
+ label: "Ask User",
46
+ description:
47
+ "Ask the user a structured question (parent session UI). Use for clarification and plan approval.",
48
+ promptSnippet: PROMPT_SNIPPET,
49
+ promptGuidelines: PROMPT_GUIDELINES,
50
+ parameters: AskUserParamsSchema,
51
+ async execute(_toolCallId, params, _signal, _onUpdate) {
52
+ const validated = validateAskParams(params as AskUserParams);
53
+ if (typeof validated === "string") {
54
+ return {
55
+ content: [{ type: "text", text: validated }],
56
+ details: {
57
+ question: params.question ?? "",
58
+ options: [],
59
+ response: null,
60
+ cancelled: true,
61
+ },
62
+ };
63
+ }
64
+ let outcome: DialogResult;
65
+ if (parentCtx.hasUI) {
66
+ outcome = await runAskDialog(parentCtx.ui, validated);
67
+ } else {
68
+ outcome = await runAskFallback(parentCtx.ui, validated);
69
+ }
70
+ const details = toToolDetails(
71
+ validated,
72
+ outcome.response,
73
+ outcome.cancelled,
74
+ );
75
+ const text = formatResultText(outcome.response, outcome.cancelled);
76
+ return {
77
+ content: [{ type: "text", text }],
78
+ details,
79
+ };
80
+ },
81
+ renderCall(args, theme) {
82
+ return renderAskCall(args, theme);
83
+ },
84
+ renderResult(result, options, theme) {
85
+ return renderAskResult(result, options, theme);
86
+ },
87
+ });
88
+ };
89
+ }
@@ -7,7 +7,13 @@ export const SUBAGENT_BLOCKED_TOOLS = new Set([
7
7
  "get_subagent_result",
8
8
  "steer_subagent",
9
9
  "blackboard",
10
- "ask_user",
10
+ ]);
11
+
12
+ const ASK_USER_ALLOWED_AGENT_TYPES = new Set([
13
+ "harness/planner",
14
+ "harness/evaluator",
15
+ "harness/adversary",
16
+ "harness/tie-breaker",
11
17
  ]);
12
18
 
13
19
  export interface ToolCallDecision {
@@ -16,12 +22,24 @@ export interface ToolCallDecision {
16
22
  newArgs?: Record<string, unknown>;
17
23
  }
18
24
 
19
- export function evaluateSubagentToolCall(toolName: string): ToolCallDecision {
25
+ export function evaluateSubagentToolCall(
26
+ toolName: string,
27
+ agentType?: string,
28
+ ): ToolCallDecision {
20
29
  if (SUBAGENT_BLOCKED_TOOLS.has(toolName)) {
21
30
  return {
22
31
  action: "block",
23
32
  reason: `Tool "${toolName}" is not available in subagent sessions (single spawn depth).`,
24
33
  };
25
34
  }
35
+ if (toolName === "ask_user") {
36
+ if (agentType && ASK_USER_ALLOWED_AGENT_TYPES.has(agentType)) {
37
+ return { action: "allow" };
38
+ }
39
+ return {
40
+ action: "block",
41
+ reason: `Tool "ask_user" is not available for ${agentType ?? "this agent"} (orchestrator-only).`,
42
+ };
43
+ }
26
44
  return { action: "allow" };
27
45
  }
@@ -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,
@@ -252,6 +252,7 @@ export class AgentManager {
252
252
  options.onSessionCreated?.(session);
253
253
  },
254
254
  systemPromptAppendix: options.systemPromptAppendix,
255
+ parentExtensionContext: ctx,
255
256
  })
256
257
  .then(({ responseText, session, aborted, steered }) => {
257
258
  // Don't overwrite status if externally stopped via abort()
@@ -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,9 @@ 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
+ import { createParentAskUserBridgeFactory } from "../parent-ask-user-bridge.js";
21
22
  import {
22
23
  getAgentConfig,
23
24
  getConfig,
@@ -39,7 +40,6 @@ const EXCLUDED_TOOL_NAMES = [
39
40
  "get_subagent_result",
40
41
  "steer_subagent",
41
42
  "blackboard",
42
- "ask_user",
43
43
  ];
44
44
 
45
45
  /** Default max turns. undefined = unlimited (no turn limit). */
@@ -152,6 +152,8 @@ export interface RunOptions {
152
152
  }) => void;
153
153
  /** Blackboard or other spawn context appended to the subagent system prompt. */
154
154
  systemPromptAppendix?: string;
155
+ /** Parent session context — used to bridge ask_user UI into subagents. */
156
+ parentExtensionContext?: ExtensionContext;
155
157
  }
156
158
 
157
159
  export interface RunResult {
@@ -328,22 +330,31 @@ export async function runAgent(
328
330
  ? `${systemPrompt}\n\n---\n\n## Spawn context\n\n${appendix}`
329
331
  : systemPrompt;
330
332
 
331
- const extensionFactories: Array<(pi: ExtensionAPI) => void> = [
332
- (pi) => {
333
- pi.on("tool_call", (event) => {
334
- const decision = evaluateSubagentToolCall(event.toolName);
335
- if (decision.action === "block") {
336
- return { block: true, reason: decision.reason };
337
- }
338
- return undefined;
339
- });
340
- pi.on("before_agent_start", (event: { systemPrompt?: string }) => {
341
- const base =
342
- typeof event.systemPrompt === "string" ? event.systemPrompt : "";
343
- return { systemPrompt: base };
344
- });
345
- },
346
- ];
333
+ const extensionFactories: Array<(pi: ExtensionAPI) => void> = [];
334
+ const askUserBridge = options.parentExtensionContext
335
+ ? createParentAskUserBridgeFactory(options.parentExtensionContext, type)
336
+ : null;
337
+ if (askUserBridge) {
338
+ extensionFactories.push(askUserBridge);
339
+ }
340
+ extensionFactories.push((pi) => {
341
+ pi.on("tool_call", (event) => {
342
+ const decision = evaluateHarnessSubagentToolCall(
343
+ event.toolName,
344
+ event.input as Record<string, unknown> | undefined,
345
+ type,
346
+ );
347
+ if (decision.action === "block") {
348
+ return { block: true, reason: decision.reason };
349
+ }
350
+ return undefined;
351
+ });
352
+ pi.on("before_agent_start", (event: { systemPrompt?: string }) => {
353
+ const base =
354
+ typeof event.systemPrompt === "string" ? event.systemPrompt : "";
355
+ return { systemPrompt: base };
356
+ });
357
+ });
347
358
 
348
359
  const loader = new DefaultResourceLoader({
349
360
  cwd: effectiveCwd,
@@ -399,6 +410,7 @@ export async function runAgent(
399
410
  const filterTools = (names: string[]) =>
400
411
  names.filter((t) => {
401
412
  if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
413
+ if (t === "ask_user" && askUserBridge) return true;
402
414
  if (disallowedSet?.has(t)) return false;
403
415
  if (builtinToolNameSet.has(t)) return true;
404
416
  if (extensions === false) return false;
@@ -412,9 +424,11 @@ export async function runAgent(
412
424
  if (activeTools.length > 0) {
413
425
  session.setActiveToolsByName(activeTools);
414
426
  } else {
415
- session.setActiveToolsByName(
416
- toolNames.filter((t) => !disallowedSet?.has(t)),
417
- );
427
+ const fallback = toolNames.filter((t) => {
428
+ if (t === "ask_user" && askUserBridge) return true;
429
+ return !disallowedSet?.has(t);
430
+ });
431
+ session.setActiveToolsByName(fallback);
418
432
  }
419
433
 
420
434
  // Bind extensions so that session_start fires and extensions can initialize
@@ -430,6 +444,12 @@ export async function runAgent(
430
444
  },
431
445
  });
432
446
 
447
+ if (askUserBridge) {
448
+ const withAsk = new Set(session.getActiveToolNames());
449
+ withAsk.add("ask_user");
450
+ session.setActiveToolsByName([...withAsk]);
451
+ }
452
+
433
453
  options.onSessionCreated?.(session);
434
454
 
435
455
  // Track turns for graceful max_turns enforcement
@@ -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,9 +14,13 @@ 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
+ import {
21
+ extractPlanApprovalsFromEntries,
22
+ getLatestRunContext,
23
+ } from "../../../lib/harness-run-context.js";
20
24
  import { getDriftReport } from "../agent-manifest.js";
21
25
  import { Blackboard } from "../blackboard.js";
22
26
  import {
@@ -1599,6 +1603,23 @@ Guidelines:
1599
1603
  cancelNudge(params.agent_id);
1600
1604
  }
1601
1605
 
1606
+ if (record.session && record.status !== "running") {
1607
+ const parentEntries = _ctx.sessionManager.getEntries();
1608
+ const runCtx = getLatestRunContext(parentEntries);
1609
+ if (runCtx) {
1610
+ const subEntries = record.session.sessionManager.getEntries();
1611
+ for (const approval of extractPlanApprovalsFromEntries(
1612
+ subEntries,
1613
+ )) {
1614
+ pi.appendEntry("harness-plan-approval", {
1615
+ plan_id: approval.plan_id ?? runCtx.plan_id,
1616
+ approved_at: approval.approved_at,
1617
+ source: "ask_user",
1618
+ });
1619
+ }
1620
+ }
1621
+ }
1622
+
1602
1623
  // Verbose: include full conversation
1603
1624
  if (params.verbose && record.session) {
1604
1625
  const conversation = getAgentConversation(record.session);
@@ -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 };
@@ -5,7 +5,7 @@
5
5
  * Uses the callback form of setWidget for themed rendering.
6
6
  */
7
7
 
8
- import { truncateToWidth } from "@mariozechner/pi-tui";
8
+ import { truncateToWidth } from "@earendil-works/pi-tui";
9
9
  import type { AgentManager } from "../agent-manager.js";
10
10
  import { getConfig } from "../agent-types.js";
11
11
  import type { AgentInvocation, SubagentType } from "../types.js";
@@ -5,7 +5,7 @@
5
5
  * Subscribes to session events for real-time streaming updates.
6
6
  */
7
7
 
8
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
8
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
9
9
  import {
10
10
  type Component,
11
11
  matchesKey,
@@ -13,7 +13,7 @@ import {
13
13
  truncateToWidth,
14
14
  visibleWidth,
15
15
  wrapTextWithAnsi,
16
- } from "@mariozechner/pi-tui";
16
+ } from "@earendil-works/pi-tui";
17
17
  import { extractText } from "../context.js";
18
18
  import type { AgentRecord } from "../types.js";
19
19
  import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
@@ -8,7 +8,7 @@
8
8
  * if real demand emerges.
9
9
  */
10
10
 
11
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
11
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
12
12
  import type { SubagentScheduler } from "../schedule.js";
13
13
  import type { ScheduledSubagent } from "../types.js";
14
14
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { randomUUID } from "node:crypto";
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
10
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
11
11
 
12
12
  type HarnessPhase = "plan" | "execute" | "evaluate" | "adversary" | "merge";
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
10
  import vendorModelRouter from "../../vendor/pi-model-router/extensions/index.js";
11
11
 
12
12
  function isHarnessRouterReady(cwd: string): boolean {