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,57 @@
|
|
|
1
|
+
import { planningStatus } from './planning.mjs';
|
|
2
|
+
import { workflowGraph, architectureGraph } from './workflow.mjs';
|
|
3
|
+
import { evidenceFresh, pendingRequirements, reviewFresh, requiresReview } from './delivery.mjs';
|
|
4
|
+
|
|
5
|
+
// Shared presentation projection: no I/O and no guessed successful transitions.
|
|
6
|
+
export function planningView(state, hash, { runningCheck } = {}) {
|
|
7
|
+
const gate = planningStatus(state, hash);
|
|
8
|
+
if (!state?.plan) return { gate, workflow: null, architecture: null, evidence: [], current: [], blockers: gate.reasons };
|
|
9
|
+
const plan = state.plan;
|
|
10
|
+
const evidence = (plan.checks || []).map(check => {
|
|
11
|
+
const receipt = state.evidence?.[check.id];
|
|
12
|
+
const status = runningCheck === check.id ? 'active' : !receipt ? 'pending' : !receipt.passed ? 'fail' : evidenceFresh(check, receipt, hash, state) ? 'done' : 'stale';
|
|
13
|
+
return { id: check.id, status, logPath: receipt?.logPath, code: receipt?.code, executedRevision: receipt?.executedRevision, fingerprint: receipt?.fingerprint };
|
|
14
|
+
});
|
|
15
|
+
let workflow;
|
|
16
|
+
try { workflow = workflowGraph(plan); }
|
|
17
|
+
catch (error) { workflow = { nodes: [], edges: [], error: error.message }; }
|
|
18
|
+
const pending = pendingRequirements(state, hash);
|
|
19
|
+
const allFresh = evidence.length > 0 && evidence.every(e => e.status === 'done') && pending.length === 0;
|
|
20
|
+
const review = !requiresReview(state) || reviewFresh(state, hash);
|
|
21
|
+
const statuses = {
|
|
22
|
+
gate_design: gate.locked ? 'blocked' : 'done',
|
|
23
|
+
gate_verify: runningCheck ? 'active' : evidence.some(e => e.status === 'fail') ? 'fail' : evidence.some(e => e.status === 'stale') ? 'stale' : allFresh ? 'done' : 'pending',
|
|
24
|
+
gate_review: review ? 'done' : state.reviewEvidence ? 'stale' : 'pending',
|
|
25
|
+
gate_deliver: state.status === 'blocked' ? 'blocked' : state.status === 'verified' && allFresh && review && !gate.locked ? 'done' : 'pending',
|
|
26
|
+
};
|
|
27
|
+
workflow.nodes = workflow.nodes.map(node => {
|
|
28
|
+
let status = statuses[node.id] || 'proposed';
|
|
29
|
+
if (node.type === 'work') {
|
|
30
|
+
const index = plan.steps.findIndex((s, i) => (typeof s === 'string' ? `legacy_${i + 1}` : s.id) === node.step);
|
|
31
|
+
status = state.stepStatus?.[`step${index}`] || 'pending';
|
|
32
|
+
if (status === 'failed') status = 'fail';
|
|
33
|
+
if (gate.locked && status !== 'done' && status !== 'fail') status = 'blocked';
|
|
34
|
+
} else if (node.type === 'check') {
|
|
35
|
+
const receipts = evidence.filter(e => node.checks.includes(e.id));
|
|
36
|
+
status = receipts.some(e => e.status === 'active') ? 'active' : receipts.some(e => e.status === 'fail') ? 'fail' : receipts.some(e => e.status === 'stale') ? 'stale' : receipts.length && receipts.every(e => e.status === 'done') ? 'done' : 'pending';
|
|
37
|
+
}
|
|
38
|
+
return { ...node, status };
|
|
39
|
+
});
|
|
40
|
+
const blockers = [...gate.reasons, ...(gate.findings || []).map(f => f.message), ...(gate.reviewFindings || []).filter(f => f.severity === 'blocking').map(f => f.description), ...pending.map(r => `Fresh requirement execution needed: ${r}`)];
|
|
41
|
+
return { gate, workflow, architecture: architectureGraph(plan), evidence, current: workflow.nodes.filter(n => n.status === 'active').map(n => ({ id: n.id, title: n.title })), blockers, revision: state.revision, provenance: 'Observed receipts and explicit progress; proposed decisions/artifacts are not inferred complete.' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function planningViewLines(view, tab = 'workflow') {
|
|
45
|
+
const lines = [`${tab.toUpperCase()} · ${view.gate.phase} · rev ${view.revision ?? '-'}`];
|
|
46
|
+
if (tab === 'architecture') {
|
|
47
|
+
for (const c of view.architecture?.nodes || []) lines.push(`${c.id} [${c.layer}] ${c.title}`, ` excludes: ${(c.excludes || []).join('; ')}`);
|
|
48
|
+
for (const p of view.architecture?.ports || []) lines.push(`${p.owner} -> port ${p.id} -> ${p.adapter} (injected at ${p.compositionRoot})`);
|
|
49
|
+
} else if (tab === 'evidence') {
|
|
50
|
+
for (const e of view.evidence) lines.push(`${e.id}: ${e.status} · executed rev ${e.executedRevision ?? '-'}`, ` ${e.logPath || 'No execution receipt'}`);
|
|
51
|
+
} else {
|
|
52
|
+
for (const n of view.workflow?.nodes || []) lines.push(`${n.id} [${n.status}] ${n.title}`);
|
|
53
|
+
for (const e of view.workflow?.edges || []) lines.push(`${e.from} -> ${e.to}${e.label ? `: ${e.label}` : ''}`);
|
|
54
|
+
}
|
|
55
|
+
if (view.blockers.length) lines.push('BLOCKERS', ...view.blockers);
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
package/lib/planning.mjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { validateWorkflow } from './workflow.mjs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
// Pure design policy. Callers supply the observed source snapshot; this module
|
|
5
|
+
// never reads files, invokes a model, executes checks, or persists reports.
|
|
6
|
+
export const designDigest = plan => createHash('sha256').update(JSON.stringify(plan)).digest('hex');
|
|
7
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
8
|
+
const list = value => Array.isArray(value) ? value : [];
|
|
9
|
+
const safe = value => text(value) && /^[a-z][a-z0-9_-]{0,63}$/.test(value);
|
|
10
|
+
|
|
11
|
+
export function validateDesign(plan) {
|
|
12
|
+
const findings = validateWorkflow(plan || {});
|
|
13
|
+
const fail = (code, message, target = 'design') => findings.push({ code, target, message, severity: 'blocking' });
|
|
14
|
+
const design = plan?.design;
|
|
15
|
+
if (!design || typeof design !== 'object' || Array.isArray(design)) {
|
|
16
|
+
fail('missing-design', 'Declare architecture, injection contracts, scenarios and risks before generating code.');
|
|
17
|
+
return findings;
|
|
18
|
+
}
|
|
19
|
+
for (const field of ['components', 'ports', 'scenarios', 'risks']) if (!Array.isArray(design[field])) fail('array', `${field} must be an array.`, field);
|
|
20
|
+
const components = list(design.components), ports = list(design.ports), scenarios = list(design.scenarios);
|
|
21
|
+
const requirements = list(plan.acceptance).map(a => a.requirement);
|
|
22
|
+
const checks = new Set(list(plan.checks).map(c => c.id));
|
|
23
|
+
const ids = (items, name) => {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (!item || !safe(item.id) || seen.has(item.id)) fail('identity', `${name} IDs must be unique safe identifiers.`, name);
|
|
27
|
+
seen.add(item?.id);
|
|
28
|
+
}
|
|
29
|
+
return seen;
|
|
30
|
+
};
|
|
31
|
+
const componentIds = ids(components, 'components');
|
|
32
|
+
ids(ports, 'ports'); ids(scenarios, 'scenarios'); ids(list(design.risks), 'risks');
|
|
33
|
+
if (!components.length) fail('components', 'At least one cohesive component is required.');
|
|
34
|
+
for (const c of components.filter(Boolean)) {
|
|
35
|
+
if (!text(c.responsibility) || !list(c.excludes).length || list(c.excludes).some(x => !text(x))) fail('srp', 'Declare one responsibility and explicit non-responsibilities.', c.id);
|
|
36
|
+
if (!['core', 'adapter', 'composition', 'presentation'].includes(c.layer)) fail('layer', 'Use core, adapter, composition or presentation.', c.id);
|
|
37
|
+
if (!Array.isArray(c.dependencies) || c.dependencies.some(id => !componentIds.has(id) || id === c.id)) fail('dependencies', `Dependencies must reference other components. Invalid: ${JSON.stringify(list(c.dependencies).filter(id => !componentIds.has(id) || id === c.id))}; allowed: ${JSON.stringify([...componentIds].filter(id => id !== c.id))}.`, c.id);
|
|
38
|
+
if (!list(c.requirements).length || c.requirements.some(r => !requirements.includes(r))) fail('ownership', `Component must reference exact acceptance requirements. Unknown: ${JSON.stringify(list(c.requirements).filter(r => !requirements.includes(r)))}. Copy plan.acceptance text; do not rewrite obligations.`, c.id);
|
|
39
|
+
if (c.layer === 'core' && list(c.dependencies).some(id => components.find(other => other?.id === id)?.layer === 'adapter')) fail('inversion', 'Core policy must depend on ports, not concrete adapters.', c.id);
|
|
40
|
+
}
|
|
41
|
+
const visiting = new Set(), visited = new Set();
|
|
42
|
+
function visit(id) {
|
|
43
|
+
if (visiting.has(id)) { fail('cycle', 'Component dependencies contain a cycle.', id); return; }
|
|
44
|
+
if (visited.has(id)) return;
|
|
45
|
+
visiting.add(id);
|
|
46
|
+
for (const dep of list(components.find(c => c?.id === id)?.dependencies)) if (componentIds.has(dep)) visit(dep);
|
|
47
|
+
visiting.delete(id); visited.add(id);
|
|
48
|
+
}
|
|
49
|
+
for (const id of componentIds) visit(id);
|
|
50
|
+
for (const p of ports.filter(Boolean)) {
|
|
51
|
+
if (!componentIds.has(p.owner) || !componentIds.has(p.adapter) || components.find(c => c?.id === p.adapter)?.layer !== 'adapter' || components.find(c => c?.id === p.compositionRoot)?.layer !== 'composition') fail('port-binding', 'Ports need an owner, concrete adapter and composition root.', p.id);
|
|
52
|
+
for (const field of ['input', 'output', 'errors', 'injection', 'testDouble', 'integrationAssertion']) if (!text(p[field])) fail('port-contract', `Port needs ${field}.`, p.id);
|
|
53
|
+
}
|
|
54
|
+
for (const c of components.filter(Boolean)) {
|
|
55
|
+
if (!Array.isArray(c.externalDependencies) || c.externalDependencies.some(id => !ports.some(p => p?.id === id && p.owner === c.id))) fail('injection', 'Every external dependency must name an owned injection port (or use an explicit empty list).', c.id);
|
|
56
|
+
}
|
|
57
|
+
for (const s of scenarios.filter(Boolean)) {
|
|
58
|
+
if (!requirements.includes(s.requirement)) fail('scenario-requirement', `Scenario must name an exact acceptance requirement. Unknown: ${JSON.stringify(s.requirement)}. Copy plan.acceptance text; do not rewrite obligations.`, s.id);
|
|
59
|
+
if (!['positive', 'boundary', 'failure'].includes(s.kind)) fail('scenario-kind', 'Use positive, boundary or failure.', s.id);
|
|
60
|
+
if (!list(s.path).length || s.path.some(id => !componentIds.has(id))) fail('scenario-path', `Scenario needs a path through declared components. Invalid: ${JSON.stringify(list(s.path).filter(id => !componentIds.has(id)))}; allowed: ${JSON.stringify([...componentIds])}.`, s.id);
|
|
61
|
+
if (!list(s.checks).length || s.checks.some(id => !checks.has(id))) fail('scenario-checks', `Scenario needs existing executable check IDs. Invalid: ${JSON.stringify(list(s.checks).filter(id => !checks.has(id)))}; allowed: ${JSON.stringify([...checks])}.`, s.id);
|
|
62
|
+
for (const field of ['input', 'expected', 'assertion']) if (!text(s[field])) fail('scenario-contract', `Scenario needs concrete ${field}.`, s.id);
|
|
63
|
+
}
|
|
64
|
+
for (const requirement of requirements) {
|
|
65
|
+
if (!components.some(c => list(c?.requirements).includes(requirement))) fail('unowned', `No component owns: ${requirement}`);
|
|
66
|
+
for (const kind of ['positive', 'negative']) if (!scenarios.some(s => s?.requirement === requirement && (kind === 'positive' ? s.kind === 'positive' : ['boundary', 'failure'].includes(s.kind)))) fail('coverage', `Missing ${kind} scenario: ${requirement}`);
|
|
67
|
+
}
|
|
68
|
+
for (const risk of list(design.risks).filter(Boolean)) {
|
|
69
|
+
if (!text(risk.description) || !['open', 'resolved'].includes(risk.status) || typeof risk.blocking !== 'boolean') fail('risk', 'Risks need description, status and blocking flag.', risk.id);
|
|
70
|
+
if (risk.status === 'open' && risk.blocking) fail('risk-open', risk.description, risk.id);
|
|
71
|
+
if (risk.status === 'resolved' && !text(risk.evidence)) fail('risk-evidence', 'Resolved risks require evidence.', risk.id);
|
|
72
|
+
}
|
|
73
|
+
return findings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function identity(state, hash) {
|
|
77
|
+
if (!state?.plan || !text(hash)) throw Error('Plan and observed source fingerprint are required.');
|
|
78
|
+
return { runId: state.runId, revision: state.revision, digest: designDigest(state.plan), fingerprint: hash };
|
|
79
|
+
}
|
|
80
|
+
function matches(receipt, state, hash) {
|
|
81
|
+
return receipt && Object.entries(identity(state, hash)).every(([key, value]) => receipt[key] === value);
|
|
82
|
+
}
|
|
83
|
+
export function validatePlanning(state, hash, now = new Date().toISOString()) {
|
|
84
|
+
const findings = validateDesign(state.plan);
|
|
85
|
+
state.planning ||= { history: [] };
|
|
86
|
+
state.planning.validation = { ...identity(state, hash), findings, at: now };
|
|
87
|
+
state.planning.history.push({ event: 'validation', receipt: structuredClone(state.planning.validation) });
|
|
88
|
+
return state.planning.validation;
|
|
89
|
+
}
|
|
90
|
+
export function inspectPlanning(state, hash, now = new Date().toISOString()) {
|
|
91
|
+
if (!matches(state.planning?.validation, state, hash) || state.planning.validation.findings.length) throw Error('Fresh passing validation required before plan inspection.');
|
|
92
|
+
const capture = { ...identity(state, hash), id: randomUUID(), at: now, plan: structuredClone(state.plan), previousFindings: structuredClone(state.planning.review?.findings || []), unresolved: structuredClone(state.planning.openFindings || []), provenance: 'Harness captured plan; reading and semantic review remain model-authored.' };
|
|
93
|
+
state.planning.capture = capture;
|
|
94
|
+
delete state.planning.approval;
|
|
95
|
+
delete state.planning.started;
|
|
96
|
+
return structuredClone(capture);
|
|
97
|
+
}
|
|
98
|
+
export function recordPlanReview(state, hash, review, now = new Date().toISOString()) {
|
|
99
|
+
if (!matches(state.planning?.validation, state, hash)) throw Error('Validate the current plan and snapshot first.');
|
|
100
|
+
if (state.planning.validation.findings.length) throw Error('Repair blocking design findings before review.');
|
|
101
|
+
if (!matches(state.planning.capture, state, hash)) throw Error('No current plan capture: call delivery_design action=inspect, then pass its result.id as review.captureId. Revision or source changes make earlier captures stale.');
|
|
102
|
+
if (review?.captureId !== state.planning.capture.id) throw Error(`review.captureId must be the id from the latest inspect result (${state.planning.capture.id}); received ${JSON.stringify(review?.captureId ?? null)}. Earlier captureIds are stale — reuse this id or re-inspect.`);
|
|
103
|
+
if (!list(review?.challenges).length || review.challenges.some(x => !text(x))) throw Error('Record concrete adversarial challenges.');
|
|
104
|
+
if (!Array.isArray(review.findings)) throw Error('review.findings must be an array. Findings need severity, description and advisory disposition.');
|
|
105
|
+
for (const [i, finding] of review.findings.entries()) {
|
|
106
|
+
if (!finding || !text(finding.description)) throw Error(`review.findings[${i}].description must be nonempty. Findings need severity, description and advisory disposition.`);
|
|
107
|
+
if (!['blocking', 'advisory'].includes(finding.severity)) throw Error(`review.findings[${i}].severity expected blocking|advisory; received ${JSON.stringify(finding.severity)}. Findings need severity, description and advisory disposition.`);
|
|
108
|
+
if (finding.severity === 'advisory' && !text(finding.disposition)) throw Error(`review.findings[${i}].disposition required for advisory. Findings need severity, description and advisory disposition.`);
|
|
109
|
+
}
|
|
110
|
+
const resolutions = list(review.resolutions);
|
|
111
|
+
const open = state.planning.openFindings || [];
|
|
112
|
+
for (const finding of open) {
|
|
113
|
+
const resolution = resolutions.find(r => r?.id === finding.id);
|
|
114
|
+
if (resolution && (state.revision <= finding.revision || !text(resolution.change) || !text(resolution.evidence))) throw Error('Blocking findings require a later design revision, explicit change and resolution evidence.');
|
|
115
|
+
}
|
|
116
|
+
if (resolutions.some(r => !r || !open.some(f => f.id === r.id))) throw Error('Resolution references an unknown blocking finding.');
|
|
117
|
+
const walkthroughs = list(review.walkthroughs);
|
|
118
|
+
if (list(state.plan.design?.scenarios).some(s => !walkthroughs.some(w => w?.scenario === s.id && text(w.trace) && text(w.assertion)))) throw Error('Walk through every scenario with a trace and assertion.');
|
|
119
|
+
if (!Array.isArray(review.limitations) || review.limitations.some(x => !text(x))) throw Error('Declare review limitations.');
|
|
120
|
+
if (walkthroughs.length !== list(state.plan.design?.scenarios).length || new Set(walkthroughs.map(w => w.scenario)).size !== walkthroughs.length) throw Error('Walkthrough IDs must be unique and match exactly the declared scenarios.');
|
|
121
|
+
const findings = review.findings.map(f => ({ ...structuredClone(f), id: randomUUID(), revision: state.revision }));
|
|
122
|
+
state.planning.openFindings = [...open.filter(f => !resolutions.some(r => r.id === f.id)), ...findings.filter(f => f.severity === 'blocking')];
|
|
123
|
+
const receipt = { ...identity(state, hash), ...structuredClone(review), findings, provenance: 'model-authored; not independent assurance', at: now };
|
|
124
|
+
// Caller-controlled content cannot override receipt identity.
|
|
125
|
+
Object.assign(receipt, identity(state, hash));
|
|
126
|
+
state.planning.review = receipt;
|
|
127
|
+
state.planning.history.push({ event: 'review', receipt: structuredClone(receipt) });
|
|
128
|
+
delete state.planning.approval;
|
|
129
|
+
return receipt;
|
|
130
|
+
}
|
|
131
|
+
export function approvePlanning(state, hash, now = new Date().toISOString()) {
|
|
132
|
+
if ((state.plan.verification ?? 'required') !== 'required') throw Error('Informational plans cannot authorize implementation.');
|
|
133
|
+
if (!matches(state.planning?.validation, state, hash) || validateDesign(state.plan).length) throw Error('Fresh passing design validation is required.');
|
|
134
|
+
if (!matches(state.planning?.review, state, hash) || (state.planning.openFindings?.length || state.planning.review.findings.some(f => f.severity === 'blocking'))) throw Error('Fresh review with no blocking findings is required.');
|
|
135
|
+
state.planning.approval = { ...identity(state, hash), at: now };
|
|
136
|
+
state.planning.history.push({ event: 'approved', receipt: { ...state.planning.approval } });
|
|
137
|
+
return state.planning.approval;
|
|
138
|
+
}
|
|
139
|
+
export function planningStatus(state, hash) {
|
|
140
|
+
if (!state?.plan) return { locked: true, phase: 'discovery', reasons: ['Create and test a design before generating code.'] };
|
|
141
|
+
if (state.workflowMode === 'standard') {
|
|
142
|
+
const required = (state.plan.verification ?? 'required') === 'required';
|
|
143
|
+
return { locked: !required, phase: required ? 'implementation' : 'informational', reasons: required ? [] : ['Informational contracts cannot authorize implementation.'], enforcement: 'Standard contract mode; final evidence and review remain required. Not an OS sandbox.' };
|
|
144
|
+
}
|
|
145
|
+
const approval = state.planning?.approval;
|
|
146
|
+
const samePlan = approval && approval.runId === state.runId && approval.revision === state.revision && approval.digest === designDigest(state.plan);
|
|
147
|
+
const started = samePlan && state.planning?.started?.approvalAt === approval.at;
|
|
148
|
+
const unlocked = (state.plan.verification ?? 'required') === 'required' && samePlan && (started || approval.fingerprint === hash);
|
|
149
|
+
return { locked: !unlocked, phase: unlocked ? (started ? 'implementation' : 'approved') : 'design-validation', reasons: unlocked ? [] : ['Implementation locked: validate, review and approve this plan revision on the current source snapshot.'], findings: state.planning?.validation?.findings || [], reviewFindings: [...(state.planning?.openFindings || []), ...(state.planning?.review?.findings || []).filter(f => f.severity !== 'blocking')], enforcement: 'Fail-closed participating-tool gate; not an OS sandbox. External processes and host access are outside this boundary.' };
|
|
150
|
+
}
|
|
151
|
+
export function startImplementation(state, hash, now = new Date().toISOString()) {
|
|
152
|
+
if (planningStatus(state, hash).locked) throw Error('Implementation locked: test and approve the plan before code, tests or commands.');
|
|
153
|
+
if (state.workflowMode === 'standard') return;
|
|
154
|
+
state.planning.started ||= { approvalAt: state.planning.approval.at, at: now };
|
|
155
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Advisory only: diagnose repeated infrastructure failures without granting
|
|
2
|
+
// permission, suppressing evidence, executing commands or weakening checks.
|
|
3
|
+
export function probeDiagnostics(previous = { failures: 0, family: null }, event) {
|
|
4
|
+
if (!['bash', 'delivery_check'].includes(event.toolName)) return { state: previous };
|
|
5
|
+
const text = (event.content || []).filter(item => item.type === 'text').map(item => item.text).join('\n');
|
|
6
|
+
const failed = event.isError === true || /(?:exit(?:ed)? (?:with )?code[: ]+[1-9]|\bERR_(?:MODULE|UNKNOWN|PACKAGE)|Cannot find (?:module|package)|"passed"\s*:\s*false|ℹ fail [1-9]|\btest failed\b)/i.test(text);
|
|
7
|
+
if (!failed) return { state: { failures: 0, family: null } };
|
|
8
|
+
const family = /module|loader|tsx|import|registerHooks|register\(/i.test(text) ? 'module-loader' : 'execution';
|
|
9
|
+
const failures = previous.family === family ? previous.failures + 1 : 1;
|
|
10
|
+
const state = { failures: Math.min(failures, 1000), family };
|
|
11
|
+
if (failures !== 2 && failures !== 4) return { state };
|
|
12
|
+
return { state, message: `Diagnostic checkpoint: ${failures} consecutive ${family} failures. Before another executable probe, state ONE observed failure and ONE changed hypothesis; preserve the original exit code and inspect complete stderr. Do not use pipelines that turn a failed test into exit 0. Prefer existing test tooling or node:test plus existing TypeScript transpilation and mocked imports over custom loader chains. Separate product defects from test infrastructure. Do not change unrelated dependencies, hide failures with stubs, or repeatedly revise the design for probe guesses. Fix the minimal mechanism, rerun the real suite, then reserve time for final review/handoff. This advice grants no execution or approval authority.` };
|
|
13
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { CloudClient, cloudControlUrl, oauthCredentials, openBrowser } from '../cloud/client.mjs';
|
|
3
|
+
import { streamSimple as streamOpenAiCompletions } from '../../vendor/agent/chunks/openai-completions-EKZT2IH2.js';
|
|
4
|
+
import { shipAgentDir } from '../ship-auth.mjs';
|
|
5
|
+
import { shipInstallId, shipRequestHeaders } from '../ship-telemetry.mjs';
|
|
6
|
+
import { packageVersion } from '../update.mjs';
|
|
7
|
+
import { CARTHAGENT_SHIP_PROVIDER_ID, CARTHAGENT_SHIP_PROVIDER_NAME } from './names.mjs';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_BASE_URL = 'https://api.experientiallabs.ai/v1';
|
|
10
|
+
const PROVIDER_ID = CARTHAGENT_SHIP_PROVIDER_ID;
|
|
11
|
+
const MANAGED_CLOUD_NON_RETRYABLE_ERRORS = /\b(?:idempotency_key_reused|request_reconciliation_required)\b/i;
|
|
12
|
+
|
|
13
|
+
const OPENAI_REASONING_LEVELS = Object.freeze({
|
|
14
|
+
off: 'none', minimal: null, low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh', max: 'max',
|
|
15
|
+
});
|
|
16
|
+
const REQUIRED_REASONING_LEVEL = Object.freeze({
|
|
17
|
+
off: null, minimal: null, low: null, medium: 'medium', high: null, xhigh: null, max: null,
|
|
18
|
+
});
|
|
19
|
+
const MERCURY_REASONING_LEVELS = Object.freeze({
|
|
20
|
+
off: null, minimal: null, low: 'low', medium: 'medium', high: 'high', xhigh: null, max: null,
|
|
21
|
+
});
|
|
22
|
+
const DEEPSEEK_REASONING_LEVELS = Object.freeze({
|
|
23
|
+
off: null, minimal: null, low: 'low', medium: null, high: 'high', xhigh: null, max: 'max',
|
|
24
|
+
});
|
|
25
|
+
// SkyPilot's DeepSeek accepts reasoning off; a non-null off level makes the
|
|
26
|
+
// engine send thinking:{type:'disabled'} (the mapped string itself is not
|
|
27
|
+
// emitted — 'none' also works upstream, but the wire form is the thinking flag).
|
|
28
|
+
const SKYPILOT_DEEPSEEK_LEVELS = Object.freeze({
|
|
29
|
+
off: 'none', minimal: null, low: 'low', medium: null, high: 'high', xhigh: null, max: 'max',
|
|
30
|
+
});
|
|
31
|
+
// Kimi K3 and GLM 5.3 always reason; 'off' is unavailable upstream.
|
|
32
|
+
const TIERED_REASONING_LEVELS = Object.freeze({
|
|
33
|
+
off: null, minimal: null, low: 'low', medium: null, high: 'high', xhigh: null, max: 'max',
|
|
34
|
+
});
|
|
35
|
+
// Beam always reasons but accepts the full low→max effort ladder; 'minimal',
|
|
36
|
+
// 'none', and the thinking flag are rejected upstream, so they stay unmapped.
|
|
37
|
+
const BEAM_REASONING_LEVELS = Object.freeze({
|
|
38
|
+
off: null, minimal: null, low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh', max: 'max',
|
|
39
|
+
});
|
|
40
|
+
// Mercury 3 accepts only 'instant' | 'low' | 'medium' | 'high'. 'instant' is its
|
|
41
|
+
// real no-reasoning mode (the thinking flag is ignored upstream), and 'xhigh'/
|
|
42
|
+
// 'max' are rejected, so they stay unmapped.
|
|
43
|
+
const MERCURY3_REASONING_LEVELS = Object.freeze({
|
|
44
|
+
off: 'instant', minimal: 'low', low: 'low', medium: 'medium', high: 'high', xhigh: null, max: null,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Experiential's /models response is intentionally OpenAI-compatible and often
|
|
48
|
+
// identity-only. Keep the capability facts needed by the engine here rather
|
|
49
|
+
// than guessing from the selected model at request time.
|
|
50
|
+
const EXPERIENTIAL_MODEL_PROFILES = Object.freeze({
|
|
51
|
+
'gpt-5.6-luna': { name: 'GPT-5.6 Luna', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
52
|
+
'gpt-5.6-luna-pro': { name: 'GPT-5.6 Luna Pro', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
53
|
+
'gpt-5.6-sol': { name: 'GPT-5.6 Sol', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
54
|
+
'gpt-5.6-terra': { name: 'GPT-5.6 Terra', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
55
|
+
'gpt-6-astra': { name: 'GPT-6 Astra', reasoning: true, thinkingLevelMap: { ...OPENAI_REASONING_LEVELS, off: null }, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
56
|
+
'gpt-6-luna': { name: 'GPT-6 Luna', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
57
|
+
'deepseek-v4-flash': { name: 'DeepSeek V4 Flash', reasoning: true, thinkingLevelMap: DEEPSEEK_REASONING_LEVELS, contextWindow: 1_000_000, maxTokens: 384_000, input: ['text'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: true, thinkingFormat: 'deepseek' } },
|
|
58
|
+
'gpt-6-sol': { name: 'GPT-6 Sol', reasoning: true, thinkingLevelMap: OPENAI_REASONING_LEVELS, contextWindow: 1_050_000, maxTokens: 128_000, input: ['text', 'image'], compat: { supportsReasoningEffort: true } },
|
|
59
|
+
'mercury-2': { name: 'Mercury 2', reasoning: true, thinkingLevelMap: MERCURY_REASONING_LEVELS, contextWindow: 128_000, maxTokens: 50_000, input: ['text'], compat: { supportsReasoningEffort: true } },
|
|
60
|
+
'kimi-k2-thinking': { name: 'Kimi K2 Thinking', reasoning: true, thinkingLevelMap: REQUIRED_REASONING_LEVEL, contextWindow: 262_144, maxTokens: 98_304, input: ['text'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: false, thinkingFormat: 'deepseek' } },
|
|
61
|
+
'qwen3-max-thinking': { name: 'Qwen3 Max Thinking', reasoning: true, thinkingLevelMap: REQUIRED_REASONING_LEVEL, contextWindow: 262_144, maxTokens: 65_536, input: ['text'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: false, thinkingFormat: 'qwen' } },
|
|
62
|
+
'sonar-reasoning-pro': { name: 'Sonar Reasoning Pro', reasoning: true, thinkingLevelMap: REQUIRED_REASONING_LEVEL, input: ['text'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: false } },
|
|
63
|
+
'deepseek-v4.1-flash': { name: 'DeepSeek V4.1 Flash', reasoning: true, thinkingLevelMap: SKYPILOT_DEEPSEEK_LEVELS, contextWindow: 1_048_576, maxTokens: 32_768, input: ['text', 'image'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: true, thinkingFormat: 'deepseek' } },
|
|
64
|
+
'glm-5.3-flash': { name: 'GLM 5.3 Flash', reasoning: true, thinkingLevelMap: TIERED_REASONING_LEVELS, contextWindow: 1_048_576, maxTokens: 32_768, input: ['text', 'image'], compat: { supportsReasoningEffort: true, thinkingFormat: 'zai' } },
|
|
65
|
+
'glm-5.3': { name: 'GLM 5.3', reasoning: true, thinkingLevelMap: TIERED_REASONING_LEVELS, contextWindow: 1_048_576, maxTokens: 32_768, input: ['text'], compat: { supportsReasoningEffort: true, thinkingFormat: 'zai' } },
|
|
66
|
+
'kimi-k3': { name: 'Kimi K3', reasoning: true, thinkingLevelMap: TIERED_REASONING_LEVELS, contextWindow: 1_048_576, maxTokens: 32_768, input: ['text'], compat: { supportsDeveloperRole: false, supportsReasoningEffort: true, thinkingFormat: 'deepseek' } },
|
|
67
|
+
'beam-1.0-501b-a23b': { name: 'Beam 1.0 501B A23B', reasoning: true, thinkingLevelMap: BEAM_REASONING_LEVELS, contextWindow: 1_048_576, maxTokens: 32_768, input: ['text'], compat: { supportsReasoningEffort: true } },
|
|
68
|
+
'mercury-3': { name: 'Mercury 3', reasoning: true, thinkingLevelMap: MERCURY3_REASONING_LEVELS, contextWindow: 1_048_576, maxTokens: 131_072, input: ['text'], compat: { supportsReasoningEffort: true } },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const MODEL_METADATA_FIELDS = ['reasoning', 'thinkingLevelMap', 'contextWindow', 'maxTokens', 'input', 'output', 'compat'];
|
|
72
|
+
|
|
73
|
+
export const EXPERIENTIAL_PROVIDER_ID = PROVIDER_ID;
|
|
74
|
+
export const EXPERIENTIAL_PROVIDER_NAME = CARTHAGENT_SHIP_PROVIDER_NAME;
|
|
75
|
+
export const EXPERIENTIAL_API_KEY_ENV = 'EXPLABS_API_KEY';
|
|
76
|
+
export const EXPERIENTIAL_GATEWAY_URL_ENV = 'EXP_GATEWAY_URL';
|
|
77
|
+
|
|
78
|
+
export function experientialBaseUrl(env = process.env) {
|
|
79
|
+
const gatewayOverride = env?.[EXPERIENTIAL_GATEWAY_URL_ENV]?.trim();
|
|
80
|
+
return (gatewayOverride || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function cloudGatewayUrl(env = process.env) {
|
|
84
|
+
return `${cloudControlUrl(env).replace(/\/v1$/, '')}/v1`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isCloudAccessToken(token) {
|
|
88
|
+
const parts = String(token || '').split('.');
|
|
89
|
+
if (parts.length !== 3) return false;
|
|
90
|
+
try {
|
|
91
|
+
const claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
92
|
+
return typeof claims.sub === 'string' && typeof claims.sid === 'string'
|
|
93
|
+
&& String(claims.scope || '').split(' ').includes('gateway:invoke');
|
|
94
|
+
} catch { return false; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createCloudOperationIdentity(id = globalThis.crypto?.randomUUID?.bind(globalThis.crypto)) {
|
|
98
|
+
if (typeof id !== 'function') throw new Error('Managed Cloud requests require UUID support.');
|
|
99
|
+
const operationKey = id();
|
|
100
|
+
if (typeof operationKey !== 'string' || !operationKey.trim()) throw new Error('Managed Cloud operation identity is invalid.');
|
|
101
|
+
return operationKey.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function isManagedCloudNonRetryableError(message) {
|
|
105
|
+
return MANAGED_CLOUD_NON_RETRYABLE_ERRORS.test(String(message || ''));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function managedCloudRequestOptions(options = {}, id) {
|
|
109
|
+
const operationKey = createCloudOperationIdentity(id);
|
|
110
|
+
const inheritedHeaders = options.requestHeaders && typeof options.requestHeaders === 'object'
|
|
111
|
+
? options.requestHeaders : {};
|
|
112
|
+
return {
|
|
113
|
+
...options,
|
|
114
|
+
// The gateway currently reserves before dispatch but cannot replay or wait
|
|
115
|
+
// for an existing operation. Retrying this identity would therefore return
|
|
116
|
+
// idempotency_key_reused/request_reconciliation_required instead of the
|
|
117
|
+
// original result. Keep one physical HTTP attempt until replay exists.
|
|
118
|
+
maxRetries: 0,
|
|
119
|
+
requestHeaders: {
|
|
120
|
+
...inheritedHeaders,
|
|
121
|
+
'Idempotency-Key': operationKey,
|
|
122
|
+
'X-Client-Request-Id': operationKey,
|
|
123
|
+
// Metadata-only Ship telemetry: thread, turn, install, client build.
|
|
124
|
+
...shipRequestHeaders({ version: packageVersion() }),
|
|
125
|
+
},
|
|
126
|
+
onPayload: async (payload, activeModel) => {
|
|
127
|
+
const next = await options.onPayload?.(payload, activeModel);
|
|
128
|
+
const body = { ...(next ?? payload), metadata: { ...(next ?? payload).metadata, operation_key: operationKey } };
|
|
129
|
+
// The engine emits thinking:{type:'disabled'} whenever no effort is
|
|
130
|
+
// selected, but always-reasoning upstreams (off level unavailable,
|
|
131
|
+
// thinkingLevelMap.off === null) reject it outright. Omit the field so
|
|
132
|
+
// the model reasons at its default instead.
|
|
133
|
+
if (body.thinking?.type === 'disabled' && activeModel?.thinkingLevelMap?.off === null) {
|
|
134
|
+
delete body.thinking;
|
|
135
|
+
}
|
|
136
|
+
return body;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function experientialModelDefinition(model) {
|
|
142
|
+
const id = typeof model === 'string' ? model : model?.id;
|
|
143
|
+
if (typeof id !== 'string' || !id.trim()) return null;
|
|
144
|
+
const normalizedId = id.trim();
|
|
145
|
+
// Managed aliases may be namespace-qualified (for example openai/gpt-6-luna)
|
|
146
|
+
// while direct Experiential discovery returns the upstream id.
|
|
147
|
+
const profileId = normalizedId.split('/').at(-1);
|
|
148
|
+
const profile = EXPERIENTIAL_MODEL_PROFILES[normalizedId] || EXPERIENTIAL_MODEL_PROFILES[profileId] || {};
|
|
149
|
+
const suppliedName = typeof model === 'object' && typeof model.name === 'string' && model.name.trim()
|
|
150
|
+
? model.name.trim() : '';
|
|
151
|
+
const definition = { id: normalizedId, name: suppliedName || profile.name || normalizedId, ...profile };
|
|
152
|
+
|
|
153
|
+
// Preserve authoritative metadata if a future Experiential response starts
|
|
154
|
+
// publishing it. Stored catalogs also pass through here during offline load.
|
|
155
|
+
if (model && typeof model === 'object') {
|
|
156
|
+
for (const field of MODEL_METADATA_FIELDS) if (model[field] !== undefined) definition[field] = model[field];
|
|
157
|
+
// Managed aliases publish their operating caps (contextWindow = the usable
|
|
158
|
+
// input window, maxTokens = the output ceiling). The model profile may
|
|
159
|
+
// declare a smaller real capability, so keep the stricter value: engine
|
|
160
|
+
// auto-compaction and the output budget then respect both bounds.
|
|
161
|
+
for (const field of ['contextWindow', 'maxTokens']) {
|
|
162
|
+
if (typeof model[field] === 'number' && typeof profile[field] === 'number') {
|
|
163
|
+
definition[field] = Math.min(model[field], profile[field]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Cover explicit fixed-thinking aliases that can be added to the provider
|
|
169
|
+
// before this curated table is updated. This is capability resolution, not
|
|
170
|
+
// context/pricing inference: the name itself declares that reasoning is on.
|
|
171
|
+
if (definition.reasoning === undefined && /(?:^|[-_.])(?:thinking|reasoning)(?:$|[-_.])/i.test(normalizedId)) {
|
|
172
|
+
definition.reasoning = true;
|
|
173
|
+
definition.thinkingLevelMap = REQUIRED_REASONING_LEVEL;
|
|
174
|
+
}
|
|
175
|
+
return definition;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function parseExperientialModels(payload) {
|
|
179
|
+
const data = Array.isArray(payload?.data) ? payload.data : [];
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
const models = [];
|
|
182
|
+
for (const item of data) {
|
|
183
|
+
const model = experientialModelDefinition(item);
|
|
184
|
+
if (!model || seen.has(model.id)) continue;
|
|
185
|
+
seen.add(model.id);
|
|
186
|
+
models.push(model);
|
|
187
|
+
}
|
|
188
|
+
return models.sort((a, b) => a.id.localeCompare(b.id));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function cloudOAuth({ env = process.env, fetchImpl = globalThis.fetch, sleep, now = Date.now, openBrowserImpl = openBrowser } = {}) {
|
|
192
|
+
const client = new CloudClient({ baseUrl: cloudControlUrl(env), fetchImpl, sleep });
|
|
193
|
+
return {
|
|
194
|
+
name: 'Carthagent Ship account',
|
|
195
|
+
async login(interaction) {
|
|
196
|
+
const tokens = await client.authorizeDevice({
|
|
197
|
+
clientName: `Carthagent CLI (${os.hostname()})`,
|
|
198
|
+
installId: shipInstallId({ agentDir: shipAgentDir(env) }),
|
|
199
|
+
signal: interaction.signal,
|
|
200
|
+
notify: event => {
|
|
201
|
+
if (event.type === 'device_code') interaction.onDeviceCode?.({
|
|
202
|
+
userCode: event.userCode,
|
|
203
|
+
verificationUri: event.verificationUri,
|
|
204
|
+
intervalSeconds: event.intervalSeconds,
|
|
205
|
+
expiresInSeconds: event.expiresInSeconds,
|
|
206
|
+
});
|
|
207
|
+
else if (event.type === 'auth_url') {
|
|
208
|
+
Promise.resolve(openBrowserImpl?.(event.url)).catch(() => false);
|
|
209
|
+
interaction.onAuth?.({ url: event.url, instructions: `${event.instructions} The URL is also shown if Carthagent cannot open it automatically.` });
|
|
210
|
+
}
|
|
211
|
+
else if (event.type === 'progress') interaction.onProgress?.(event.message);
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
return oauthCredentials(tokens, now());
|
|
215
|
+
},
|
|
216
|
+
async refreshToken(credentials, signal) {
|
|
217
|
+
return oauthCredentials(await client.refresh({ refreshToken: credentials.refresh, signal }), now());
|
|
218
|
+
},
|
|
219
|
+
getApiKey(credentials) { return credentials.access; },
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function experientialProviderConfig({ env = process.env, fetchImpl = globalThis.fetch, sleep, now, openBrowserImpl } = {}) {
|
|
224
|
+
const oauth = cloudOAuth({ env, fetchImpl, sleep, now, openBrowserImpl });
|
|
225
|
+
const baseUrl = experientialBaseUrl(env);
|
|
226
|
+
const dynamicModels = [];
|
|
227
|
+
const installModels = models => {
|
|
228
|
+
dynamicModels.splice(0, dynamicModels.length, ...models);
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
name: EXPERIENTIAL_PROVIDER_NAME,
|
|
232
|
+
baseUrl,
|
|
233
|
+
api: 'openai-completions',
|
|
234
|
+
apiKey: `$${EXPERIENTIAL_API_KEY_ENV}`,
|
|
235
|
+
authHeader: true,
|
|
236
|
+
oauth,
|
|
237
|
+
streamSimple(model, context, options = {}) {
|
|
238
|
+
const managed = isCloudAccessToken(options.apiKey);
|
|
239
|
+
const requestOptions = managed ? managedCloudRequestOptions(options) : options;
|
|
240
|
+
const stream = streamOpenAiCompletions(managed ? { ...model, baseUrl: cloudGatewayUrl(env) } : model, context, requestOptions);
|
|
241
|
+
if (!managed) return stream;
|
|
242
|
+
const result = stream.result.bind(stream);
|
|
243
|
+
stream.result = async () => {
|
|
244
|
+
const message = await result();
|
|
245
|
+
if (message?.stopReason === 'error' && isManagedCloudNonRetryableError(message.errorMessage)) {
|
|
246
|
+
message.errorMessage = `Carthagent Ship cannot safely replay this operation yet. ${message.errorMessage}`;
|
|
247
|
+
}
|
|
248
|
+
return message;
|
|
249
|
+
};
|
|
250
|
+
return stream;
|
|
251
|
+
},
|
|
252
|
+
models: dynamicModels,
|
|
253
|
+
async refreshModels(context) {
|
|
254
|
+
const storedModels = Array.isArray(context.stored) ? context.stored : (Array.isArray(context.stored?.models) ? context.stored.models : []);
|
|
255
|
+
if (storedModels.length) {
|
|
256
|
+
const restored = storedModels.map(experientialModelDefinition).filter(Boolean);
|
|
257
|
+
if (context.publish) await context.publish({ update: () => installModels(restored) });
|
|
258
|
+
else installModels(restored);
|
|
259
|
+
}
|
|
260
|
+
if (!context.allowNetwork) return storedModels;
|
|
261
|
+
const managed = context.credential?.type === 'oauth';
|
|
262
|
+
const key = context.credential?.type === 'api_key' ? context.credential.key
|
|
263
|
+
: managed ? context.credential.access : '';
|
|
264
|
+
if (!key) return storedModels;
|
|
265
|
+
if (typeof fetchImpl !== 'function') throw new Error('Carthagent Ship model discovery requires fetch support.');
|
|
266
|
+
const response = await fetchImpl(`${managed ? cloudGatewayUrl(env) : baseUrl}/models`, {
|
|
267
|
+
method: 'GET',
|
|
268
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
269
|
+
signal: context.signal,
|
|
270
|
+
});
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
const detail = String(await response.text()).trim().slice(0, 240);
|
|
273
|
+
throw new Error(`Carthagent Ship model discovery failed (${response.status})${detail ? `: ${detail}` : ''}`);
|
|
274
|
+
}
|
|
275
|
+
const models = parseExperientialModels(await response.json());
|
|
276
|
+
if (!models.length) throw new Error('Carthagent Ship returned no models for this account.');
|
|
277
|
+
if (context.publish) {
|
|
278
|
+
await context.publish({
|
|
279
|
+
persist: { models, checkedAt: Date.now() },
|
|
280
|
+
update: () => installModels(models),
|
|
281
|
+
});
|
|
282
|
+
} else installModels(models);
|
|
283
|
+
return models;
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function registerExperientialProvider(runtime, options) {
|
|
289
|
+
if (!runtime || typeof runtime.registerProvider !== 'function') throw new TypeError('A ModelRuntime is required.');
|
|
290
|
+
runtime.registerProvider(PROVIDER_ID, experientialProviderConfig(options));
|
|
291
|
+
return runtime;
|
|
292
|
+
}
|