ultimate-pi 0.16.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 +4 -2
- package/.pi/agents/harness/planning/implementation-researcher.md +1 -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-debate-tools.ts +12 -3
- 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 +553 -84
- package/.pi/extensions/harness-subagent-submit.ts +43 -33
- package/.pi/extensions/harness-telemetry.ts +29 -4
- package/.pi/extensions/lib/debate-bus-core.ts +15 -9
- 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 +105 -19
- package/.pi/extensions/lib/harness-subagent-policy.ts +37 -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 +91 -28
- 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 +67 -7
- package/.pi/extensions/lib/plan-debate-focus.ts +21 -9
- package/.pi/extensions/lib/plan-debate-gate.ts +101 -17
- package/.pi/extensions/lib/plan-debate-lanes.ts +57 -3
- package/.pi/extensions/lib/plan-debate-round-status.ts +18 -7
- package/.pi/extensions/lib/plan-messenger.ts +4 -0
- package/.pi/extensions/lib/plan-review-gate.ts +59 -0
- package/.pi/extensions/lib/posthog-client.ts +76 -0
- package/.pi/extensions/policy-gate.ts +24 -19
- package/.pi/extensions/trace-recorder.ts +1 -0
- 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/fixtures/plan-phase/minimal-med-fast/artifacts/implementation-research.yaml +28 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/review-round-consolidated.yaml +25 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-packet.yaml +196 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-review.md +14 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/research-brief.yaml +62 -0
- package/.pi/harness/evals/smoke/sentrux-stub.json +1 -1
- package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +43 -17
- 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/plan-review-round-draft.schema.json +1 -1
- 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/model-router.example.json +13 -4
- 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 +139 -57
- 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-setup.md +4 -4
- package/.pi/prompts/harness-steer.md +30 -0
- package/.pi/scripts/graphify-kb-updater.mjs +358 -0
- package/.pi/scripts/harness-generate-model-router.mjs +118 -36
- package/.pi/scripts/harness-model-router-routing.test.mjs +97 -0
- package/.pi/scripts/harness-sync-model-router.mjs +15 -2
- package/.pi/scripts/harness-verify.mjs +51 -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 +22 -0
- package/package.json +5 -4
- package/vendor/pi-model-router/UPSTREAM_PIN.md +3 -1
- package/vendor/pi-model-router/extensions/commands.ts +4 -4
- package/vendor/pi-model-router/extensions/index.ts +21 -0
- package/vendor/pi-model-router/extensions/provider.ts +130 -79
- package/vendor/pi-model-router/extensions/routing.ts +148 -0
- package/vendor/pi-model-router/extensions/state.ts +3 -0
- package/vendor/pi-model-router/extensions/types.ts +9 -0
- package/vendor/pi-model-router/extensions/ui.ts +16 -2
- package/.pi/prompts/git-sync.md +0 -124
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* in before_agent_start so trace-recorder reuses it on agent_start.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
9
|
import { dirname, join } from "node:path";
|
|
11
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
11
|
import { Type } from "@sinclair/typebox";
|
|
13
12
|
import {
|
|
14
13
|
canonicalPlanPath,
|
|
14
|
+
claimRunOwnership,
|
|
15
15
|
createFreshRunContext,
|
|
16
|
+
criticalPathWorkItemIdsFromPlanPacket,
|
|
16
17
|
driftGateActive,
|
|
17
|
-
|
|
18
|
+
evaluateCrossSessionResume,
|
|
18
19
|
extractWritePathFromToolInput,
|
|
19
20
|
formatActivePlanBlock,
|
|
21
|
+
formatCrossSessionResumeMessage,
|
|
20
22
|
formatPlanContextBlock,
|
|
21
23
|
getLatestHarnessTurn,
|
|
22
24
|
getLatestPolicyPhase,
|
|
@@ -40,13 +42,20 @@ import {
|
|
|
40
42
|
nowIso,
|
|
41
43
|
type PlanPacketSummary,
|
|
42
44
|
parseHarnessSlashInput,
|
|
45
|
+
parseHarnessUseRunArgs,
|
|
43
46
|
parsePlanApprovalFromMessage,
|
|
44
47
|
planPacketSummary,
|
|
48
|
+
readExecutorHandoffFromRun,
|
|
45
49
|
readPlanPacketFromPath,
|
|
50
|
+
readReviewOutcomeFromRun,
|
|
46
51
|
resolveArgsForCommand,
|
|
52
|
+
resolveCompletionStatuses,
|
|
47
53
|
saveProjectActiveRun,
|
|
48
54
|
saveRunContextToDisk,
|
|
55
|
+
sessionHasResumePromptForRun,
|
|
56
|
+
shouldAutoClaimHarnessRun,
|
|
49
57
|
shouldReuseHarnessRunId,
|
|
58
|
+
steerMaxAttemptsFromEnv,
|
|
50
59
|
userVisiblePromptSlice,
|
|
51
60
|
validatePlanOverridePath,
|
|
52
61
|
validatePlanPacket,
|
|
@@ -61,6 +70,7 @@ import {
|
|
|
61
70
|
evaluateHarnessSubagentToolCall,
|
|
62
71
|
isSubmitToolName,
|
|
63
72
|
} from "./lib/harness-subagent-policy.js";
|
|
73
|
+
import { bootstrapHarnessSubprocessFromEnv } from "./lib/harness-subprocess-bootstrap.js";
|
|
64
74
|
import { isReviewRoundArtifactPath } from "./lib/plan-debate-gate.js";
|
|
65
75
|
import { isReviewRoundYamlWriteAllowed } from "./lib/plan-debate-write-guard.js";
|
|
66
76
|
|
|
@@ -83,6 +93,21 @@ function persistContext(pi: ExtensionAPI, ctx: HarnessRunContext): void {
|
|
|
83
93
|
pi.appendEntry("harness-run-context", ctx);
|
|
84
94
|
void saveRunContextToDisk(ctx);
|
|
85
95
|
void saveProjectActiveRun(ctx);
|
|
96
|
+
pi.events.emit("harness-run-context:updated", { run_id: ctx.run_id });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function syncPolicyFromRunContext(
|
|
100
|
+
pi: ExtensionAPI,
|
|
101
|
+
entries: unknown[],
|
|
102
|
+
runCtx: HarnessRunContext,
|
|
103
|
+
): void {
|
|
104
|
+
syncPolicyFromPlan(
|
|
105
|
+
pi,
|
|
106
|
+
entries,
|
|
107
|
+
runCtx.plan_id ?? "plan-unknown",
|
|
108
|
+
runCtx.phase,
|
|
109
|
+
runCtx.plan_ready,
|
|
110
|
+
);
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
function extractTaskSummary(args: string, prompt?: string): string | null {
|
|
@@ -164,6 +189,10 @@ function syncPolicyFromPlan(
|
|
|
164
189
|
});
|
|
165
190
|
}
|
|
166
191
|
|
|
192
|
+
function hydrateFromSession(entries: unknown[]): HarnessRunContext | null {
|
|
193
|
+
return getLatestRunContext(entries);
|
|
194
|
+
}
|
|
195
|
+
|
|
167
196
|
async function hydrateFromDisk(
|
|
168
197
|
sessionId: string,
|
|
169
198
|
projectRoot: string,
|
|
@@ -201,15 +230,57 @@ function needsClarificationFollowUp(ctx: HarnessRunContext | null): boolean {
|
|
|
201
230
|
return ctx?.status === "active" && ctx.last_outcome === "needs_clarification";
|
|
202
231
|
}
|
|
203
232
|
|
|
233
|
+
async function offerCrossSessionResume(
|
|
234
|
+
pi: ExtensionAPI,
|
|
235
|
+
ctx: {
|
|
236
|
+
hasUI: boolean;
|
|
237
|
+
sessionManager: { getEntries(): unknown[] };
|
|
238
|
+
ui: {
|
|
239
|
+
notify(
|
|
240
|
+
message: string,
|
|
241
|
+
type?: "info" | "warning" | "error",
|
|
242
|
+
): void;
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
): Promise<void> {
|
|
246
|
+
const projectRoot = process.cwd();
|
|
247
|
+
const entries = getEntries(ctx);
|
|
248
|
+
const info = await evaluateCrossSessionResume(projectRoot, entries);
|
|
249
|
+
if (!info || sessionHasResumePromptForRun(entries, info.runId)) return;
|
|
250
|
+
|
|
251
|
+
const content = formatCrossSessionResumeMessage(info);
|
|
252
|
+
pi.appendEntry("harness-session-resume-prompt", {
|
|
253
|
+
run_id: info.runId,
|
|
254
|
+
resume_command: info.resumeCommand,
|
|
255
|
+
shown_at: nowIso(),
|
|
256
|
+
});
|
|
257
|
+
pi.sendMessage({
|
|
258
|
+
customType: "harness-session-resume-prompt",
|
|
259
|
+
content,
|
|
260
|
+
display: true,
|
|
261
|
+
});
|
|
262
|
+
if (ctx.hasUI) {
|
|
263
|
+
ctx.ui.notify(
|
|
264
|
+
`Harness run on disk. Resume with ${info.resumeCommand}`,
|
|
265
|
+
"info",
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
pi.events.emit("harness-cross-session-resume", {
|
|
269
|
+
run_id: info.runId,
|
|
270
|
+
resume_command: info.resumeCommand,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
204
274
|
export default function harnessRunContext(pi: ExtensionAPI) {
|
|
205
275
|
if (!claimExtensionLoad("harness-run-context", MODULE_URL)) return;
|
|
206
276
|
let activeCtx: HarnessRunContext | null = null;
|
|
207
277
|
|
|
208
278
|
pi.on("session_start", async (_event, ctx) => {
|
|
209
|
-
const sessionId = ctx.sessionManager.getSessionId();
|
|
210
|
-
const projectRoot = process.cwd();
|
|
211
279
|
const entries = getEntries(ctx);
|
|
212
|
-
activeCtx =
|
|
280
|
+
activeCtx = hydrateFromSession(entries);
|
|
281
|
+
const booted = await bootstrapHarnessSubprocessFromEnv(pi, ctx);
|
|
282
|
+
if (booted) activeCtx = booted;
|
|
283
|
+
if (!booted) await offerCrossSessionResume(pi, ctx);
|
|
213
284
|
});
|
|
214
285
|
|
|
215
286
|
pi.on("input", async (event) => {
|
|
@@ -338,36 +409,57 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
338
409
|
}
|
|
339
410
|
|
|
340
411
|
if (command === "harness-use-run") {
|
|
341
|
-
const
|
|
342
|
-
if (!runId) {
|
|
412
|
+
const parsed = parseHarnessUseRunArgs(args);
|
|
413
|
+
if (!parsed.runId) {
|
|
343
414
|
return {
|
|
344
415
|
message: {
|
|
345
416
|
customType: "harness-run-context-block",
|
|
346
417
|
display: true,
|
|
347
|
-
content: "Usage: /harness-use-run <run-id>",
|
|
418
|
+
content: "Usage: /harness-use-run <run-id> [--claim] [--readonly]",
|
|
348
419
|
},
|
|
349
420
|
};
|
|
350
421
|
}
|
|
351
|
-
const disk = await loadRunContextFromDisk(runId, projectRoot);
|
|
422
|
+
const disk = await loadRunContextFromDisk(parsed.runId, projectRoot);
|
|
352
423
|
if (!disk) {
|
|
353
424
|
return {
|
|
354
425
|
message: {
|
|
355
426
|
customType: "harness-run-context-block",
|
|
356
427
|
display: true,
|
|
357
|
-
content: `No run directory for ${runId}. Check .pi/harness/runs/.`,
|
|
428
|
+
content: `No run directory for ${parsed.runId}. Check .pi/harness/runs/.`,
|
|
358
429
|
},
|
|
359
430
|
};
|
|
360
431
|
}
|
|
361
432
|
activeCtx = {
|
|
362
433
|
...disk,
|
|
363
434
|
pi_session_id: sessionId,
|
|
364
|
-
turn_override_run_id: runId,
|
|
435
|
+
turn_override_run_id: parsed.runId,
|
|
365
436
|
};
|
|
366
|
-
if (
|
|
437
|
+
if (parsed.claim) {
|
|
438
|
+
activeCtx = claimRunOwnership(activeCtx, sessionId);
|
|
439
|
+
}
|
|
440
|
+
const statuses = await resolveCompletionStatuses(
|
|
441
|
+
getEntries(ctx),
|
|
442
|
+
activeCtx.run_id,
|
|
443
|
+
projectRoot,
|
|
444
|
+
);
|
|
445
|
+
if (activeCtx.owner_pi_session_id !== sessionId && !parsed.claim) {
|
|
367
446
|
activeCtx.next_recommended_command =
|
|
368
|
-
"Read-only:
|
|
447
|
+
"Read-only: use /harness-use-run <run-id> --claim to take ownership, or /harness-new-run.";
|
|
448
|
+
} else {
|
|
449
|
+
activeCtx.next_recommended_command = nextStepAfterOutcome({
|
|
450
|
+
phase: activeCtx.phase,
|
|
451
|
+
planStatus: activeCtx.plan_ready ? "ready" : null,
|
|
452
|
+
lastCompletedStep: activeCtx.last_completed_step,
|
|
453
|
+
lastOutcome: activeCtx.last_outcome,
|
|
454
|
+
executionStatus: statuses.executionStatus,
|
|
455
|
+
evalStatus: statuses.evalStatus,
|
|
456
|
+
adversaryComplete: statuses.adversaryComplete,
|
|
457
|
+
aborted: activeCtx.status === "aborted",
|
|
458
|
+
});
|
|
369
459
|
}
|
|
460
|
+
activeCtx.updated_at = nowIso();
|
|
370
461
|
persistContext(pi, activeCtx);
|
|
462
|
+
syncPolicyFromRunContext(pi, getEntries(ctx), activeCtx);
|
|
371
463
|
return {
|
|
372
464
|
systemPrompt: `${event.systemPrompt}\n\n${formatPlanContextBlock(activeCtx)}`,
|
|
373
465
|
};
|
|
@@ -445,6 +537,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
445
537
|
const crossSessionCmd = new Set([
|
|
446
538
|
"harness-eval",
|
|
447
539
|
"harness-review",
|
|
540
|
+
"harness-steer",
|
|
448
541
|
"harness-critic",
|
|
449
542
|
"harness-trace",
|
|
450
543
|
"harness-incident",
|
|
@@ -484,6 +577,13 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
484
577
|
activeCtx.updated_at = new Date().toISOString();
|
|
485
578
|
activeCtx.pi_session_id = sessionId;
|
|
486
579
|
|
|
580
|
+
if (
|
|
581
|
+
shouldAutoClaimHarnessRun(command, args) &&
|
|
582
|
+
activeCtx.owner_pi_session_id !== sessionId
|
|
583
|
+
) {
|
|
584
|
+
activeCtx = claimRunOwnership(activeCtx, sessionId);
|
|
585
|
+
}
|
|
586
|
+
|
|
487
587
|
if (resolved.planPath && resolved.runId) {
|
|
488
588
|
const check = validatePlanOverridePath(
|
|
489
589
|
resolved.planPath,
|
|
@@ -518,24 +618,45 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
518
618
|
activeCtx.last_completed_step === "execute" &&
|
|
519
619
|
activeCtx.last_outcome === "completed"
|
|
520
620
|
) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
621
|
+
return {
|
|
622
|
+
message: {
|
|
623
|
+
customType: "harness-run-context-block",
|
|
624
|
+
display: true,
|
|
625
|
+
content:
|
|
626
|
+
"Execute already completed for this run. Next: /harness-review (same session), or /harness-abort to replan.",
|
|
627
|
+
},
|
|
628
|
+
};
|
|
524
629
|
}
|
|
525
630
|
|
|
526
631
|
let planSummary: PlanPacketSummary | null = null;
|
|
632
|
+
let planPacketForSpawn: Awaited<ReturnType<typeof readPlanPacketFromPath>> =
|
|
633
|
+
null;
|
|
527
634
|
if (activeCtx.plan_packet_path) {
|
|
528
|
-
|
|
529
|
-
|
|
635
|
+
planPacketForSpawn = await readPlanPacketFromPath(
|
|
636
|
+
activeCtx.plan_packet_path,
|
|
637
|
+
);
|
|
638
|
+
if (planPacketForSpawn) {
|
|
530
639
|
planSummary = planPacketSummary(
|
|
531
|
-
|
|
640
|
+
planPacketForSpawn,
|
|
532
641
|
activeCtx.plan_packet_path,
|
|
533
642
|
activeCtx.plan_ready ? "ready" : "draft",
|
|
534
643
|
);
|
|
535
|
-
activeCtx.plan_id =
|
|
644
|
+
activeCtx.plan_id = planPacketForSpawn.plan_id ?? activeCtx.plan_id;
|
|
536
645
|
}
|
|
537
646
|
}
|
|
538
647
|
|
|
648
|
+
let contextSpawnOpts:
|
|
649
|
+
| Parameters<typeof formatPlanContextBlock>[1]
|
|
650
|
+
| undefined;
|
|
651
|
+
if (command === "harness-run" && planPacketForSpawn) {
|
|
652
|
+
const criticalIds =
|
|
653
|
+
criticalPathWorkItemIdsFromPlanPacket(planPacketForSpawn);
|
|
654
|
+
contextSpawnOpts = {
|
|
655
|
+
mode: "execute",
|
|
656
|
+
critical_path_work_item_ids: criticalIds,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
539
660
|
let activePlanBlock = "";
|
|
540
661
|
if (command === "harness-plan" || command === "harness-auto") {
|
|
541
662
|
const mode =
|
|
@@ -549,6 +670,16 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
549
670
|
"execute",
|
|
550
671
|
planSummary,
|
|
551
672
|
);
|
|
673
|
+
} else if (command === "harness-steer") {
|
|
674
|
+
activePlanBlock = formatActivePlanBlock(
|
|
675
|
+
activeCtx,
|
|
676
|
+
"execute",
|
|
677
|
+
planSummary,
|
|
678
|
+
);
|
|
679
|
+
contextSpawnOpts = {
|
|
680
|
+
mode: "repair",
|
|
681
|
+
repair_brief_path: "artifacts/repair-brief.yaml",
|
|
682
|
+
};
|
|
552
683
|
} else if (
|
|
553
684
|
command === "harness-eval" ||
|
|
554
685
|
command === "harness-review" ||
|
|
@@ -560,11 +691,12 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
560
691
|
persistContext(pi, activeCtx);
|
|
561
692
|
|
|
562
693
|
return {
|
|
563
|
-
systemPrompt: `${event.systemPrompt}\n\n${formatPlanContextBlock(activeCtx)}${activePlanBlock ? `\n\n${activePlanBlock}` : ""}`,
|
|
694
|
+
systemPrompt: `${event.systemPrompt}\n\n${formatPlanContextBlock(activeCtx, contextSpawnOpts)}${activePlanBlock ? `\n\n${activePlanBlock}` : ""}`,
|
|
564
695
|
};
|
|
565
696
|
});
|
|
566
697
|
|
|
567
698
|
pi.on("agent_end", async (_event, ctx) => {
|
|
699
|
+
const projectRoot = process.cwd();
|
|
568
700
|
const entries = getEntries(ctx);
|
|
569
701
|
if (!activeCtx) {
|
|
570
702
|
activeCtx = getLatestRunContext(entries);
|
|
@@ -591,9 +723,6 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
591
723
|
: parseHarnessSlashInput(userVisiblePromptSlice(lastPrompt));
|
|
592
724
|
if (!parsed && !needsClarificationFollowUp(activeCtx)) return;
|
|
593
725
|
|
|
594
|
-
const policyPhase = getLatestPolicyPhase(entries) ?? activeCtx.phase;
|
|
595
|
-
activeCtx.phase = policyPhase;
|
|
596
|
-
|
|
597
726
|
if (parsed?.command === "harness-abort") {
|
|
598
727
|
activeCtx.status = "aborted";
|
|
599
728
|
activeCtx.plan_ready = false;
|
|
@@ -654,27 +783,82 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
654
783
|
|
|
655
784
|
activeCtx.plan_ready = planReady;
|
|
656
785
|
|
|
657
|
-
const statuses =
|
|
786
|
+
const statuses = await resolveCompletionStatuses(
|
|
787
|
+
entries,
|
|
788
|
+
activeCtx.run_id,
|
|
789
|
+
projectRoot,
|
|
790
|
+
);
|
|
658
791
|
if (parsed?.command === "harness-run") {
|
|
659
792
|
activeCtx.last_completed_step = "execute";
|
|
660
|
-
|
|
661
|
-
|
|
793
|
+
let execStatus = statuses.executionStatus;
|
|
794
|
+
if (!execStatus) {
|
|
795
|
+
const handoff = await readExecutorHandoffFromRun(
|
|
796
|
+
activeCtx.run_id,
|
|
797
|
+
projectRoot,
|
|
798
|
+
);
|
|
799
|
+
execStatus = handoff?.execution_status ?? null;
|
|
800
|
+
}
|
|
801
|
+
activeCtx.last_outcome = execStatus ?? "completed";
|
|
802
|
+
activeCtx.phase = "evaluate";
|
|
662
803
|
}
|
|
663
|
-
if (parsed?.command === "harness-
|
|
664
|
-
activeCtx.last_completed_step = "
|
|
665
|
-
activeCtx.
|
|
804
|
+
if (parsed?.command === "harness-steer") {
|
|
805
|
+
activeCtx.last_completed_step = "steer";
|
|
806
|
+
activeCtx.steer_attempt = (activeCtx.steer_attempt ?? 0) + 1;
|
|
807
|
+
activeCtx.steer_max_attempts =
|
|
808
|
+
activeCtx.steer_max_attempts ?? steerMaxAttemptsFromEnv();
|
|
809
|
+
activeCtx.phase = "execute";
|
|
810
|
+
syncPolicyFromRunContext(pi, getEntries(ctx), activeCtx);
|
|
811
|
+
}
|
|
812
|
+
if (
|
|
813
|
+
parsed?.command === "harness-eval" ||
|
|
814
|
+
parsed?.command === "harness-review" ||
|
|
815
|
+
parsed?.command === "harness-critic"
|
|
816
|
+
) {
|
|
817
|
+
activeCtx.last_completed_step =
|
|
818
|
+
parsed.command === "harness-critic" ? "adversary" : "review";
|
|
819
|
+
if (statuses.evalStatus) {
|
|
820
|
+
activeCtx.last_outcome = statuses.evalStatus;
|
|
821
|
+
}
|
|
822
|
+
if (statuses.adversaryComplete) {
|
|
823
|
+
activeCtx.phase = "adversary";
|
|
824
|
+
activeCtx.last_completed_step = "adversary";
|
|
825
|
+
} else if (statuses.evalStatus) {
|
|
826
|
+
activeCtx.phase = "evaluate";
|
|
827
|
+
}
|
|
666
828
|
}
|
|
667
829
|
|
|
830
|
+
const reviewOutcome = await readReviewOutcomeFromRun(
|
|
831
|
+
activeCtx.run_id,
|
|
832
|
+
projectRoot,
|
|
833
|
+
);
|
|
834
|
+
const reviewComplete =
|
|
835
|
+
activeCtx.last_completed_step === "review" ||
|
|
836
|
+
activeCtx.last_completed_step === "adversary";
|
|
668
837
|
const next = nextStepAfterOutcome({
|
|
669
838
|
phase: activeCtx.phase,
|
|
670
|
-
planStatus: statuses.planStatus
|
|
839
|
+
planStatus: statuses.planStatus,
|
|
840
|
+
lastCompletedStep: activeCtx.last_completed_step,
|
|
841
|
+
lastOutcome: activeCtx.last_outcome,
|
|
671
842
|
executionStatus: statuses.executionStatus,
|
|
672
843
|
evalStatus: statuses.evalStatus,
|
|
844
|
+
adversaryComplete: statuses.adversaryComplete,
|
|
673
845
|
aborted: activeCtx.status === "aborted",
|
|
846
|
+
remediationClass: reviewOutcome?.remediation_class ?? null,
|
|
847
|
+
steerAttempt: activeCtx.steer_attempt ?? 0,
|
|
848
|
+
steerMaxAttempts:
|
|
849
|
+
activeCtx.steer_max_attempts ?? steerMaxAttemptsFromEnv(),
|
|
850
|
+
reviewComplete,
|
|
674
851
|
});
|
|
675
852
|
activeCtx.next_recommended_command = next;
|
|
676
853
|
activeCtx.updated_at = new Date().toISOString();
|
|
677
854
|
|
|
855
|
+
if (
|
|
856
|
+
parsed?.command === "harness-run" &&
|
|
857
|
+
activeCtx.last_outcome === "completed"
|
|
858
|
+
) {
|
|
859
|
+
syncPolicyFromRunContext(pi, getEntries(ctx), activeCtx);
|
|
860
|
+
}
|
|
861
|
+
|
|
678
862
|
persistContext(pi, activeCtx);
|
|
679
863
|
|
|
680
864
|
pi.appendEntry("harness-step-handoff", {
|
|
@@ -719,26 +903,6 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
719
903
|
});
|
|
720
904
|
|
|
721
905
|
pi.on("tool_call", async (event, ctx) => {
|
|
722
|
-
// #region agent log
|
|
723
|
-
fetch("http://127.0.0.1:7928/ingest/a5d40896-34cb-4f12-97db-df7ada0b22f0", {
|
|
724
|
-
method: "POST",
|
|
725
|
-
headers: {
|
|
726
|
-
"Content-Type": "application/json",
|
|
727
|
-
"X-Debug-Session-Id": "2ca12b",
|
|
728
|
-
},
|
|
729
|
-
body: JSON.stringify({
|
|
730
|
-
sessionId: "2ca12b",
|
|
731
|
-
location: "harness-run-context.ts:tool_call",
|
|
732
|
-
message: "submit policy hook",
|
|
733
|
-
data: {
|
|
734
|
-
toolName: event.toolName,
|
|
735
|
-
typeofIsSubmitToolName: typeof isSubmitToolName,
|
|
736
|
-
},
|
|
737
|
-
timestamp: Date.now(),
|
|
738
|
-
hypothesisId: "H1",
|
|
739
|
-
}),
|
|
740
|
-
}).catch(() => {});
|
|
741
|
-
// #endregion
|
|
742
906
|
if (isSubmitToolName(event.toolName)) {
|
|
743
907
|
const decision = evaluateHarnessSubagentToolCall(
|
|
744
908
|
event.toolName,
|
|
@@ -997,6 +1161,19 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
997
1161
|
}
|
|
998
1162
|
const pathArg = String((params as { path?: string }).path ?? "").trim();
|
|
999
1163
|
const content = String((params as { content?: string }).content ?? "");
|
|
1164
|
+
const HARNESS_YAML_INLINE_MAX = 32 * 1024;
|
|
1165
|
+
if (content.length > HARNESS_YAML_INLINE_MAX) {
|
|
1166
|
+
return {
|
|
1167
|
+
content: [
|
|
1168
|
+
{
|
|
1169
|
+
type: "text",
|
|
1170
|
+
text: `Content exceeds ${HARNESS_YAML_INLINE_MAX} bytes. Subagent must submit_* to disk, then use merge_harness_yaml with source_path or a small patch.`,
|
|
1171
|
+
},
|
|
1172
|
+
],
|
|
1173
|
+
details: { path: pathArg, bytes: content.length },
|
|
1174
|
+
isError: true,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1000
1177
|
if (!pathArg || !content.trim()) {
|
|
1001
1178
|
return {
|
|
1002
1179
|
content: [
|
|
@@ -1025,6 +1202,34 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
1025
1202
|
};
|
|
1026
1203
|
}
|
|
1027
1204
|
const relForGate = pathArg.replace(/\\/g, "/");
|
|
1205
|
+
const subagentOnly = new Set([
|
|
1206
|
+
"artifacts/eval-verdict.yaml",
|
|
1207
|
+
"artifacts/adversary-report.yaml",
|
|
1208
|
+
]);
|
|
1209
|
+
if (subagentOnly.has(relForGate)) {
|
|
1210
|
+
return {
|
|
1211
|
+
content: [
|
|
1212
|
+
{
|
|
1213
|
+
type: "text",
|
|
1214
|
+
text: `Path not allowed: ${pathArg}. Post-run verdicts must be written via submit_* in harness/evaluator or harness/adversary subagents; parent gates with harness_artifact_ready only.`,
|
|
1215
|
+
},
|
|
1216
|
+
],
|
|
1217
|
+
details: { path: pathArg },
|
|
1218
|
+
isError: true,
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
if (/\.json$/i.test(relForGate) && relForGate.startsWith("artifacts/")) {
|
|
1222
|
+
return {
|
|
1223
|
+
content: [
|
|
1224
|
+
{
|
|
1225
|
+
type: "text",
|
|
1226
|
+
text: `Path not allowed: ${pathArg}. Plan artifacts under artifacts/ must be .yaml (use submit_* from subagents or write_harness_yaml with YAML content).`,
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
details: { path: pathArg },
|
|
1230
|
+
isError: true,
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1028
1233
|
if (
|
|
1029
1234
|
isReviewRoundArtifactPath(relForGate) &&
|
|
1030
1235
|
!isReviewRoundYamlWriteAllowed()
|
|
@@ -1066,18 +1271,34 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
1066
1271
|
});
|
|
1067
1272
|
|
|
1068
1273
|
pi.registerTool({
|
|
1069
|
-
name: "
|
|
1070
|
-
label: "Harness
|
|
1274
|
+
name: "merge_harness_yaml",
|
|
1275
|
+
label: "Merge Harness YAML",
|
|
1071
1276
|
description:
|
|
1072
|
-
"
|
|
1277
|
+
"Shallow-merge a patch or another run artifact into an existing harness YAML file (path-first).",
|
|
1278
|
+
promptSnippet:
|
|
1279
|
+
"Merge artifact paths without pasting large bodies into tool args.",
|
|
1280
|
+
promptGuidelines: [
|
|
1281
|
+
"Prefer source_path pointing at artifacts/*.yaml from subagent submit_*.",
|
|
1282
|
+
"Use patch for small top-level keys only.",
|
|
1283
|
+
],
|
|
1073
1284
|
parameters: Type.Object({
|
|
1074
|
-
|
|
1075
|
-
minItems: 1,
|
|
1285
|
+
path: Type.String({
|
|
1076
1286
|
description:
|
|
1077
|
-
"
|
|
1287
|
+
"Target path under the active run, e.g. research-brief.yaml",
|
|
1078
1288
|
}),
|
|
1289
|
+
patch: Type.Optional(
|
|
1290
|
+
Type.String({
|
|
1291
|
+
description: "Small YAML/JSON object merged into the target",
|
|
1292
|
+
}),
|
|
1293
|
+
),
|
|
1294
|
+
source_path: Type.Optional(
|
|
1295
|
+
Type.String({
|
|
1296
|
+
description:
|
|
1297
|
+
"Relative path under the run to merge into target (e.g. artifacts/implementation-research.yaml)",
|
|
1298
|
+
}),
|
|
1299
|
+
),
|
|
1079
1300
|
}),
|
|
1080
|
-
async execute(
|
|
1301
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1081
1302
|
const entries = getEntries(ctx);
|
|
1082
1303
|
const runCtx = getLatestRunContext(entries) ?? activeCtx;
|
|
1083
1304
|
if (!runCtx?.run_id) {
|
|
@@ -1087,8 +1308,38 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
1087
1308
|
isError: true,
|
|
1088
1309
|
};
|
|
1089
1310
|
}
|
|
1090
|
-
const
|
|
1311
|
+
const pathArg = String((params as { path?: string }).path ?? "").trim();
|
|
1312
|
+
const patchRaw = String((params as { patch?: string }).patch ?? "");
|
|
1313
|
+
const sourcePath = String(
|
|
1314
|
+
(params as { source_path?: string }).source_path ?? "",
|
|
1315
|
+
).trim();
|
|
1316
|
+
if (!pathArg || (!patchRaw.trim() && !sourcePath)) {
|
|
1317
|
+
return {
|
|
1318
|
+
content: [
|
|
1319
|
+
{
|
|
1320
|
+
type: "text",
|
|
1321
|
+
text: "merge_harness_yaml requires path and patch or source_path.",
|
|
1322
|
+
},
|
|
1323
|
+
],
|
|
1324
|
+
details: {},
|
|
1325
|
+
isError: true,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1091
1328
|
const projectRoot = process.cwd();
|
|
1329
|
+
const absPath = normalizeHarnessPath(pathArg, projectRoot);
|
|
1330
|
+
const scoped = await isPlanPhaseScopedWrite(absPath, runCtx, projectRoot);
|
|
1331
|
+
if (!scoped) {
|
|
1332
|
+
return {
|
|
1333
|
+
content: [
|
|
1334
|
+
{
|
|
1335
|
+
type: "text",
|
|
1336
|
+
text: `Path not allowed: ${pathArg}.`,
|
|
1337
|
+
},
|
|
1338
|
+
],
|
|
1339
|
+
details: { path: pathArg },
|
|
1340
|
+
isError: true,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1092
1343
|
const runRoot = join(
|
|
1093
1344
|
projectRoot,
|
|
1094
1345
|
".pi",
|
|
@@ -1096,59 +1347,277 @@ export default function harnessRunContext(pi: ExtensionAPI) {
|
|
|
1096
1347
|
"runs",
|
|
1097
1348
|
runCtx.run_id,
|
|
1098
1349
|
);
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1350
|
+
let existing: Record<string, unknown> = {};
|
|
1351
|
+
try {
|
|
1352
|
+
const { readYamlFile } = await import("../lib/harness-yaml.js");
|
|
1353
|
+
const cur = await readYamlFile(absPath, pathArg);
|
|
1354
|
+
if (cur && typeof cur === "object" && !Array.isArray(cur)) {
|
|
1355
|
+
existing = cur as Record<string, unknown>;
|
|
1356
|
+
}
|
|
1357
|
+
} catch {
|
|
1358
|
+
existing = {};
|
|
1359
|
+
}
|
|
1360
|
+
let patchDoc: Record<string, unknown>;
|
|
1361
|
+
if (sourcePath) {
|
|
1362
|
+
const srcRel = sourcePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1363
|
+
const srcAbs = srcRel.startsWith(".pi/")
|
|
1364
|
+
? normalizeHarnessPath(srcRel, projectRoot)
|
|
1365
|
+
: join(runRoot, srcRel);
|
|
1104
1366
|
try {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1367
|
+
patchDoc = parseStructuredDocument(
|
|
1368
|
+
await readFile(srcAbs, "utf-8"),
|
|
1369
|
+
sourcePath,
|
|
1370
|
+
) as Record<string, unknown>;
|
|
1371
|
+
} catch (err) {
|
|
1372
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1373
|
+
return {
|
|
1374
|
+
content: [{ type: "text", text: msg }],
|
|
1375
|
+
details: { source_path: sourcePath },
|
|
1376
|
+
isError: true,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
} else {
|
|
1380
|
+
try {
|
|
1381
|
+
patchDoc = parseStructuredDocument(patchRaw, pathArg) as Record<
|
|
1382
|
+
string,
|
|
1383
|
+
unknown
|
|
1384
|
+
>;
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1387
|
+
return {
|
|
1388
|
+
content: [{ type: "text", text: msg }],
|
|
1389
|
+
details: { path: pathArg },
|
|
1390
|
+
isError: true,
|
|
1391
|
+
};
|
|
1109
1392
|
}
|
|
1110
1393
|
}
|
|
1111
|
-
const
|
|
1394
|
+
const merged = { ...existing, ...patchDoc };
|
|
1395
|
+
await mkdir(dirname(absPath), { recursive: true });
|
|
1396
|
+
await writeYamlFile(absPath, merged);
|
|
1112
1397
|
return {
|
|
1113
1398
|
content: [
|
|
1114
1399
|
{
|
|
1115
1400
|
type: "text",
|
|
1116
|
-
text:
|
|
1117
|
-
? `All ${present.length} artifact(s) present.`
|
|
1118
|
-
: `Missing: ${missing.join(", ")}`,
|
|
1401
|
+
text: `Merged into ${pathArg} as canonical YAML.`,
|
|
1119
1402
|
},
|
|
1120
1403
|
],
|
|
1121
|
-
details: {
|
|
1122
|
-
|
|
1404
|
+
details: { path: absPath },
|
|
1405
|
+
};
|
|
1406
|
+
},
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
pi.registerTool({
|
|
1410
|
+
name: "harness_synthesize_repair_brief",
|
|
1411
|
+
label: "Synthesize Repair Brief",
|
|
1412
|
+
description:
|
|
1413
|
+
"Build artifacts/repair-brief.yaml from review-outcome, eval-verdict, and adversary paths (no large inline bodies).",
|
|
1414
|
+
promptSnippet:
|
|
1415
|
+
"After /harness-review when remediation_class is implementation_gap.",
|
|
1416
|
+
promptGuidelines: [
|
|
1417
|
+
"Pass artifact paths only; tool reads YAML from disk.",
|
|
1418
|
+
"Default output: artifacts/repair-brief.yaml with steer_attempt from run context + 1.",
|
|
1419
|
+
],
|
|
1420
|
+
parameters: Type.Object({
|
|
1421
|
+
review_outcome_path: Type.Optional(Type.String()),
|
|
1422
|
+
eval_verdict_path: Type.Optional(Type.String()),
|
|
1423
|
+
adversary_report_path: Type.Optional(Type.String()),
|
|
1424
|
+
plan_packet_path: Type.Optional(Type.String()),
|
|
1425
|
+
output_path: Type.Optional(
|
|
1426
|
+
Type.String({
|
|
1427
|
+
description: "Default artifacts/repair-brief.yaml",
|
|
1428
|
+
}),
|
|
1429
|
+
),
|
|
1430
|
+
}),
|
|
1431
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1432
|
+
const entries = getEntries(ctx);
|
|
1433
|
+
const runCtx = getLatestRunContext(entries) ?? activeCtx;
|
|
1434
|
+
if (!runCtx?.run_id) {
|
|
1435
|
+
return {
|
|
1436
|
+
content: [{ type: "text", text: "No active harness run." }],
|
|
1437
|
+
details: {},
|
|
1438
|
+
isError: true,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
const projectRoot = process.cwd();
|
|
1442
|
+
const steerAttempt = (runCtx.steer_attempt ?? 0) + 1;
|
|
1443
|
+
const { synthesizeRepairBrief } = await import(
|
|
1444
|
+
"../lib/harness-repair-brief.js"
|
|
1445
|
+
);
|
|
1446
|
+
const brief = await synthesizeRepairBrief({
|
|
1447
|
+
runId: runCtx.run_id,
|
|
1448
|
+
projectRoot,
|
|
1449
|
+
steerAttempt,
|
|
1450
|
+
reviewOutcomePath: (params as { review_outcome_path?: string })
|
|
1451
|
+
.review_outcome_path,
|
|
1452
|
+
evalVerdictPath: (params as { eval_verdict_path?: string })
|
|
1453
|
+
.eval_verdict_path,
|
|
1454
|
+
adversaryReportPath: (params as { adversary_report_path?: string })
|
|
1455
|
+
.adversary_report_path,
|
|
1456
|
+
planPacketPath:
|
|
1457
|
+
(params as { plan_packet_path?: string }).plan_packet_path ??
|
|
1458
|
+
runCtx.plan_packet_path ??
|
|
1459
|
+
"plan-packet.yaml",
|
|
1460
|
+
});
|
|
1461
|
+
const outputPath =
|
|
1462
|
+
String((params as { output_path?: string }).output_path ?? "").trim() ||
|
|
1463
|
+
"artifacts/repair-brief.yaml";
|
|
1464
|
+
const absOut = normalizeHarnessPath(
|
|
1465
|
+
outputPath.startsWith(runCtx.run_id)
|
|
1466
|
+
? outputPath
|
|
1467
|
+
: join(
|
|
1468
|
+
projectRoot,
|
|
1469
|
+
".pi",
|
|
1470
|
+
"harness",
|
|
1471
|
+
"runs",
|
|
1472
|
+
runCtx.run_id,
|
|
1473
|
+
outputPath,
|
|
1474
|
+
),
|
|
1475
|
+
projectRoot,
|
|
1476
|
+
);
|
|
1477
|
+
const scoped = await isPlanPhaseScopedWrite(absOut, runCtx, projectRoot);
|
|
1478
|
+
if (!scoped) {
|
|
1479
|
+
return {
|
|
1480
|
+
content: [
|
|
1481
|
+
{
|
|
1482
|
+
type: "text",
|
|
1483
|
+
text: `Output path not allowed: ${outputPath}`,
|
|
1484
|
+
},
|
|
1485
|
+
],
|
|
1486
|
+
details: {},
|
|
1487
|
+
isError: true,
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
await mkdir(dirname(absOut), { recursive: true });
|
|
1491
|
+
await writeYamlFile(absOut, brief);
|
|
1492
|
+
return {
|
|
1493
|
+
content: [
|
|
1494
|
+
{
|
|
1495
|
+
type: "text",
|
|
1496
|
+
text: `Wrote ${outputPath} (steer_attempt=${steerAttempt}).`,
|
|
1497
|
+
},
|
|
1498
|
+
],
|
|
1499
|
+
details: { path: absOut, steer_attempt: steerAttempt },
|
|
1500
|
+
};
|
|
1501
|
+
},
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
pi.registerTool({
|
|
1505
|
+
name: "harness_artifact_ready",
|
|
1506
|
+
label: "Harness Artifact Ready",
|
|
1507
|
+
description:
|
|
1508
|
+
"Check harness artifact paths exist and pass minimal schema/content gates under the active run.",
|
|
1509
|
+
parameters: Type.Object({
|
|
1510
|
+
paths: Type.Array(Type.String(), {
|
|
1511
|
+
minItems: 1,
|
|
1512
|
+
description:
|
|
1513
|
+
"Relative paths under the run dir, e.g. artifacts/decomposition.yaml",
|
|
1514
|
+
}),
|
|
1515
|
+
}),
|
|
1516
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1517
|
+
const entries = getEntries(ctx);
|
|
1518
|
+
const runCtx = getLatestRunContext(entries) ?? activeCtx;
|
|
1519
|
+
if (!runCtx?.run_id) {
|
|
1520
|
+
return {
|
|
1521
|
+
content: [{ type: "text", text: "No active harness run." }],
|
|
1522
|
+
details: {},
|
|
1523
|
+
isError: true,
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
const paths = (params as { paths?: string[] }).paths ?? [];
|
|
1527
|
+
const projectRoot = process.cwd();
|
|
1528
|
+
const runRoot = join(
|
|
1529
|
+
projectRoot,
|
|
1530
|
+
".pi",
|
|
1531
|
+
"harness",
|
|
1532
|
+
"runs",
|
|
1533
|
+
runCtx.run_id,
|
|
1534
|
+
);
|
|
1535
|
+
const specsDir = join(projectRoot, ".pi", "harness", "specs");
|
|
1536
|
+
const { validateHarnessArtifactPaths } = await import(
|
|
1537
|
+
"./lib/harness-artifact-gate.js"
|
|
1538
|
+
);
|
|
1539
|
+
const gate = await validateHarnessArtifactPaths(runRoot, paths, specsDir);
|
|
1540
|
+
const text = gate.ok
|
|
1541
|
+
? `All ${gate.present.length} artifact(s) present and valid.`
|
|
1542
|
+
: [
|
|
1543
|
+
gate.missing.length > 0
|
|
1544
|
+
? `Missing: ${gate.missing.join(", ")}`
|
|
1545
|
+
: null,
|
|
1546
|
+
gate.errors.length > 0 ? gate.errors.join("\n") : null,
|
|
1547
|
+
]
|
|
1548
|
+
.filter(Boolean)
|
|
1549
|
+
.join("\n");
|
|
1550
|
+
return {
|
|
1551
|
+
content: [{ type: "text", text }],
|
|
1552
|
+
details: {
|
|
1553
|
+
ok: gate.ok,
|
|
1554
|
+
present: gate.present,
|
|
1555
|
+
missing: gate.missing,
|
|
1556
|
+
errors: gate.errors,
|
|
1557
|
+
run_id: runCtx.run_id,
|
|
1558
|
+
},
|
|
1559
|
+
isError: !gate.ok,
|
|
1123
1560
|
};
|
|
1124
1561
|
},
|
|
1125
1562
|
});
|
|
1126
1563
|
|
|
1127
1564
|
pi.registerCommand("harness-use-run", {
|
|
1128
|
-
description:
|
|
1565
|
+
description:
|
|
1566
|
+
"Point this session at an existing run directory (recovery; --claim for write ownership)",
|
|
1129
1567
|
handler: async (args, ctx) => {
|
|
1130
|
-
const
|
|
1131
|
-
if (!runId) {
|
|
1568
|
+
const parsed = parseHarnessUseRunArgs(args);
|
|
1569
|
+
if (!parsed.runId) {
|
|
1132
1570
|
if (ctx.hasUI)
|
|
1133
|
-
ctx.ui.notify(
|
|
1571
|
+
ctx.ui.notify(
|
|
1572
|
+
"Usage: /harness-use-run <run-id> [--claim] [--readonly]",
|
|
1573
|
+
"warning",
|
|
1574
|
+
);
|
|
1134
1575
|
return;
|
|
1135
1576
|
}
|
|
1136
1577
|
const projectRoot = process.cwd();
|
|
1137
|
-
const
|
|
1578
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
1579
|
+
const disk = await loadRunContextFromDisk(parsed.runId, projectRoot);
|
|
1138
1580
|
if (!disk) {
|
|
1139
|
-
if (ctx.hasUI) ctx.ui.notify(`Run not found: ${runId}`, "error");
|
|
1581
|
+
if (ctx.hasUI) ctx.ui.notify(`Run not found: ${parsed.runId}`, "error");
|
|
1140
1582
|
return;
|
|
1141
1583
|
}
|
|
1142
1584
|
activeCtx = {
|
|
1143
1585
|
...disk,
|
|
1144
|
-
pi_session_id:
|
|
1586
|
+
pi_session_id: sessionId,
|
|
1145
1587
|
};
|
|
1588
|
+
if (parsed.claim) {
|
|
1589
|
+
activeCtx = claimRunOwnership(activeCtx, sessionId);
|
|
1590
|
+
}
|
|
1591
|
+
const statuses = await resolveCompletionStatuses(
|
|
1592
|
+
getEntries(ctx),
|
|
1593
|
+
activeCtx.run_id,
|
|
1594
|
+
projectRoot,
|
|
1595
|
+
);
|
|
1596
|
+
if (activeCtx.owner_pi_session_id !== sessionId && !parsed.claim) {
|
|
1597
|
+
activeCtx.next_recommended_command =
|
|
1598
|
+
"Read-only: use /harness-use-run <run-id> --claim to take ownership.";
|
|
1599
|
+
} else {
|
|
1600
|
+
activeCtx.next_recommended_command = nextStepAfterOutcome({
|
|
1601
|
+
phase: activeCtx.phase,
|
|
1602
|
+
planStatus: activeCtx.plan_ready ? "ready" : null,
|
|
1603
|
+
lastCompletedStep: activeCtx.last_completed_step,
|
|
1604
|
+
lastOutcome: activeCtx.last_outcome,
|
|
1605
|
+
executionStatus: statuses.executionStatus,
|
|
1606
|
+
evalStatus: statuses.evalStatus,
|
|
1607
|
+
adversaryComplete: statuses.adversaryComplete,
|
|
1608
|
+
aborted: activeCtx.status === "aborted",
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
activeCtx.updated_at = nowIso();
|
|
1146
1612
|
persistContext(pi, activeCtx);
|
|
1147
|
-
|
|
1613
|
+
syncPolicyFromRunContext(pi, getEntries(ctx), activeCtx);
|
|
1614
|
+
if (ctx.hasUI) {
|
|
1615
|
+
const mode = parsed.claim ? "claimed" : "bound (read-only)";
|
|
1148
1616
|
ctx.ui.notify(
|
|
1149
|
-
`Session
|
|
1617
|
+
`Session ${mode} to run ${parsed.runId}. See /harness-run-status.`,
|
|
1150
1618
|
"info",
|
|
1151
1619
|
);
|
|
1620
|
+
}
|
|
1152
1621
|
},
|
|
1153
1622
|
});
|
|
1154
1623
|
}
|