@hassangameryt/freeflow 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +96 -149
- package/capabilities/cognitive-routing/SKILL.md +21 -129
- package/capabilities/cognitive-routing/references/coordinator.md +163 -0
- package/capabilities/cognitive-routing/references/worker.md +38 -0
- package/capabilities/compaction/SKILL.md +3 -3
- package/capabilities/compaction/references/summary-format.md +26 -15
- package/capabilities/messaging/SKILL.md +49 -0
- package/capabilities/task-memory/SKILL.md +87 -0
- package/command-surface.json +6 -54
- package/gemini-extension.json +1 -1
- package/hooks/claude/hooks.json +13 -3
- package/hooks/codex/hooks.json +2 -1
- package/hooks/shared/runtime-context.mjs +69 -77
- package/package.json +25 -8
- package/pi-extension/dist/ask-user/ask.js +108 -0
- package/pi-extension/dist/ask-user/form.js +250 -0
- package/pi-extension/dist/ask-user/questions.js +66 -0
- package/pi-extension/dist/ask-user/tool.js +124 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
- package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
- package/pi-extension/dist/cognitive-routing/config.js +3 -4
- package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
- package/pi-extension/dist/cognitive-routing/gate.js +20 -5
- package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
- package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
- package/pi-extension/dist/cognitive-routing/projection.js +23 -6
- package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
- package/pi-extension/dist/cognitive-routing/render.js +15 -15
- package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
- package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
- package/pi-extension/dist/cognitive-routing/session.js +3 -1
- package/pi-extension/dist/cognitive-routing/sources.js +29 -4
- package/pi-extension/dist/cognitive-routing/state.js +18 -8
- package/pi-extension/dist/cognitive-routing/status.js +4 -3
- package/pi-extension/dist/cognitive-routing/tools.js +29 -15
- package/pi-extension/dist/cognitive-routing/types.js +10 -1
- package/pi-extension/dist/compaction/carry.js +36 -22
- package/pi-extension/dist/compaction/config.js +4 -2
- package/pi-extension/dist/compaction/controller.js +52 -29
- package/pi-extension/dist/compaction/harness.js +19 -3
- package/pi-extension/dist/compaction/results.js +14 -13
- package/pi-extension/dist/compaction/tool.js +6 -6
- package/pi-extension/dist/host/catalog.js +20 -35
- package/pi-extension/dist/host/config-validation.js +233 -0
- package/pi-extension/dist/host/config.js +78 -91
- package/pi-extension/dist/host/inbound.js +142 -0
- package/pi-extension/dist/host/projection-tags.js +1 -1
- package/pi-extension/dist/host/prompts.js +24 -13
- package/pi-extension/dist/host/read-only-session.js +6 -1
- package/pi-extension/dist/host/request-history.js +7 -3
- package/pi-extension/dist/host/rows.js +25 -0
- package/pi-extension/dist/host/runtime-state.js +17 -2
- package/pi-extension/dist/host/section-keeper.js +85 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
- package/pi-extension/dist/host/settings/settings-view.js +27 -8
- package/pi-extension/dist/host/status.js +19 -5
- package/pi-extension/dist/index.js +638 -72
- package/pi-extension/dist/messaging/config.js +27 -0
- package/pi-extension/dist/messaging/messaging.js +398 -0
- package/pi-extension/dist/messaging/registry.js +57 -0
- package/pi-extension/dist/messaging/render.js +90 -0
- package/pi-extension/dist/messaging/tools.js +109 -0
- package/pi-extension/dist/messaging/transport.js +101 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
- package/pi-extension/dist/task-memory/config.js +49 -0
- package/pi-extension/dist/task-memory/registry.js +119 -0
- package/pi-extension/dist/task-memory/render.js +107 -0
- package/pi-extension/dist/task-memory/runtime.js +994 -0
- package/pi-extension/dist/task-memory/tool.js +233 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
- package/pi-extension/dist/tool-execution/background.js +220 -28
- package/pi-extension/dist/tool-execution/config.js +14 -3
- package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
- package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
- package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
- package/pi-extension/dist/tool-execution/tools.js +2 -2
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +24 -14
- package/runtime/prompts/interaction-contract.md +7 -4
- package/runtime/prompts/tool-execution.md +3 -16
- package/runtime/prompts/working-method.md +16 -99
- package/skills/deliver-work/SKILL.md +76 -0
- package/skills/deliver-work/references/branch-closeout.md +134 -0
- package/skills/deliver-work/references/commit.md +83 -0
- package/skills/deliver-work/references/launch.md +94 -0
- package/skills/deliver-work/references/release.md +85 -0
- package/skills/deliver-work/scripts/commit-state.mjs +139 -0
- package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
- package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
- package/skills/deliver-work/scripts/push-route.mjs +122 -0
- package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
- package/skills/discuss-work/SKILL.md +148 -0
- package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
- package/skills/discuss-work/references/learning-action.md +36 -0
- package/skills/discuss-work/scripts/choices.mjs +101 -0
- package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
- package/skills/evaluate-behavior/SKILL.md +107 -0
- package/skills/evaluate-behavior/references/authoring.md +381 -0
- package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
- package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
- package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
- package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
- package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
- package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
- package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
- package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
- package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
- package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
- package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
- package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
- package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
- package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
- package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
- package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
- package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
- package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
- package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
- package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
- package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
- package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
- package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
- package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
- package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
- package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
- package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
- package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
- package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
- package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
- package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
- package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
- package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
- package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
- package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
- package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
- package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
- package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
- package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
- package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
- package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
- package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
- package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
- package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
- package/skills/execute-work/SKILL.md +96 -104
- package/skills/execute-work/references/code-practices.md +27 -51
- package/skills/execute-work/references/design.md +271 -0
- package/skills/execute-work/references/diagnose.md +117 -0
- package/skills/execute-work/references/migration.md +108 -0
- package/skills/execute-work/references/simplify.md +65 -0
- package/skills/execute-work/scripts/self-review.mjs +422 -0
- package/skills/execute-work/scripts/touch-map.mjs +290 -0
- package/skills/review-work/SKILL.md +89 -65
- package/skills/review-work/references/adjudication.md +94 -0
- package/skills/review-work/references/artifact.md +198 -0
- package/skills/review-work/references/evals-and-judges.md +66 -0
- package/skills/review-work/references/work.md +177 -0
- package/skills/review-work/scripts/check-brief.mjs +128 -0
- package/skills/review-work/scripts/review-packet.mjs +233 -0
- package/skills/setup-freeflow/SKILL.md +68 -17
- package/skills/track-work/SKILL.md +52 -20
- package/skills/track-work/references/handoff.md +190 -0
- package/skills/track-work/references/projects.md +51 -0
- package/skills/track-work/references/working-record-format.md +36 -18
- package/skills/track-work/scripts/lib/document.mjs +17 -13
- package/skills/track-work/scripts/lib/format.mjs +2 -0
- package/skills/track-work/scripts/lib/recover.mjs +254 -0
- package/skills/track-work/scripts/lib/registry.mjs +656 -0
- package/skills/track-work/scripts/lib/search.mjs +40 -0
- package/skills/track-work/scripts/lib/store.mjs +46 -15
- package/skills/track-work/scripts/lib/views.mjs +50 -1
- package/skills/track-work/scripts/working-record.mjs +232 -13
- package/skills/verify-work/SKILL.md +45 -37
- package/skills/verify-work/references/feature-map.md +120 -0
- package/skills/verify-work/references/runtime-evidence.md +203 -0
- package/skills/verify-work/references/test-design.md +25 -6
- package/skills/verify-work/scripts/test-changes.mjs +246 -0
- package/skills/write-docs/SKILL.md +113 -25
- package/skills/write-docs/references/documentation-shapes.md +68 -3
- package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
- package/skills/write-docs/scripts/artifact-check.mjs +148 -0
- package/skills/write-docs/scripts/docs-check.mjs +96 -6
- package/skills/write-docs/scripts/plan-lint.mjs +123 -0
- package/skills/write-skill/SKILL.md +92 -52
- package/skills/write-skill/references/activation-and-wording.md +143 -0
- package/skills/write-skill/references/placement-and-dependencies.md +154 -0
- package/skills/write-skill/scripts/lib/compare.mjs +701 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
- package/skills/write-skill/scripts/skill-author.mjs +51 -8
- package/capabilities/cognitive-routing/references/both-mode.md +0 -36
- package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
- package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
- package/plugin.json +0 -33
- package/skills/action-selection/SKILL.md +0 -92
- package/skills/action-selection/references/trajectory-stalls.md +0 -73
- package/skills/bypass/SKILL.md +0 -153
- package/skills/commit-work/SKILL.md +0 -93
- package/skills/commit-work/references/git-state-edges.md +0 -94
- package/skills/commit-work/references/simple-push.md +0 -94
- package/skills/commit-work/references/staging-decisions.md +0 -106
- package/skills/decision-gate/SKILL.md +0 -87
- package/skills/design-for-depth/SKILL.md +0 -108
- package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
- package/skills/design-for-depth/references/implementation-decisions.md +0 -76
- package/skills/design-for-depth/references/interface-design-loop.md +0 -105
- package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
- package/skills/diagnose-failure/SKILL.md +0 -140
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
- package/skills/discuss/SKILL.md +0 -140
- package/skills/evaluate-skill/SKILL.md +0 -188
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
- package/skills/evaluate-skill/references/evaluation-design.md +0 -112
- package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
- package/skills/evaluate-skill/references/review-and-revision.md +0 -94
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
- package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
- package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
- package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
- package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
- package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
- package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
- package/skills/execute-work/references/execute-work-edges.md +0 -78
- package/skills/finish-branch/SKILL.md +0 -117
- package/skills/finish-branch/references/integration-options.md +0 -80
- package/skills/handoff/SKILL.md +0 -82
- package/skills/handoff/references/handoff-templates.md +0 -150
- package/skills/launch-work/SKILL.md +0 -116
- package/skills/launch-work/references/launch-readiness.md +0 -85
- package/skills/migration-work/SKILL.md +0 -115
- package/skills/migration-work/references/migration-lifecycle.md +0 -82
- package/skills/release-work/SKILL.md +0 -118
- package/skills/release-work/references/release-evidence.md +0 -78
- package/skills/review-artifact/SKILL.md +0 -143
- package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
- package/skills/review-work/references/adjudicate-work-review.md +0 -97
- package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
- package/skills/review-work/references/security-risk-lens.md +0 -67
- package/skills/setup-freeflow/references/activation-contract.md +0 -103
- package/skills/setup-freeflow/references/host-setup.md +0 -67
- package/skills/simplify-code/SKILL.md +0 -85
- package/skills/simplify-code/references/simplification-patterns.md +0 -70
- package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
- package/skills/verify-work/references/integration-evidence.md +0 -49
- package/skills/verify-work/references/performance-evidence.md +0 -59
- package/skills/workflow/SKILL.md +0 -171
- package/skills/workflow/references/domain-skill-composition.md +0 -45
- package/skills/write-plan/SKILL.md +0 -92
- package/skills/write-plan/references/plan-shapes.md +0 -156
- package/skills/write-skill/references/activation-boundaries.md +0 -106
- package/skills/write-skill/references/agent-first-instructions.md +0 -133
- package/skills/write-skill/references/progressive-disclosure.md +0 -129
- package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
- package/skills/write-spec/SKILL.md +0 -85
- package/skills/write-spec/references/artifact-standards.md +0 -86
- package/skills/write-spec/references/decision-records.md +0 -80
- package/skills/write-spec/references/spec-shapes.md +0 -143
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// How the evaluator launches Pi itself: the command-line arguments for a subject and the one-shot JSON-mode process
|
|
2
|
+
// that reports Pi's events. RPC sessions are in rpc.mjs.
|
|
3
|
+
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { once } from "node:events";
|
|
6
|
+
import { createWriteStream } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
mergeUsage,
|
|
11
|
+
messageText,
|
|
12
|
+
NO_PROGRESS_MS,
|
|
13
|
+
pipeStream,
|
|
14
|
+
processGroupExists,
|
|
15
|
+
signalProcessTree,
|
|
16
|
+
TERMINATION_GRACE_MS,
|
|
17
|
+
waitForForcedKill,
|
|
18
|
+
writeStream,
|
|
19
|
+
} from "../../process.mjs";
|
|
20
|
+
|
|
21
|
+
const guardExtension = fileURLToPath(new URL("../../../pi-guard.mjs", import.meta.url));
|
|
22
|
+
|
|
23
|
+
/** The evaluator's guard extension, which every subject process (and a shell's confinement probe) loads first. */
|
|
24
|
+
export function guardExtensionPath() {
|
|
25
|
+
return guardExtension;
|
|
26
|
+
}
|
|
27
|
+
const commandToolExtension = fileURLToPath(new URL("../../../pi-command-tool.mjs", import.meta.url));
|
|
28
|
+
const containerRouteExtension = fileURLToPath(new URL("../../../pi-container-route.mjs", import.meta.url));
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The extension that moves shell commands into a subject's container. It loads after every other extension, so
|
|
32
|
+
* theirs see each command as the model wrote it; the guard refuses container shell calls without it.
|
|
33
|
+
*/
|
|
34
|
+
export function containerRouteExtensionPath() {
|
|
35
|
+
return containerRouteExtension;
|
|
36
|
+
}
|
|
37
|
+
const observerExtension = fileURLToPath(new URL("../../../pi-observer.mjs", import.meta.url));
|
|
38
|
+
|
|
39
|
+
export function piArguments(
|
|
40
|
+
group,
|
|
41
|
+
environment,
|
|
42
|
+
runtime,
|
|
43
|
+
mode,
|
|
44
|
+
observeContext,
|
|
45
|
+
sessionDirectory,
|
|
46
|
+
model,
|
|
47
|
+
freeflowEntry,
|
|
48
|
+
hostExtensions = [],
|
|
49
|
+
containerRoute = false,
|
|
50
|
+
) {
|
|
51
|
+
const args = ["--mode", mode];
|
|
52
|
+
if (mode === "json") args.push("-p");
|
|
53
|
+
if (runtime.session) args.push("--session-dir", sessionDirectory);
|
|
54
|
+
else args.push("--no-session");
|
|
55
|
+
args.push("--no-extensions", "--extension", guardExtension);
|
|
56
|
+
// Provider extensions (such as a subscription's auth extension) load right after the guard.
|
|
57
|
+
for (const extension of hostExtensions) args.push("--extension", extension.path);
|
|
58
|
+
if (group.commands !== undefined) args.push("--extension", commandToolExtension);
|
|
59
|
+
if (freeflowEntry !== null) args.push("--extension", freeflowEntry);
|
|
60
|
+
for (const bundle of runtime.extensions) args.push("--extension", bundle.entry);
|
|
61
|
+
if (observeContext) args.push("--extension", observerExtension);
|
|
62
|
+
if (containerRoute) args.push("--extension", containerRouteExtension);
|
|
63
|
+
args.push("--no-skills", "--no-prompt-templates", "--no-themes", "--no-context-files", "--no-approve");
|
|
64
|
+
if (group.tools.length === 0) args.push("--no-tools");
|
|
65
|
+
else args.push("--tools", group.tools.join(","));
|
|
66
|
+
for (const skill of environment.skills) args.push("--skill", skill.path);
|
|
67
|
+
if (model?.model) args.push("--model", model.model);
|
|
68
|
+
if (model?.thinking) args.push("--thinking", model.thinking);
|
|
69
|
+
if (mode === "json") args.push(group.input.prompt);
|
|
70
|
+
return args;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function runPiProcess({ command, args, cwd, eventsFile, stderrFile, signal, environment }) {
|
|
74
|
+
const child = spawn(command, args, {
|
|
75
|
+
cwd,
|
|
76
|
+
detached: process.platform !== "win32",
|
|
77
|
+
env: environment,
|
|
78
|
+
shell: false,
|
|
79
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
80
|
+
});
|
|
81
|
+
const events = createWriteStream(eventsFile, { encoding: "utf8" });
|
|
82
|
+
const stderr = createWriteStream(stderrFile, { encoding: "utf8" });
|
|
83
|
+
const transcript = [];
|
|
84
|
+
const calls = new Map();
|
|
85
|
+
const toolActivity = [];
|
|
86
|
+
const successfulReads = [];
|
|
87
|
+
const parseErrors = [];
|
|
88
|
+
const protocolErrors = [];
|
|
89
|
+
let settled = false;
|
|
90
|
+
let response = "";
|
|
91
|
+
let usage = null;
|
|
92
|
+
let assistantError = null;
|
|
93
|
+
let observedModel = null;
|
|
94
|
+
let buffer = "";
|
|
95
|
+
let eventLine = 0;
|
|
96
|
+
const processGroupId = child.pid;
|
|
97
|
+
let watchdog;
|
|
98
|
+
let forcedKill;
|
|
99
|
+
/** @type {Promise<void> | null} */
|
|
100
|
+
let forcedKillPromise = null;
|
|
101
|
+
let terminationReason = null;
|
|
102
|
+
|
|
103
|
+
const resetWatchdog = () => {
|
|
104
|
+
clearTimeout(watchdog);
|
|
105
|
+
watchdog = setTimeout(() => requestTermination("no-progress-watchdog"), NO_PROGRESS_MS);
|
|
106
|
+
watchdog.unref();
|
|
107
|
+
};
|
|
108
|
+
const requestTermination = (reason) => {
|
|
109
|
+
if (terminationReason !== null) return;
|
|
110
|
+
terminationReason = reason;
|
|
111
|
+
signalProcessTree(processGroupId, child, "SIGTERM");
|
|
112
|
+
if (process.platform !== "win32") {
|
|
113
|
+
forcedKillPromise = new Promise((resolve) => {
|
|
114
|
+
forcedKill = setTimeout(() => {
|
|
115
|
+
signalProcessTree(processGroupId, child, "SIGKILL");
|
|
116
|
+
resolve();
|
|
117
|
+
}, TERMINATION_GRACE_MS);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const onAbort = () => requestTermination("cancelled");
|
|
122
|
+
if (signal?.aborted) onAbort();
|
|
123
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
124
|
+
resetWatchdog();
|
|
125
|
+
|
|
126
|
+
const stderrTask = pipeStream(child.stderr, stderr, resetWatchdog);
|
|
127
|
+
const stdoutTask = (async () => {
|
|
128
|
+
for await (const chunk of child.stdout) {
|
|
129
|
+
resetWatchdog();
|
|
130
|
+
buffer += chunk.toString("utf8");
|
|
131
|
+
while (true) {
|
|
132
|
+
const newline = buffer.indexOf("\n");
|
|
133
|
+
if (newline === -1) break;
|
|
134
|
+
const line = buffer.slice(0, newline);
|
|
135
|
+
buffer = buffer.slice(newline + 1);
|
|
136
|
+
await writeStream(events, `${line}\n`);
|
|
137
|
+
observeLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (buffer.length > 0) {
|
|
141
|
+
await writeStream(events, buffer);
|
|
142
|
+
observeLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
|
|
143
|
+
}
|
|
144
|
+
events.end();
|
|
145
|
+
await once(events, "finish");
|
|
146
|
+
})();
|
|
147
|
+
|
|
148
|
+
function observeLine(line) {
|
|
149
|
+
eventLine += 1;
|
|
150
|
+
if (line.trim() === "") return;
|
|
151
|
+
let event;
|
|
152
|
+
try {
|
|
153
|
+
event = JSON.parse(line);
|
|
154
|
+
} catch {
|
|
155
|
+
parseErrors.push({ line: eventLine, reason: "invalid-json" });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (event.type === "extension_error") {
|
|
159
|
+
protocolErrors.push({
|
|
160
|
+
reason: "extension-error",
|
|
161
|
+
extensionPath: event.extensionPath ?? null,
|
|
162
|
+
event: event.event ?? null,
|
|
163
|
+
error: event.error ?? null,
|
|
164
|
+
});
|
|
165
|
+
requestTermination("protocol-error");
|
|
166
|
+
}
|
|
167
|
+
if (event.type === "agent_settled") settled = true;
|
|
168
|
+
if (event.type === "tool_execution_start") {
|
|
169
|
+
// Same shape as RPC turns record, so graders and the advisory reviewer see one-shot tool use too.
|
|
170
|
+
const activity = {
|
|
171
|
+
toolCallId: event.toolCallId ?? null,
|
|
172
|
+
toolName: event.toolName ?? null,
|
|
173
|
+
args: event.args ?? null,
|
|
174
|
+
completed: false,
|
|
175
|
+
isError: null,
|
|
176
|
+
};
|
|
177
|
+
toolActivity.push(activity);
|
|
178
|
+
calls.set(event.toolCallId, activity);
|
|
179
|
+
}
|
|
180
|
+
if (event.type === "tool_execution_end") {
|
|
181
|
+
const call = calls.get(event.toolCallId);
|
|
182
|
+
if (call) {
|
|
183
|
+
call.completed = true;
|
|
184
|
+
call.isError = event.isError === true;
|
|
185
|
+
}
|
|
186
|
+
if (!event.isError && call?.toolName === "read" && typeof call.args?.path === "string") {
|
|
187
|
+
successfulReads.push(call.args.path);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (event.type === "message_end" && event.message) {
|
|
191
|
+
transcript.push(event.message);
|
|
192
|
+
if (event.message.role === "assistant") {
|
|
193
|
+
response = messageText(event.message);
|
|
194
|
+
usage = mergeUsage(usage, event.message.usage);
|
|
195
|
+
observedModel = {
|
|
196
|
+
provider: event.message.provider ?? null,
|
|
197
|
+
model: event.message.model ?? null,
|
|
198
|
+
};
|
|
199
|
+
assistantError =
|
|
200
|
+
event.message.stopReason === "error" || event.message.stopReason === "aborted"
|
|
201
|
+
? (event.message.errorMessage ?? `assistant stopped with ${event.message.stopReason}`)
|
|
202
|
+
: null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let exitCode = null;
|
|
208
|
+
let exitSignal = null;
|
|
209
|
+
try {
|
|
210
|
+
const [code, closedSignal] = await once(child, "close");
|
|
211
|
+
exitCode = code;
|
|
212
|
+
exitSignal = closedSignal;
|
|
213
|
+
await Promise.all([stdoutTask, stderrTask]);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
requestTermination("process-error");
|
|
216
|
+
throw error;
|
|
217
|
+
} finally {
|
|
218
|
+
clearTimeout(watchdog);
|
|
219
|
+
if (forcedKillPromise !== null) {
|
|
220
|
+
if (processGroupExists(processGroupId)) await waitForForcedKill(forcedKillPromise);
|
|
221
|
+
else clearTimeout(forcedKill);
|
|
222
|
+
}
|
|
223
|
+
signal?.removeEventListener("abort", onAbort);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
exitCode,
|
|
228
|
+
signal: exitSignal,
|
|
229
|
+
settled,
|
|
230
|
+
assistantError,
|
|
231
|
+
model: observedModel,
|
|
232
|
+
terminationReason,
|
|
233
|
+
parseErrors,
|
|
234
|
+
protocolErrors,
|
|
235
|
+
successfulReads,
|
|
236
|
+
toolActivity,
|
|
237
|
+
transcript,
|
|
238
|
+
response,
|
|
239
|
+
usage,
|
|
240
|
+
};
|
|
241
|
+
}
|
package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs
RENAMED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
waitForForcedKill,
|
|
16
16
|
waitForProcessTreeExit,
|
|
17
17
|
writeStream,
|
|
18
|
-
} from "
|
|
18
|
+
} from "../../process.mjs";
|
|
19
19
|
|
|
20
20
|
const MAX_INCOMPLETE_FRAME_BYTES = 16 * 1024 * 1024;
|
|
21
21
|
|
|
@@ -28,7 +28,25 @@ export async function runRpcBodySession(options) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
class RpcSession {
|
|
31
|
-
constructor({
|
|
31
|
+
constructor({
|
|
32
|
+
command = "pi",
|
|
33
|
+
args,
|
|
34
|
+
cwd,
|
|
35
|
+
eventsFile,
|
|
36
|
+
stderrFile,
|
|
37
|
+
signal,
|
|
38
|
+
environment,
|
|
39
|
+
afterTurn = null,
|
|
40
|
+
nextPrompt = null,
|
|
41
|
+
settings = {},
|
|
42
|
+
resumeBefore = [],
|
|
43
|
+
}) {
|
|
44
|
+
this.settings = settings;
|
|
45
|
+
// Turn numbers before which the session is closed and resumed from its own file, as `pi --resume` would.
|
|
46
|
+
this.resumeBefore = new Set(resumeBefore);
|
|
47
|
+
// After the last declared prompt settles, `nextPrompt(turns)` may return one more prompt (a simulated user's
|
|
48
|
+
// reply), or null to end the session.
|
|
49
|
+
this.nextPrompt = nextPrompt;
|
|
32
50
|
this.child = spawn(command, args, {
|
|
33
51
|
cwd,
|
|
34
52
|
detached: process.platform !== "win32",
|
|
@@ -47,6 +65,10 @@ class RpcSession {
|
|
|
47
65
|
this.parseErrors = [];
|
|
48
66
|
this.protocolErrors = [];
|
|
49
67
|
this.activeTurn = null;
|
|
68
|
+
// A run Pi starts on its own while no prompt is in flight: an extension's message that starts a turn, such as
|
|
69
|
+
// Freeflow's notice that a background command exited or a monitor printed a line. Its activity joins the latest
|
|
70
|
+
// turn when it settles; the next prompt waits for it.
|
|
71
|
+
this.unprompted = null;
|
|
50
72
|
this.decoder = new StringDecoder("utf8");
|
|
51
73
|
this.buffer = "";
|
|
52
74
|
this.incompleteFrameBytes = 0;
|
|
@@ -77,7 +99,7 @@ class RpcSession {
|
|
|
77
99
|
}
|
|
78
100
|
|
|
79
101
|
async run(prompts, targetPath = null) {
|
|
80
|
-
let deliveredPrompts = prompts;
|
|
102
|
+
let deliveredPrompts = [...prompts];
|
|
81
103
|
/** @type {any} */
|
|
82
104
|
let delivery = { kind: "unavailable", turn: 1, targetPath };
|
|
83
105
|
try {
|
|
@@ -88,12 +110,23 @@ class RpcSession {
|
|
|
88
110
|
delivery = { kind: "explicit-skill-command", turn: 1, targetPath, command };
|
|
89
111
|
}
|
|
90
112
|
for (const [index, prompt] of deliveredPrompts.entries()) {
|
|
113
|
+
await this.unpromptedSettled();
|
|
91
114
|
if (this.signal?.aborted || this.terminationReason !== null) break;
|
|
92
115
|
if (delivery.kind === "unavailable") delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
|
|
116
|
+
const resumed = this.resumeBefore.has(index + 1) ? await this.resume() : null;
|
|
93
117
|
const turn = await this.runTurn(index + 1, prompt);
|
|
118
|
+
if (resumed !== null) turn.resumed = resumed;
|
|
94
119
|
this.turns.push(turn);
|
|
95
120
|
if (this.afterTurn !== null) turn.workspace = await this.afterTurn(turn.turn);
|
|
96
121
|
if (turn.assistantError !== null) break;
|
|
122
|
+
if (this.nextPrompt !== null && index === deliveredPrompts.length - 1 && this.terminationReason === null) {
|
|
123
|
+
// A run a notice started right after the turn is part of what the user sees before replying.
|
|
124
|
+
await this.unpromptedSettled();
|
|
125
|
+
const next = await this.nextPrompt(this.turns);
|
|
126
|
+
// The simulated user's time is not the subject's lack of progress.
|
|
127
|
+
this.resetWatchdog();
|
|
128
|
+
if (typeof next === "string") deliveredPrompts.push(next);
|
|
129
|
+
}
|
|
97
130
|
}
|
|
98
131
|
} catch (error) {
|
|
99
132
|
this.operationError = error;
|
|
@@ -104,6 +137,7 @@ class RpcSession {
|
|
|
104
137
|
});
|
|
105
138
|
this.requestTermination("protocol-error");
|
|
106
139
|
}
|
|
140
|
+
await this.unpromptedSettled();
|
|
107
141
|
} finally {
|
|
108
142
|
if (!this.child.stdin.destroyed) this.child.stdin.end();
|
|
109
143
|
}
|
|
@@ -114,9 +148,25 @@ class RpcSession {
|
|
|
114
148
|
return { ...this.observation(deliveredPrompts, exitCode, exitSignal), delivery };
|
|
115
149
|
}
|
|
116
150
|
|
|
151
|
+
// The subject's effective settings decide retry and compaction; the evaluator's defaults keep both off.
|
|
117
152
|
async configure() {
|
|
118
|
-
await this.sendCommand({ type: "set_auto_retry", enabled:
|
|
119
|
-
await this.sendCommand({ type: "set_auto_compaction", enabled:
|
|
153
|
+
await this.sendCommand({ type: "set_auto_retry", enabled: this.settings.retry?.enabled === true });
|
|
154
|
+
await this.sendCommand({ type: "set_auto_compaction", enabled: this.settings.compaction?.enabled === true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Closes the session and opens it again from its file: Pi tears the session down (`session_shutdown`) and starts
|
|
159
|
+
* it anew with `session_start` reason `resume`, as a user resuming it would.
|
|
160
|
+
*/
|
|
161
|
+
async resume() {
|
|
162
|
+
/** @type {any} */
|
|
163
|
+
const state = await this.sendCommand({ type: "get_state" });
|
|
164
|
+
const sessionFile = state.data?.sessionFile;
|
|
165
|
+
if (typeof sessionFile !== "string" || sessionFile === "") throw new Error("Pi RPC session has no file to resume");
|
|
166
|
+
/** @type {any} */
|
|
167
|
+
const result = await this.sendCommand({ type: "switch_session", sessionPath: sessionFile });
|
|
168
|
+
if (result.data?.cancelled === true) throw new Error("Pi cancelled resuming the session");
|
|
169
|
+
return { sessionFile };
|
|
120
170
|
}
|
|
121
171
|
|
|
122
172
|
async resolveSkillCommand(targetPath) {
|
|
@@ -145,22 +195,31 @@ class RpcSession {
|
|
|
145
195
|
resolveSettled = resolve;
|
|
146
196
|
rejectSettled = reject;
|
|
147
197
|
});
|
|
198
|
+
// An unprompted run still going when the prompt is sent becomes part of this turn.
|
|
199
|
+
const joined = this.unprompted;
|
|
200
|
+
this.unprompted = null;
|
|
148
201
|
this.activeTurn = {
|
|
149
202
|
turn: turnNumber,
|
|
150
203
|
prompt,
|
|
151
|
-
response: "",
|
|
152
|
-
transcript: [],
|
|
153
|
-
successfulReads: [],
|
|
154
|
-
toolActivity: [],
|
|
155
|
-
calls: new Map(),
|
|
204
|
+
response: joined?.response ?? "",
|
|
205
|
+
transcript: joined?.transcript ?? [],
|
|
206
|
+
successfulReads: joined?.successfulReads ?? [],
|
|
207
|
+
toolActivity: joined?.toolActivity ?? [],
|
|
208
|
+
calls: joined?.calls ?? new Map(),
|
|
156
209
|
promptAccepted: false,
|
|
157
|
-
usage: null,
|
|
158
|
-
assistantError: null,
|
|
210
|
+
usage: joined?.usage ?? null,
|
|
211
|
+
assistantError: joined?.assistantError ?? null,
|
|
159
212
|
settled: false,
|
|
160
213
|
resolveSettled,
|
|
161
214
|
rejectSettled,
|
|
162
215
|
};
|
|
163
|
-
|
|
216
|
+
joined?.resolveSettled();
|
|
217
|
+
// Pi refuses a prompt while it runs, unless told how to queue it. A run a notice starts as the prompt is sent
|
|
218
|
+
// takes the prompt as its follow-up, and the turn records both.
|
|
219
|
+
await Promise.all([
|
|
220
|
+
this.sendCommand({ type: "prompt", message: prompt, streamingBehavior: "followUp" }),
|
|
221
|
+
settlement,
|
|
222
|
+
]);
|
|
164
223
|
const result = finishTurn(this.activeTurn);
|
|
165
224
|
this.activeTurn = null;
|
|
166
225
|
return result;
|
|
@@ -238,6 +297,61 @@ class RpcSession {
|
|
|
238
297
|
} else if (event.type === "response") this.observeResponse(event);
|
|
239
298
|
else if (event.type === "agent_settled") this.observeSettlement();
|
|
240
299
|
else if (this.activeTurn !== null) this.observeTurnEvent(event);
|
|
300
|
+
else if (event.type === "agent_start" || this.unprompted !== null) this.observeUnpromptedEvent(event);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Events of a run Pi started without a prompt; it opens with agent_start. */
|
|
304
|
+
observeUnpromptedEvent(event) {
|
|
305
|
+
if (this.unprompted === null) {
|
|
306
|
+
let resolveSettled;
|
|
307
|
+
const settled = new Promise((resolve) => {
|
|
308
|
+
resolveSettled = resolve;
|
|
309
|
+
});
|
|
310
|
+
this.unprompted = {
|
|
311
|
+
response: "",
|
|
312
|
+
transcript: [],
|
|
313
|
+
successfulReads: [],
|
|
314
|
+
toolActivity: [],
|
|
315
|
+
calls: new Map(),
|
|
316
|
+
usage: null,
|
|
317
|
+
assistantError: null,
|
|
318
|
+
settled,
|
|
319
|
+
resolveSettled,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
this.observeTurnEvent(event, this.unprompted);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Resolves once no unprompted run is in progress. */
|
|
326
|
+
async unpromptedSettled() {
|
|
327
|
+
while (this.unprompted !== null && this.terminationReason === null) {
|
|
328
|
+
const { settled } = this.unprompted;
|
|
329
|
+
await Promise.race([settled, this.closed.catch(() => {})]);
|
|
330
|
+
if (this.unprompted?.settled === settled && this.terminationReason === null) {
|
|
331
|
+
// The process closed while the run was in progress; nothing more will arrive.
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** An unprompted run settled: its activity joins the latest turn, as if that turn had continued. */
|
|
338
|
+
settleUnprompted() {
|
|
339
|
+
const run = this.unprompted;
|
|
340
|
+
this.unprompted = null;
|
|
341
|
+
const turn = this.turns.at(-1);
|
|
342
|
+
if (turn === undefined) {
|
|
343
|
+
run.resolveSettled();
|
|
344
|
+
this.failProtocol("unprompted-run-before-first-turn", { line: this.eventLine });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
turn.transcript.push(...run.transcript);
|
|
348
|
+
turn.successfulReads.push(...run.successfulReads);
|
|
349
|
+
turn.toolActivity.push(...run.toolActivity);
|
|
350
|
+
if (run.response !== "") turn.response = run.response;
|
|
351
|
+
turn.usage = mergeUsage(turn.usage, run.usage);
|
|
352
|
+
turn.assistantError ??= run.assistantError;
|
|
353
|
+
turn.unpromptedRuns = (turn.unpromptedRuns ?? 0) + 1;
|
|
354
|
+
run.resolveSettled();
|
|
241
355
|
}
|
|
242
356
|
|
|
243
357
|
observeResponse(event) {
|
|
@@ -268,6 +382,10 @@ class RpcSession {
|
|
|
268
382
|
}
|
|
269
383
|
|
|
270
384
|
observeSettlement() {
|
|
385
|
+
if (this.activeTurn === null && this.unprompted !== null) {
|
|
386
|
+
this.settleUnprompted();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
271
389
|
if (this.activeTurn === null || this.activeTurn.settled) {
|
|
272
390
|
this.failProtocol("unexpected-agent-settled", { line: this.eventLine });
|
|
273
391
|
return;
|
|
@@ -276,7 +394,7 @@ class RpcSession {
|
|
|
276
394
|
this.activeTurn.resolveSettled();
|
|
277
395
|
}
|
|
278
396
|
|
|
279
|
-
observeTurnEvent(event) {
|
|
397
|
+
observeTurnEvent(event, turn = this.activeTurn) {
|
|
280
398
|
if (event.type === "tool_execution_start") {
|
|
281
399
|
const activity = {
|
|
282
400
|
toolCallId: event.toolCallId ?? null,
|
|
@@ -285,39 +403,39 @@ class RpcSession {
|
|
|
285
403
|
completed: false,
|
|
286
404
|
isError: null,
|
|
287
405
|
};
|
|
288
|
-
|
|
289
|
-
if (typeof event.toolCallId === "string")
|
|
406
|
+
turn.toolActivity.push(activity);
|
|
407
|
+
if (typeof event.toolCallId === "string") turn.calls.set(event.toolCallId, activity);
|
|
290
408
|
return;
|
|
291
409
|
}
|
|
292
410
|
if (event.type === "tool_execution_end") {
|
|
293
|
-
this.observeToolEnd(event);
|
|
411
|
+
this.observeToolEnd(event, turn);
|
|
294
412
|
return;
|
|
295
413
|
}
|
|
296
|
-
if (event.type === "message_end" && event.message) this.observeMessage(event.message);
|
|
414
|
+
if (event.type === "message_end" && event.message) this.observeMessage(event.message, turn);
|
|
297
415
|
}
|
|
298
416
|
|
|
299
|
-
observeToolEnd(event) {
|
|
300
|
-
const activity =
|
|
417
|
+
observeToolEnd(event, turn) {
|
|
418
|
+
const activity = turn.calls.get(event.toolCallId);
|
|
301
419
|
if (!activity) return;
|
|
302
420
|
activity.completed = true;
|
|
303
421
|
activity.isError = event.isError === true;
|
|
304
422
|
if (event.isError !== true && activity.toolName === "read" && typeof activity.args?.path === "string") {
|
|
305
|
-
|
|
423
|
+
turn.successfulReads.push(activity.args.path);
|
|
306
424
|
}
|
|
307
425
|
}
|
|
308
426
|
|
|
309
|
-
observeMessage(message) {
|
|
310
|
-
|
|
427
|
+
observeMessage(message, turn) {
|
|
428
|
+
turn.transcript.push(message);
|
|
311
429
|
this.transcript.push(message);
|
|
312
430
|
if (message.role !== "assistant") return;
|
|
313
|
-
|
|
314
|
-
|
|
431
|
+
turn.response = messageText(message);
|
|
432
|
+
turn.usage = mergeUsage(turn.usage, message.usage);
|
|
315
433
|
this.observedModel = {
|
|
316
434
|
provider: message.provider ?? null,
|
|
317
435
|
model: message.model ?? null,
|
|
318
436
|
};
|
|
319
437
|
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
320
|
-
|
|
438
|
+
turn.assistantError = message.errorMessage ?? `assistant stopped with ${message.stopReason}`;
|
|
321
439
|
}
|
|
322
440
|
}
|
|
323
441
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
const run = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The identity of the working-tree inputs a run reads: the definition root's HEAD commit, and which of the
|
|
10
|
+
* inputs differ from it (modified, untracked, or ignored). A run is canonical only when nothing differs, so
|
|
11
|
+
* every input it read can be recovered from that commit.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} root the definition root
|
|
14
|
+
* @param {string[]} inputs absolute paths of files and directories the run reads from the working tree
|
|
15
|
+
* @returns {Promise<{commit: string | null, uncommitted: string[], repository: boolean}>}
|
|
16
|
+
*/
|
|
17
|
+
export async function inputIdentity(root, inputs) {
|
|
18
|
+
const canonicalRoot = await realpath(root);
|
|
19
|
+
let toplevel;
|
|
20
|
+
try {
|
|
21
|
+
toplevel = await realpath((await git(canonicalRoot, ["rev-parse", "--show-toplevel"])).trim());
|
|
22
|
+
} catch {
|
|
23
|
+
return { commit: null, uncommitted: [], repository: false };
|
|
24
|
+
}
|
|
25
|
+
const commit = await git(toplevel, ["rev-parse", "--verify", "HEAD"])
|
|
26
|
+
.then((output) => output.trim())
|
|
27
|
+
.catch(() => null);
|
|
28
|
+
const paths = [];
|
|
29
|
+
for (const input of inputs) {
|
|
30
|
+
const resolved = await realpath(input).catch(() => path.resolve(input));
|
|
31
|
+
paths.push(path.relative(toplevel, resolved) || ".");
|
|
32
|
+
}
|
|
33
|
+
if (paths.length === 0) return { commit, uncommitted: [], repository: true };
|
|
34
|
+
const status = await git(toplevel, [
|
|
35
|
+
"status",
|
|
36
|
+
"--porcelain=v1",
|
|
37
|
+
"-z",
|
|
38
|
+
"--untracked-files=all",
|
|
39
|
+
"--ignored=matching",
|
|
40
|
+
"--",
|
|
41
|
+
...paths,
|
|
42
|
+
]);
|
|
43
|
+
const uncommitted = new Set();
|
|
44
|
+
const entries = status.split("\0").filter(Boolean);
|
|
45
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
46
|
+
const entry = entries[index];
|
|
47
|
+
const code = entry.slice(0, 2);
|
|
48
|
+
uncommitted.add(
|
|
49
|
+
path
|
|
50
|
+
.relative(canonicalRoot, path.join(toplevel, entry.slice(3)))
|
|
51
|
+
.split(path.sep)
|
|
52
|
+
.join("/"),
|
|
53
|
+
);
|
|
54
|
+
if (code[0] === "R" || code[0] === "C") index += 1; // a rename or copy carries its source path next
|
|
55
|
+
}
|
|
56
|
+
if (commit === null) {
|
|
57
|
+
for (const entry of paths) uncommitted.add(path.relative(canonicalRoot, path.join(toplevel, entry)));
|
|
58
|
+
}
|
|
59
|
+
return { commit, uncommitted: [...uncommitted].sort(), repository: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The message that refuses a canonical run whose inputs cannot be recovered from a commit. */
|
|
63
|
+
export function identityRefusal(identity) {
|
|
64
|
+
if (!identity.repository) {
|
|
65
|
+
return "the evaluation inputs are not in a Git repository, so no commit identifies them; run with --dry-run";
|
|
66
|
+
}
|
|
67
|
+
if (identity.uncommitted.length === 0) return null;
|
|
68
|
+
const shown = identity.uncommitted.slice(0, 10).join(", ");
|
|
69
|
+
const more = identity.uncommitted.length > 10 ? ` and ${identity.uncommitted.length - 10} more` : "";
|
|
70
|
+
return `uncommitted inputs: ${shown}${more}; commit them, or run with --dry-run`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The installed host's version, as `pi --version` prints it. */
|
|
74
|
+
export async function installedHostVersion(command, environment) {
|
|
75
|
+
const { stdout } = await run(command, ["--version"], { env: environment, timeout: 30000 });
|
|
76
|
+
return stdout.trim().split(/\s+/).pop() ?? "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function git(cwd, args) {
|
|
80
|
+
const { stdout } = await run("git", args, { cwd, maxBuffer: 16 * 1024 * 1024 });
|
|
81
|
+
return stdout;
|
|
82
|
+
}
|