ultimate-pi 0.17.0 → 0.18.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-context/SKILL.md +13 -6
- package/.agents/skills/harness-debate-plan/SKILL.md +37 -20
- package/.agents/skills/harness-eval/SKILL.md +6 -21
- package/.agents/skills/harness-governor/SKILL.md +4 -3
- package/.agents/skills/harness-orchestration/SKILL.md +39 -51
- package/.agents/skills/harness-plan/SKILL.md +23 -12
- package/.agents/skills/harness-review/SKILL.md +52 -0
- package/.agents/skills/harness-sentrux-setup/SKILL.md +13 -1
- package/.agents/skills/harness-steer/SKILL.md +14 -0
- package/.pi/agents/harness/adversary.md +3 -10
- package/.pi/agents/harness/evaluator.md +3 -12
- package/.pi/agents/harness/executor.md +12 -14
- package/.pi/agents/harness/planning/decompose.md +7 -4
- package/.pi/agents/harness/planning/hypothesis-validator.md +2 -0
- package/.pi/agents/harness/planning/hypothesis.md +3 -1
- package/.pi/agents/harness/planning/plan-adversary.md +2 -0
- package/.pi/agents/harness/planning/plan-evaluator.md +2 -0
- package/.pi/agents/harness/planning/plan-synthesizer.md +25 -0
- package/.pi/agents/harness/planning/planning-context.md +48 -0
- package/.pi/agents/harness/planning/review-integrator.md +2 -0
- package/.pi/agents/harness/planning/scout-graphify.md +3 -1
- package/.pi/agents/harness/planning/scout-semantic.md +3 -1
- package/.pi/agents/harness/planning/scout-structure.md +3 -1
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +2 -0
- package/.pi/agents/harness/sentrux-steward.md +51 -0
- package/.pi/extensions/00-posthog-network-bootstrap.ts +11 -0
- package/.pi/extensions/harness-live-widget.ts +27 -1
- package/.pi/extensions/harness-plan-approval.ts +62 -56
- package/.pi/extensions/harness-run-context.ts +541 -84
- package/.pi/extensions/harness-subagent-submit.ts +43 -10
- package/.pi/extensions/lib/harness-artifact-gate.ts +182 -0
- package/.pi/extensions/lib/harness-posthog.ts +9 -5
- package/.pi/extensions/lib/harness-spawn-topology.ts +188 -0
- package/.pi/extensions/lib/harness-subagent-auth.ts +1 -0
- package/.pi/extensions/lib/harness-subagent-policy.ts +23 -19
- package/.pi/extensions/lib/harness-subagent-precheck.ts +35 -9
- package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +66 -2
- package/.pi/extensions/lib/harness-subagent-submit-registry.ts +21 -3
- package/.pi/extensions/lib/harness-subagents-bridge.ts +7 -29
- package/.pi/extensions/lib/harness-subprocess-bootstrap.ts +73 -0
- package/.pi/extensions/lib/plan-approval/create-plan.ts +2 -3
- package/.pi/extensions/lib/plan-approval/resolve-disk.ts +102 -0
- package/.pi/extensions/lib/plan-approval/schema.ts +22 -8
- package/.pi/extensions/lib/plan-approval/types.ts +1 -1
- package/.pi/extensions/lib/plan-approval/validate.ts +2 -2
- package/.pi/extensions/lib/plan-approval-readiness.ts +241 -0
- package/.pi/extensions/lib/plan-debate-eligibility.ts +12 -5
- package/.pi/extensions/lib/plan-debate-gate.ts +22 -1
- package/.pi/extensions/lib/plan-debate-lanes.ts +32 -2
- package/.pi/extensions/lib/plan-review-gate.ts +8 -0
- package/.pi/extensions/lib/posthog-client.ts +76 -0
- package/.pi/extensions/policy-gate.ts +24 -19
- package/.pi/harness/agents.manifest.json +24 -16
- package/.pi/harness/corpus/cron.example +8 -0
- package/.pi/harness/corpus/graphify-kb-updater.config.json +159 -0
- package/.pi/harness/corpus/systemd/graphify-kb-updater.env.template +4 -0
- package/.pi/harness/corpus/systemd/graphify-kb-updater.service +17 -0
- package/.pi/harness/corpus/systemd/graphify-kb-updater.timer +11 -0
- package/.pi/harness/docs/adrs/0001-harness-constitution.md +2 -1
- package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +7 -6
- package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +6 -1
- package/.pi/harness/docs/adrs/0031-harness-run-context.md +1 -1
- package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +7 -0
- package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +3 -3
- package/.pi/harness/docs/adrs/0036-implementation-research-and-selective-debate.md +8 -5
- package/.pi/harness/docs/adrs/0039-harness-post-run-review-gate.md +47 -0
- package/.pi/harness/docs/adrs/0040-practice-grounded-orchestration.md +40 -0
- package/.pi/harness/docs/adrs/0041-intelligent-planning-reconnaissance.md +39 -0
- package/.pi/harness/docs/adrs/0042-agent-native-orchestration.md +35 -0
- package/.pi/harness/docs/adrs/0043-path-first-harness-tools.md +38 -0
- package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +36 -0
- package/.pi/harness/docs/adrs/README.md +10 -0
- package/.pi/harness/docs/graphify-kb-updater-runbook.md +157 -0
- package/.pi/harness/docs/practice-map.md +110 -0
- package/.pi/harness/env.harness.template +5 -3
- package/.pi/harness/evals/smoke/sentrux-stub.json +1 -1
- package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +5 -2
- package/.pi/harness/specs/README.md +1 -1
- package/.pi/harness/specs/harness-run-context.schema.json +11 -0
- package/.pi/harness/specs/harness-spawn-context.schema.json +14 -0
- package/.pi/harness/specs/plan-execution-plan.schema.json +39 -1
- package/.pi/harness/specs/plan-packet.schema.json +4 -0
- package/.pi/harness/specs/plan-phase-status.schema.json +17 -0
- package/.pi/harness/specs/plan-phase-waiver.schema.json +25 -0
- package/.pi/harness/specs/plan-planning-context.schema.json +50 -0
- package/.pi/harness/specs/repair-brief.schema.json +45 -0
- package/.pi/harness/specs/review-outcome.schema.json +46 -0
- package/.pi/harness/specs/sentrux-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +43 -0
- package/.pi/harness/specs/steer-state.schema.json +20 -0
- package/.pi/lib/harness-context-mode-policy.ts +256 -0
- package/.pi/lib/harness-repair-brief.ts +145 -0
- package/.pi/lib/harness-run-context.ts +591 -32
- package/.pi/lib/harness-ui-state.ts +87 -9
- package/.pi/prompts/harness-auto.md +9 -9
- package/.pi/prompts/harness-critic.md +3 -30
- package/.pi/prompts/harness-eval.md +4 -37
- package/.pi/prompts/harness-plan.md +118 -54
- package/.pi/prompts/harness-review.md +150 -15
- package/.pi/prompts/harness-run.md +62 -10
- package/.pi/prompts/harness-sentrux-steward.md +55 -0
- package/.pi/prompts/harness-steer.md +30 -0
- package/.pi/scripts/graphify-kb-updater.mjs +358 -0
- package/.pi/scripts/harness-verify.mjs +22 -6
- package/.pi/scripts/harness-web-policy-guard.mjs +68 -0
- package/.pi/scripts/validate-plan-dag.mjs +3 -3
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +11 -0
- package/package.json +5 -4
- package/.pi/prompts/git-sync.md +0 -124
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared write pipeline for harness subagent submit tools.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { mkdir } from "node:fs/promises";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
5
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { validateAgainstHarnessSchema } from "../../lib/harness-schema-validate.js";
|
|
8
8
|
import { resolveGuardedRunDir } from "../../lib/harness-subagent-submit-path.js";
|
|
9
9
|
import { writeYamlFile } from "../../lib/harness-yaml.js";
|
|
@@ -24,6 +24,54 @@ export interface SubmitPipelineResult {
|
|
|
24
24
|
human_required?: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export async function loadSubmitDocument(opts: {
|
|
28
|
+
projectRoot: string;
|
|
29
|
+
runDir: string;
|
|
30
|
+
document?: Record<string, unknown>;
|
|
31
|
+
source_path?: string;
|
|
32
|
+
}): Promise<
|
|
33
|
+
| { ok: true; document: Record<string, unknown> }
|
|
34
|
+
| { ok: false; validation_errors: string[] }
|
|
35
|
+
> {
|
|
36
|
+
if (opts.document && typeof opts.document === "object") {
|
|
37
|
+
return { ok: true, document: opts.document };
|
|
38
|
+
}
|
|
39
|
+
const rel = opts.source_path?.trim();
|
|
40
|
+
if (!rel) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
validation_errors: ["submit_* requires document or source_path"],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const abs = resolve(opts.runDir, rel.replace(/^\//, ""));
|
|
47
|
+
if (!abs.startsWith(resolve(opts.runDir))) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
validation_errors: [
|
|
51
|
+
"source_path must stay under the active run directory",
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const raw = await readFile(abs, "utf-8");
|
|
57
|
+
const { parse } = await import("yaml");
|
|
58
|
+
const doc = parse(raw) as Record<string, unknown>;
|
|
59
|
+
if (!doc || typeof doc !== "object") {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
validation_errors: ["source_path did not parse to an object"],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, document: doc };
|
|
66
|
+
} catch (e) {
|
|
67
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
validation_errors: [`source_path read failed: ${msg}`],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
27
75
|
export async function executeSubmitPipeline(opts: {
|
|
28
76
|
projectRoot: string;
|
|
29
77
|
specsDir: string;
|
|
@@ -56,6 +104,22 @@ export async function executeSubmitPipeline(opts: {
|
|
|
56
104
|
await mkdir(dirname(absPath), { recursive: true });
|
|
57
105
|
await writeYamlFile(absPath, opts.document);
|
|
58
106
|
|
|
107
|
+
if (opts.spec.toolName === "submit_executor_handoff") {
|
|
108
|
+
const rollback = opts.document.rollback_refs;
|
|
109
|
+
if (rollback && typeof rollback === "object" && !Array.isArray(rollback)) {
|
|
110
|
+
const rollbackPath = join(
|
|
111
|
+
runResolved.runDir,
|
|
112
|
+
"artifacts",
|
|
113
|
+
"executor-rollback.yaml",
|
|
114
|
+
);
|
|
115
|
+
await mkdir(dirname(rollbackPath), { recursive: true });
|
|
116
|
+
await writeYamlFile(rollbackPath, {
|
|
117
|
+
schema_version: "1.0.0",
|
|
118
|
+
...(rollback as Record<string, unknown>),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
59
123
|
let laneResult: ApplyDebateLaneResult | undefined;
|
|
60
124
|
if (opts.spec.debateLane) {
|
|
61
125
|
laneResult = await applyDebateLaneFromDoc({
|
|
@@ -22,6 +22,12 @@ function roundPath(prefix: string, doc: Record<string, unknown>): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
|
|
25
|
+
{
|
|
26
|
+
toolName: "submit_planning_context",
|
|
27
|
+
agents: ["harness/planning/planning-context"],
|
|
28
|
+
schemaFile: "plan-planning-context.schema.json",
|
|
29
|
+
artifactPath: "artifacts/planning-context.yaml",
|
|
30
|
+
},
|
|
25
31
|
{
|
|
26
32
|
toolName: "submit_scout_findings",
|
|
27
33
|
agents: [
|
|
@@ -42,13 +48,16 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
|
|
|
42
48
|
},
|
|
43
49
|
{
|
|
44
50
|
toolName: "submit_decomposition_brief",
|
|
45
|
-
agents: ["harness/planning/decompose"],
|
|
51
|
+
agents: ["harness/planning/decompose", "harness/planning/plan-synthesizer"],
|
|
46
52
|
schemaFile: "plan-decomposition-brief.schema.json",
|
|
47
53
|
artifactPath: "artifacts/decomposition.yaml",
|
|
48
54
|
},
|
|
49
55
|
{
|
|
50
56
|
toolName: "submit_hypothesis_brief",
|
|
51
|
-
agents: [
|
|
57
|
+
agents: [
|
|
58
|
+
"harness/planning/hypothesis",
|
|
59
|
+
"harness/planning/plan-synthesizer",
|
|
60
|
+
],
|
|
52
61
|
schemaFile: "plan-hypothesis-brief.schema.json",
|
|
53
62
|
artifactPath: "artifacts/hypothesis.yaml",
|
|
54
63
|
},
|
|
@@ -66,7 +75,10 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
|
|
|
66
75
|
},
|
|
67
76
|
{
|
|
68
77
|
toolName: "submit_execution_plan_brief",
|
|
69
|
-
agents: [
|
|
78
|
+
agents: [
|
|
79
|
+
"harness/planning/execution-plan-author",
|
|
80
|
+
"harness/planning/plan-synthesizer",
|
|
81
|
+
],
|
|
70
82
|
schemaFile: "plan-execution-plan-brief.schema.json",
|
|
71
83
|
artifactPath: "artifacts/execution-plan-draft.yaml",
|
|
72
84
|
},
|
|
@@ -129,6 +141,12 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
|
|
|
129
141
|
artifactPath: "artifacts/human-required.yaml",
|
|
130
142
|
humanRequired: true,
|
|
131
143
|
},
|
|
144
|
+
{
|
|
145
|
+
toolName: "submit_sentrux_manifest_proposal",
|
|
146
|
+
agents: ["harness/sentrux-steward"],
|
|
147
|
+
schemaFile: "sentrux-manifest-proposal.schema.json",
|
|
148
|
+
artifactPath: "artifacts/sentrux-manifest-proposal.yaml",
|
|
149
|
+
},
|
|
132
150
|
] as const;
|
|
133
151
|
|
|
134
152
|
export const SUBMIT_TOOLS_BY_AGENT: Readonly<
|
|
@@ -118,32 +118,6 @@ export function createHarnessSubagentsExtension(
|
|
|
118
118
|
resolveSubprocessEnv: (task, agent) => {
|
|
119
119
|
if (!agent.name.startsWith("harness/")) return undefined;
|
|
120
120
|
const ctx = parseSpawnContextFromTask(task);
|
|
121
|
-
// #region agent log
|
|
122
|
-
fetch(
|
|
123
|
-
"http://127.0.0.1:7928/ingest/a5d40896-34cb-4f12-97db-df7ada0b22f0",
|
|
124
|
-
{
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: {
|
|
127
|
-
"Content-Type": "application/json",
|
|
128
|
-
"X-Debug-Session-Id": "2ca12b",
|
|
129
|
-
},
|
|
130
|
-
body: JSON.stringify({
|
|
131
|
-
sessionId: "2ca12b",
|
|
132
|
-
hypothesisId: "H1",
|
|
133
|
-
location: "harness-subagents-bridge.ts:resolveSubprocessEnv",
|
|
134
|
-
message: "parsed spawn context for subprocess env",
|
|
135
|
-
data: {
|
|
136
|
-
agent: agent.name,
|
|
137
|
-
hasCtx: Boolean(ctx?.run_id),
|
|
138
|
-
run_id: ctx?.run_id ?? null,
|
|
139
|
-
run_dir: ctx?.run_dir ?? null,
|
|
140
|
-
taskPrefix: task.slice(0, 160),
|
|
141
|
-
},
|
|
142
|
-
timestamp: Date.now(),
|
|
143
|
-
}),
|
|
144
|
-
},
|
|
145
|
-
).catch(() => {});
|
|
146
|
-
// #endregion
|
|
147
121
|
if (!ctx?.run_id) return undefined;
|
|
148
122
|
return {
|
|
149
123
|
HARNESS_RUN_ID: ctx.run_id,
|
|
@@ -168,11 +142,16 @@ export function createHarnessSubagentsExtension(
|
|
|
168
142
|
return { ok: false, message: budget.message };
|
|
169
143
|
}
|
|
170
144
|
const entries = ctx.sessionManager.getEntries();
|
|
171
|
-
const
|
|
172
|
-
const
|
|
145
|
+
const runCtx = getLatestRunContext(entries);
|
|
146
|
+
const phase = inferPhaseForPrecheck(entries);
|
|
147
|
+
const pre = await precheckHarnessSubagentSpawn(
|
|
173
148
|
params as Parameters<typeof precheckHarnessSubagentSpawn>[0],
|
|
174
149
|
agents,
|
|
175
150
|
phase,
|
|
151
|
+
{
|
|
152
|
+
projectRoot: ctx.cwd,
|
|
153
|
+
runId: runCtx?.run_id ?? null,
|
|
154
|
+
},
|
|
176
155
|
);
|
|
177
156
|
if (!pre.ok) {
|
|
178
157
|
return { ok: false, message: pre.message };
|
|
@@ -185,7 +164,6 @@ export function createHarnessSubagentsExtension(
|
|
|
185
164
|
return { ok: false, message: refreshMsg };
|
|
186
165
|
}
|
|
187
166
|
}
|
|
188
|
-
const runCtx = getLatestRunContext(entries);
|
|
189
167
|
const runId =
|
|
190
168
|
runCtx?.run_id ??
|
|
191
169
|
getRunIdFromSession(entries, lastSessionId) ??
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed harness-run-context + policy-gate session entries in subagent subprocesses.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionContext,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import {
|
|
10
|
+
getLatestRunContext,
|
|
11
|
+
type HarnessRunContext,
|
|
12
|
+
isHarnessSubprocess,
|
|
13
|
+
loadRunContextForSubprocess,
|
|
14
|
+
nowIso,
|
|
15
|
+
policyBootstrapFromRunContext,
|
|
16
|
+
} from "../../lib/harness-run-context.js";
|
|
17
|
+
|
|
18
|
+
type PolicyState = {
|
|
19
|
+
phase: "plan" | "execute" | "evaluate" | "adversary" | "merge";
|
|
20
|
+
approvedPlan: boolean;
|
|
21
|
+
planId: string | null;
|
|
22
|
+
budgetBypass: boolean;
|
|
23
|
+
aborted: boolean;
|
|
24
|
+
abortReason: string | null;
|
|
25
|
+
abortedAt: string | null;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function defaultPolicyState(): PolicyState {
|
|
30
|
+
return {
|
|
31
|
+
phase: "plan",
|
|
32
|
+
approvedPlan: false,
|
|
33
|
+
planId: null,
|
|
34
|
+
budgetBypass: false,
|
|
35
|
+
aborted: false,
|
|
36
|
+
abortReason: null,
|
|
37
|
+
abortedAt: null,
|
|
38
|
+
updatedAt: nowIso(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Append disk-backed run + policy entries when subprocess has no session context yet. */
|
|
43
|
+
export async function bootstrapHarnessSubprocessFromEnv(
|
|
44
|
+
pi: ExtensionAPI,
|
|
45
|
+
ctx: ExtensionContext,
|
|
46
|
+
): Promise<HarnessRunContext | null> {
|
|
47
|
+
if (!isHarnessSubprocess()) return null;
|
|
48
|
+
const entries = ctx.sessionManager.getEntries();
|
|
49
|
+
if (getLatestRunContext(entries)) return getLatestRunContext(entries);
|
|
50
|
+
|
|
51
|
+
const projectRoot = ctx.cwd;
|
|
52
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
53
|
+
const disk = await loadRunContextForSubprocess(projectRoot);
|
|
54
|
+
if (!disk?.plan_ready) return null;
|
|
55
|
+
|
|
56
|
+
const runCtx: HarnessRunContext = {
|
|
57
|
+
...disk,
|
|
58
|
+
pi_session_id: sessionId,
|
|
59
|
+
};
|
|
60
|
+
pi.appendEntry("harness-run-context", runCtx);
|
|
61
|
+
|
|
62
|
+
const boot = policyBootstrapFromRunContext(runCtx);
|
|
63
|
+
const policy: PolicyState = {
|
|
64
|
+
...defaultPolicyState(),
|
|
65
|
+
phase: boot.phase,
|
|
66
|
+
approvedPlan: boot.approvedPlan,
|
|
67
|
+
planId: boot.planId,
|
|
68
|
+
updatedAt: nowIso(),
|
|
69
|
+
};
|
|
70
|
+
pi.appendEntry("harness-policy-state", policy);
|
|
71
|
+
|
|
72
|
+
return runCtx;
|
|
73
|
+
}
|
|
@@ -12,12 +12,11 @@ import {
|
|
|
12
12
|
import { writeYamlFile } from "../../../lib/harness-yaml.js";
|
|
13
13
|
import { writePlanReviewMarkdown } from "./plan-review.js";
|
|
14
14
|
|
|
15
|
-
export const CREATE_PLAN_SNIPPET =
|
|
16
|
-
"create_plan({ plan_packet: { ...approved PlanPacket } })";
|
|
15
|
+
export const CREATE_PLAN_SNIPPET = "create_plan()";
|
|
17
16
|
|
|
18
17
|
export const CREATE_PLAN_GUIDELINES = [
|
|
19
18
|
"Call create_plan only after the user approves via approve_plan (Approve selection).",
|
|
20
|
-
"
|
|
19
|
+
"Uses plan-packet.yaml on disk at plan_packet_path (path-first; no inline packet).",
|
|
21
20
|
"Never use write or edit for plan-packet.yaml; create_plan is the only allowed plan write.",
|
|
22
21
|
];
|
|
23
22
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
canonicalPlanPath,
|
|
4
|
+
getLatestRunContext,
|
|
5
|
+
harnessRunsRoot,
|
|
6
|
+
type PlanPacketLike,
|
|
7
|
+
RESEARCH_BRIEF_BASENAME,
|
|
8
|
+
readPlanPacketFromPath,
|
|
9
|
+
validatePlanPacket,
|
|
10
|
+
} from "../../../lib/harness-run-context.js";
|
|
11
|
+
import { readYamlFile } from "../../../lib/harness-yaml.js";
|
|
12
|
+
import type { ApprovePlanParams, PlanResearchBrief } from "./types.js";
|
|
13
|
+
|
|
14
|
+
function isNonEmptyPacket(
|
|
15
|
+
packet: PlanPacketLike | null | undefined,
|
|
16
|
+
): packet is PlanPacketLike {
|
|
17
|
+
return Boolean(
|
|
18
|
+
packet &&
|
|
19
|
+
typeof packet === "object" &&
|
|
20
|
+
Object.keys(packet).length > 0 &&
|
|
21
|
+
packet.plan_id,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function loadResearchBriefFromRun(
|
|
26
|
+
runId: string,
|
|
27
|
+
projectRoot: string,
|
|
28
|
+
): Promise<PlanResearchBrief | undefined> {
|
|
29
|
+
try {
|
|
30
|
+
const path = join(
|
|
31
|
+
harnessRunsRoot(projectRoot),
|
|
32
|
+
runId,
|
|
33
|
+
RESEARCH_BRIEF_BASENAME,
|
|
34
|
+
);
|
|
35
|
+
return (await readYamlFile(
|
|
36
|
+
path,
|
|
37
|
+
RESEARCH_BRIEF_BASENAME,
|
|
38
|
+
)) as PlanResearchBrief;
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Path-first approve_plan: load packet + research brief from active run dir. */
|
|
45
|
+
export async function resolveApprovePlanParamsFromDisk(
|
|
46
|
+
params: ApprovePlanParams,
|
|
47
|
+
entries: unknown[],
|
|
48
|
+
projectRoot: string,
|
|
49
|
+
): Promise<
|
|
50
|
+
| {
|
|
51
|
+
ok: true;
|
|
52
|
+
plan_packet: PlanPacketLike;
|
|
53
|
+
research_brief?: PlanResearchBrief;
|
|
54
|
+
}
|
|
55
|
+
| { ok: false; error: string }
|
|
56
|
+
> {
|
|
57
|
+
const inline = params.plan_packet;
|
|
58
|
+
if (isNonEmptyPacket(inline)) {
|
|
59
|
+
const validation = validatePlanPacket(inline);
|
|
60
|
+
if (!validation.valid) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: `approve_plan: invalid plan_packet — ${validation.errors.join("; ")}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
plan_packet: inline,
|
|
69
|
+
research_brief: params.research_brief ?? undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const runCtx = getLatestRunContext(entries);
|
|
74
|
+
if (!runCtx?.run_id) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error:
|
|
78
|
+
'approve_plan: no active harness run. Run /harness-plan "<task>" first.',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const planPath =
|
|
82
|
+
runCtx.plan_packet_path ?? canonicalPlanPath(runCtx.run_id, projectRoot);
|
|
83
|
+
const packet = await readPlanPacketFromPath(planPath);
|
|
84
|
+
if (!isNonEmptyPacket(packet)) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
error:
|
|
88
|
+
"approve_plan: plan_packet missing on disk. Write plan-packet.yaml draft before approve_plan.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const validation = validatePlanPacket(packet);
|
|
92
|
+
if (!validation.valid) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: `approve_plan: invalid plan_packet on disk — ${validation.errors.join("; ")}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const research_brief =
|
|
99
|
+
params.research_brief ??
|
|
100
|
+
(await loadResearchBriefFromRun(runCtx.run_id, projectRoot));
|
|
101
|
+
return { ok: true, plan_packet: packet, research_brief };
|
|
102
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
|
|
3
3
|
export const ApprovePlanParamsSchema = Type.Object({
|
|
4
|
-
plan_packet: Type.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
plan_packet: Type.Optional(
|
|
5
|
+
Type.Object(
|
|
6
|
+
{},
|
|
7
|
+
{
|
|
8
|
+
description:
|
|
9
|
+
"Optional inline PlanPacket (deprecated). Default: read plan-packet.yaml from active run (ADR 0043).",
|
|
10
|
+
},
|
|
11
|
+
),
|
|
10
12
|
),
|
|
11
13
|
human_summary: Type.Optional(
|
|
12
14
|
Type.String({
|
|
@@ -45,10 +47,22 @@ export const ApprovePlanParamsSchema = Type.Object({
|
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
export const PROMPT_SNIPPET =
|
|
48
|
-
"approve_plan({
|
|
50
|
+
"approve_plan({ human_summary?: string }) — loads plan-packet.yaml from active run";
|
|
49
51
|
|
|
50
52
|
export const PROMPT_GUIDELINES = [
|
|
51
|
-
"Call approve_plan once
|
|
53
|
+
"Call approve_plan once when plan-packet.yaml is on disk (path-first; do not embed full packet in tool args).",
|
|
52
54
|
"Use ask_user only for clarification — not for final plan approval.",
|
|
53
55
|
"On Request changes, revise the plan and call approve_plan again.",
|
|
54
56
|
];
|
|
57
|
+
|
|
58
|
+
export const CreatePlanParamsSchema = Type.Object({
|
|
59
|
+
plan_packet: Type.Optional(
|
|
60
|
+
Type.Object(
|
|
61
|
+
{},
|
|
62
|
+
{
|
|
63
|
+
description:
|
|
64
|
+
"Optional inline packet (deprecated). Default: read approved plan from plan_packet_path.",
|
|
65
|
+
},
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
});
|
|
@@ -22,7 +22,7 @@ export interface PlanResearchBrief {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface ApprovePlanParams {
|
|
25
|
-
plan_packet
|
|
25
|
+
plan_packet?: PlanPacketLike;
|
|
26
26
|
human_summary?: string;
|
|
27
27
|
research_brief?: PlanResearchBrief | null;
|
|
28
28
|
options?: Array<string | { title: string; description?: string }>;
|
|
@@ -15,8 +15,8 @@ export function validateApprovePlanParams(
|
|
|
15
15
|
params: ApprovePlanParams,
|
|
16
16
|
): ValidatedApprovePlanParams | string {
|
|
17
17
|
const packet = params.plan_packet;
|
|
18
|
-
if (!packet || typeof packet !== "object") {
|
|
19
|
-
return "approve_plan: plan_packet
|
|
18
|
+
if (!packet || typeof packet !== "object" || !packet.plan_id) {
|
|
19
|
+
return "approve_plan: plan_packet must be resolved from disk before validate (use resolveApprovePlanParamsFromDisk).";
|
|
20
20
|
}
|
|
21
21
|
const validation = validatePlanPacket(packet as PlanPacketLike);
|
|
22
22
|
if (!validation.valid) {
|