ultimate-pi 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harness-debate-plan/SKILL.md +44 -0
- package/.agents/skills/harness-decisions/SKILL.md +1 -1
- package/.agents/skills/harness-orchestration/SKILL.md +54 -28
- package/.agents/skills/harness-plan/SKILL.md +15 -20
- package/.pi/agents/harness/adversary.md +0 -1
- package/.pi/agents/harness/evaluator.md +0 -1
- package/.pi/agents/harness/executor.md +1 -2
- package/.pi/agents/harness/incident-recorder.md +0 -1
- package/.pi/agents/harness/meta-optimizer.md +0 -1
- package/.pi/agents/harness/planning/decompose.md +3 -4
- package/.pi/agents/harness/planning/execution-plan-author.md +30 -0
- package/.pi/agents/harness/planning/hypothesis-validator.md +23 -0
- package/.pi/agents/harness/planning/hypothesis.md +3 -4
- package/.pi/agents/harness/planning/plan-adversary.md +10 -42
- package/.pi/agents/harness/planning/plan-evaluator.md +18 -0
- package/.pi/agents/harness/planning/review-integrator.md +23 -0
- package/.pi/agents/harness/planning/scout-graphify.md +11 -5
- package/.pi/agents/harness/planning/scout-semantic.md +11 -6
- package/.pi/agents/harness/planning/scout-structure.md +12 -6
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +18 -0
- package/.pi/agents/harness/planning/stack-researcher.md +24 -0
- package/.pi/agents/harness/tie-breaker.md +0 -1
- package/.pi/agents/harness/trace-librarian.md +0 -1
- package/.pi/extensions/debate-orchestrator.ts +90 -53
- package/.pi/extensions/harness-plan-approval.ts +2 -2
- package/.pi/extensions/harness-run-context.ts +145 -5
- package/.pi/extensions/harness-subagents.ts +2 -2
- package/.pi/extensions/lib/harness-posthog.ts +6 -1
- package/.pi/extensions/lib/harness-spawn-budget.ts +75 -0
- package/.pi/extensions/lib/harness-subagent-auth.ts +123 -0
- package/.pi/extensions/lib/{harness-subagents/harness-subagent-policy.ts → harness-subagent-policy.ts} +3 -6
- package/.pi/extensions/lib/harness-subagent-precheck.ts +95 -0
- package/.pi/extensions/lib/harness-subagents-bridge.ts +176 -0
- package/.pi/extensions/lib/plan-approval/create-plan.ts +4 -7
- package/.pi/extensions/lib/plan-approval/plan-review.ts +1 -1
- package/.pi/extensions/lib/plan-approval/types.ts +7 -1
- package/.pi/extensions/lib/plan-debate-envelope.ts +84 -0
- package/.pi/extensions/lib/{harness-subagents/spawn-policy.ts → spawn-policy.ts} +1 -0
- package/.pi/extensions/policy-gate.ts +1 -1
- package/.pi/extensions/review-integrity.ts +48 -29
- package/.pi/harness/agents.manifest.json +37 -25
- package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +4 -3
- package/.pi/harness/docs/adrs/0033-parent-orchestrated-planning.md +1 -1
- package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +27 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r1.yaml +25 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r4.yaml +26 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/sprint-audit-r4.yaml +5 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/plan-packet.yaml +196 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/plan-review.md +14 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/research-brief.yaml +32 -0
- package/.pi/harness/evals/smoke/run-context.fixture.json +1 -1
- package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +88 -0
- package/.pi/harness/specs/harness-posthog-event.schema.json +6 -1
- package/.pi/harness/specs/plan-execution-plan-brief.schema.json +13 -0
- package/.pi/harness/specs/plan-execution-plan.schema.json +255 -0
- package/.pi/harness/specs/plan-packet.schema.json +14 -5
- package/.pi/harness/specs/plan-review-round-draft.schema.json +68 -0
- package/.pi/harness/specs/plan-sprint-audit-turn.schema.json +29 -0
- package/.pi/harness/specs/plan-stack-brief.schema.json +65 -0
- package/.pi/harness/specs/plan-validation-turn.schema.json +42 -0
- package/.pi/harness/specs/round-result.schema.json +16 -9
- package/.pi/lib/debate-orchestrator-types.ts +38 -0
- package/.pi/lib/harness-agent-discovery.mjs +81 -0
- package/.pi/lib/harness-run-context.ts +64 -38
- package/.pi/lib/harness-yaml.mjs +73 -0
- package/.pi/lib/harness-yaml.ts +90 -0
- package/.pi/prompts/harness-auto.md +13 -11
- package/.pi/prompts/harness-critic.md +2 -2
- package/.pi/prompts/harness-eval.md +3 -3
- package/.pi/prompts/harness-incident.md +2 -2
- package/.pi/prompts/harness-plan.md +79 -93
- package/.pi/prompts/harness-review.md +2 -2
- package/.pi/prompts/harness-router-tune.md +1 -1
- package/.pi/prompts/harness-run.md +2 -2
- package/.pi/prompts/harness-setup.md +15 -6
- package/.pi/prompts/harness-trace.md +2 -2
- package/.pi/scripts/harness-agents-manifest.mjs +1 -1
- package/.pi/scripts/harness-verify.mjs +28 -19
- package/.pi/scripts/validate-plan-dag.mjs +258 -0
- package/.pi/scripts/vendor-sync-pi-subagents.sh +19 -0
- package/CHANGELOG.md +12 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/biome.json +2 -2
- package/package.json +6 -4
- package/.pi/agents/harness/planner.md +0 -13
- package/.pi/agents/harness/planning/hypothesis-eval.md +0 -59
- package/.pi/agents/harness/planning/planner.md +0 -20
- package/.pi/extensions/lib/harness-subagents/agent-loader.ts +0 -126
- package/.pi/extensions/lib/harness-subagents/agent-manifest.ts +0 -119
- package/.pi/extensions/lib/harness-subagents/agent-parser.ts +0 -87
- package/.pi/extensions/lib/harness-subagents/blackboard-tool.ts +0 -118
- package/.pi/extensions/lib/harness-subagents/blackboard.ts +0 -175
- package/.pi/extensions/lib/harness-subagents/parent-ask-user-bridge.ts +0 -10
- package/.pi/extensions/lib/harness-subagents/parent-harness-ui-bridge.ts +0 -137
- package/.pi/extensions/lib/harness-subagents/parent-harness-ui-hooks.ts +0 -77
- package/.pi/extensions/lib/harness-subagents/types-blackboard.ts +0 -27
- package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +0 -558
- package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +0 -666
- package/.pi/extensions/lib/harness-subagents/vendored/agent-types.ts +0 -175
- package/.pi/extensions/lib/harness-subagents/vendored/context.ts +0 -59
- package/.pi/extensions/lib/harness-subagents/vendored/cross-extension-rpc.ts +0 -134
- package/.pi/extensions/lib/harness-subagents/vendored/custom-agents.ts +0 -5
- package/.pi/extensions/lib/harness-subagents/vendored/default-agents.ts +0 -123
- package/.pi/extensions/lib/harness-subagents/vendored/env.ts +0 -43
- package/.pi/extensions/lib/harness-subagents/vendored/group-join.ts +0 -144
- package/.pi/extensions/lib/harness-subagents/vendored/index.ts +0 -2460
- package/.pi/extensions/lib/harness-subagents/vendored/invocation-config.ts +0 -52
- package/.pi/extensions/lib/harness-subagents/vendored/memory.ts +0 -182
- package/.pi/extensions/lib/harness-subagents/vendored/model-resolver.ts +0 -92
- package/.pi/extensions/lib/harness-subagents/vendored/output-file.ts +0 -115
- package/.pi/extensions/lib/harness-subagents/vendored/prompts.ts +0 -103
- package/.pi/extensions/lib/harness-subagents/vendored/schedule-store.ts +0 -177
- package/.pi/extensions/lib/harness-subagents/vendored/schedule.ts +0 -416
- package/.pi/extensions/lib/harness-subagents/vendored/settings.ts +0 -210
- package/.pi/extensions/lib/harness-subagents/vendored/skill-loader.ts +0 -108
- package/.pi/extensions/lib/harness-subagents/vendored/types.ts +0 -187
- package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +0 -639
- package/.pi/extensions/lib/harness-subagents/vendored/ui/conversation-viewer.ts +0 -324
- package/.pi/extensions/lib/harness-subagents/vendored/ui/schedule-menu.ts +0 -110
- package/.pi/extensions/lib/harness-subagents/vendored/usage.ts +0 -71
- package/.pi/extensions/lib/harness-subagents/vendored/worktree.ts +0 -195
- /package/.pi/extensions/{00-ultimate-pi-system-prompt.ts → custom-system-prompt.ts} +0 -0
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agents.manifest.json drift detection (package agents vs installed hashes).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
type DiscoveredAgentFile,
|
|
9
|
-
loadPackageAgentHashes,
|
|
10
|
-
sha256Content,
|
|
11
|
-
} from "./agent-loader.js";
|
|
12
|
-
|
|
13
|
-
export interface ManifestEntry {
|
|
14
|
-
path: string;
|
|
15
|
-
sha256: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AgentsManifest {
|
|
19
|
-
schema_version: string;
|
|
20
|
-
package: string;
|
|
21
|
-
package_version: string;
|
|
22
|
-
generated_at: string;
|
|
23
|
-
agents: Record<string, ManifestEntry>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface DriftItem {
|
|
27
|
-
id: string;
|
|
28
|
-
kind: "missing_in_manifest" | "hash_mismatch" | "missing_on_disk";
|
|
29
|
-
expected?: string;
|
|
30
|
-
actual?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DriftReport {
|
|
34
|
-
ok: boolean;
|
|
35
|
-
packageVersion: string;
|
|
36
|
-
items: DriftItem[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function readPackageVersion(packageRoot: string): string {
|
|
40
|
-
try {
|
|
41
|
-
const pkg = JSON.parse(
|
|
42
|
-
readFileSync(join(packageRoot, "package.json"), "utf-8"),
|
|
43
|
-
) as { version?: string };
|
|
44
|
-
return pkg.version ?? "unknown";
|
|
45
|
-
} catch {
|
|
46
|
-
return "unknown";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function readAgentsManifest(packageRoot: string): AgentsManifest | null {
|
|
51
|
-
const path = join(packageRoot, ".pi", "harness", "agents.manifest.json");
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(readFileSync(path, "utf-8")) as AgentsManifest;
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getDriftReport(packageRoot: string): DriftReport {
|
|
60
|
-
const manifest = readAgentsManifest(packageRoot);
|
|
61
|
-
const onDisk = loadPackageAgentHashes(packageRoot);
|
|
62
|
-
const packageVersion = readPackageVersion(packageRoot);
|
|
63
|
-
const items: DriftItem[] = [];
|
|
64
|
-
|
|
65
|
-
if (!manifest) {
|
|
66
|
-
return {
|
|
67
|
-
ok: false,
|
|
68
|
-
packageVersion,
|
|
69
|
-
items: [{ id: "*", kind: "missing_on_disk" }],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
for (const [id, entry] of onDisk) {
|
|
74
|
-
const expected = manifest.agents[id];
|
|
75
|
-
if (!expected) {
|
|
76
|
-
items.push({ id, kind: "missing_in_manifest" });
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (expected.sha256 !== entry.sha256) {
|
|
80
|
-
items.push({
|
|
81
|
-
id,
|
|
82
|
-
kind: "hash_mismatch",
|
|
83
|
-
expected: expected.sha256,
|
|
84
|
-
actual: entry.sha256,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const id of Object.keys(manifest.agents)) {
|
|
90
|
-
if (!onDisk.has(id)) {
|
|
91
|
-
items.push({ id, kind: "missing_on_disk" });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { ok: items.length === 0, packageVersion, items };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function buildManifestFromFiles(
|
|
99
|
-
files: Iterable<DiscoveredAgentFile>,
|
|
100
|
-
packageName: string,
|
|
101
|
-
packageVersion: string,
|
|
102
|
-
): AgentsManifest {
|
|
103
|
-
const agents: Record<string, ManifestEntry> = {};
|
|
104
|
-
for (const f of files) {
|
|
105
|
-
if (f.source !== "package") continue;
|
|
106
|
-
const relPath = `.pi/agents/${f.id}.md`;
|
|
107
|
-
agents[f.id] = {
|
|
108
|
-
path: relPath,
|
|
109
|
-
sha256: sha256Content(f.content),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
schema_version: "1.0.0",
|
|
114
|
-
package: packageName,
|
|
115
|
-
package_version: packageVersion,
|
|
116
|
-
generated_at: new Date().toISOString(),
|
|
117
|
-
agents,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse harness agent .md files into AgentConfig (path id = posix relative path).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { BUILTIN_TOOL_NAMES } from "./vendored/agent-types.js";
|
|
7
|
-
import type {
|
|
8
|
-
AgentConfig,
|
|
9
|
-
MemoryScope,
|
|
10
|
-
ThinkingLevel,
|
|
11
|
-
} from "./vendored/types.js";
|
|
12
|
-
|
|
13
|
-
function str(val: unknown): string | undefined {
|
|
14
|
-
return typeof val === "string" ? val : undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function nonNegativeInt(val: unknown): number | undefined {
|
|
18
|
-
return typeof val === "number" && val >= 0 ? val : undefined;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function parseCsvField(val: unknown): string[] | undefined {
|
|
22
|
-
if (val === undefined || val === null) return undefined;
|
|
23
|
-
const s = String(val).trim();
|
|
24
|
-
if (!s || s === "none") return undefined;
|
|
25
|
-
const items = s
|
|
26
|
-
.split(",")
|
|
27
|
-
.map((t) => t.trim())
|
|
28
|
-
.filter(Boolean);
|
|
29
|
-
return items.length > 0 ? items : undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function csvList(val: unknown, defaults: string[]): string[] {
|
|
33
|
-
if (val === undefined || val === null) return defaults;
|
|
34
|
-
return parseCsvField(val) ?? [];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function csvListOptional(val: unknown): string[] | undefined {
|
|
38
|
-
return parseCsvField(val);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseMemory(val: unknown): MemoryScope | undefined {
|
|
42
|
-
if (val === "user" || val === "project" || val === "local") return val;
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function inheritField(val: unknown): true | string[] | false {
|
|
47
|
-
if (val === undefined || val === null || val === true) return true;
|
|
48
|
-
if (val === false || val === "none") return false;
|
|
49
|
-
const items = csvList(val, []);
|
|
50
|
-
return items.length > 0 ? items : false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function parseAgentMarkdown(
|
|
54
|
-
agentId: string,
|
|
55
|
-
content: string,
|
|
56
|
-
source: "package" | "project" | "global",
|
|
57
|
-
): AgentConfig {
|
|
58
|
-
const { frontmatter: fm, body } =
|
|
59
|
-
parseFrontmatter<Record<string, unknown>>(content);
|
|
60
|
-
|
|
61
|
-
const yamlName = str(fm.name);
|
|
62
|
-
const displayName = str(fm.display_name) ?? yamlName;
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
name: agentId,
|
|
66
|
-
displayName,
|
|
67
|
-
description: str(fm.description) ?? agentId,
|
|
68
|
-
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
69
|
-
disallowedTools: csvListOptional(fm.disallowed_tools),
|
|
70
|
-
extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
|
|
71
|
-
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
72
|
-
model: str(fm.model),
|
|
73
|
-
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
74
|
-
maxTurns: nonNegativeInt(fm.max_turns),
|
|
75
|
-
systemPrompt: body.trim(),
|
|
76
|
-
promptMode: fm.prompt_mode === "append" ? "append" : "replace",
|
|
77
|
-
inheritContext:
|
|
78
|
-
fm.inherit_context != null ? fm.inherit_context === true : undefined,
|
|
79
|
-
runInBackground:
|
|
80
|
-
fm.run_in_background != null ? fm.run_in_background === true : undefined,
|
|
81
|
-
isolated: fm.isolated != null ? fm.isolated === true : undefined,
|
|
82
|
-
memory: parseMemory(fm.memory),
|
|
83
|
-
isolation: fm.isolation === "worktree" ? "worktree" : undefined,
|
|
84
|
-
enabled: fm.enabled !== false,
|
|
85
|
-
source: source === "package" ? "global" : source,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Orchestrator blackboard tool (list/read/query/wait/delete).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { Type } from "@sinclair/typebox";
|
|
7
|
-
import type { Blackboard } from "./blackboard.js";
|
|
8
|
-
import type { BlackboardQuery } from "./types-blackboard.js";
|
|
9
|
-
|
|
10
|
-
function textResult(text: string) {
|
|
11
|
-
return {
|
|
12
|
-
content: [{ type: "text" as const, text }],
|
|
13
|
-
details: {},
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function registerBlackboardTool(
|
|
18
|
-
pi: ExtensionAPI,
|
|
19
|
-
blackboard: Blackboard,
|
|
20
|
-
): void {
|
|
21
|
-
pi.registerTool(
|
|
22
|
-
defineTool({
|
|
23
|
-
name: "blackboard",
|
|
24
|
-
label: "Blackboard",
|
|
25
|
-
description:
|
|
26
|
-
"Shared knowledge store for harness orchestration. Actions: list, read, query, wait, delete. " +
|
|
27
|
-
"Use namespaced keys (e.g. scout:findings). Spawn context injection is capped at ~8k chars.",
|
|
28
|
-
parameters: Type.Object({
|
|
29
|
-
action: Type.Union([
|
|
30
|
-
Type.Literal("list"),
|
|
31
|
-
Type.Literal("read"),
|
|
32
|
-
Type.Literal("query"),
|
|
33
|
-
Type.Literal("wait"),
|
|
34
|
-
Type.Literal("delete"),
|
|
35
|
-
]),
|
|
36
|
-
key: Type.Optional(Type.String()),
|
|
37
|
-
pattern: Type.Optional(Type.String()),
|
|
38
|
-
agent_id: Type.Optional(Type.String()),
|
|
39
|
-
agent_name: Type.Optional(Type.String()),
|
|
40
|
-
category: Type.Optional(Type.String()),
|
|
41
|
-
timeout_ms: Type.Optional(
|
|
42
|
-
Type.Number({ description: "For wait action (default 30000)." }),
|
|
43
|
-
),
|
|
44
|
-
}),
|
|
45
|
-
execute: async (_id, params) => {
|
|
46
|
-
const action = params.action as string;
|
|
47
|
-
|
|
48
|
-
if (action === "list") {
|
|
49
|
-
return textResult(blackboard.serialize());
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (action === "read") {
|
|
53
|
-
const key = params.key as string | undefined;
|
|
54
|
-
if (!key) return textResult("read requires key.");
|
|
55
|
-
const entry = blackboard.get(key);
|
|
56
|
-
if (!entry) {
|
|
57
|
-
return textResult(`No entry for key "${key}".`);
|
|
58
|
-
}
|
|
59
|
-
return textResult(JSON.stringify(entry, null, 2));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (action === "query") {
|
|
63
|
-
const q: BlackboardQuery = {};
|
|
64
|
-
if (params.pattern) q.pattern = params.pattern as string;
|
|
65
|
-
if (params.agent_id) q.agentId = params.agent_id as string;
|
|
66
|
-
if (params.agent_name) q.agentName = params.agent_name as string;
|
|
67
|
-
if (params.category) q.category = params.category as string;
|
|
68
|
-
if (params.key) q.keys = [params.key as string];
|
|
69
|
-
return textResult(JSON.stringify(blackboard.toJSON(q), null, 2));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (action === "delete") {
|
|
73
|
-
const key = params.key as string | undefined;
|
|
74
|
-
if (!key) return textResult("delete requires key.");
|
|
75
|
-
const removed = blackboard.delete(key);
|
|
76
|
-
return textResult(
|
|
77
|
-
removed ? `Deleted "${key}".` : `Key "${key}" not found.`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (action === "wait") {
|
|
82
|
-
const pattern = (params.pattern ?? params.key) as string | undefined;
|
|
83
|
-
if (!pattern) {
|
|
84
|
-
return textResult("wait requires pattern or key.");
|
|
85
|
-
}
|
|
86
|
-
const timeoutMs = (params.timeout_ms as number) ?? 30_000;
|
|
87
|
-
const start = Date.now();
|
|
88
|
-
while (Date.now() - start < timeoutMs) {
|
|
89
|
-
const matches = blackboard.query({ pattern });
|
|
90
|
-
if (matches.length > 0) {
|
|
91
|
-
return textResult(
|
|
92
|
-
JSON.stringify(blackboard.toJSON({ pattern }), null, 2),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
96
|
-
}
|
|
97
|
-
return textResult(`Timeout waiting for pattern "${pattern}".`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return textResult("Unknown action.");
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function buildBlackboardContextInjection(
|
|
107
|
-
blackboard: Blackboard,
|
|
108
|
-
spec?: { agentId?: string; keys?: string[]; agentName?: string },
|
|
109
|
-
): string | undefined {
|
|
110
|
-
if (!spec) return undefined;
|
|
111
|
-
const q: BlackboardQuery = {};
|
|
112
|
-
if (spec.agentId) q.agentId = spec.agentId;
|
|
113
|
-
if (spec.agentName) q.agentName = spec.agentName;
|
|
114
|
-
if (spec.keys?.length) q.keys = spec.keys;
|
|
115
|
-
const serialized = blackboard.serialize(q);
|
|
116
|
-
if (serialized === "(blackboard is empty)") return undefined;
|
|
117
|
-
return serialized;
|
|
118
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared blackboard for orchestrator ↔ subagent handoffs (~8k injection cap).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
BlackboardEntry,
|
|
7
|
-
BlackboardQuery,
|
|
8
|
-
PostMetadata,
|
|
9
|
-
} from "./types-blackboard.js";
|
|
10
|
-
|
|
11
|
-
const MAX_VALUE_DISPLAY_CHARS = 500;
|
|
12
|
-
export const MAX_SERIALIZE_TOTAL_CHARS = 8_000;
|
|
13
|
-
|
|
14
|
-
function truncateForDisplay(value: unknown): string {
|
|
15
|
-
const str =
|
|
16
|
-
typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
17
|
-
if (str.length <= MAX_VALUE_DISPLAY_CHARS) return str;
|
|
18
|
-
return `${str.slice(0, MAX_VALUE_DISPLAY_CHARS)}...(truncated)`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type PostHandler = (key: string, entry: BlackboardEntry) => void;
|
|
22
|
-
|
|
23
|
-
export class Blackboard {
|
|
24
|
-
private entries = new Map<string, BlackboardEntry>();
|
|
25
|
-
private postHandlers: PostHandler[] = [];
|
|
26
|
-
|
|
27
|
-
post(
|
|
28
|
-
namespacedKey: string,
|
|
29
|
-
value: unknown,
|
|
30
|
-
agentId: string,
|
|
31
|
-
agentName: string,
|
|
32
|
-
metadata?: PostMetadata,
|
|
33
|
-
): void {
|
|
34
|
-
if (metadata?.supersedes) {
|
|
35
|
-
this.entries.delete(metadata.supersedes);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const entry: BlackboardEntry = {
|
|
39
|
-
key: namespacedKey,
|
|
40
|
-
value,
|
|
41
|
-
agentId,
|
|
42
|
-
agentName,
|
|
43
|
-
timestamp: Date.now(),
|
|
44
|
-
metadata,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
this.entries.set(namespacedKey, entry);
|
|
48
|
-
|
|
49
|
-
for (const handler of this.postHandlers) {
|
|
50
|
-
try {
|
|
51
|
-
handler(namespacedKey, entry);
|
|
52
|
-
} catch {
|
|
53
|
-
/* ignore */
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get(key: string): BlackboardEntry | undefined {
|
|
59
|
-
return this.entries.get(key);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getAll(): Map<string, BlackboardEntry> {
|
|
63
|
-
return new Map(this.entries);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
query(query: BlackboardQuery): BlackboardEntry[] {
|
|
67
|
-
let results = [...this.entries.values()];
|
|
68
|
-
|
|
69
|
-
if (query.keys?.length) {
|
|
70
|
-
results = results.filter((e) => query.keys?.includes(e.key));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (query.pattern) {
|
|
74
|
-
const pat = query.pattern;
|
|
75
|
-
if (pat instanceof RegExp) {
|
|
76
|
-
results = results.filter((e) => pat.test(e.key));
|
|
77
|
-
} else {
|
|
78
|
-
const escaped = pat.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
79
|
-
const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
|
|
80
|
-
results = results.filter((e) => re.test(e.key) || e.key.includes(pat));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (query.agentId) {
|
|
85
|
-
results = results.filter((e) => e.agentId === query.agentId);
|
|
86
|
-
}
|
|
87
|
-
if (query.agentName) {
|
|
88
|
-
results = results.filter((e) => e.agentName === query.agentName);
|
|
89
|
-
}
|
|
90
|
-
if (query.category) {
|
|
91
|
-
results = results.filter((e) => e.metadata?.category === query.category);
|
|
92
|
-
}
|
|
93
|
-
if (query.after !== undefined) {
|
|
94
|
-
results = results.filter((e) => e.timestamp > query.after!);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return results;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
serialize(query?: BlackboardQuery): string {
|
|
101
|
-
const entries = query ? this.query(query) : [...this.entries.values()];
|
|
102
|
-
|
|
103
|
-
if (entries.length === 0) return "(blackboard is empty)";
|
|
104
|
-
|
|
105
|
-
const lines: string[] = [`Blackboard (${entries.length} entries):`];
|
|
106
|
-
let totalChars = lines[0].length;
|
|
107
|
-
|
|
108
|
-
for (const entry of entries) {
|
|
109
|
-
const summary = entry.metadata?.summary
|
|
110
|
-
? entry.metadata.summary
|
|
111
|
-
: truncateForDisplay(entry.value);
|
|
112
|
-
const category = entry.metadata?.category
|
|
113
|
-
? ` [${entry.metadata.category}]`
|
|
114
|
-
: "";
|
|
115
|
-
const ts = new Date(entry.timestamp).toISOString().slice(11, 19);
|
|
116
|
-
const keyLine = ` ${entry.key}${category} (${entry.agentName} @ ${ts})`;
|
|
117
|
-
const valLine = ` ${summary}`;
|
|
118
|
-
const entryChars = keyLine.length + valLine.length + 2;
|
|
119
|
-
|
|
120
|
-
if (totalChars + entryChars > MAX_SERIALIZE_TOTAL_CHARS) {
|
|
121
|
-
lines.push(
|
|
122
|
-
" ... (more entries truncated — use blackboard query with specific keys)",
|
|
123
|
-
);
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
lines.push(keyLine, valLine);
|
|
127
|
-
totalChars += entryChars;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return lines.join("\n");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
toJSON(query?: BlackboardQuery): object {
|
|
134
|
-
const entries = query ? this.query(query) : [...this.entries.values()];
|
|
135
|
-
return {
|
|
136
|
-
count: entries.length,
|
|
137
|
-
entries: entries.map((e) => ({
|
|
138
|
-
key: e.key,
|
|
139
|
-
agentId: e.agentId,
|
|
140
|
-
agentName: e.agentName,
|
|
141
|
-
timestamp: e.timestamp,
|
|
142
|
-
summary: e.metadata?.summary ?? null,
|
|
143
|
-
category: e.metadata?.category ?? null,
|
|
144
|
-
value: e.value,
|
|
145
|
-
})),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
clear(): void {
|
|
150
|
-
this.entries.clear();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
restore(data: {
|
|
154
|
-
key: string;
|
|
155
|
-
value: unknown;
|
|
156
|
-
agentId: string;
|
|
157
|
-
agentName: string;
|
|
158
|
-
timestamp: number;
|
|
159
|
-
metadata?: PostMetadata;
|
|
160
|
-
}): void {
|
|
161
|
-
this.entries.set(data.key, { ...data });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
delete(key: string): boolean {
|
|
165
|
-
return this.entries.delete(key);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
get size(): number {
|
|
169
|
-
return this.entries.size;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
on(event: "post", handler: PostHandler): void {
|
|
173
|
-
if (event === "post") this.postHandlers.push(handler);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated Import from parent-harness-ui-bridge.js — kept for stable import paths.
|
|
3
|
-
*/
|
|
4
|
-
export {
|
|
5
|
-
agentTypeAllowsParentAskUser,
|
|
6
|
-
agentTypeAllowsParentHarnessUi,
|
|
7
|
-
createParentAskUserBridgeFactory,
|
|
8
|
-
createParentHarnessUiBridgeFactory,
|
|
9
|
-
type ParentHarnessUiHooks,
|
|
10
|
-
} from "./parent-harness-ui-bridge.js";
|
|
@@ -1,137 +0,0 @@
|
|
|
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 type {
|
|
10
|
-
PlanPacketLike,
|
|
11
|
-
PlanUserApproval,
|
|
12
|
-
} from "../../../lib/harness-run-context.js";
|
|
13
|
-
import { parsePlanApprovalFromMessage } from "../../../lib/harness-run-context.js";
|
|
14
|
-
import { runAskDialog } from "../ask-user/dialog.js";
|
|
15
|
-
import { runAskFallback } from "../ask-user/fallback.js";
|
|
16
|
-
import { renderAskCall, renderAskResult } from "../ask-user/render.js";
|
|
17
|
-
import {
|
|
18
|
-
PROMPT_GUIDELINES as ASK_PROMPT_GUIDELINES,
|
|
19
|
-
PROMPT_SNIPPET as ASK_PROMPT_SNIPPET,
|
|
20
|
-
AskUserParamsSchema,
|
|
21
|
-
} from "../ask-user/schema.js";
|
|
22
|
-
import type { AskUserParams, DialogResult } from "../ask-user/types.js";
|
|
23
|
-
import {
|
|
24
|
-
formatResultText,
|
|
25
|
-
toToolDetails,
|
|
26
|
-
validateAskParams,
|
|
27
|
-
} from "../ask-user/validate.js";
|
|
28
|
-
|
|
29
|
-
const HARNESS_UI_AGENT_TYPES = new Set([
|
|
30
|
-
"harness/evaluator",
|
|
31
|
-
"harness/adversary",
|
|
32
|
-
"harness/tie-breaker",
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
export interface ParentHarnessUiHooks {
|
|
36
|
-
projectRoot?: string;
|
|
37
|
-
getParentEntries?: () => unknown[];
|
|
38
|
-
getParentRunContext?: () =>
|
|
39
|
-
| import("../../../lib/harness-run-context.js").HarnessRunContext
|
|
40
|
-
| null;
|
|
41
|
-
onPlanApproval?: (approval: PlanUserApproval) => void;
|
|
42
|
-
appendPlanDraft?: (draft: {
|
|
43
|
-
plan_packet: PlanPacketLike;
|
|
44
|
-
human_summary?: string;
|
|
45
|
-
}) => void;
|
|
46
|
-
onPlanCommitted?: (
|
|
47
|
-
runCtx: import("../../../lib/harness-run-context.js").HarnessRunContext,
|
|
48
|
-
packet: PlanPacketLike,
|
|
49
|
-
planPath: string,
|
|
50
|
-
) => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function agentTypeAllowsParentHarnessUi(agentType: string): boolean {
|
|
54
|
-
return HARNESS_UI_AGENT_TYPES.has(agentType);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @deprecated Use agentTypeAllowsParentHarnessUi */
|
|
58
|
-
export function agentTypeAllowsParentAskUser(agentType: string): boolean {
|
|
59
|
-
return agentTypeAllowsParentHarnessUi(agentType);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function notifyPlanApproval(
|
|
63
|
-
hooks: ParentHarnessUiHooks | undefined,
|
|
64
|
-
details: unknown,
|
|
65
|
-
toolName: "ask_user" | "approve_plan",
|
|
66
|
-
): void {
|
|
67
|
-
if (!hooks?.onPlanApproval) return;
|
|
68
|
-
const approval = parsePlanApprovalFromMessage({ toolName, details });
|
|
69
|
-
if (approval) hooks.onPlanApproval(approval);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function createParentHarnessUiBridgeFactory(
|
|
73
|
-
parentCtx: ExtensionContext,
|
|
74
|
-
agentType: string,
|
|
75
|
-
hooks?: ParentHarnessUiHooks,
|
|
76
|
-
): ((pi: ExtensionAPI) => void) | null {
|
|
77
|
-
if (!agentTypeAllowsParentHarnessUi(agentType)) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
return (pi: ExtensionAPI) => {
|
|
81
|
-
pi.registerTool({
|
|
82
|
-
name: "ask_user",
|
|
83
|
-
label: "Ask User",
|
|
84
|
-
description:
|
|
85
|
-
"Ask the user a structured question (parent session UI). Plan approval uses approve_plan on the parent orchestrator only.",
|
|
86
|
-
promptSnippet: ASK_PROMPT_SNIPPET,
|
|
87
|
-
promptGuidelines: ASK_PROMPT_GUIDELINES,
|
|
88
|
-
parameters: AskUserParamsSchema,
|
|
89
|
-
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
90
|
-
const validated = validateAskParams(params as AskUserParams);
|
|
91
|
-
if (typeof validated === "string") {
|
|
92
|
-
return {
|
|
93
|
-
content: [{ type: "text", text: validated }],
|
|
94
|
-
details: {
|
|
95
|
-
question: params.question ?? "",
|
|
96
|
-
options: [],
|
|
97
|
-
response: null,
|
|
98
|
-
cancelled: true,
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
let outcome: DialogResult;
|
|
103
|
-
if (parentCtx.hasUI) {
|
|
104
|
-
outcome = await runAskDialog(parentCtx.ui, validated);
|
|
105
|
-
} else {
|
|
106
|
-
outcome = await runAskFallback(parentCtx.ui, validated);
|
|
107
|
-
}
|
|
108
|
-
const details = toToolDetails(
|
|
109
|
-
validated,
|
|
110
|
-
outcome.response,
|
|
111
|
-
outcome.cancelled,
|
|
112
|
-
);
|
|
113
|
-
notifyPlanApproval(hooks, details, "ask_user");
|
|
114
|
-
const text = formatResultText(outcome.response, outcome.cancelled);
|
|
115
|
-
return {
|
|
116
|
-
content: [{ type: "text", text }],
|
|
117
|
-
details,
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
renderCall(args, theme) {
|
|
121
|
-
return renderAskCall(args, theme);
|
|
122
|
-
},
|
|
123
|
-
renderResult(result, options, theme) {
|
|
124
|
-
return renderAskResult(result, options, theme);
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** @deprecated Use createParentHarnessUiBridgeFactory */
|
|
131
|
-
export function createParentAskUserBridgeFactory(
|
|
132
|
-
parentCtx: ExtensionContext,
|
|
133
|
-
agentType: string,
|
|
134
|
-
hooks?: ParentHarnessUiHooks,
|
|
135
|
-
): ((pi: ExtensionAPI) => void) | null {
|
|
136
|
-
return createParentHarnessUiBridgeFactory(parentCtx, agentType, hooks);
|
|
137
|
-
}
|