@hassangameryt/freeflow 0.4.0 → 0.6.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/plugin.json +2 -1
- package/README.md +150 -155
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +17 -13
- package/hooks/freeflow-runtime-context.mjs +393 -159
- package/hooks/hooks.json +26 -1
- package/hooks/tests/check-runtime-context-hook.sh +223 -54
- package/package.json +25 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +32 -10
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +6 -10
- package/skills/setup-freeflow/references/activation-contract.md +5 -10
- package/skills/setup-freeflow/references/host-setup.md +3 -7
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
- package/pi-extension/dist/output-router/native-safety-net.js +0 -216
- package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
- package/pi-extension/dist/output-router/renderers.js +0 -1023
- package/pi-extension/dist/output-router/router-tools.js +0 -554
- package/pi-extension/dist/output-router/schemas.js +0 -383
- package/pi-extension/dist/output-router/status.js +0 -744
- package/pi-extension/dist/output-router/utils.js +0 -502
- package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
- package/pi-extension/src/output-router/native-safety-net.ts +0 -248
- package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
- package/pi-extension/src/output-router/renderers.ts +0 -1108
- package/pi-extension/src/output-router/router-tools.ts +0 -579
- package/pi-extension/src/output-router/schemas.ts +0 -387
- package/pi-extension/src/output-router/status.ts +0 -791
- package/pi-extension/src/output-router/utils.ts +0 -554
- package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
- package/pi-extension/tests/output-router/search.test.js +0 -722
- package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
- package/router/dist/benchmarks/benchmark-harness.js +0 -92
- package/router/dist/benchmarks/benchmarks.d.ts +0 -109
- package/router/dist/benchmarks/benchmarks.js +0 -979
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
- package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
- package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
- package/router/dist/benchmarks/command-benchmarks.js +0 -698
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
- package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
- package/router/dist/benchmarks/context-saving-baseline.js +0 -470
- package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
- package/router/dist/benchmarks/index-benchmarks.js +0 -862
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
- package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
- package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
- package/router/dist/config/config.d.ts +0 -103
- package/router/dist/config/config.js +0 -632
- package/router/dist/config/router-contract.d.ts +0 -10
- package/router/dist/config/router-contract.js +0 -54
- package/router/dist/config/schema.d.ts +0 -27
- package/router/dist/config/schema.js +0 -648
- package/router/dist/config/types.d.ts +0 -573
- package/router/dist/config/types.js +0 -41
- package/router/dist/evidence/bounded-evidence.d.ts +0 -40
- package/router/dist/evidence/bounded-evidence.js +0 -323
- package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
- package/router/dist/evidence/evidence-range-selector.js +0 -232
- package/router/dist/evidence/evidence-search.d.ts +0 -27
- package/router/dist/evidence/evidence-search.js +0 -490
- package/router/dist/evidence/evidence.d.ts +0 -38
- package/router/dist/evidence/evidence.js +0 -200
- package/router/dist/evidence/failure-contracts.d.ts +0 -37
- package/router/dist/evidence/failure-contracts.js +0 -166
- package/router/dist/evidence/line-ranges.d.ts +0 -24
- package/router/dist/evidence/line-ranges.js +0 -22
- package/router/dist/experiments/local-index.d.ts +0 -64
- package/router/dist/experiments/local-index.js +0 -581
- package/router/dist/index.d.ts +0 -30
- package/router/dist/index.js +0 -32
- package/router/dist/local/local-traversal.d.ts +0 -18
- package/router/dist/local/local-traversal.js +0 -334
- package/router/dist/processing/engine.d.ts +0 -132
- package/router/dist/processing/engine.js +0 -533
- package/router/dist/processing/reducers.d.ts +0 -280
- package/router/dist/processing/reducers.js +0 -1673
- package/router/dist/processing/renderers.d.ts +0 -31
- package/router/dist/processing/renderers.js +0 -195
- package/router/dist/processing/scripts.d.ts +0 -91
- package/router/dist/processing/scripts.js +0 -386
- package/router/dist/repo/repo-traversal.d.ts +0 -18
- package/router/dist/repo/repo-traversal.js +0 -245
- package/router/dist/routing/observed-reducers.d.ts +0 -41
- package/router/dist/routing/observed-reducers.js +0 -661
- package/router/dist/routing/observed-routing.d.ts +0 -26
- package/router/dist/routing/observed-routing.js +0 -378
- package/router/dist/routing/parsers.d.ts +0 -16
- package/router/dist/routing/parsers.js +0 -521
- package/router/dist/routing/run-filters.d.ts +0 -60
- package/router/dist/routing/run-filters.js +0 -238
- package/router/dist/routing/run-reducers.d.ts +0 -49
- package/router/dist/routing/run-reducers.js +0 -122
- package/router/dist/sandbox/adapter-roots.d.ts +0 -46
- package/router/dist/sandbox/adapter-roots.js +0 -66
- package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
- package/router/dist/sandbox/eryx-python-adapter.js +0 -868
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
- package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
- package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
- package/router/dist/sandbox/script-sandbox.d.ts +0 -134
- package/router/dist/sandbox/script-sandbox.js +0 -337
- package/router/dist/setup/script-transform-adapters.d.ts +0 -32
- package/router/dist/setup/script-transform-adapters.js +0 -278
- package/router/dist/tools/batch.d.ts +0 -33
- package/router/dist/tools/batch.js +0 -586
- package/router/dist/tools/run.d.ts +0 -62
- package/router/dist/tools/run.js +0 -1304
- package/router/dist/tools/search.d.ts +0 -66
- package/router/dist/tools/search.js +0 -1364
- package/router/dist/transform/engine.d.ts +0 -168
- package/router/dist/transform/engine.js +0 -2072
- package/router/dist/vault/vault-index.d.ts +0 -125
- package/router/dist/vault/vault-index.js +0 -416
- package/router/dist/vault/vault.d.ts +0 -138
- package/router/dist/vault/vault.js +0 -630
- package/runtime/interaction-contract.md +0 -16
- package/skills/discuss/references/checkpoints.md +0 -66
- package/skills/execute-work/references/execution-loop.md +0 -77
- package/skills/output-router/SKILL.md +0 -206
- package/skills/output-router/references/safety-policy.md +0 -40
- package/skills/setup-freeflow/references/output-router-setup.md +0 -110
- package/skills/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -16,6 +17,10 @@ import {
|
|
|
16
17
|
} from "./grade-support.mjs";
|
|
17
18
|
|
|
18
19
|
const ACTIVATION_EXPECTATIONS = new Set(["never", "on-turn", "by-turn", "not-before-turn"]);
|
|
20
|
+
const TOOL_CALL_EXPECTATIONS = new Set(["called", "succeeded", "failed", "not-called"]);
|
|
21
|
+
const CONTEXT_TEXT_EXPECTATIONS = new Set(["contains", "not-contains", "equals"]);
|
|
22
|
+
const CONTEXT_SURFACES = new Set(["system-prompt", "provider-context"]);
|
|
23
|
+
const CONTEXT_REQUEST_SELECTORS = new Set(["first", "last"]);
|
|
19
24
|
|
|
20
25
|
export async function gradeSkillRead(group, runs, expectation) {
|
|
21
26
|
const turn = expectation.turn ?? null;
|
|
@@ -100,6 +105,95 @@ export async function gradeResourceRead(group, runs, expectation) {
|
|
|
100
105
|
);
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
export async function gradeContextText(group, runs, expectation) {
|
|
109
|
+
const turn = expectation.turn ?? null;
|
|
110
|
+
const surface = expectation.surface ?? "system-prompt";
|
|
111
|
+
const request = expectation.request ?? "last";
|
|
112
|
+
if (
|
|
113
|
+
!validBodyExpectation(group, expectation, turn) ||
|
|
114
|
+
!CONTEXT_SURFACES.has(surface) ||
|
|
115
|
+
(!CONTEXT_REQUEST_SELECTORS.has(request) && !Number.isInteger(request)) ||
|
|
116
|
+
(Number.isInteger(request) && request < 1) ||
|
|
117
|
+
!CONTEXT_TEXT_EXPECTATIONS.has(expectation.expect) ||
|
|
118
|
+
typeof expectation.value !== "string"
|
|
119
|
+
) {
|
|
120
|
+
return { error: "invalid context-text expectation" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const expected = { expect: expectation.expect, value: expectation.value, surface, request, turn };
|
|
124
|
+
const run = runs[expectation.variant];
|
|
125
|
+
if (!run || run.state !== "complete") return unavailableCheck(expectation, expected);
|
|
126
|
+
const observed = await contextObservation(run, turn, surface, request);
|
|
127
|
+
if (observed === null) return unavailableCheck(expectation, expected);
|
|
128
|
+
const passed = textMatches(expectation.expect, expectation.value, observed.text);
|
|
129
|
+
return completedCheck(expectation, expected, compactContextObservation(observed, passed), passed);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function gradeToolCall(group, runs, expectation) {
|
|
133
|
+
const turn = expectation.turn ?? null;
|
|
134
|
+
const contains = expectation.argumentContains ?? [];
|
|
135
|
+
const notContains = expectation.argumentNotContains ?? [];
|
|
136
|
+
if (
|
|
137
|
+
!validBodyExpectation(group, expectation, turn) ||
|
|
138
|
+
typeof expectation.tool !== "string" ||
|
|
139
|
+
expectation.tool.trim() === "" ||
|
|
140
|
+
!TOOL_CALL_EXPECTATIONS.has(expectation.expect) ||
|
|
141
|
+
!Array.isArray(contains) ||
|
|
142
|
+
!contains.every((value) => typeof value === "string") ||
|
|
143
|
+
!Array.isArray(notContains) ||
|
|
144
|
+
!notContains.every((value) => typeof value === "string")
|
|
145
|
+
) {
|
|
146
|
+
return { error: "invalid tool-call expectation" };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const expected = {
|
|
150
|
+
tool: expectation.tool,
|
|
151
|
+
expect: expectation.expect,
|
|
152
|
+
turn,
|
|
153
|
+
argumentContains: contains,
|
|
154
|
+
argumentNotContains: notContains,
|
|
155
|
+
};
|
|
156
|
+
const run = runs[expectation.variant];
|
|
157
|
+
if (!run || run.state !== "complete") return unavailableCheck(expectation, expected);
|
|
158
|
+
|
|
159
|
+
const calls = toolCalls(run, turn).filter((call) => call.toolName === expectation.tool);
|
|
160
|
+
const matchingCalls = calls.filter((call) => {
|
|
161
|
+
const argumentsText = JSON.stringify(call.args ?? null);
|
|
162
|
+
return (
|
|
163
|
+
contains.every((value) => argumentsText.includes(value)) &&
|
|
164
|
+
notContains.every((value) => !argumentsText.includes(value))
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
const succeededCalls = matchingCalls.filter((call) => call.completed === true && call.isError !== true);
|
|
168
|
+
const failedCalls = matchingCalls.filter((call) => call.completed === true && call.isError === true);
|
|
169
|
+
let passed;
|
|
170
|
+
if (expectation.expect === "called") {
|
|
171
|
+
passed = matchingCalls.length > 0;
|
|
172
|
+
} else if (expectation.expect === "succeeded") {
|
|
173
|
+
passed = succeededCalls.length > 0;
|
|
174
|
+
} else if (expectation.expect === "failed") {
|
|
175
|
+
passed = failedCalls.length > 0;
|
|
176
|
+
} else if (contains.length > 0 || notContains.length > 0) {
|
|
177
|
+
passed = matchingCalls.length === 0;
|
|
178
|
+
} else {
|
|
179
|
+
passed = calls.length === 0;
|
|
180
|
+
}
|
|
181
|
+
const observed = {
|
|
182
|
+
tool: expectation.tool,
|
|
183
|
+
turn,
|
|
184
|
+
callCount: calls.length,
|
|
185
|
+
matchingCallCount: matchingCalls.length,
|
|
186
|
+
succeededCount: succeededCalls.length,
|
|
187
|
+
failedCount: failedCalls.length,
|
|
188
|
+
calls: calls.map((call) => ({
|
|
189
|
+
args: call.args ?? null,
|
|
190
|
+
completed: call.completed ?? null,
|
|
191
|
+
isError: call.isError ?? null,
|
|
192
|
+
})),
|
|
193
|
+
};
|
|
194
|
+
return completedCheck(expectation, expected, observed, passed);
|
|
195
|
+
}
|
|
196
|
+
|
|
103
197
|
export async function gradeResponseText(group, runs, expectation) {
|
|
104
198
|
const turn = expectation.turn ?? null;
|
|
105
199
|
if (
|
|
@@ -123,6 +217,67 @@ export async function gradeResponseText(group, runs, expectation) {
|
|
|
123
217
|
);
|
|
124
218
|
}
|
|
125
219
|
|
|
220
|
+
function compactContextObservation(observation, matched) {
|
|
221
|
+
return {
|
|
222
|
+
surface: observation.surface,
|
|
223
|
+
turn: observation.turn,
|
|
224
|
+
request: observation.request,
|
|
225
|
+
characters: observation.characters,
|
|
226
|
+
sha256: createHash("sha256").update(observation.text, "utf8").digest("hex"),
|
|
227
|
+
matched,
|
|
228
|
+
preview: observation.text.slice(0, 240),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function contextObservation(run, turn, surface, request) {
|
|
233
|
+
const observations = await loadContextObservations(run);
|
|
234
|
+
if (observations === null) return null;
|
|
235
|
+
const matching = observations.filter((entry) => entry.surface === surface);
|
|
236
|
+
const inTurn = turn === null ? matching : matching.filter((entry) => entry.turn === turn);
|
|
237
|
+
if (inTurn.length === 0) return null;
|
|
238
|
+
let observation;
|
|
239
|
+
if (request === "first") observation = inTurn[0];
|
|
240
|
+
else if (request === "last") observation = inTurn.at(-1);
|
|
241
|
+
else observation = inTurn.find((entry) => (entry.requestInTurn ?? 1) === request);
|
|
242
|
+
const text = surface === "system-prompt" ? (observation?.text ?? observation?.systemPrompt) : observation?.text;
|
|
243
|
+
if (!observation || typeof text !== "string") return null;
|
|
244
|
+
return {
|
|
245
|
+
surface,
|
|
246
|
+
turn: observation.turn ?? turn,
|
|
247
|
+
request: observation.requestInTurn ?? 1,
|
|
248
|
+
characters: observation.characters ?? text.length,
|
|
249
|
+
text,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function loadContextObservations(run) {
|
|
254
|
+
const artifact = run.contextObservationArtifact;
|
|
255
|
+
if (!artifact || typeof artifact.path !== "string" || typeof artifact.sha256 !== "string") return null;
|
|
256
|
+
const contents = await readFile(artifact.path, "utf8");
|
|
257
|
+
const actualHash = createHash("sha256").update(contents, "utf8").digest("hex");
|
|
258
|
+
if (actualHash !== artifact.sha256) throw new Error("context observation artifact changed after run persistence");
|
|
259
|
+
const observations = [];
|
|
260
|
+
for (const line of contents.split("\n").filter(Boolean)) {
|
|
261
|
+
try {
|
|
262
|
+
observations.push(JSON.parse(line));
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`context observation artifact is malformed: ${error instanceof Error ? error.message : String(error)}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return observations;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function toolCalls(run, turn) {
|
|
273
|
+
if (turn === null) {
|
|
274
|
+
if (Array.isArray(run.turns)) return run.turns.flatMap((entry) => entry.toolActivity ?? []);
|
|
275
|
+
return Array.isArray(run.toolActivity) ? run.toolActivity : [];
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(run.turns)) return turn === 1 && Array.isArray(run.toolActivity) ? run.toolActivity : [];
|
|
278
|
+
return run.turns.find((entry) => entry.turn === turn)?.toolActivity ?? [];
|
|
279
|
+
}
|
|
280
|
+
|
|
126
281
|
function activationMatches(expectation, observed) {
|
|
127
282
|
if (expectation.expect === "never") return !observed.targetRead;
|
|
128
283
|
if (!Number.isInteger(expectation.turn) || expectation.turn < 1) return false;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { gradeComparisons } from "./grade-comparison.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
gradeContextText,
|
|
4
|
+
gradeResourceRead,
|
|
5
|
+
gradeResponseText,
|
|
6
|
+
gradeSkillRead,
|
|
7
|
+
gradeToolCall,
|
|
8
|
+
} from "./grade-interaction.mjs";
|
|
3
9
|
import { gradeChangedPaths, gradeFileText, gradeJson, gradePath } from "./grade-workspace.mjs";
|
|
4
10
|
|
|
5
11
|
const GRADERS = new Map([
|
|
@@ -10,6 +16,8 @@ const GRADERS = new Map([
|
|
|
10
16
|
["file-text", gradeFileText],
|
|
11
17
|
["json", gradeJson],
|
|
12
18
|
["response-text", gradeResponseText],
|
|
19
|
+
["context-text", gradeContextText],
|
|
20
|
+
["tool-call", gradeToolCall],
|
|
13
21
|
]);
|
|
14
22
|
|
|
15
23
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
2
3
|
import { once } from "node:events";
|
|
3
4
|
import { createWriteStream } from "node:fs";
|
|
4
|
-
import { realpath } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
@@ -21,12 +22,32 @@ import {
|
|
|
21
22
|
fingerprintDirectory,
|
|
22
23
|
materializeEnvironment,
|
|
23
24
|
materializeFixture,
|
|
25
|
+
materializeRuntime,
|
|
24
26
|
snapshotWorkspace,
|
|
25
27
|
verifyEnvironment,
|
|
28
|
+
verifyRuntime,
|
|
26
29
|
workspaceChanges,
|
|
27
30
|
} from "./sandbox.mjs";
|
|
28
31
|
|
|
29
32
|
const guardExtension = fileURLToPath(new URL("../pi-guard.mjs", import.meta.url));
|
|
33
|
+
const observerExtension = fileURLToPath(new URL("../pi-observer.mjs", import.meta.url));
|
|
34
|
+
const HOST_COMMANDS = { pi: "pi", piflow: "piflow" };
|
|
35
|
+
const BASE_PROCESS_ENVIRONMENT_KEYS = [
|
|
36
|
+
"PATH",
|
|
37
|
+
"HOME",
|
|
38
|
+
"TMPDIR",
|
|
39
|
+
"TMP",
|
|
40
|
+
"TEMP",
|
|
41
|
+
"LANG",
|
|
42
|
+
"LC_ALL",
|
|
43
|
+
"XDG_CONFIG_HOME",
|
|
44
|
+
"XDG_DATA_HOME",
|
|
45
|
+
"XDG_CACHE_HOME",
|
|
46
|
+
"PIFLOW_CODING_AGENT_DIR",
|
|
47
|
+
"PIFLOW_CODING_AGENT_SESSION_DIR",
|
|
48
|
+
"PI_PACKAGE_DIR",
|
|
49
|
+
"PI_OFFLINE",
|
|
50
|
+
];
|
|
30
51
|
const TURN_WORKSPACE_KINDS = new Set(["path", "changed-paths", "file-text", "json"]);
|
|
31
52
|
|
|
32
53
|
export class VariantSetupError extends Error {
|
|
@@ -36,19 +57,20 @@ export class VariantSetupError extends Error {
|
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
|
|
39
|
-
export async function runDescription({ group, variant, root, variantDirectory, fixture, signal }) {
|
|
60
|
+
export async function runDescription({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
|
|
40
61
|
if (group.input.turns !== undefined) {
|
|
41
|
-
return runPersistentDescription({ group, variant, root, variantDirectory, fixture, signal });
|
|
62
|
+
return runPersistentDescription({ group, variant, root, variantDirectory, fixture, runtime, signal });
|
|
42
63
|
}
|
|
43
|
-
return runOneShotDescription({ group, variant, root, variantDirectory, fixture, signal });
|
|
64
|
+
return runOneShotDescription({ group, variant, root, variantDirectory, fixture, runtime, signal });
|
|
44
65
|
}
|
|
45
66
|
|
|
46
|
-
export async function runBody({ group, variant, root, variantDirectory, fixture, signal }) {
|
|
47
|
-
const options = { group, variant, root, variantDirectory, fixture, signal };
|
|
67
|
+
export async function runBody({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
|
|
68
|
+
const options = { group, variant, root, variantDirectory, fixture, runtime, signal };
|
|
48
69
|
const subject = await prepareSubject(options, "rpc");
|
|
49
70
|
const before = await fingerprintDirectory(subject.workspace);
|
|
50
71
|
const declaredPrompts = group.input.prompt === undefined ? [...group.input.turns] : [group.input.prompt];
|
|
51
72
|
const observation = await runRpcBodySession({
|
|
73
|
+
command: subject.host.command,
|
|
52
74
|
args: subject.args,
|
|
53
75
|
cwd: subject.workspace,
|
|
54
76
|
eventsFile: subject.eventsFile,
|
|
@@ -61,6 +83,11 @@ export async function runBody({ group, variant, root, variantDirectory, fixture,
|
|
|
61
83
|
? (turn) => snapshotWorkspace({ workspace: subject.workspace, variantDirectory, turn, before })
|
|
62
84
|
: null,
|
|
63
85
|
});
|
|
86
|
+
const contextObservation = await readContextObservations(
|
|
87
|
+
subject.contextObservationPath,
|
|
88
|
+
subject.requiresContextObservation,
|
|
89
|
+
);
|
|
90
|
+
observation.contextObservationArtifact = contextObservation.artifact;
|
|
64
91
|
await verifySubjectResources(subject, observation);
|
|
65
92
|
const after = await fingerprintDirectory(subject.workspace);
|
|
66
93
|
|
|
@@ -132,14 +159,17 @@ function subjectRun({ group, variant }, subject, observation) {
|
|
|
132
159
|
source: subject.environment.source,
|
|
133
160
|
skills: subject.environment.skills,
|
|
134
161
|
context: subject.environment.context,
|
|
162
|
+
runtime: subject.runtime,
|
|
135
163
|
contextDelivery: subject.environment.contextDelivery,
|
|
136
164
|
targetPath: subject.environment.targetPath,
|
|
137
165
|
},
|
|
166
|
+
contextObservationArtifact: observation.contextObservationArtifact ?? null,
|
|
138
167
|
response: observation.response,
|
|
139
168
|
transcript: observation.transcript,
|
|
140
169
|
usage: observation.usage,
|
|
141
170
|
process: {
|
|
142
|
-
command:
|
|
171
|
+
command: subject.host.command,
|
|
172
|
+
host: subject.host.name,
|
|
143
173
|
args: subject.args,
|
|
144
174
|
exitCode: observation.exitCode,
|
|
145
175
|
signal: observation.signal,
|
|
@@ -156,6 +186,7 @@ function subjectRun({ group, variant }, subject, observation) {
|
|
|
156
186
|
async function runOneShotDescription(options) {
|
|
157
187
|
const subject = await prepareSubject(options, "json");
|
|
158
188
|
const observation = await runPiProcess({
|
|
189
|
+
command: subject.host.command,
|
|
159
190
|
args: subject.args,
|
|
160
191
|
cwd: subject.workspace,
|
|
161
192
|
eventsFile: subject.eventsFile,
|
|
@@ -163,6 +194,11 @@ async function runOneShotDescription(options) {
|
|
|
163
194
|
signal: options.signal,
|
|
164
195
|
environment: subject.processEnvironment,
|
|
165
196
|
});
|
|
197
|
+
const contextObservation = await readContextObservations(
|
|
198
|
+
subject.contextObservationPath,
|
|
199
|
+
subject.requiresContextObservation,
|
|
200
|
+
);
|
|
201
|
+
observation.contextObservationArtifact = contextObservation.artifact;
|
|
166
202
|
await verifySubjectResources(subject, observation);
|
|
167
203
|
const successfulReadPaths = await canonicalReadPaths(observation.successfulReads, subject.workspace);
|
|
168
204
|
const targetRead =
|
|
@@ -181,6 +217,7 @@ async function runOneShotDescription(options) {
|
|
|
181
217
|
async function runPersistentDescription(options) {
|
|
182
218
|
const subject = await prepareSubject(options, "rpc");
|
|
183
219
|
const observation = await runRpcDescriptionSession({
|
|
220
|
+
command: subject.host.command,
|
|
184
221
|
args: subject.args,
|
|
185
222
|
cwd: subject.workspace,
|
|
186
223
|
eventsFile: subject.eventsFile,
|
|
@@ -189,6 +226,11 @@ async function runPersistentDescription(options) {
|
|
|
189
226
|
prompts: options.group.input.turns,
|
|
190
227
|
environment: subject.processEnvironment,
|
|
191
228
|
});
|
|
229
|
+
const contextObservation = await readContextObservations(
|
|
230
|
+
subject.contextObservationPath,
|
|
231
|
+
subject.requiresContextObservation,
|
|
232
|
+
);
|
|
233
|
+
observation.contextObservationArtifact = contextObservation.artifact;
|
|
192
234
|
await verifySubjectResources(subject, observation);
|
|
193
235
|
|
|
194
236
|
const turns = [];
|
|
@@ -222,52 +264,130 @@ async function runPersistentDescription(options) {
|
|
|
222
264
|
});
|
|
223
265
|
}
|
|
224
266
|
|
|
225
|
-
async function prepareSubject({ group, variant, root, variantDirectory, fixture: fixtureSnapshot }, mode) {
|
|
267
|
+
async function prepareSubject({ group, variant, root, variantDirectory, fixture: fixtureSnapshot, runtime }, mode) {
|
|
226
268
|
const workspace = path.join(variantDirectory, "workspace");
|
|
227
269
|
|
|
228
270
|
let environment;
|
|
229
271
|
let fixture;
|
|
272
|
+
let materializedRuntime;
|
|
230
273
|
try {
|
|
231
274
|
environment = await materializeEnvironment({
|
|
232
275
|
environment: group.variants[variant],
|
|
233
276
|
root,
|
|
234
277
|
variantDirectory,
|
|
235
278
|
});
|
|
279
|
+
materializedRuntime = await materializeRuntime({ runtime, variantDirectory });
|
|
236
280
|
fixture = await materializeFixture({ fixture: fixtureSnapshot, workspace });
|
|
237
281
|
} catch (error) {
|
|
238
282
|
if (error instanceof VariantSetupError) throw error;
|
|
239
283
|
throw new VariantSetupError(error instanceof Error ? error.message : String(error));
|
|
240
284
|
}
|
|
241
285
|
|
|
242
|
-
const
|
|
286
|
+
const host = { name: materializedRuntime.host, command: HOST_COMMANDS[materializedRuntime.host] };
|
|
287
|
+
const contextObservationPath = path.join(variantDirectory, "context-observations.jsonl");
|
|
288
|
+
const requiresContextObservation = group.expectations.some((expectation) => expectation.kind === "context-text");
|
|
289
|
+
const sessionDirectory = path.join(variantDirectory, "session");
|
|
290
|
+
if (materializedRuntime.session) await mkdir(sessionDirectory, { recursive: true });
|
|
291
|
+
const args = piArguments(group, environment, materializedRuntime, mode, requiresContextObservation, sessionDirectory);
|
|
243
292
|
const allowedRoots = [
|
|
244
293
|
await realpath(workspace),
|
|
245
294
|
...environment.skills.map((skill) => skill.path),
|
|
246
295
|
...environment.context.map((entry) => entry.path),
|
|
296
|
+
...materializedRuntime.extensions.flatMap((bundle) => bundle.resources.map((resource) => resource.path)),
|
|
247
297
|
];
|
|
248
298
|
return {
|
|
249
299
|
args,
|
|
250
300
|
environment,
|
|
301
|
+
runtime: materializedRuntime,
|
|
302
|
+
host,
|
|
251
303
|
fixture,
|
|
252
304
|
workspace,
|
|
305
|
+
contextObservationPath,
|
|
306
|
+
requiresContextObservation,
|
|
307
|
+
sessionDirectory,
|
|
253
308
|
eventsFile: path.join(variantDirectory, "events.jsonl"),
|
|
254
309
|
stderrFile: path.join(variantDirectory, "stderr.log"),
|
|
255
310
|
startedAt: new Date().toISOString(),
|
|
256
|
-
processEnvironment: {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
},
|
|
311
|
+
processEnvironment: createProcessEnvironment({
|
|
312
|
+
runtime: materializedRuntime,
|
|
313
|
+
group,
|
|
314
|
+
workspace,
|
|
315
|
+
allowedRoots,
|
|
316
|
+
contextManifestPath: environment.contextDelivery.manifestPath,
|
|
317
|
+
contextObservationPath,
|
|
318
|
+
requiresContextObservation,
|
|
319
|
+
}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function createProcessEnvironment({
|
|
324
|
+
runtime,
|
|
325
|
+
group,
|
|
326
|
+
workspace,
|
|
327
|
+
allowedRoots,
|
|
328
|
+
contextManifestPath,
|
|
329
|
+
contextObservationPath,
|
|
330
|
+
requiresContextObservation,
|
|
331
|
+
}) {
|
|
332
|
+
const processEnvironment = Object.fromEntries(
|
|
333
|
+
BASE_PROCESS_ENVIRONMENT_KEYS.flatMap((key) => (process.env[key] === undefined ? [] : [[key, process.env[key]]])),
|
|
334
|
+
);
|
|
335
|
+
Object.assign(processEnvironment, {
|
|
336
|
+
PWD: workspace,
|
|
337
|
+
...runtime.environment.literal,
|
|
338
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
339
|
+
PI_TELEMETRY: "0",
|
|
340
|
+
SKILL_EVAL_ALLOWED_ROOTS: JSON.stringify(allowedRoots),
|
|
341
|
+
SKILL_EVAL_WRITABLE_ROOT: workspace,
|
|
342
|
+
SKILL_EVAL_ALLOWED_TOOLS: JSON.stringify(group.tools),
|
|
343
|
+
SKILL_EVAL_CONTEXT_MANIFEST: contextManifestPath ?? "",
|
|
344
|
+
SKILL_EVAL_HOST: runtime.host,
|
|
345
|
+
});
|
|
346
|
+
for (const key of runtime.environment.inherit) {
|
|
347
|
+
if (process.env[key] === undefined) {
|
|
348
|
+
throw new VariantSetupError(`inherited runtime environment variable is unavailable: ${key}`);
|
|
349
|
+
}
|
|
350
|
+
processEnvironment[key] = process.env[key];
|
|
351
|
+
}
|
|
352
|
+
if (requiresContextObservation) processEnvironment.SKILL_EVAL_CONTEXT_OBSERVATION_PATH = contextObservationPath;
|
|
353
|
+
return processEnvironment;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function readContextObservations(file, required) {
|
|
357
|
+
const contents = await readFile(file, "utf8").catch((error) => {
|
|
358
|
+
if (error?.code === "ENOENT") {
|
|
359
|
+
if (required) throw new Error("context observer produced no evidence");
|
|
360
|
+
return "";
|
|
361
|
+
}
|
|
362
|
+
throw error;
|
|
363
|
+
});
|
|
364
|
+
const observations = [];
|
|
365
|
+
for (const line of contents.split("\n").filter(Boolean)) {
|
|
366
|
+
try {
|
|
367
|
+
observations.push(JSON.parse(line));
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw new Error(`context observation is malformed: ${error instanceof Error ? error.message : String(error)}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
observations,
|
|
374
|
+
artifact:
|
|
375
|
+
contents === ""
|
|
376
|
+
? null
|
|
377
|
+
: {
|
|
378
|
+
path: file,
|
|
379
|
+
sha256: createHash("sha256").update(contents, "utf8").digest("hex"),
|
|
380
|
+
bytes: Buffer.byteLength(contents, "utf8"),
|
|
381
|
+
count: observations.length,
|
|
382
|
+
surfaces: [...new Set(observations.map((entry) => entry.surface).filter(Boolean))],
|
|
383
|
+
},
|
|
265
384
|
};
|
|
266
385
|
}
|
|
267
386
|
|
|
268
387
|
async function verifySubjectResources(subject, observation) {
|
|
269
388
|
try {
|
|
270
389
|
await verifyEnvironment(subject.environment);
|
|
390
|
+
await verifyRuntime(subject.runtime);
|
|
271
391
|
} catch (error) {
|
|
272
392
|
observation.protocolErrors ??= [];
|
|
273
393
|
observation.protocolErrors.push({
|
|
@@ -300,20 +420,15 @@ function subjectRunState(observation) {
|
|
|
300
420
|
return "infrastructure-failed";
|
|
301
421
|
}
|
|
302
422
|
|
|
303
|
-
function piArguments(group, environment, mode) {
|
|
423
|
+
function piArguments(group, environment, runtime, mode, observeContext, sessionDirectory) {
|
|
304
424
|
const args = ["--mode", mode];
|
|
305
425
|
if (mode === "json") args.push("-p");
|
|
306
|
-
args.push(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"--no-prompt-templates",
|
|
313
|
-
"--no-themes",
|
|
314
|
-
"--no-context-files",
|
|
315
|
-
"--no-approve",
|
|
316
|
-
);
|
|
426
|
+
if (runtime.session) args.push("--session-dir", sessionDirectory);
|
|
427
|
+
else args.push("--no-session");
|
|
428
|
+
args.push("--no-extensions", "--extension", guardExtension);
|
|
429
|
+
for (const bundle of runtime.extensions) args.push("--extension", bundle.entry);
|
|
430
|
+
if (observeContext) args.push("--extension", observerExtension);
|
|
431
|
+
args.push("--no-skills", "--no-prompt-templates", "--no-themes", "--no-context-files", "--no-approve");
|
|
317
432
|
if (group.tools.length === 0) args.push("--no-tools");
|
|
318
433
|
else args.push("--tools", group.tools.join(","));
|
|
319
434
|
for (const skill of environment.skills) args.push("--skill", skill.path);
|
|
@@ -323,8 +438,8 @@ function piArguments(group, environment, mode) {
|
|
|
323
438
|
return args;
|
|
324
439
|
}
|
|
325
440
|
|
|
326
|
-
async function runPiProcess({ args, cwd, eventsFile, stderrFile, signal, environment }) {
|
|
327
|
-
const child = spawn(
|
|
441
|
+
async function runPiProcess({ command, args, cwd, eventsFile, stderrFile, signal, environment }) {
|
|
442
|
+
const child = spawn(command, args, {
|
|
328
443
|
cwd,
|
|
329
444
|
detached: process.platform !== "win32",
|
|
330
445
|
env: environment,
|
|
@@ -28,8 +28,8 @@ export async function runRpcBodySession(options) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
class RpcSession {
|
|
31
|
-
constructor({ args, cwd, eventsFile, stderrFile, signal, environment, afterTurn = null }) {
|
|
32
|
-
this.child = spawn(
|
|
31
|
+
constructor({ command = "pi", args, cwd, eventsFile, stderrFile, signal, environment, afterTurn = null }) {
|
|
32
|
+
this.child = spawn(command, args, {
|
|
33
33
|
cwd,
|
|
34
34
|
detached: process.platform !== "win32",
|
|
35
35
|
env: environment,
|
|
@@ -79,17 +79,17 @@ class RpcSession {
|
|
|
79
79
|
async run(prompts, targetPath = null) {
|
|
80
80
|
let deliveredPrompts = prompts;
|
|
81
81
|
/** @type {any} */
|
|
82
|
-
let delivery = { kind: "
|
|
82
|
+
let delivery = { kind: "unavailable", turn: 1, targetPath };
|
|
83
83
|
try {
|
|
84
84
|
await this.configure();
|
|
85
85
|
if (targetPath !== null) {
|
|
86
|
-
delivery = { kind: "unavailable", turn: 1, targetPath };
|
|
87
86
|
const command = await this.resolveSkillCommand(targetPath);
|
|
88
87
|
deliveredPrompts = [`/${command} ${prompts[0]}`, ...prompts.slice(1)];
|
|
89
88
|
delivery = { kind: "explicit-skill-command", turn: 1, targetPath, command };
|
|
90
89
|
}
|
|
91
90
|
for (const [index, prompt] of deliveredPrompts.entries()) {
|
|
92
91
|
if (this.signal?.aborted || this.terminationReason !== null) break;
|
|
92
|
+
if (delivery.kind === "unavailable") delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
|
|
93
93
|
const turn = await this.runTurn(index + 1, prompt);
|
|
94
94
|
this.turns.push(turn);
|
|
95
95
|
if (this.afterTurn !== null) turn.workspace = await this.afterTurn(turn.turn);
|
|
@@ -5,11 +5,14 @@ import { loadDefinition, selectDefinition } from "./definitions.mjs";
|
|
|
5
5
|
import { createInvocationId, fileIdentity, writeJson, writeText } from "./evidence.mjs";
|
|
6
6
|
import { gradeDeterministic } from "./grade.mjs";
|
|
7
7
|
import { runBody, runDescription, VariantSetupError } from "./pi.mjs";
|
|
8
|
-
import { prepareFixture } from "./sandbox.mjs";
|
|
8
|
+
import { prepareFixture, prepareRuntime } from "./sandbox.mjs";
|
|
9
9
|
|
|
10
10
|
const VARIANTS = ["baseline", "candidate"];
|
|
11
11
|
const DESCRIPTION_TOOLS = new Set(["read"]);
|
|
12
12
|
const BODY_TOOLS = new Set(["read", "write", "edit"]);
|
|
13
|
+
// Native tools must be separated from extension tools before CLI tool selection is built.
|
|
14
|
+
const NATIVE_TOOLS = new Set(["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"]);
|
|
15
|
+
const DEFAULT_RUNTIME = { host: "pi", session: false, extensions: [], environment: { literal: {}, inherit: [] } };
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* @param {string} definitionFile
|
|
@@ -69,8 +72,18 @@ function assertSupportedSelection(selection) {
|
|
|
69
72
|
throw new Error("command expectations are not supported");
|
|
70
73
|
}
|
|
71
74
|
const supportedTools = group.type === "body" ? BODY_TOOLS : DESCRIPTION_TOOLS;
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
const unsupportedNativeTools = group.tools.filter((tool) => NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
|
|
76
|
+
if (unsupportedNativeTools.length > 0) {
|
|
77
|
+
throw new Error(`${group.type} execution does not support native tools: ${unsupportedNativeTools.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
const customTools = group.tools.filter((tool) => !NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
|
|
80
|
+
if (group.type === "description" && customTools.length > 0) {
|
|
81
|
+
throw new Error(`${group.type} execution does not support custom tools: ${customTools.join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
if (customTools.length > 0 && (group.runtime?.extensions?.length ?? 0) === 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`${group.type} execution requires declared extensions for custom tools: ${customTools.join(", ")}`,
|
|
86
|
+
);
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
}
|
|
@@ -82,12 +95,18 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
|
|
|
82
95
|
await writeJson(path.join(groupDirectory, "definition.json"), group);
|
|
83
96
|
|
|
84
97
|
let fixture = null;
|
|
85
|
-
let
|
|
98
|
+
let runtime = null;
|
|
99
|
+
let resourceError = null;
|
|
86
100
|
if (!signal?.aborted) {
|
|
87
101
|
try {
|
|
88
102
|
fixture = await prepareFixture({ declaredPath: group.fixture, root, groupDirectory });
|
|
103
|
+
runtime = await prepareRuntime({
|
|
104
|
+
runtime: group.runtime ?? DEFAULT_RUNTIME,
|
|
105
|
+
root,
|
|
106
|
+
groupDirectory,
|
|
107
|
+
});
|
|
89
108
|
} catch (error) {
|
|
90
|
-
|
|
109
|
+
resourceError = error instanceof Error ? error.message : String(error);
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
112
|
|
|
@@ -100,10 +119,10 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
|
|
|
100
119
|
runs[variant] = notSelectedRun(group, variant);
|
|
101
120
|
} else if (signal?.aborted) {
|
|
102
121
|
runs[variant] = cancelledRun(group, variant);
|
|
103
|
-
} else if (
|
|
104
|
-
runs[variant] =
|
|
122
|
+
} else if (resourceError === null) {
|
|
123
|
+
runs[variant] = await executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal });
|
|
105
124
|
} else {
|
|
106
|
-
runs[variant] =
|
|
125
|
+
runs[variant] = invalidRun(group, variant, resourceError);
|
|
107
126
|
}
|
|
108
127
|
const persisted = await persistRunOrFailure(variantDirectory, runs[variant]);
|
|
109
128
|
runs[variant] = persisted.run;
|
|
@@ -120,7 +139,8 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
|
|
|
120
139
|
(variant) => runs[variant],
|
|
121
140
|
);
|
|
122
141
|
const selectedState = selectedGroupState(selectedRuns, persistedGrade.grade);
|
|
123
|
-
|
|
142
|
+
let groupState = selectedState;
|
|
143
|
+
if (selectedState !== "cancelled" && resourceError !== null) groupState = "invalid";
|
|
124
144
|
const groupResult = {
|
|
125
145
|
id: group.id,
|
|
126
146
|
position: selected.position,
|
|
@@ -205,10 +225,10 @@ function errorMessage(error) {
|
|
|
205
225
|
return error instanceof Error ? error.message : String(error);
|
|
206
226
|
}
|
|
207
227
|
|
|
208
|
-
async function executeVariant({ group, variant, root, variantDirectory, fixture, signal }) {
|
|
228
|
+
async function executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
|
|
209
229
|
try {
|
|
210
230
|
const run = group.type === "body" ? runBody : runDescription;
|
|
211
|
-
return await run({ group, variant, root, variantDirectory, fixture, signal });
|
|
231
|
+
return await run({ group, variant, root, variantDirectory, fixture, runtime, signal });
|
|
212
232
|
} catch (error) {
|
|
213
233
|
if (error instanceof VariantSetupError) return invalidRun(group, variant, error.message);
|
|
214
234
|
return infrastructureFailedRun(group, variant, error);
|
|
@@ -241,16 +261,18 @@ async function persistRun(variantDirectory, run) {
|
|
|
241
261
|
await writeText(path.join(variantDirectory, "final.md"), response === "" ? "" : `${response}\n`);
|
|
242
262
|
const runFile = path.join(variantDirectory, "run.json");
|
|
243
263
|
const hasProcessEvidence = run.process !== undefined;
|
|
244
|
-
|
|
264
|
+
const persisted = {
|
|
245
265
|
...run,
|
|
246
|
-
...(Array.isArray(run.turns) ? { turns: externalizeTurnTranscripts(run.turns) } : {}),
|
|
247
266
|
transcript: "transcript.json",
|
|
248
267
|
artifacts: {
|
|
249
268
|
events: hasProcessEvidence ? "events.jsonl" : null,
|
|
250
269
|
final: "final.md",
|
|
251
270
|
stderr: hasProcessEvidence ? "stderr.log" : null,
|
|
271
|
+
context: run.contextObservationArtifact ? "context-observations.jsonl" : null,
|
|
252
272
|
},
|
|
253
|
-
}
|
|
273
|
+
};
|
|
274
|
+
if (Array.isArray(run.turns)) persisted.turns = externalizeTurnTranscripts(run.turns);
|
|
275
|
+
await writeJson(runFile, persisted);
|
|
254
276
|
return runFile;
|
|
255
277
|
}
|
|
256
278
|
|