carthagent 0.0.0-stage → 1.0.7
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/LICENSE +21 -0
- package/README.md +219 -2
- package/bin/ctg.mjs +736 -0
- package/docs/budgets.md +72 -0
- package/docs/compaction.md +34 -0
- package/docs/context-efficiency-plan.md +24 -0
- package/docs/context-efficiency-review.md +27 -0
- package/docs/context-efficiency.md +49 -0
- package/docs/deepseek-jev-benchmark-plan.md +70 -0
- package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
- package/docs/deepseek-jev-benchmark-restart.md +9 -0
- package/docs/deepseek-jev-benchmark-results.md +77 -0
- package/docs/deepseek-jev-benchmark-running.md +60 -0
- package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
- package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
- package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
- package/docs/deepseek-jev-http400-diagnosis.md +45 -0
- package/docs/delivery-review-evidence.md +53 -0
- package/docs/delivery-timeout-repair-plan.md +51 -0
- package/docs/evaluation-readiness.md +31 -0
- package/docs/evaluation.md +70 -0
- package/docs/extending.md +354 -0
- package/docs/glm-pilot-execution.md +9 -0
- package/docs/glm-pilot-plan.md +15 -0
- package/docs/glm-pilot-results.md +71 -0
- package/docs/graph-context-optimization.md +15 -0
- package/docs/jev-repository-pilot-plan.md +25 -0
- package/docs/jev-repository-pilot-protocol.md +39 -0
- package/docs/jev-repository-pilot-restart.md +7 -0
- package/docs/jev-repository-pilot-results.md +79 -0
- package/docs/jev.md +32 -0
- package/docs/model-registry.md +25 -0
- package/docs/native-compatibility.md +117 -0
- package/docs/opencode-archive.md +9 -0
- package/docs/opencode-migration.md +115 -0
- package/docs/opencode-runtime.md +94 -0
- package/docs/opencode-workspace.md +31 -0
- package/docs/owned-runtime-workflow-registration.md +17 -0
- package/docs/plan-advisor-evaluation.md +38 -0
- package/docs/planning-startup-fix-plan.md +23 -0
- package/docs/planning-startup-fix-review.md +24 -0
- package/docs/planning.md +118 -0
- package/docs/production-merge-review.md +23 -0
- package/docs/quality.md +35 -0
- package/docs/repository-benchmark-design.md +111 -0
- package/docs/review-assurance.md +9 -0
- package/docs/review-experiment-results.md +59 -0
- package/docs/tested-planning-roadmap.md +58 -0
- package/docs/tested-planning.md +58 -0
- package/docs/tui-pickers.md +9 -0
- package/docs/webdriver.md +83 -0
- package/docs/workflow-modes.md +26 -0
- package/docs/workflow.d2 +17 -0
- package/docs/workflow.svg +149 -0
- package/docs/workspace-coordination.md +9 -0
- package/evaluate.mjs +249 -0
- package/extensions/codegraph.ts +36 -0
- package/extensions/delivery.ts +819 -0
- package/extensions/experiential.ts +21 -0
- package/extensions/subscriptions.ts +21 -0
- package/guidance/profiles/authenticated-web.json +24 -0
- package/guidance/profiles/data-visualization.json +25 -0
- package/guidance/profiles/database-change.json +24 -0
- package/guidance/profiles/external-api.json +24 -0
- package/guidance/profiles/transactional-data.json +25 -0
- package/guidance/profiles/web-application.json +26 -0
- package/lib/acceptance-review.mjs +4 -0
- package/lib/browser-bin.mjs +160 -0
- package/lib/browser-errors.mjs +54 -0
- package/lib/browser.mjs +219 -0
- package/lib/budget.mjs +37 -0
- package/lib/cdp.mjs +221 -0
- package/lib/cloud/client.mjs +275 -0
- package/lib/cloud/control-plane.mjs +350 -0
- package/lib/cloud/gateway.mjs +380 -0
- package/lib/cloud/http.mjs +135 -0
- package/lib/cloud/ledger.mjs +215 -0
- package/lib/codegraph-query.mjs +60 -0
- package/lib/codegraph.mjs +378 -0
- package/lib/context-archive.mjs +18 -0
- package/lib/delivery-context.mjs +132 -0
- package/lib/delivery-guidance.mjs +9 -0
- package/lib/delivery-review.mjs +94 -0
- package/lib/delivery.mjs +610 -0
- package/lib/engine.mjs +137 -0
- package/lib/guidance.mjs +71 -0
- package/lib/jev-advisor.mjs +47 -0
- package/lib/opencode/archive.mjs +122 -0
- package/lib/opencode/compatibility.mjs +19 -0
- package/lib/opencode/credentials.mjs +205 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
- package/lib/opencode/evaluation.mjs +242 -0
- package/lib/opencode/file-lock.mjs +86 -0
- package/lib/opencode/headless.mjs +200 -0
- package/lib/opencode/review.mjs +213 -0
- package/lib/opencode/runtime.mjs +246 -0
- package/lib/opencode/service.mjs +249 -0
- package/lib/opencode/session-options.mjs +60 -0
- package/lib/opencode/workspace.mjs +133 -0
- package/lib/openrouter-jev.mjs +37 -0
- package/lib/outcome-contract.mjs +21 -0
- package/lib/plan-advisor.mjs +38 -0
- package/lib/planning-access.mjs +7 -0
- package/lib/planning-advice.mjs +28 -0
- package/lib/planning-discovery.mjs +14 -0
- package/lib/planning-guide.mjs +34 -0
- package/lib/planning-view.mjs +57 -0
- package/lib/planning.mjs +155 -0
- package/lib/probe-diagnostics.mjs +13 -0
- package/lib/providers/experiential.mjs +292 -0
- package/lib/providers/model-catalog.json +700 -0
- package/lib/providers/model-catalog.mjs +7 -0
- package/lib/providers/names.mjs +13 -0
- package/lib/providers/subscription-models.mjs +483 -0
- package/lib/reports.mjs +80 -0
- package/lib/review-state.mjs +49 -0
- package/lib/review.mjs +114 -0
- package/lib/scenarios.mjs +99 -0
- package/lib/ship-auth.mjs +44 -0
- package/lib/ship-jev.mjs +59 -0
- package/lib/ship-telemetry.mjs +183 -0
- package/lib/tui/ansi.mjs +255 -0
- package/lib/tui/app.mjs +2445 -0
- package/lib/tui/auth.mjs +249 -0
- package/lib/tui/carthage.mjs +109 -0
- package/lib/tui/clipboard.mjs +122 -0
- package/lib/tui/d2.mjs +431 -0
- package/lib/tui/feed.mjs +467 -0
- package/lib/tui/framing.mjs +80 -0
- package/lib/tui/glyphs.mjs +172 -0
- package/lib/tui/hash-worker.mjs +11 -0
- package/lib/tui/mock-engine.mjs +222 -0
- package/lib/tui/models.mjs +73 -0
- package/lib/tui/picker.mjs +18 -0
- package/lib/tui/theme.mjs +663 -0
- package/lib/tui/title.mjs +7 -0
- package/lib/update-channel.mjs +62 -0
- package/lib/update.mjs +230 -0
- package/lib/webdriver.mjs +82 -0
- package/lib/workflow.mjs +88 -0
- package/lib/workspace.mjs +39 -0
- package/metadata.json +8 -0
- package/package.json +94 -4
- package/plugins/carthagent/index.ts +3 -0
- package/plugins/carthagent/package.json +28 -0
- package/plugins/carthagent/src/archive-rpc.ts +46 -0
- package/plugins/carthagent/src/bootstrap.ts +38 -0
- package/plugins/carthagent/src/index.ts +62 -0
- package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
- package/plugins/carthagent/src/rpc.ts +76 -0
- package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
- package/plugins/carthagent/src/server/budgets.ts +21 -0
- package/plugins/carthagent/src/server/codegraph.ts +46 -0
- package/plugins/carthagent/src/server/delivery.ts +488 -0
- package/plugins/carthagent/src/server/device-auth.ts +91 -0
- package/plugins/carthagent/src/server/native-delivery.ts +26 -0
- package/plugins/carthagent/src/server/orchestration.ts +56 -0
- package/plugins/carthagent/src/server/policy.ts +24 -0
- package/plugins/carthagent/src/server/providers.ts +424 -0
- package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
- package/plugins/carthagent/src/server/session-controls.ts +82 -0
- package/plugins/carthagent/src/server/telemetry.ts +92 -0
- package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
- package/plugins/carthagent/src/session-rpc.ts +31 -0
- package/plugins/carthagent/src/testing.ts +85 -0
- package/plugins/carthagent/src/workspace-rpc.ts +133 -0
- package/plugins/carthagent/test-plugin/index.ts +4 -0
- package/plugins/carthagent/test-plugin/package.json +1 -0
- package/plugins/carthagent/tsconfig.json +19 -0
- package/plugins/carthagent/tui.ts +1 -0
- package/prompts/delivery.md +7 -0
- package/public/index.html +983 -0
- package/scenarios/cli/grade_cli.py +69 -0
- package/scenarios/cli/scenario.json +21 -0
- package/scenarios/game/baseline/minecraft.py +193 -0
- package/scenarios/game/context.md +1 -0
- package/scenarios/game/grade_minecraft_clone.py +603 -0
- package/scenarios/game/requirements.txt +2 -0
- package/scenarios/game/scenario.json +33 -0
- package/server.mjs +341 -0
- package/skills/game-development/SKILL.md +36 -0
- package/skills/game-development/assets/ursina_starter.py +98 -0
- package/skills/game-development/scripts/verify_ursina.py +80 -0
- package/skills/software-delivery/SKILL.md +50 -0
- package/skills/typescript-delivery/SKILL.md +69 -0
- package/tests/acceptance-review.test.mjs +13 -0
- package/tests/auth.test.mjs +234 -0
- package/tests/bounded-summary.test.mjs +68 -0
- package/tests/browser-bin.test.mjs +91 -0
- package/tests/browser.test.mjs +96 -0
- package/tests/budget-engine.test.mjs +110 -0
- package/tests/budget.test.mjs +23 -0
- package/tests/cli.test.mjs +245 -0
- package/tests/clipboard.test.mjs +79 -0
- package/tests/cloud-client.test.mjs +129 -0
- package/tests/cloud-control-plane.test.mjs +144 -0
- package/tests/cloud-gateway.test.mjs +147 -0
- package/tests/cloud-http.test.mjs +102 -0
- package/tests/cloud-ledger.test.mjs +75 -0
- package/tests/codegraph-query.test.mjs +66 -0
- package/tests/codegraph.test.mjs +142 -0
- package/tests/delivery-context.test.mjs +66 -0
- package/tests/delivery-review.test.mjs +139 -0
- package/tests/delivery.test.mjs +343 -0
- package/tests/engine-api.mjs +24 -0
- package/tests/experiential-extension.test.mjs +37 -0
- package/tests/experiential.test.mjs +300 -0
- package/tests/extension.test.mjs +921 -0
- package/tests/fixtures/archive-plugin/index.ts +10 -0
- package/tests/guidance.test.mjs +78 -0
- package/tests/model-registry.test.mjs +68 -0
- package/tests/models.test.mjs +70 -0
- package/tests/openai-idempotency.test.mjs +99 -0
- package/tests/opencode-archive-rpc.test.mjs +82 -0
- package/tests/opencode-delivery.test.mjs +231 -0
- package/tests/opencode-providers.test.mjs +414 -0
- package/tests/opencode-runtime.test.mjs +455 -0
- package/tests/opencode-session-controls.test.mjs +115 -0
- package/tests/opencode-session-options.test.mjs +26 -0
- package/tests/opencode-workspace.test.mjs +339 -0
- package/tests/outcome-contract.test.mjs +17 -0
- package/tests/picker.test.mjs +34 -0
- package/tests/plan-advisor.test.mjs +88 -0
- package/tests/plan-review-loop.test.mjs +53 -0
- package/tests/planning-advice.test.mjs +27 -0
- package/tests/planning-contract.test.mjs +65 -0
- package/tests/planning-startup.test.mjs +38 -0
- package/tests/planning-view.test.mjs +33 -0
- package/tests/planning.test.mjs +223 -0
- package/tests/probe-diagnostics.test.mjs +21 -0
- package/tests/pull-request-rpc.test.mjs +366 -0
- package/tests/review-state.test.mjs +32 -0
- package/tests/review.test.mjs +127 -0
- package/tests/server.test.mjs +252 -0
- package/tests/ship-jev.test.mjs +77 -0
- package/tests/ship-telemetry.test.mjs +333 -0
- package/tests/standard-workflow.test.mjs +28 -0
- package/tests/subscription-models.test.mjs +416 -0
- package/tests/subscriptions-extension.test.mjs +27 -0
- package/tests/terminal-title.test.mjs +13 -0
- package/tests/test_grade_minecraft_clone.py +199 -0
- package/tests/test_starter.py +78 -0
- package/tests/theme.test.mjs +175 -0
- package/tests/tui-copy-paste.test.mjs +54 -0
- package/tests/tui.test.mjs +808 -0
- package/tests/update-channels.test.mjs +129 -0
- package/tests/update.test.mjs +175 -0
- package/tests/webdriver.test.mjs +31 -0
- package/tests/workflow.test.mjs +47 -0
- package/tests/workspace-rpc.test.mjs +236 -0
- package/tests/workspace.test.mjs +24 -0
- package/tools/benchmark/advisor.test.mjs +7 -0
- package/tools/benchmark/advisor.ts +27 -0
- package/tools/benchmark/campaign.mjs +45 -0
- package/tools/benchmark/diagnose-request.mjs +40 -0
- package/tools/benchmark/eval-common.mjs +32 -0
- package/tools/benchmark/eval-ledger.mjs +22 -0
- package/tools/benchmark/eval-shop.mjs +26 -0
- package/tools/benchmark/eval-voxel.mjs +20 -0
- package/tools/benchmark/evaluate.mjs +24 -0
- package/tools/benchmark/evaluators.test.mjs +13 -0
- package/tools/benchmark/fixtures/shop/server.mjs +18 -0
- package/tools/benchmark/fixtures/shop/ui.html +8 -0
- package/tools/benchmark/freeze.mjs +30 -0
- package/tools/benchmark/gateway.mjs +91 -0
- package/tools/benchmark/gateway.test.mjs +38 -0
- package/tools/benchmark/generator.mjs +11 -0
- package/tools/benchmark/generator.test.mjs +26 -0
- package/tools/benchmark/guard.test.mjs +133 -0
- package/tools/benchmark/guard.ts +136 -0
- package/tools/benchmark/native-plugin/index.ts +24 -0
- package/tools/benchmark/planning-startup.test.mjs +47 -0
- package/tools/benchmark/preflight-live.mjs +31 -0
- package/tools/benchmark/preflight.mjs +22 -0
- package/tools/benchmark/replay-diagnostic.mjs +23 -0
- package/tools/benchmark/runner.mjs +141 -0
- package/tools/benchmark/runner.test.mjs +93 -0
- package/tools/benchmark/runtime.mjs +23 -0
- package/tools/benchmark/selftest.mjs +47 -0
- package/tools/benchmark/smoke-jev.mjs +5 -0
- package/tools/benchmark/tasks.mjs +20 -0
- package/tools/benchmark/transport.mjs +65 -0
- package/tools/benchmark/transport.test.mjs +44 -0
- package/tools/benchmark/upstream-error.mjs +18 -0
- package/tools/benchmark/upstream-error.test.mjs +10 -0
- package/tools/browser-check.mjs +109 -0
- package/tools/check-syntax.mjs +27 -0
- package/tools/evaluate-plan-advisor.mjs +20 -0
- package/tools/native-fixture.mjs +194 -0
- package/tools/native-pty-smoke.py +108 -0
- package/tools/native-report-plugin/index.ts +50 -0
- package/tools/native-report-plugin/package.json +7 -0
- package/tools/patch-compaction.mjs +24 -0
- package/tools/patch-model-catalog.mjs +12 -0
- package/tools/patch-print-shutdown.mjs +39 -0
- package/tools/repo-pilot/evaluator.cjs +32 -0
- package/tools/repo-pilot/reference.mjs +43 -0
- package/tools/repo-pilot/runner.mjs +85 -0
- package/tools/repo-pilot/sandbox-entry.mjs +12 -0
- package/tools/repo-pilot/tasks.mjs +20 -0
- package/tools/repo-pilot/type-evaluator.cjs +6 -0
- package/tools/test-native-controls.mjs +84 -0
- package/tools/test-native-delivery-host.mjs +307 -0
- package/tools/test-native-evaluation.mjs +46 -0
- package/tools/test-native-host.mjs +385 -0
- package/tools/test-native-outcomes.mjs +73 -0
- package/tools/test-native-package.mjs +70 -0
- package/tools/test-native-pty.mjs +20 -0
- package/tools/test-native-report.mjs +88 -0
- package/tools/test-native-swe-host.mjs +132 -0
- package/tools/test-native-swe.mjs +122 -0
- package/tools/test-picker-pty.py +45 -0
- package/tools/test-python.mjs +29 -0
- package/tools/test-webdriver.mjs +54 -0
- package/tools/verify-release-version.mjs +37 -0
- package/tools/webdriver-check.mjs +52 -0
- package/vendor/agent/bounded-summary.mjs +88 -0
- package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
- package/vendor/agent/chunks/anthropic.js +75 -0
- package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
- package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
- package/vendor/agent/chunks/chord-context.js +96 -0
- package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
- package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
- package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
- package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
- package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
- package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
- package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
- package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
- package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
- package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
- package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
- package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
- package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
- package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
- package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
- package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
- package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
- package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
- package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
- package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
- package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
- package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
- package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
- package/vendor/agent/chunks/github-copilot.js +2 -0
- package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
- package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
- package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
- package/vendor/agent/chunks/image-resize-worker.js +2 -0
- package/vendor/agent/chunks/kimi-coding.js +2 -0
- package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
- package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
- package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
- package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
- package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
- package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
- package/vendor/agent/chunks/openai-codex.js +75 -0
- package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
- package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
- package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
- package/vendor/agent/chunks/openrouter.js +75 -0
- package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
- package/vendor/agent/chunks/radius.js +75 -0
- package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
- package/vendor/agent/chunks/xai.js +2 -0
- package/vendor/agent/cli.js +3 -0
- package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/index.js +2 -0
- package/vendor/agent/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/package.json +14 -0
- package/vendor/agent/rpc-entry.js +3 -0
- package/vendor/agent/theme/dark.json +91 -0
- package/vendor/agent/theme/light.json +90 -0
- package/vendor/agent/theme/theme-schema.json +356 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/** Offline OpenAI-compatible SSE fixture used by the real OpenCode host test. */
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { once } from 'node:events';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import { OpenCode } from '@opencode/client';
|
|
10
|
+
import { resolveOpenCode } from '../lib/opencode/runtime.mjs';
|
|
11
|
+
|
|
12
|
+
const root = resolve(import.meta.dirname, '..');
|
|
13
|
+
|
|
14
|
+
export function createNativeFixture({ respond, inspect } = {}) {
|
|
15
|
+
let calls = 0;
|
|
16
|
+
const server = createServer(async (request, response) => {
|
|
17
|
+
let body = '';
|
|
18
|
+
for await (const chunk of request) body += chunk;
|
|
19
|
+
calls++;
|
|
20
|
+
const parsed = body ? JSON.parse(body) : {};
|
|
21
|
+
inspect?.({ request, body: parsed, call: calls });
|
|
22
|
+
if (!parsed.stream) {
|
|
23
|
+
response.writeHead(400, { 'content-type': 'application/json' });
|
|
24
|
+
response.end(JSON.stringify({ error: { message: 'stream=true required' } }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
response.writeHead(200, { 'content-type': 'text/event-stream', connection: 'keep-alive' });
|
|
28
|
+
response.flushHeaders();
|
|
29
|
+
await respond?.({ request, body: parsed, call: calls, response });
|
|
30
|
+
response.end('data: [DONE]\n\n');
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
server,
|
|
34
|
+
get calls() { return calls; },
|
|
35
|
+
listen: () => new Promise((resolve, reject) => {
|
|
36
|
+
server.once('error', reject);
|
|
37
|
+
server.listen(0, '127.0.0.1', () => {
|
|
38
|
+
server.off('error', reject);
|
|
39
|
+
resolve(`http://127.0.0.1:${server.address().port}/v1`);
|
|
40
|
+
});
|
|
41
|
+
}),
|
|
42
|
+
close: () => new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function toolCall(response, { id = 'fixture-call', name, arguments: input }) {
|
|
47
|
+
response.write(`data: ${JSON.stringify({
|
|
48
|
+
id: 'fixture-response', object: 'chat.completion.chunk', created: 1, model: 'fixture',
|
|
49
|
+
choices: [{ index: 0, delta: { role: 'assistant', tool_calls: [{
|
|
50
|
+
index: 0, id, type: 'function', function: { name, arguments: JSON.stringify(input) },
|
|
51
|
+
}] }, finish_reason: 'tool_calls' }],
|
|
52
|
+
})}\n\n`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function assistantText(response, text) {
|
|
56
|
+
response.write(`data: ${JSON.stringify({
|
|
57
|
+
id: 'fixture-response', object: 'chat.completion.chunk', created: 1, model: 'fixture',
|
|
58
|
+
choices: [{ index: 0, delta: { role: 'assistant', content: text }, finish_reason: 'stop' }],
|
|
59
|
+
})}\n\n`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Disposable native OpenCode host and loopback provider for acceptance probes. */
|
|
63
|
+
export async function nativeFixture({ plugin = join(root, 'plugins/carthagent'), plugins: additionalPlugins = [], extraPlugins = [], respond = (_body, res) => streamText(res), env: extraEnv = {} } = {}) {
|
|
64
|
+
const pluginPaths = [plugin, ...additionalPlugins, ...extraPlugins].filter(Boolean).map(path => resolve(path));
|
|
65
|
+
const temp = await mkdtemp(join(tmpdir(), 'ctg-native-fixture-'));
|
|
66
|
+
const cwd = join(temp, 'project');
|
|
67
|
+
const config = join(temp, 'config/opencode');
|
|
68
|
+
const requests = [];
|
|
69
|
+
const providerErrors = [];
|
|
70
|
+
let child;
|
|
71
|
+
let client;
|
|
72
|
+
let output = '';
|
|
73
|
+
const provider = createServer((req, res) => {
|
|
74
|
+
void (async () => {
|
|
75
|
+
let raw = '';
|
|
76
|
+
for await (const chunk of req) raw += chunk;
|
|
77
|
+
const body = JSON.parse(raw || '{}');
|
|
78
|
+
requests.push(body);
|
|
79
|
+
await respond(body, res, req);
|
|
80
|
+
})().catch(error => {
|
|
81
|
+
providerErrors.push(error);
|
|
82
|
+
if (!res.headersSent) res.writeHead(500);
|
|
83
|
+
res.end('Fixture provider failed');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
const password = randomBytes(24).toString('hex');
|
|
87
|
+
const stop = async () => {
|
|
88
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
89
|
+
const exited = once(child, 'exit');
|
|
90
|
+
child.kill('SIGTERM');
|
|
91
|
+
const timer = setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
92
|
+
try { await exited; } finally { clearTimeout(timer); }
|
|
93
|
+
};
|
|
94
|
+
const close = async () => {
|
|
95
|
+
await stop();
|
|
96
|
+
provider.closeAllConnections();
|
|
97
|
+
if (provider.listening) await new Promise(done => provider.close(done));
|
|
98
|
+
await rm(temp, { recursive: true, force: true });
|
|
99
|
+
};
|
|
100
|
+
const request = (milliseconds = 30_000) => ({ signal: AbortSignal.timeout(milliseconds) });
|
|
101
|
+
const start = async () => {
|
|
102
|
+
if (child && child.exitCode === null && child.signalCode === null) throw Error('Fixture host already running');
|
|
103
|
+
output = '';
|
|
104
|
+
const env = Object.fromEntries(['PATH', 'SystemRoot', 'WINDIR', 'COMSPEC', 'PATHEXT', 'LANG', 'LC_ALL']
|
|
105
|
+
.filter(key => process.env[key]).map(key => [key, process.env[key]]));
|
|
106
|
+
Object.assign(env, {
|
|
107
|
+
HOME: join(temp, 'home'), USERPROFILE: join(temp, 'home'), APPDATA: join(temp, 'appdata'), LOCALAPPDATA: join(temp, 'localappdata'),
|
|
108
|
+
XDG_CONFIG_HOME: join(temp, 'config'), XDG_DATA_HOME: join(temp, 'data'),
|
|
109
|
+
XDG_CACHE_HOME: join(temp, 'cache'), XDG_STATE_HOME: join(temp, 'state'),
|
|
110
|
+
OPENCODE_DB: join(temp, 'database.sqlite'), OPENCODE_SERVER_PASSWORD: password,
|
|
111
|
+
...extraEnv,
|
|
112
|
+
});
|
|
113
|
+
child = spawn(await resolveOpenCode(),
|
|
114
|
+
['serve', '--hostname', '127.0.0.1', '--port', '0', '--print-logs', '--log-level', 'debug'], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
115
|
+
const baseUrl = await new Promise((done, reject) => {
|
|
116
|
+
const timer = setTimeout(() => reject(Error(`Host startup timeout: ${output}`)), 30_000);
|
|
117
|
+
const collect = chunk => {
|
|
118
|
+
output = (output + chunk).slice(-16384);
|
|
119
|
+
const url = output.match(/http:\/\/127\.0\.0\.1:\d+/)?.[0];
|
|
120
|
+
if (url) { clearTimeout(timer); done(url); }
|
|
121
|
+
};
|
|
122
|
+
child.stdout.on('data', collect);
|
|
123
|
+
child.stderr.on('data', collect);
|
|
124
|
+
child.once('error', error => { clearTimeout(timer); reject(error); });
|
|
125
|
+
child.once('exit', code => { clearTimeout(timer); reject(Error(`Host exited ${code}: ${output}`)); });
|
|
126
|
+
});
|
|
127
|
+
client = OpenCode.make({ baseUrl, headers: { authorization: `Basic ${Buffer.from(`opencode:${password}`).toString('base64')}` } });
|
|
128
|
+
const version = (await client.server.info(request())).version;
|
|
129
|
+
if (version !== '2.0.32') throw Error(`Expected pinned host 2.0.32, got ${version}`);
|
|
130
|
+
return client;
|
|
131
|
+
};
|
|
132
|
+
try {
|
|
133
|
+
await Promise.all(['project', 'home', 'config/opencode', 'data', 'state', 'cache', 'appdata', 'localappdata']
|
|
134
|
+
.map(directory => mkdir(join(temp, directory), { recursive: true })));
|
|
135
|
+
provider.listen(0, '127.0.0.1');
|
|
136
|
+
await once(provider, 'listening');
|
|
137
|
+
await writeFile(join(config, 'opencode.json'), JSON.stringify({
|
|
138
|
+
...(pluginPaths.length ? { plugins: pluginPaths } : {}), update: 'disable', model: 'ctg-fixture/fixture', default_agent: 'build',
|
|
139
|
+
providers: { 'ctg-fixture': {
|
|
140
|
+
package: '@opencode/ai/providers/openai-compatible',
|
|
141
|
+
settings: { baseURL: `http://127.0.0.1:${provider.address().port}/v1`, apiKey: 'offline-fixture' },
|
|
142
|
+
models: { fixture: { limit: { context: 32000, output: 1024 }, cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 } } },
|
|
143
|
+
} },
|
|
144
|
+
}));
|
|
145
|
+
await start();
|
|
146
|
+
} catch (error) {
|
|
147
|
+
await close();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
temp, cwd, requests, providerErrors, request, start, stop, close,
|
|
152
|
+
model: { providerID: 'ctg-fixture', id: 'fixture' },
|
|
153
|
+
location: { directory: cwd },
|
|
154
|
+
get client() { return client; },
|
|
155
|
+
get output() { return output; },
|
|
156
|
+
async outcome(sessionID) {
|
|
157
|
+
await client.session.wait({ sessionID }, request());
|
|
158
|
+
const deadline = Date.now() + 5000;
|
|
159
|
+
while (true) {
|
|
160
|
+
const session = await client.session.get({ sessionID }, request());
|
|
161
|
+
if (session.outcome) return session.outcome;
|
|
162
|
+
if (Date.now() >= deadline) throw Error(`Missing terminal outcome for ${sessionID}`);
|
|
163
|
+
await new Promise(done => setTimeout(done, 20));
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
async active(pluginID = 'carthagent', location = { directory: cwd }) {
|
|
167
|
+
if (pluginID && typeof pluginID === 'object') {
|
|
168
|
+
location = pluginID;
|
|
169
|
+
pluginID = 'carthagent';
|
|
170
|
+
}
|
|
171
|
+
await client.session.create({ location, model:{providerID:'ctg-fixture',id:'fixture'}, title:`${pluginID} activation probe` }, request());
|
|
172
|
+
const deadline = Date.now() + 15_000;
|
|
173
|
+
while (true) {
|
|
174
|
+
const inventory = await client.plugin.list({ location }, request());
|
|
175
|
+
const plugin = inventory.data.find(item => item.id === pluginID);
|
|
176
|
+
if (plugin?.state.status === 'active') return;
|
|
177
|
+
if (plugin?.state.status === 'failed') throw Error(`Native fixture plugin failed: ${JSON.stringify(plugin)}\n${output}`);
|
|
178
|
+
if (Date.now() >= deadline) throw Error(`Native plugin ${pluginID} activation timed out: ${JSON.stringify(plugin)}\n${output}`);
|
|
179
|
+
await new Promise(done => setTimeout(done, 50));
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function streamText(response, text = 'Fixture response complete.') {
|
|
186
|
+
response.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
187
|
+
const chunk = (delta, finish_reason = null) => `data: ${JSON.stringify({
|
|
188
|
+
id: 'fixture', object: 'chat.completion.chunk', created: 1, model: 'fixture',
|
|
189
|
+
choices: [{ index: 0, delta, finish_reason }],
|
|
190
|
+
...(finish_reason ? { usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 } } : {}),
|
|
191
|
+
})}\n\n`;
|
|
192
|
+
response.write(chunk({ role: 'assistant', content: text }));
|
|
193
|
+
response.end(`${chunk({}, 'stop')}data: [DONE]\n\n`);
|
|
194
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Exercise a native CTG plugin command in a disposable real terminal host."""
|
|
3
|
+
import argparse
|
|
4
|
+
import fcntl
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import pty
|
|
9
|
+
import re
|
|
10
|
+
import select
|
|
11
|
+
import shutil
|
|
12
|
+
import signal
|
|
13
|
+
import struct
|
|
14
|
+
import subprocess
|
|
15
|
+
import tempfile
|
|
16
|
+
import termios
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument('--binary', default=shutil.which('opencode'))
|
|
23
|
+
parser.add_argument('--plugin', default=str(pathlib.Path(__file__).resolve().parents[1] / 'plugins/carthagent/test-plugin'))
|
|
24
|
+
parser.add_argument('--delivery-plugin', default=str(pathlib.Path(__file__).resolve().parents[1] / 'plugins/carthagent/test-plugin'))
|
|
25
|
+
parser.add_argument('--command', default='/ctg-status')
|
|
26
|
+
parser.add_argument('--expect', default=None)
|
|
27
|
+
parser.add_argument('--completion', default='Carthagent runtime status')
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
if not args.binary:
|
|
30
|
+
parser.error('OpenCode binary is required')
|
|
31
|
+
args.binary = str(pathlib.Path(args.binary).resolve())
|
|
32
|
+
with tempfile.TemporaryDirectory(prefix='ctg-pty-') as temp:
|
|
33
|
+
root = pathlib.Path(temp)
|
|
34
|
+
for name in ['home', 'project', 'config/opencode', 'data', 'state', 'cache']:
|
|
35
|
+
(root / name).mkdir(parents=True, exist_ok=True)
|
|
36
|
+
(root / 'config/opencode/opencode.json').write_text(json.dumps({
|
|
37
|
+
'plugins': list(dict.fromkeys([str(pathlib.Path(args.plugin).resolve()), str(pathlib.Path(args.delivery_plugin).resolve())])), 'update': 'disable',
|
|
38
|
+
}))
|
|
39
|
+
expected = args.expect or 'OpenCode 2.0.32 ·'
|
|
40
|
+
env = {key: os.environ[key] for key in ['PATH', 'LANG', 'LC_ALL'] if key in os.environ}
|
|
41
|
+
env.update({
|
|
42
|
+
'HOME': str(root / 'home'), 'TERM': 'xterm-256color', 'COLORTERM': 'truecolor',
|
|
43
|
+
'XDG_CONFIG_HOME': str(root / 'config'), 'XDG_DATA_HOME': str(root / 'data'),
|
|
44
|
+
'XDG_STATE_HOME': str(root / 'state'), 'XDG_CACHE_HOME': str(root / 'cache'),
|
|
45
|
+
'OPENCODE_DB': str(root / 'database.sqlite'),
|
|
46
|
+
})
|
|
47
|
+
master, slave = pty.openpty()
|
|
48
|
+
fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 36, 120, 0, 0))
|
|
49
|
+
child = subprocess.Popen([args.binary, '--standalone'], cwd=root / 'project', env=env,
|
|
50
|
+
stdin=slave, stdout=slave, stderr=slave, start_new_session=True)
|
|
51
|
+
os.close(slave)
|
|
52
|
+
output = bytearray()
|
|
53
|
+
started = time.monotonic()
|
|
54
|
+
sent = False
|
|
55
|
+
completion_accepted = False
|
|
56
|
+
submitted = False
|
|
57
|
+
try:
|
|
58
|
+
while time.monotonic() - started < 30:
|
|
59
|
+
if child.poll() is not None:
|
|
60
|
+
raise RuntimeError(f'Native TUI exited early: {child.returncode}')
|
|
61
|
+
if select.select([master], [], [], 0.1)[0]:
|
|
62
|
+
chunk = os.read(master, 65536)
|
|
63
|
+
output.extend(chunk)
|
|
64
|
+
# Terminal capability requests need replies on a headless PTY.
|
|
65
|
+
if b'\x1b[6n' in chunk:
|
|
66
|
+
os.write(master, b'\x1b[1;1R')
|
|
67
|
+
if b'\x1b[c' in chunk:
|
|
68
|
+
os.write(master, b'\x1b[?1;2c')
|
|
69
|
+
if not sent and time.monotonic() - started > 8:
|
|
70
|
+
os.write(master, args.command.encode())
|
|
71
|
+
sent = True
|
|
72
|
+
# The completion row is evidence that the TUI plugin has
|
|
73
|
+
# registered its command. Enter submits the highlighted slash
|
|
74
|
+
# command; inspect only output produced after that submission.
|
|
75
|
+
if sent and not completion_accepted and args.completion.encode() in output:
|
|
76
|
+
completion_accepted = True
|
|
77
|
+
os.write(master, b'\r')
|
|
78
|
+
submitted = True
|
|
79
|
+
output.clear()
|
|
80
|
+
plain_output = re.sub(rb'\x1b\[[0-?]*[ -/]*[@-~]', b'', bytes(output))
|
|
81
|
+
if submitted and expected.encode() in plain_output:
|
|
82
|
+
if b'runtime unavailable' in plain_output or args.completion.encode() in plain_output:
|
|
83
|
+
raise RuntimeError('Command was unavailable or its RPC failed')
|
|
84
|
+
print(json.dumps({'command': args.command, 'observed': expected,
|
|
85
|
+
'completionAccepted': completion_accepted,
|
|
86
|
+
'commandSubmitted': submitted,
|
|
87
|
+
'elapsedMs': round((time.monotonic() - started) * 1000),
|
|
88
|
+
'terminalBytes': len(output)}))
|
|
89
|
+
return
|
|
90
|
+
plain = re.sub(rb'\x1b\[[0-?]*[ -/]*[@-~]', b'', bytes(output))
|
|
91
|
+
for log in (root / 'data').rglob('*.log'):
|
|
92
|
+
lines = [line for line in log.read_text(errors='replace').splitlines()
|
|
93
|
+
if 'plugin' in line.lower() or 'error' in line.lower()]
|
|
94
|
+
print('\n'.join(lines[-30:]))
|
|
95
|
+
raise RuntimeError(f'Expected post-command response {expected!r} not rendered (sent={sent}, completionAccepted={completion_accepted}, submitted={submitted}): {plain[-8000:].decode(errors="replace")}')
|
|
96
|
+
finally:
|
|
97
|
+
if child.poll() is None:
|
|
98
|
+
os.killpg(child.pid, signal.SIGTERM)
|
|
99
|
+
try:
|
|
100
|
+
child.wait(timeout=5)
|
|
101
|
+
except subprocess.TimeoutExpired:
|
|
102
|
+
os.killpg(child.pid, signal.SIGKILL)
|
|
103
|
+
child.wait()
|
|
104
|
+
os.close(master)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Plugin } from '@opencode/plugin';
|
|
2
|
+
import { registerWorkspaceRpc } from '../../plugins/carthagent/src/server/workspace-rpc';
|
|
3
|
+
import { Rpc } from '@opencode/plugin/rpc';
|
|
4
|
+
import { Workspace } from '../../plugins/carthagent/src/workspace-rpc';
|
|
5
|
+
|
|
6
|
+
const SchemaProbe = Rpc.define({
|
|
7
|
+
id: 'carthagent.report-schema-probe',
|
|
8
|
+
methods: {
|
|
9
|
+
incompleteVerified: { input: { type: 'object', additionalProperties: false }, output: { oneOf: Workspace.methods.delivery.output.oneOf } },
|
|
10
|
+
invalidWithFullFields: { input: { type: 'object', additionalProperties: false }, output: Workspace.methods.delivery.output },
|
|
11
|
+
validUnavailable: { input: { type: 'object', additionalProperties: false }, output: { oneOf: Workspace.methods.delivery.output.oneOf } },
|
|
12
|
+
validInvalid: { input: { type: 'object', additionalProperties: false }, output: { oneOf: Workspace.methods.delivery.output.oneOf } },
|
|
13
|
+
validActive: { input: { type: 'object', additionalProperties: false }, output: { oneOf: Workspace.methods.delivery.output.oneOf } },
|
|
14
|
+
},
|
|
15
|
+
events: {},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export default Plugin.define({
|
|
19
|
+
id: 'carthagent-report-fixture',
|
|
20
|
+
async setup(context) {
|
|
21
|
+
const disposeWorkspace = await registerWorkspaceRpc(context);
|
|
22
|
+
try {
|
|
23
|
+
const probe = await context.rpc.register(SchemaProbe, {
|
|
24
|
+
incompleteVerified: async () => ({ status: 'verified' }),
|
|
25
|
+
invalidWithFullFields: async () => ({
|
|
26
|
+
status: 'invalid', freshness: 'current', completion: 'ready', gate: 'open',
|
|
27
|
+
runId: 'forged-run', revision: 1, fingerprint: 'a'.repeat(64),
|
|
28
|
+
plan: {}, workflow: {}, architecture: {}, evidence: [], truncation: {},
|
|
29
|
+
}),
|
|
30
|
+
validUnavailable: async () => ({ status: 'unavailable' }),
|
|
31
|
+
validInvalid: async () => ({ status: 'invalid' }),
|
|
32
|
+
validActive: async () => ({
|
|
33
|
+
status: 'active', freshness: 'current', completion: 'incomplete', gate: 'open',
|
|
34
|
+
runId: 'active-run', revision: 1, fingerprint: 'a'.repeat(64),
|
|
35
|
+
plan: { goal: 'Goal', verification: 'required', acceptance: [], artifacts: [], outputs: [], assumptions: [], checks: [], steps: [], scenarios: [], risks: [] },
|
|
36
|
+
workflow: { authored: false, semantics: '', nodes: [], edges: [] },
|
|
37
|
+
architecture: { nodes: [], edges: [], ports: [] }, evidence: [],
|
|
38
|
+
truncation: Object.fromEntries(['checks', 'acceptance', 'steps', 'artifacts', 'outputs', 'assumptions', 'scenarios', 'risks', 'workflowNodes', 'workflowEdges', 'architectureNodes', 'architectureEdges', 'architecturePorts'].map(key => [key, { returned: 0, total: 0 }])),
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
return async () => {
|
|
42
|
+
try { await probe.dispose(); }
|
|
43
|
+
finally { await disposeWorkspace(); }
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
await disposeWorkspace();
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Reapply after refreshing the vendored engine. Fail closed if upstream changes.
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
const path = new URL('../vendor/agent/chunks/chunk-JVUZSMYM.js', import.meta.url);
|
|
4
|
+
let source = readFileSync(path, 'utf8');
|
|
5
|
+
if (source.includes('import{boundedSummary,compactionThreshold,serializedContextTokens}')) process.exit(0);
|
|
6
|
+
function replaceOnce(from,to) {
|
|
7
|
+
if (source.split(from).length !== 2) throw new Error(`Expected one vendor anchor: ${from.slice(0,100)}`);
|
|
8
|
+
source = source.replace(from,to);
|
|
9
|
+
}
|
|
10
|
+
source = 'import{boundedSummary,compactionThreshold,serializedContextTokens}from"../bounded-summary.mjs";\n' + source;
|
|
11
|
+
for (const name of ['shouldCompact','shouldCompact2']) replaceOnce(`function ${name}(contextTokens,contextWindow,settings2){return settings2.enabled&&Number.isFinite(contextWindow)&&contextWindow>0?contextTokens>contextWindow-settings2.reserveTokens:!1}`,`function ${name}(contextTokens,contextWindow,settings2){return settings2.enabled&&Number.isFinite(contextWindow)&&contextWindow>0?contextTokens>compactionThreshold(contextWindow,settings2.reserveTokens):!1}`);
|
|
12
|
+
replaceOnce('return retryAssistantCall(async()=>streamFn?(await streamFn(model,context,requestOptions)).result():completeSimple(model,context,requestOptions),retry,requestOptions.signal,callbacks)', 'return boundedSummary(model,context,requestOptions,(boundedContext,boundedOptions)=>retryAssistantCall(async()=>streamFn?(await streamFn(model,boundedContext,boundedOptions)).result():completeSimple(model,boundedContext,boundedOptions),retry,boundedOptions.signal,callbacks))');
|
|
13
|
+
const summaryCall = 'response=await request({systemPrompt:SUMMARIZATION_SYSTEM_PROMPT2,messages:summarizationMessages},createSummaryRequestOptions(completionOptions,context),context)';
|
|
14
|
+
if (source.split(summaryCall).length !== 3) throw new Error('Expected history and turn-prefix summary calls');
|
|
15
|
+
source = source.replaceAll(summaryCall, 'response=await boundedSummary(model,{systemPrompt:SUMMARIZATION_SYSTEM_PROMPT2,messages:summarizationMessages},createSummaryRequestOptions(completionOptions,context),(boundedContext,boundedOptions)=>request(boundedContext,boundedOptions,context))');
|
|
16
|
+
// Include serialized UTF-8 size even when cached-token usage is available.
|
|
17
|
+
for (const [from,to] of [
|
|
18
|
+
['return{tokens:estimated,usageTokens:0,trailingTokens:estimated,lastUsageIndex:null}', 'return{tokens:Math.max(estimated,serializedContextTokens(messages)),usageTokens:0,trailingTokens:estimated,lastUsageIndex:null}'],
|
|
19
|
+
['return{tokens:usageTokens+trailingTokens,usageTokens,trailingTokens,lastUsageIndex:usageInfo.index}', 'return{tokens:Math.max(usageTokens+trailingTokens,serializedContextTokens(messages)),usageTokens,trailingTokens,lastUsageIndex:usageInfo.index}'],
|
|
20
|
+
]) {
|
|
21
|
+
if (source.split(from).length !== 3) throw new Error('Expected both context estimator paths');
|
|
22
|
+
source=source.replaceAll(from,to);
|
|
23
|
+
}
|
|
24
|
+
writeFileSync(path,source);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Reapply after refreshing the vendored engine; fail closed on changed anchors.
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const file = new URL('../vendor/agent/chunks/chunk-JVUZSMYM.js', import.meta.url);
|
|
5
|
+
let source = readFileSync(file, 'utf8');
|
|
6
|
+
const marker = '/* Carthagent owns model discovery; no pi.dev catalog. */';
|
|
7
|
+
if (!source.includes(marker)) {
|
|
8
|
+
const anchor = 'function withRemoteCatalog(provider,catalogBaseUrl=DEFAULT_CATALOG_BASE_URL,localGeneratedAt){';
|
|
9
|
+
if (source.split(anchor).length !== 2) throw new Error('Vendored model catalog anchor changed');
|
|
10
|
+
source = source.replace(anchor, `${anchor}${marker}return provider;`);
|
|
11
|
+
writeFileSync(file, source);
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Reapply after refreshing the vendored engine. Fail closed if upstream changes.
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const path = process.argv[2]
|
|
7
|
+
? pathToFileURL(resolve(process.argv[2]))
|
|
8
|
+
: new URL('../vendor/agent/chunks/chunk-JVUZSMYM.js', import.meta.url);
|
|
9
|
+
let source = readFileSync(path, 'utf8');
|
|
10
|
+
const marker = 'undici.getGlobalDispatcher()===dispatcher&&await dispatcher.destroy(),await flushRawStdout()';
|
|
11
|
+
|
|
12
|
+
if (source.includes(marker)) {
|
|
13
|
+
// Already patched; accept only the exact expected declaration/finalizer pair.
|
|
14
|
+
const declaration = 'signalCleanupHandlers=[],dispatcher=undici.getGlobalDispatcher(),disposeRuntime=async()=>';
|
|
15
|
+
if (source.split(marker).length !== 2 || source.split(declaration).length !== 2) {
|
|
16
|
+
throw new Error('Found print shutdown marker without its dispatcher ownership guard');
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
const declaration = 'signalCleanupHandlers=[],disposeRuntime=async()=>';
|
|
20
|
+
const replacement = 'signalCleanupHandlers=[],dispatcher=undici.getGlobalDispatcher(),disposeRuntime=async()=>';
|
|
21
|
+
if (source.split(declaration).length !== 2) {
|
|
22
|
+
throw new Error('Expected exactly one print-mode runtime declaration anchor');
|
|
23
|
+
}
|
|
24
|
+
source = source.replace(declaration, replacement);
|
|
25
|
+
|
|
26
|
+
const finalizers = [
|
|
27
|
+
'await disposeRuntime(),await flushRawStdout()',
|
|
28
|
+
'await disposeRuntime(),await globalThis[Symbol.for("carthagent.httpDispatcher.close")]?.(),await flushRawStdout()',
|
|
29
|
+
'await disposeRuntime(),undici.getGlobalDispatcher()===dispatcher&&await dispatcher.destroy(),await flushRawStdout()',
|
|
30
|
+
];
|
|
31
|
+
const finalizer = finalizers.find(anchor => source.split(anchor).length === 2);
|
|
32
|
+
const patchedFinalizer = 'await disposeRuntime(),undici.getGlobalDispatcher()===dispatcher&&await dispatcher.destroy(),await flushRawStdout()';
|
|
33
|
+
if (!finalizer) {
|
|
34
|
+
throw new Error('Expected exactly one print-mode finalizer anchor');
|
|
35
|
+
}
|
|
36
|
+
source = source.replace(finalizer, patchedFinalizer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
writeFileSync(path, source);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const fastify = require(path.join(process.cwd(),'fastify.js'));
|
|
4
|
+
const task = process.argv[2];const rows=[];
|
|
5
|
+
async function category(name, fn){try{await fn();rows.push({name,passed:true});}catch(e){rows.push({name,passed:false,error:String(e.message).slice(0,1200)});}}
|
|
6
|
+
async function scenario(fn, {real=false,hook=false}={}) {
|
|
7
|
+
const app=fastify();let failure;
|
|
8
|
+
app.register(async child=>{
|
|
9
|
+
if(hook)child.addHook('onSend',async(req,reply,payload)=>{try{fn(reply,req);}catch(e){failure=e;}return payload;});
|
|
10
|
+
child.get('/',(req,reply)=>{if(!hook)try{fn(reply,req);}catch(e){failure=e;}reply.send('ok');});
|
|
11
|
+
});
|
|
12
|
+
try{let result;if(real){await app.listen({port:0,host:'127.0.0.1'});const r=await fetch(app.listeningOrigin);result={statusCode:r.status,headers:Object.fromEntries(r.headers),cookies:r.headers.getSetCookie()};await r.text();}else result=await app.inject('/');if(failure)throw failure;assert.equal(result.statusCode,200);return result;}finally{await app.close();}
|
|
13
|
+
}
|
|
14
|
+
(async()=>{
|
|
15
|
+
if(task==='append'){
|
|
16
|
+
await category('chainable effective header and order',()=>scenario(r=>{assert.equal(r.appendHeader('X-Test','one'),r);r.appendHeader('x-test',[0,'','two,two']);assert.deepEqual(r.getHeader('X-TEST'),['one','0','','two,two']);}));
|
|
17
|
+
await category('raw precedence and no alias mutation',()=>scenario(r=>{const old=['a'];r.raw.setHeader('x-test',old);r.appendHeader('X-Test',['b']);assert.deepEqual(old,['a']);assert.deepEqual(r.getHeader('x-test'),['a','b']);r.header('x-test','override');r.appendHeader('x-test','c');assert.deepEqual(r.getHeader('x-test'),['override','c']);}));
|
|
18
|
+
await category('set-cookie append exactly once',async()=>{const v=await scenario(r=>{r.header('set-cookie',['a=1']);r.appendHeader('Set-Cookie',['b=2','c=3']);},{real:true});assert.deepEqual(v.cookies,['a=1','b=2','c=3']);});
|
|
19
|
+
await category('array copied empty no-op',()=>scenario(r=>{const values=['a'];r.appendHeader('x-test',values);values.push('b');assert.deepEqual(r.getHeader('x-test'),['a']);r.appendHeader('x-test',[]);assert.deepEqual(r.getHeader('x-test'),['a']);}));
|
|
20
|
+
await category('atomic invalid values and names',()=>scenario(r=>{r.header('x-test','old');for(const bad of [undefined,null,{},NaN,Infinity,['good','bad\r\nx'],['good',{}],['good','x\u0000']]){assert.throws(()=>r.appendHeader('x-test',bad),TypeError);assert.equal(r.getHeader('x-test'),'old');}for(const name of ['bad name','',':bad',7])assert.throws(()=>r.appendHeader(name,[]),TypeError);}));
|
|
21
|
+
await category('onSend real HTTP and normal header unaffected',async()=>{const v=await scenario(r=>{r.header('x-other','before');r.header('x-other','after');r.appendHeader('x-test','a').appendHeader('x-test','b');},{real:true,hook:true});assert.equal(v.headers['x-test'],'a, b');assert.equal(v.headers['x-other'],'after');});
|
|
22
|
+
} else {
|
|
23
|
+
await category('chainable token normalization',()=>scenario(r=>{assert.equal(r.vary('Accept-Encoding, Accept'),r);r.vary(['accept-encoding',' Origin , accept']);assert.equal(r.getHeader('vary'),'Accept-Encoding, Accept, Origin');}));
|
|
24
|
+
await category('raw header merge Fastify precedence',()=>scenario(r=>{r.raw.setHeader('Vary',['Accept','accept']);r.vary('Origin');assert.equal(r.getHeader('vary'),'Accept, Origin');r.header('vary','X-Custom');r.vary('Accept');assert.equal(r.getHeader('vary'),'X-Custom, Accept');}));
|
|
25
|
+
await category('wildcard dominance all input validated',()=>scenario(r=>{r.vary('Accept');r.vary(['Origin','*']);assert.equal(r.getHeader('vary'),'*');r.vary('Accept-Encoding');assert.equal(r.getHeader('vary'),'*');assert.throws(()=>r.vary(['*','bad name']),TypeError);assert.equal(r.getHeader('vary'),'*');}));
|
|
26
|
+
await category('empty no-op and no input mutation',()=>scenario(r=>{r.header('vary',['Accept','Accept']);const old=r.getHeader('vary');r.vary(' , \t,');assert.equal(r.getHeader('vary'),old);const values=['Origin','Accept'];r.vary(values);assert.deepEqual(values,['Origin','Accept']);assert.equal(r.getHeader('vary'),'Accept, Origin');}));
|
|
27
|
+
await category('invalid input atomic incl CRLF',()=>scenario(r=>{r.header('vary','Accept');for(const bad of [null,undefined,7,{},['Origin',3],['Origin',['Accept']],['Origin','bad name'],'Accept\r\n','\nAccept','bad:token']){assert.throws(()=>r.vary(bad),TypeError);assert.equal(r.getHeader('vary'),'Accept');}}));
|
|
28
|
+
await category('onSend real HTTP',async()=>{const v=await scenario(r=>{r.header('x-other','same');r.vary('Accept-Encoding').vary('Origin');},{real:true,hook:true});assert.equal(v.headers.vary,'Accept-Encoding, Origin');assert.equal(v.headers['x-other'],'same');});
|
|
29
|
+
}
|
|
30
|
+
await category('independent requests',async()=>{const app=fastify();app.get('/:id',(req,r)=>{if(task==='append')r.appendHeader('x-test',req.params.id);else r.vary(req.params.id);return 'ok';});try{const a=await app.inject('/Accept'),b=await app.inject('/Origin');assert.equal(String(a.headers[task==='append'?'x-test':'vary']),'Accept');assert.equal(String(b.headers[task==='append'?'x-test':'vary']),'Origin');}finally{await app.close();}});
|
|
31
|
+
console.log(JSON.stringify({task,passed:rows.filter(x=>x.passed).length,total:rows.length,categories:rows}));
|
|
32
|
+
})().catch(e=>{console.error(e);process.exitCode=2;});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export function reference(cwd, task, mutant=false) {
|
|
4
|
+
const file=join(cwd,'lib/reply.js');let source=readFileSync(file,'utf8');
|
|
5
|
+
const code=task==='append'?`
|
|
6
|
+
Reply.prototype.appendHeader = function (name, input) {
|
|
7
|
+
const { validateHeaderName, validateHeaderValue } = require('node:http')
|
|
8
|
+
if (typeof name !== 'string') throw new TypeError('Invalid name')
|
|
9
|
+
validateHeaderName(name)
|
|
10
|
+
const items = Array.isArray(input) ? input : [input]
|
|
11
|
+
const values = items.map(value => {
|
|
12
|
+
if (!(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value)))) throw new TypeError('Invalid value')
|
|
13
|
+
validateHeaderValue(name, String(value))
|
|
14
|
+
return String(value)
|
|
15
|
+
})
|
|
16
|
+
if (!values.length) return this
|
|
17
|
+
const old = this.getHeader(name)
|
|
18
|
+
const merged = ${mutant?'values':'(old === undefined ? [] : Array.isArray(old) ? [...old] : [old]).concat(values)'}
|
|
19
|
+
this[kReplyHeaders][name.toLowerCase()] = merged
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
`: `
|
|
23
|
+
Reply.prototype.vary = function (input) {
|
|
24
|
+
const items = Array.isArray(input) ? input : [input]
|
|
25
|
+
const parse = values => values.flatMap(value => {
|
|
26
|
+
if (typeof value !== 'string' || /[\\r\\n]/.test(value)) throw new TypeError('Invalid Vary')
|
|
27
|
+
return value.split(',').map(s => s.replace(/^[ \\t]+|[ \\t]+$/g, '')).filter(Boolean).map(token => {
|
|
28
|
+
if (!/^[!#$%&'*+.^_\x60|~0-9A-Za-z-]+$/.test(token)) throw new TypeError('Invalid field')
|
|
29
|
+
return token
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
const tokens = parse(items)
|
|
33
|
+
if (!tokens.length) return this
|
|
34
|
+
const old = this.getHeader('vary')
|
|
35
|
+
const all = parse(old === undefined ? [] : Array.isArray(old) ? old : [String(old)]).concat(tokens)
|
|
36
|
+
const seen = new Set(), output = []
|
|
37
|
+
for (const token of all) { const key = ${mutant?'token':'token.toLowerCase()'}; if (!seen.has(key)) { seen.add(key); output.push(token) } }
|
|
38
|
+
this.header('vary', seen.has('*') ? '*' : output.join(', '))
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
source=source.replace('Reply.prototype.getHeader = function (key) {',code+'\nReply.prototype.getHeader = function (key) {');writeFileSync(file,source);
|
|
43
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { createServer, connect } from 'node:net';
|
|
3
|
+
import { mkdirSync, writeFileSync, readFileSync, appendFileSync, cpSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { startGateway } from '../benchmark/gateway.mjs';
|
|
7
|
+
import { GLM_FLASH as MODEL, generatorProfile } from '../benchmark/generator.mjs';
|
|
8
|
+
const PROFILE = generatorProfile(MODEL);
|
|
9
|
+
import { safeEnv, provisionTools, ENGINE_ROOT, stopGroup, sha } from '../benchmark/runtime.mjs';
|
|
10
|
+
import { initializeCampaign, campaignAccount } from '../benchmark/campaign.mjs';
|
|
11
|
+
import { terminalStatus } from '../benchmark/runner.mjs';
|
|
12
|
+
import { TASKS, MATRIX, BASE } from './tasks.mjs';
|
|
13
|
+
export const ROOT='/home/baissi/benchmarks/glm-repo-pilot-v1';
|
|
14
|
+
const SETUP='/home/baissi/benchmarks/jev-repo-pilot';
|
|
15
|
+
const REPO=resolve(dirname(fileURLToPath(import.meta.url)),'../..');
|
|
16
|
+
const BWRAP=SETUP+'/setup/bubblewrap-root/usr/bin/bwrap';
|
|
17
|
+
const CAMPAIGN='/home/baissi/benchmarks/jev-repo-pilot/campaign.json';
|
|
18
|
+
const NODE=dirname(dirname(process.execPath));
|
|
19
|
+
function walk(root){return readdirSync(root,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(join(root,e.name)):e.isFile()?[join(root,e.name)]:[]);}
|
|
20
|
+
export function manifest(){return Object.fromEntries([...['lib','extensions','docs','skills','tools/repo-pilot','tools/benchmark'].flatMap(d=>walk(join(REPO,d))),...walk(ROOT+'/reference'),...walk(ROOT+'/system'),...walk(ROOT+'/setup/fastify').filter(p=>!p.includes('/.git/')), ...walk(ENGINE_ROOT), ...walk(REPO+'/vendor'), ...walk(REPO+'/node_modules'),process.execPath,BWRAP].sort().map(p=>[p,sha(readFileSync(p))]));}
|
|
21
|
+
export function freeze(){writeFileSync(ROOT+'/freeze.json',JSON.stringify({at:new Date().toISOString(),base:BASE,generator:PROFILE,hashes:manifest(),limits:{candidate:14,smoke:1,cell:2.3,seconds:900,tools:120},compaction:{enabled:true,reserveTokens:70000,keepRecentTokens:6000}},null,2),{flag:'wx'});}
|
|
22
|
+
function verify(){const frozen=JSON.parse(readFileSync(ROOT+'/freeze.json'));if(JSON.stringify(frozen.hashes)!==JSON.stringify(manifest()))throw Error('Freeze mismatch');}
|
|
23
|
+
export function sandboxArgs(run, {broker=true,evaluator=false}={}){
|
|
24
|
+
const args=['--die-with-parent','--new-session','--unshare-user','--unshare-pid','--unshare-ipc','--unshare-uts','--unshare-net','--cap-drop','ALL','--ro-bind','/usr','/usr','--ro-bind','/lib','/lib','--ro-bind','/lib64','/lib64','--symlink','usr/bin','/bin','--proc','/proc','--dev','/dev','--ro-bind',ROOT+'/system/hosts','/etc/hosts','--ro-bind',ROOT+'/system/nsswitch.conf','/etc/nsswitch.conf','--tmpfs','/tmp','--dir','/home','--dir','/home/agent','--ro-bind',NODE,'/node','--ro-bind',ENGINE_ROOT,'/engine','--bind',run+'/workspace','/work','--ro-bind',ROOT+'/setup/fastify/node_modules','/work/node_modules','--bind',run+'/config','/config','--bind',run+'/sessions','/sessions','--bind',run+'/logs','/logs','--ro-bind',ROOT+'/reference','/reference'];
|
|
25
|
+
for(const d of ['lib','extensions','docs','skills','vendor','node_modules'])args.push('--ro-bind',REPO+'/'+d,'/harness/'+d);
|
|
26
|
+
args.push('--ro-bind',REPO+'/tools/benchmark/guard.ts','/guard.ts','--ro-bind',REPO+'/tools/repo-pilot/sandbox-entry.mjs','/entry.mjs');
|
|
27
|
+
if(broker)args.push('--ro-bind',run+'/broker.sock','/broker.sock');
|
|
28
|
+
if(evaluator)args.push('--ro-bind',REPO+'/tools/repo-pilot/evaluator.cjs','/evaluate.cjs','--ro-bind',ROOT+'/setup/fastify/test','/work/test','--ro-bind',REPO+'/tools/repo-pilot/type-evaluator.cjs','/types.cjs');
|
|
29
|
+
args.push('--chdir','/work','--clearenv');
|
|
30
|
+
const env={HOME:'/home/agent',USER:'agent',PATH:'/node/bin:/usr/bin:/bin',LANG:'C.UTF-8',CI:'1',PI_CODING_AGENT_DIR:'/config',PI_OFFLINE:'1',PI_TELEMETRY:'0',BENCH_GUARD_LOG:'/logs/guard.jsonl',BENCH_TOOL_LIMIT:'120'};
|
|
31
|
+
for(const[k,v]of Object.entries(env))args.push('--setenv',k,v);
|
|
32
|
+
return args;
|
|
33
|
+
}
|
|
34
|
+
export function prepareRun(name,prompt){
|
|
35
|
+
const run=ROOT+'/'+name;mkdirSync(run);for(const d of ['config','sessions','logs'])mkdirSync(run+'/'+d);
|
|
36
|
+
cpSync(ROOT+'/setup/fastify',run+'/workspace',{recursive:true,filter:p=>!p.includes('/node_modules')});
|
|
37
|
+
writeFileSync(run+'/workspace/TASK.md',prompt);appendFileSync(run+'/workspace/.gitignore','\n.harness/\nartifacts/\n');
|
|
38
|
+
const git=spawnSync('git',['-c','user.name=Pilot','-c','user.email=pilot@localhost','add','.'],{cwd:run+'/workspace'});if(git.status)throw Error('git add');
|
|
39
|
+
if(spawnSync('git',['-c','user.name=Pilot','-c','user.email=pilot@localhost','commit','-qm','Frozen pilot task and dependency lock'],{cwd:run+'/workspace'}).status)throw Error('git commit');
|
|
40
|
+
provisionTools(run+'/config');writeFileSync(run+'/config/auth.json','{}');
|
|
41
|
+
writeFileSync(run+'/config/settings.json',JSON.stringify({retry:{enabled:false,provider:{maxRetries:0}},compaction:{enabled:true,reserveTokens:70000,keepRecentTokens:6000}}));
|
|
42
|
+
writeFileSync(run+'/config/models.json',JSON.stringify({providers:{benchmark:{baseUrl:'http://127.0.0.1:12345/v1',api:'openai-completions',apiKey:'local-dummy',models:[{id:MODEL,reasoning:true,input:['text'],contextWindow:100000,maxTokens:16384,cost:{input:PROFILE.input,output:PROFILE.output,cacheRead:PROFILE.cacheRead,cacheWrite:0},compat:{supportsDeveloperRole:false,thinkingFormat:'openrouter',maxTokensField:'max_tokens',supportsStore:false}}]}}}));return run;
|
|
43
|
+
}
|
|
44
|
+
export async function run(task,arm,{smoke=false,prompt:override}={}){
|
|
45
|
+
if(!smoke)verify();
|
|
46
|
+
const prompt=override||TASKS[task];const name=smoke?`smoke-${task}-${arm}`:`${task}-${arm}`;
|
|
47
|
+
const dir=prepareRun(name,prompt);
|
|
48
|
+
if(smoke && task==='compact')writeFileSync(dir+'/config/settings.json',JSON.stringify({retry:{enabled:false},compaction:{enabled:true,reserveTokens:90000,keepRecentTokens:2000}}));
|
|
49
|
+
const key=JSON.parse(readFileSync('/home/baissi/.pi/agent/auth.json')).openrouter.key;
|
|
50
|
+
const gateway=await startGateway({key,generator:MODEL,limit:smoke?.7:2.3,allowJev:arm==='jev',receiptPath:dir+'/gateway.jsonl',account:campaignAccount(CAMPAIGN,smoke?'smoke':'candidate',name)});
|
|
51
|
+
const port=Number(new URL(gateway.url).port);const connections=new Set();
|
|
52
|
+
const proxy=createServer(client=>{const upstream=connect(port,'127.0.0.1');connections.add(client);connections.add(upstream);client.pipe(upstream);upstream.pipe(client);for(const s of [client,upstream]){s.on('error',()=>{client.destroy();upstream.destroy();});s.on('close',()=>connections.delete(s));}});
|
|
53
|
+
await new Promise(r=>proxy.listen(dir+'/broker.sock',r));
|
|
54
|
+
const args=sandboxArgs(dir);args.push('/node/bin/node','/entry.mjs','/engine/dist/cli.js','--mode','json','-p','--offline','--no-extensions','--no-skills','--no-context-files','--no-prompt-templates','--no-themes','--no-approve','--provider','benchmark','--model',MODEL,'--thinking','high','--session-dir','/sessions','-e','/guard.ts');
|
|
55
|
+
if(arm!=='plain')args.push('-e','/harness/extensions/delivery.ts','--skill','/harness/skills/software-delivery/SKILL.md','--append-system-prompt','Use delivery_design action=guide for exact schema before authoring design; /harness/docs/tested-planning.md has extended details only if needed. This is an existing library patch: inspect relevant files, submit a concrete plan, use focused edits and preserve upstream tests.');
|
|
56
|
+
if(arm==='jev')args.push('--delivery-jev','--delivery-jev-broker','http://127.0.0.1:12345/jev');
|
|
57
|
+
args.push('--tools',['read','bash','edit','write','grep','find','ls',...(arm==='plain'?[]:['delivery_plan','delivery_revise','delivery_design','delivery_progress','delivery_check','delivery_review','delivery_status','delivery_finish'])].join(','),prompt);
|
|
58
|
+
writeFileSync(dir+'/invocation.json',JSON.stringify({task,arm,generator:PROFILE,base:BASE,args,settings:JSON.parse(readFileSync(dir+'/config/settings.json')),at:new Date().toISOString()},null,2));
|
|
59
|
+
const started=Date.now(),events=[];let pending='',stopReason=null,count=0;const decoder=new TextDecoder();
|
|
60
|
+
const child=spawn(BWRAP,args,{env:safeEnv(),stdio:['ignore','pipe','pipe'],detached:true});
|
|
61
|
+
const kill=reason=>{stopReason ||= reason;try{process.kill(-child.pid,'SIGTERM');}catch{}setTimeout(()=>{try{process.kill(-child.pid,'SIGKILL');}catch{}},2000).unref();};
|
|
62
|
+
child.stdout.on('data',chunk=>{appendFileSync(dir+'/events.jsonl',chunk);pending+=decoder.decode(chunk,{stream:true});let nl;while((nl=pending.indexOf('\n'))>=0){const line=pending.slice(0,nl);pending=pending.slice(nl+1);try{const e=JSON.parse(line);if(e.type==='message_end')events.push(e);if(e.type==='tool_execution_start')count++;if(e.type==='tool_execution_end'&&e.result?.content?.some(c=>c.text?.includes('Benchmark tool budget exhausted')))kill('tool-budget');}catch{}}});
|
|
63
|
+
child.stderr.on('data',chunk=>appendFileSync(dir+'/stderr.log',chunk));const timer=setTimeout(()=>kill('wall-time'),(smoke?240:900)*1000);
|
|
64
|
+
let exit;try{exit=await new Promise((r,j)=>{child.on('error',j);child.on('exit',(code,signal)=>r({code,signal}));});}finally{clearTimeout(timer);await stopGroup(child);for(const s of connections)s.destroy();await new Promise(r=>proxy.close(r));await gateway.close();}
|
|
65
|
+
const classification=terminalStatus(events,exit,stopReason,gateway.budget);
|
|
66
|
+
let resource=stopReason;if(gateway.receipts.some(r=>r.type==='failure'&&/input bound/i.test(r.reason||'')))resource='input-byte-limit';if(classification.terminalModelReason==='length')resource='output-token-limit';
|
|
67
|
+
const result={task,arm,...exit,...classification,resource,wallMs:Date.now()-started,toolStarts:count,budget:gateway.budget};writeFileSync(dir+'/result.json',JSON.stringify(result,null,2));console.log(JSON.stringify(result));return result;
|
|
68
|
+
}
|
|
69
|
+
export function evaluate(task,arm){
|
|
70
|
+
const dir=ROOT+'/'+task+'-'+arm;const args=sandboxArgs(dir,{broker:false,evaluator:true});
|
|
71
|
+
const r=spawnSync(BWRAP,[...args,'/node/bin/node','/evaluate.cjs',task],{encoding:'utf8',timeout:60000,maxBuffer:4e6});writeFileSync(dir+'/evaluation.log',r.stdout+r.stderr);
|
|
72
|
+
const evaluation=r.status===0?JSON.parse(r.stdout.trim()):{infrastructureError:true,status:r.status};
|
|
73
|
+
const baseline=spawnSync(BWRAP,[...sandboxArgs(dir,{broker:false,evaluator:true}),'/node/bin/node','--test','test/internals/reply.test.js','test/internals/request.test.js'],{encoding:'utf8',timeout:120000,maxBuffer:8e6});writeFileSync(dir+'/regressions.log',baseline.stdout+baseline.stderr);
|
|
74
|
+
const diff=spawnSync('git',['diff','--stat'],{cwd:dir+'/workspace',encoding:'utf8'}).stdout;
|
|
75
|
+
const types=spawnSync(BWRAP,[...sandboxArgs(dir,{broker:false,evaluator:true}),'/node/bin/node','/types.cjs',task],{encoding:'utf8',timeout:60000,maxBuffer:4e6});writeFileSync(dir+'/types.log',types.stdout+types.stderr);
|
|
76
|
+
const result={...evaluation,protectedRegressionExit:baseline.status,externalTypesExit:types.status,diff};writeFileSync(dir+'/evaluation.json',JSON.stringify(result,null,2));return result;
|
|
77
|
+
}
|
|
78
|
+
if(process.argv[1]===fileURLToPath(import.meta.url)){
|
|
79
|
+
const [action,task,arm]=process.argv.slice(2);
|
|
80
|
+
if(action==='init')initializeCampaign(CAMPAIGN,{candidateLimit:14,smokeLimit:1,historicalCandidate:0,historicalSmoke:0});
|
|
81
|
+
else if(action==='freeze')freeze();
|
|
82
|
+
else if(action==='smoke')await run(task,arm,{smoke:true,prompt:readFileSync(ROOT+'/smoke-prompt.txt','utf8')});
|
|
83
|
+
else if(action==='matrix'){for(const[t,a]of MATRIX){const r=await run(t,a);evaluate(t,a);if(!r.resource&&['infrastructure-error','process-error','missing-terminal-message','model-error'].includes(r.status))throw Error('Infrastructure stop; no automatic retry');}}
|
|
84
|
+
else if(action==='evaluate')console.log(evaluate(task,arm));
|
|
85
|
+
}
|