forgeos 0.1.0-alpha.7 → 0.1.0-alpha.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/forge-agent-fabric/SKILL.md +178 -0
- package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
- package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
- package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
- package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
- package/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +870 -9
- package/LICENSE +21 -0
- package/README.md +121 -6
- package/adapters/go/README.md +23 -0
- package/adapters/go/go.mod +3 -0
- package/adapters/go/http.go +149 -0
- package/adapters/go/registry.go +234 -0
- package/adapters/go/types.go +136 -0
- package/adapters/java/README.md +68 -0
- package/adapters/java/pom.xml +47 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
- package/adapters/java-spring-boot-starter/README.md +32 -0
- package/adapters/java-spring-boot-starter/pom.xml +49 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
- package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
- package/bin/forge.mjs +18 -0
- package/docs/agent-fabric-programs.md +151 -0
- package/docs/agent-fabric.md +582 -0
- package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
- package/docs/architecture/agent-fabric/README.md +180 -0
- package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +81 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS.md +39 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS_EVIDENCE.md +53 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +49 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +590 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +1070 -0
- package/docs/forge-protocol.md +189 -0
- package/docs/repository-agent-benchmark.md +179 -0
- package/docs/repository-analysis.md +139 -0
- package/docs/repository-context-quality.md +179 -0
- package/docs/repository-runtime-observation.md +82 -0
- package/examples/agent-fabric-v2/README.md +70 -0
- package/examples/agent-fabric-v2/item-child.workflow.ts +11 -0
- package/examples/agent-fabric-v2/make-request.mjs +4 -0
- package/examples/agent-fabric-v2/map-child.workflow.ts +10 -0
- package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
- package/examples/agent-fabric-v2/registry.example.json +256 -0
- package/examples/agent-fabric-v2/run-ui-fixture.ts +48 -0
- package/examples/agent-fabric-v2/ui-audit.workflow.ts +28 -0
- package/examples/agent-fabric-v2/ui-fixture-worker.cjs +36 -0
- package/examples/agent-fabric-v2/visual-population.json +13 -0
- package/examples/agent-fabric-v2/workers/check.mjs +2 -0
- package/examples/agent-fabric-v2/workers/common.mjs +7 -0
- package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
- package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
- package/examples/agent-fabric-v2/workers/review.mjs +3 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +60 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
- package/package.json +60 -5
- package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
- package/schemas/forge-manifest.schema.json +389 -0
- package/scripts/field-test-forgeos.mjs +1021 -0
- package/scripts/install-agent-fabric-skill.mjs +105 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1592 -125
- package/src/forge/agent-adapters/types.ts +225 -1
- package/src/forge/agent-fabric/adapter.ts +131 -0
- package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
- package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
- package/src/forge/agent-fabric/attached-task-service.ts +127 -0
- package/src/forge/agent-fabric/attached-task-store.ts +109 -0
- package/src/forge/agent-fabric/authority.ts +298 -0
- package/src/forge/agent-fabric/canonical.ts +135 -0
- package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
- package/src/forge/agent-fabric/codex-sdk-worker.ts +203 -0
- package/src/forge/agent-fabric/conductor.ts +754 -0
- package/src/forge/agent-fabric/dictionary.ts +12 -0
- package/src/forge/agent-fabric/errors.ts +32 -0
- package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
- package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
- package/src/forge/agent-fabric/index.ts +48 -0
- package/src/forge/agent-fabric/journal.ts +116 -0
- package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
- package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
- package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
- package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
- package/src/forge/agent-fabric/local-approval-window.ts +211 -0
- package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
- package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
- package/src/forge/agent-fabric/local-control-store.ts +239 -0
- package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
- package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
- package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
- package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
- package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
- package/src/forge/agent-fabric/local-intelligence.ts +281 -0
- package/src/forge/agent-fabric/local-paths.ts +18 -0
- package/src/forge/agent-fabric/local-task-contract.ts +203 -0
- package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
- package/src/forge/agent-fabric/local-task-server.ts +374 -0
- package/src/forge/agent-fabric/local-task-service.ts +982 -0
- package/src/forge/agent-fabric/local-verification.ts +551 -0
- package/src/forge/agent-fabric/managed-environment.ts +205 -0
- package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
- package/src/forge/agent-fabric/managed-run-service.ts +401 -0
- package/src/forge/agent-fabric/managed-run-store.ts +165 -0
- package/src/forge/agent-fabric/managed-workspace.ts +253 -0
- package/src/forge/agent-fabric/p0a.ts +104 -0
- package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
- package/src/forge/agent-fabric/planning.ts +155 -0
- package/src/forge/agent-fabric/program-api.ts +37 -0
- package/src/forge/agent-fabric/program-contract.ts +283 -0
- package/src/forge/agent-fabric/program-dsl.ts +126 -0
- package/src/forge/agent-fabric/program-evidence.ts +22 -0
- package/src/forge/agent-fabric/program-image.ts +15 -0
- package/src/forge/agent-fabric/program-lock.ts +50 -0
- package/src/forge/agent-fabric/program-owner.ts +16 -0
- package/src/forge/agent-fabric/program-scheduler.ts +38 -0
- package/src/forge/agent-fabric/program-service.ts +793 -0
- package/src/forge/agent-fabric/program-store.ts +142 -0
- package/src/forge/agent-fabric/program-structure.ts +65 -0
- package/src/forge/agent-fabric/program-types.ts +110 -0
- package/src/forge/agent-fabric/program-view.ts +12 -0
- package/src/forge/agent-fabric/program-worker.ts +108 -0
- package/src/forge/agent-fabric/project-profile.ts +50 -0
- package/src/forge/agent-fabric/project-registry.ts +120 -0
- package/src/forge/agent-fabric/project-runtime.ts +85 -0
- package/src/forge/agent-fabric/readiness.ts +26 -0
- package/src/forge/agent-fabric/reducer.ts +863 -0
- package/src/forge/agent-fabric/repository-context.ts +83 -0
- package/src/forge/agent-fabric/resource-ledger.ts +266 -0
- package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
- package/src/forge/agent-fabric/types.ts +424 -0
- package/src/forge/agent-fabric/validation.ts +500 -0
- package/src/forge/agent-fabric/workflow-engine.ts +218 -0
- package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
- package/src/forge/agent-fabric/workflow-templates.ts +11 -0
- package/src/forge/agent-memory/bridge.ts +1728 -0
- package/src/forge/agent-memory/context-pack.ts +284 -0
- package/src/forge/agent-memory/hook-runner.ts +331 -0
- package/src/forge/agent-memory/mcp.ts +597 -0
- package/src/forge/agent-memory/normalize.ts +498 -0
- package/src/forge/agent-memory/redaction.ts +103 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -0
- package/src/forge/agent-memory/sources/codex.ts +161 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +215 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +896 -0
- package/src/forge/brownfield-import/types.ts +127 -0
- package/src/forge/cair/action-journal.ts +61 -0
- package/src/forge/cair/action-parser.ts +314 -0
- package/src/forge/cair/action-validator.ts +40 -0
- package/src/forge/cair/actions.ts +1818 -0
- package/src/forge/cair/format.ts +77 -0
- package/src/forge/cair/index.ts +118 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +255 -0
- package/src/forge/cli/adaptive.ts +51 -0
- package/src/forge/cli/ai.ts +16 -1
- package/src/forge/cli/auth.ts +459 -15
- package/src/forge/cli/authmd.ts +441 -0
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/build.ts +20 -4
- package/src/forge/cli/changed.ts +418 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1859 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +1339 -0
- package/src/forge/cli/deps.ts +150 -23
- package/src/forge/cli/dev.ts +1137 -53
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/env-placeholders.ts +62 -0
- package/src/forge/cli/evolution.ts +40 -0
- package/src/forge/cli/fabric.ts +253 -0
- package/src/forge/cli/field-test.ts +568 -0
- package/src/forge/cli/golden-path.ts +487 -0
- package/src/forge/cli/handoff.ts +336 -0
- package/src/forge/cli/index.ts +1 -0
- package/src/forge/cli/last-run.ts +84 -0
- package/src/forge/cli/main.ts +196 -3
- package/src/forge/cli/new.ts +169 -17
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +398 -6
- package/src/forge/cli/parse.ts +1377 -38
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/query.ts +32 -0
- package/src/forge/cli/release.ts +35 -11
- package/src/forge/cli/repository.ts +151 -0
- package/src/forge/cli/run.ts +42 -0
- package/src/forge/cli/secrets.ts +137 -1
- package/src/forge/cli/seed.ts +581 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2282 -0
- package/src/forge/cli/verify.ts +1458 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +3509 -0
- package/src/forge/compiler/agent-contract/build.ts +728 -64
- package/src/forge/compiler/agent-contract/types.ts +45 -2
- package/src/forge/compiler/ai-registry/parse.ts +71 -2
- package/src/forge/compiler/api-surface/build.ts +47 -0
- package/src/forge/compiler/app-graph/build.ts +112 -9
- package/src/forge/compiler/app-graph/extract.ts +107 -0
- package/src/forge/compiler/app-graph/module-graph.ts +73 -78
- package/src/forge/compiler/app-graph/parser.ts +24 -24
- package/src/forge/compiler/app-graph/profile.ts +26 -0
- package/src/forge/compiler/app-graph/tsconfig-hash.ts +41 -4
- package/src/forge/compiler/app-graph/versions.ts +1 -1
- package/src/forge/compiler/classifier/capabilities.ts +3 -2
- package/src/forge/compiler/classifier/classify.ts +32 -8
- package/src/forge/compiler/classifier/secrets.ts +3 -2
- package/src/forge/compiler/classifier/signals.ts +91 -1
- package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
- package/src/forge/compiler/client-sdk/render-client.ts +295 -13
- package/src/forge/compiler/data-graph/build.ts +29 -1
- package/src/forge/compiler/data-graph/parse.ts +20 -4
- package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
- package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
- package/src/forge/compiler/data-graph/sql/types.ts +1 -0
- package/src/forge/compiler/diagnostics/codes.ts +38 -0
- package/src/forge/compiler/diagnostics/create.ts +28 -2
- package/src/forge/compiler/diagnostics/index.ts +2 -0
- package/src/forge/compiler/emitter/barrel.ts +3 -0
- package/src/forge/compiler/emitter/render.ts +9 -0
- package/src/forge/compiler/external-manifest/registry.ts +217 -0
- package/src/forge/compiler/external-manifest/types.ts +91 -0
- package/src/forge/compiler/external-manifest/validate.ts +373 -0
- package/src/forge/compiler/frontend-graph/build.ts +142 -14
- package/src/forge/compiler/integration/add.ts +991 -33
- package/src/forge/compiler/integration/plan.ts +43 -12
- package/src/forge/compiler/integration/render.ts +29 -0
- package/src/forge/compiler/integration/snapshot.ts +2 -0
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +38 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +1439 -0
- package/src/forge/compiler/make-registry/build.ts +8 -9
- package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/orchestrator/manifest.ts +1 -1
- package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
- package/src/forge/compiler/orchestrator/plan.ts +249 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +101 -33
- package/src/forge/compiler/orchestrator/serialize.ts +33 -8
- package/src/forge/compiler/orchestrator/types.ts +1 -1
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-graph/dts-extractor.ts +14 -1
- package/src/forge/compiler/package-manager/adapter.ts +4 -1
- package/src/forge/compiler/package-manager/commands.ts +4 -0
- package/src/forge/compiler/package-manager/executor.ts +30 -1
- package/src/forge/compiler/package-manager/version.ts +36 -12
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/policy-registry/build.ts +47 -2
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +63 -0
- package/src/forge/compiler/recipes/index.ts +2 -0
- package/src/forge/compiler/recipes/profiles.ts +81 -0
- package/src/forge/compiler/recipes/registry.ts +6 -0
- package/src/forge/compiler/test-graph/build.ts +11 -3
- package/src/forge/compiler/types/app-graph.ts +8 -2
- package/src/forge/compiler/types/cli.ts +76 -1
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/frontend-graph.ts +2 -2
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/delta/broker-runner.mjs +5 -0
- package/src/forge/delta/broker.ts +429 -0
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +67 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/process-identity.ts +37 -0
- package/src/forge/delta/queue-status.ts +190 -0
- package/src/forge/delta/recorder.ts +540 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +240 -0
- package/src/forge/delta/session.ts +142 -0
- package/src/forge/delta/status.ts +1139 -0
- package/src/forge/delta/store.ts +3329 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +1061 -22
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +285 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +222 -30
- package/src/forge/impact/types.ts +33 -1
- package/src/forge/intent/index.ts +35 -16
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +42 -6
- package/src/forge/make/templates.ts +211 -4
- package/src/forge/make/types.ts +2 -1
- package/src/forge/policy.ts +1 -1
- package/src/forge/react/index.ts +160 -2
- package/src/forge/refactor/index.ts +1 -0
- package/src/forge/repair/rules/index.ts +2 -2
- package/src/forge/repository-analysis/adapters/common.ts +72 -0
- package/src/forge/repository-analysis/adapters/docker.ts +185 -0
- package/src/forge/repository-analysis/adapters/java.ts +133 -0
- package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
- package/src/forge/repository-analysis/adapters/vue.ts +71 -0
- package/src/forge/repository-analysis/analyze.ts +434 -0
- package/src/forge/repository-analysis/benchmark.ts +336 -0
- package/src/forge/repository-analysis/cache-gc.ts +154 -0
- package/src/forge/repository-analysis/check-selection.ts +123 -0
- package/src/forge/repository-analysis/context-node.ts +17 -0
- package/src/forge/repository-analysis/context.ts +456 -0
- package/src/forge/repository-analysis/http-resolution.ts +135 -0
- package/src/forge/repository-analysis/identity.ts +45 -0
- package/src/forge/repository-analysis/index.ts +18 -0
- package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
- package/src/forge/repository-analysis/quality.ts +60 -0
- package/src/forge/repository-analysis/retrieval.ts +309 -0
- package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
- package/src/forge/repository-analysis/runtime-observation.ts +325 -0
- package/src/forge/repository-analysis/scanner.ts +174 -0
- package/src/forge/repository-analysis/storage.ts +193 -0
- package/src/forge/repository-analysis/test-associations.ts +59 -0
- package/src/forge/repository-analysis/types.ts +99 -0
- package/src/forge/repository-manifest/index.ts +178 -0
- package/src/forge/repository-manifest/types.ts +63 -0
- package/src/forge/review/index.ts +162 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/ai/providers.ts +19 -4
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +61 -6
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/runtime/db/factory.ts +1 -3
- package/src/forge/runtime/db/generated-client.ts +13 -2
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +285 -2
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +663 -0
- package/src/forge/runtime/runner/run-entry.ts +16 -7
- package/src/forge/runtime/telemetry/buffer.ts +4 -1
- package/src/forge/runtime/telemetry/scrubber.ts +35 -5
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +71 -7
- package/src/forge/ui/index.ts +831 -19
- package/src/forge/ui/types.ts +3 -1
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +422 -0
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +292 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +393 -0
- package/templates/agent-workroom/AGENTS.md +29 -0
- package/templates/agent-workroom/README.md +34 -0
- package/templates/agent-workroom/forge.config.ts +3 -0
- package/templates/agent-workroom/package.json +34 -0
- package/templates/agent-workroom/pnpm-workspace.yaml +2 -0
- package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
- package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
- package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
- package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
- package/templates/agent-workroom/src/forge/schema.ts +54 -0
- package/templates/agent-workroom/src/policies.ts +6 -0
- package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
- package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
- package/templates/agent-workroom/tsconfig.json +16 -0
- package/templates/agent-workroom/web/index.html +13 -0
- package/templates/agent-workroom/web/package.json +21 -0
- package/templates/agent-workroom/web/src/App.tsx +345 -0
- package/templates/agent-workroom/web/src/lib/forge.ts +20 -0
- package/templates/agent-workroom/web/src/main.tsx +13 -0
- package/templates/agent-workroom/web/src/styles.css +545 -0
- package/templates/agent-workroom/web/tsconfig.json +27 -0
- package/templates/agent-workroom/web/vite.config.ts +36 -0
- package/templates/b2b-support-web/package.json +2 -0
- package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
- package/templates/b2b-support-web/web/app/page.tsx +2 -2
- package/templates/b2b-support-web/web/lib/forge.ts +9 -2
- package/templates/b2b-support-web/web/next.config.ts +24 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +2 -1
- package/templates/minimal-web/pnpm-workspace.yaml +2 -0
- package/templates/minimal-web/web/index.html +1 -0
- package/templates/minimal-web/web/src/App.tsx +12 -6
- package/templates/minimal-web/web/src/lib/forge.ts +9 -2
- package/templates/minimal-web/web/src/styles.css +34 -0
- package/templates/minimal-web/web/vite.config.ts +36 -0
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/.yarnrc.yml +1 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +37 -0
- package/templates/nuxt-web/pnpm-workspace.yaml +2 -0
- package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/nuxt-web/src/commands/createNote.ts +26 -0
- package/templates/nuxt-web/src/forge/schema.ts +12 -0
- package/templates/nuxt-web/src/policies.ts +6 -0
- package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
- package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
- package/templates/nuxt-web/tsconfig.json +17 -0
- package/templates/nuxt-web/web/app.vue +67 -0
- package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
- package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
- package/templates/nuxt-web/web/composables/forge.ts +13 -0
- package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
- package/templates/nuxt-web/web/nuxt.config.ts +11 -0
- package/templates/nuxt-web/web/package.json +22 -0
- package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
- package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
- package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
- package/templates/nuxt-web/web/tsconfig.json +6 -0
- package/templates/vendor-access/.vscode/settings.json +14 -0
- package/templates/vendor-access/README.md +30 -0
- package/templates/vendor-access/forge.config.ts +3 -0
- package/templates/vendor-access/package.json +35 -0
- package/templates/vendor-access/pnpm-workspace.yaml +2 -0
- package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
- package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
- package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
- package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
- package/templates/vendor-access/src/forge/schema.ts +74 -0
- package/templates/vendor-access/src/policies.ts +11 -0
- package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/tsconfig.json +17 -0
- package/templates/vendor-access/web/index.html +13 -0
- package/templates/vendor-access/web/package.json +21 -0
- package/templates/vendor-access/web/src/App.tsx +573 -0
- package/templates/vendor-access/web/src/lib/forge.ts +20 -0
- package/templates/vendor-access/web/src/main.tsx +205 -0
- package/templates/vendor-access/web/src/styles.css +682 -0
- package/templates/vendor-access/web/tsconfig.json +18 -0
- package/templates/vendor-access/web/vite.config.ts +36 -0
- package/src/forge/_generated/actionSubscriptions.json +0 -2
- package/src/forge/_generated/actionSubscriptions.ts +0 -10
- package/src/forge/_generated/agentAdapterManifest.json +0 -2
- package/src/forge/_generated/agentAdapterManifest.ts +0 -73
- package/src/forge/_generated/agentContract.json +0 -2
- package/src/forge/_generated/agentContract.ts +0 -7829
- package/src/forge/_generated/agentQuickstart.md +0 -34
- package/src/forge/_generated/agentTools.json +0 -2
- package/src/forge/_generated/agentTools.md +0 -16
- package/src/forge/_generated/agentTools.ts +0 -12
- package/src/forge/_generated/aiContext.ts +0 -125
- package/src/forge/_generated/aiModels.json +0 -2
- package/src/forge/_generated/aiModels.ts +0 -51
- package/src/forge/_generated/aiProviders.json +0 -2
- package/src/forge/_generated/aiProviders.ts +0 -23
- package/src/forge/_generated/aiRegistry.json +0 -2
- package/src/forge/_generated/aiRegistry.ts +0 -31
- package/src/forge/_generated/api.json +0 -2
- package/src/forge/_generated/api.ts +0 -8
- package/src/forge/_generated/appGraph.json +0 -2
- package/src/forge/_generated/appGraph.ts +0 -14937
- package/src/forge/_generated/appMap.md +0 -55
- package/src/forge/_generated/artifactManifest.json +0 -2
- package/src/forge/_generated/artifactManifest.ts +0 -7
- package/src/forge/_generated/authClaims.json +0 -2
- package/src/forge/_generated/authClaims.ts +0 -13
- package/src/forge/_generated/authConfig.json +0 -2
- package/src/forge/_generated/authConfig.ts +0 -17
- package/src/forge/_generated/authContext.ts +0 -23
- package/src/forge/_generated/authRegistry.json +0 -2
- package/src/forge/_generated/authRegistry.ts +0 -25
- package/src/forge/_generated/buildInfo.json +0 -2
- package/src/forge/_generated/buildInfo.ts +0 -9
- package/src/forge/_generated/capabilityMap.json +0 -2
- package/src/forge/_generated/capabilityMap.md +0 -15
- package/src/forge/_generated/capabilityMap.ts +0 -17
- package/src/forge/_generated/client.ts +0 -282
- package/src/forge/_generated/clientApi.ts +0 -9
- package/src/forge/_generated/clientManifest.json +0 -2
- package/src/forge/_generated/clientManifest.ts +0 -39
- package/src/forge/_generated/clientTypes.ts +0 -78
- package/src/forge/_generated/configRegistry.json +0 -2
- package/src/forge/_generated/configRegistry.ts +0 -4
- package/src/forge/_generated/dataGraph.json +0 -2
- package/src/forge/_generated/dataGraph.ts +0 -8
- package/src/forge/_generated/db.json +0 -2
- package/src/forge/_generated/db.ts +0 -2
- package/src/forge/_generated/dbSecurityManifest.json +0 -2
- package/src/forge/_generated/dbSecurityManifest.ts +0 -15
- package/src/forge/_generated/dbSessionContext.json +0 -2
- package/src/forge/_generated/dbSessionContext.ts +0 -39
- package/src/forge/_generated/deployManifest.json +0 -2
- package/src/forge/_generated/deployManifest.ts +0 -14
- package/src/forge/_generated/devManifest.json +0 -2
- package/src/forge/_generated/devManifest.ts +0 -62
- package/src/forge/_generated/envSchema.json +0 -2
- package/src/forge/_generated/envSchema.ts +0 -59
- package/src/forge/_generated/frontendGraph.json +0 -2
- package/src/forge/_generated/frontendGraph.ts +0 -27
- package/src/forge/_generated/importGuards.json +0 -2
- package/src/forge/_generated/importGuards.ts +0 -686
- package/src/forge/_generated/index.ts +0 -68
- package/src/forge/_generated/liveProductionManifest.json +0 -2
- package/src/forge/_generated/liveProductionManifest.ts +0 -23
- package/src/forge/_generated/liveProtocol.json +0 -2
- package/src/forge/_generated/liveProtocol.ts +0 -21
- package/src/forge/_generated/liveQueryRegistry.json +0 -2
- package/src/forge/_generated/liveQueryRegistry.ts +0 -9
- package/src/forge/_generated/liveTransportConfig.json +0 -2
- package/src/forge/_generated/liveTransportConfig.ts +0 -19
- package/src/forge/_generated/makeRegistry.json +0 -2
- package/src/forge/_generated/makeRegistry.ts +0 -177
- package/src/forge/_generated/makeTemplates.json +0 -2
- package/src/forge/_generated/makeTemplates.ts +0 -66
- package/src/forge/_generated/mockMap.json +0 -2
- package/src/forge/_generated/mockMap.ts +0 -7
- package/src/forge/_generated/operationPlaybooks.md +0 -167
- package/src/forge/_generated/packageGraph.json +0 -2
- package/src/forge/_generated/packageGraph.ts +0 -249334
- package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
- package/src/forge/_generated/permissionMatrix.json +0 -2
- package/src/forge/_generated/permissionMatrix.ts +0 -7
- package/src/forge/_generated/policyRegistry.json +0 -2
- package/src/forge/_generated/policyRegistry.ts +0 -11
- package/src/forge/_generated/queryRegistry.json +0 -2
- package/src/forge/_generated/queryRegistry.ts +0 -9
- package/src/forge/_generated/react.d.ts +0 -22
- package/src/forge/_generated/react.ts +0 -29
- package/src/forge/_generated/reactManifest.json +0 -2
- package/src/forge/_generated/reactManifest.ts +0 -19
- package/src/forge/_generated/rlsPolicies.json +0 -2
- package/src/forge/_generated/rlsPolicies.sql +0 -34
- package/src/forge/_generated/rlsPolicies.ts +0 -6
- package/src/forge/_generated/runtimeGraph.json +0 -2
- package/src/forge/_generated/runtimeGraph.ts +0 -8
- package/src/forge/_generated/runtimeMatrix.json +0 -2
- package/src/forge/_generated/runtimeMatrix.ts +0 -334130
- package/src/forge/_generated/runtimeRegistry.ts +0 -2
- package/src/forge/_generated/runtimeRules.md +0 -91
- package/src/forge/_generated/secretRegistry.json +0 -2
- package/src/forge/_generated/secretRegistry.ts +0 -50
- package/src/forge/_generated/secretsContext.ts +0 -11
- package/src/forge/_generated/serverApi.ts +0 -10
- package/src/forge/_generated/sourceMapManifest.json +0 -2
- package/src/forge/_generated/sourceMapManifest.ts +0 -7
- package/src/forge/_generated/sqlPlan.json +0 -2
- package/src/forge/_generated/sqlPlan.ts +0 -88
- package/src/forge/_generated/subscriptionManifest.json +0 -2
- package/src/forge/_generated/subscriptionManifest.ts +0 -7
- package/src/forge/_generated/symbolicationManifest.json +0 -2
- package/src/forge/_generated/symbolicationManifest.ts +0 -17
- package/src/forge/_generated/telemetryRegistry.json +0 -2
- package/src/forge/_generated/telemetryRegistry.ts +0 -9
- package/src/forge/_generated/telemetrySinks.json +0 -2
- package/src/forge/_generated/telemetrySinks.ts +0 -11
- package/src/forge/_generated/tenantScope.json +0 -2
- package/src/forge/_generated/tenantScope.ts +0 -8
- package/src/forge/_generated/testGraph.json +0 -2
- package/src/forge/_generated/testGraph.ts +0 -3452
- package/src/forge/_generated/testPlanRegistry.json +0 -2
- package/src/forge/_generated/testPlanRegistry.ts +0 -33
- package/src/forge/_generated/uiRoutes.json +0 -2
- package/src/forge/_generated/uiRoutes.ts +0 -16
- package/src/forge/_generated/uiScenarios.json +0 -2
- package/src/forge/_generated/uiScenarios.ts +0 -30
- package/src/forge/_generated/uiTestManifest.json +0 -2
- package/src/forge/_generated/uiTestManifest.ts +0 -27
- package/src/forge/_generated/workflowRegistry.json +0 -2
- package/src/forge/_generated/workflowRegistry.ts +0 -9
- package/src/forge/_generated/workflowSubscriptions.json +0 -2
- package/src/forge/_generated/workflowSubscriptions.ts +0 -10
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AgentFabricError } from "./errors.ts";
|
|
3
|
+
import type { Digest, DigestFunction } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* P0a canonical JSON profile.
|
|
7
|
+
*
|
|
8
|
+
* This profile is intentionally explicit and deterministic for the current
|
|
9
|
+
* TypeScript/JavaScript reference implementation. Cross-language JCS
|
|
10
|
+
* conformance is a separate protocol profile and is not claimed here.
|
|
11
|
+
*/
|
|
12
|
+
export const P0A_CANONICALIZATION_PROFILE = "forge-canonical-json/v0.1" as const;
|
|
13
|
+
|
|
14
|
+
function canonicalizationFailure(message: string): never {
|
|
15
|
+
throw new AgentFabricError("AF_CANONICALIZATION_FAILED", message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function assertJsonObjectShape(value: object): void {
|
|
19
|
+
const prototype = Object.getPrototypeOf(value);
|
|
20
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
21
|
+
canonicalizationFailure("Canonical objects must have Object.prototype or null prototype");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
25
|
+
if (typeof key !== "string") {
|
|
26
|
+
canonicalizationFailure("Canonical objects cannot contain symbol properties");
|
|
27
|
+
}
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
29
|
+
if (!descriptor?.enumerable) {
|
|
30
|
+
canonicalizationFailure(`Canonical object property ${key} must be enumerable`);
|
|
31
|
+
}
|
|
32
|
+
if (!("value" in descriptor)) {
|
|
33
|
+
canonicalizationFailure(`Canonical object property ${key} cannot be an accessor`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertJsonArrayShape(value: readonly unknown[]): void {
|
|
39
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) {
|
|
40
|
+
canonicalizationFailure("Canonical arrays must have Array.prototype");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
44
|
+
const expectedIndexKeys = new Set(Array.from({ length: value.length }, (_, index) => String(index)));
|
|
45
|
+
|
|
46
|
+
for (const key of ownKeys) {
|
|
47
|
+
if (key === "length") continue;
|
|
48
|
+
if (typeof key !== "string" || !expectedIndexKeys.has(key)) {
|
|
49
|
+
canonicalizationFailure("Canonical arrays cannot contain extra or symbol properties");
|
|
50
|
+
}
|
|
51
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
52
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
53
|
+
canonicalizationFailure(`Canonical array index ${key} must be an enumerable data property`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
58
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) {
|
|
59
|
+
canonicalizationFailure("Canonical arrays cannot be sparse");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalize(value: unknown, seen: Set<object>): unknown {
|
|
65
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof value === "number") {
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
canonicalizationFailure("Canonical values cannot contain non-finite numbers");
|
|
72
|
+
}
|
|
73
|
+
return Object.is(value, -0) ? 0 : value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
77
|
+
canonicalizationFailure(`Unsupported canonical value type: ${typeof value}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === "bigint") {
|
|
81
|
+
canonicalizationFailure("BigInt values must be encoded as strings before canonicalization");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (seen.has(value)) canonicalizationFailure("Canonical values cannot be cyclic");
|
|
86
|
+
assertJsonArrayShape(value);
|
|
87
|
+
seen.add(value);
|
|
88
|
+
const normalized: unknown[] = [];
|
|
89
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
90
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
91
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
92
|
+
seen.delete(value);
|
|
93
|
+
canonicalizationFailure(`Canonical array index ${index} must be an own data property`);
|
|
94
|
+
}
|
|
95
|
+
normalized[index] = normalize(descriptor.value, seen);
|
|
96
|
+
}
|
|
97
|
+
seen.delete(value);
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof value === "object") {
|
|
102
|
+
if (seen.has(value)) canonicalizationFailure("Canonical values cannot be cyclic");
|
|
103
|
+
assertJsonObjectShape(value);
|
|
104
|
+
seen.add(value);
|
|
105
|
+
const record = value as Record<string, unknown>;
|
|
106
|
+
// A null-prototype accumulator is required so an own `__proto__` key is
|
|
107
|
+
// preserved as data rather than invoking Object.prototype's legacy setter.
|
|
108
|
+
const normalized = Object.create(null) as Record<string, unknown>;
|
|
109
|
+
for (const key of Object.keys(record).sort()) {
|
|
110
|
+
if (record[key] === undefined) {
|
|
111
|
+
canonicalizationFailure(`Canonical object property ${key} is undefined`);
|
|
112
|
+
}
|
|
113
|
+
normalized[key] = normalize(record[key], seen);
|
|
114
|
+
}
|
|
115
|
+
seen.delete(value);
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
canonicalizationFailure("Unsupported canonical value");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function stableStringify(value: unknown): string {
|
|
123
|
+
return JSON.stringify(normalize(value, new Set<object>()));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function sha256Digest(canonicalValue: string): Digest {
|
|
127
|
+
return `sha256:${createHash("sha256").update(canonicalValue).digest("hex")}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function digestCanonical(
|
|
131
|
+
value: unknown,
|
|
132
|
+
digest: DigestFunction,
|
|
133
|
+
): Digest {
|
|
134
|
+
return digest(stableStringify(value));
|
|
135
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams, type SpawnOptionsWithoutStdio } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { realpathSync, statSync } from "node:fs";
|
|
4
|
+
import type { Digest } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
7
|
+
const MAX_TIMEOUT_MS = 10 * 60_000;
|
|
8
|
+
const MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
9
|
+
const MAX_PROMPT_BYTES = 64 * 1024;
|
|
10
|
+
const MAX_FINDINGS = 100;
|
|
11
|
+
|
|
12
|
+
export interface CodexAdversarialFinding {
|
|
13
|
+
severity: "blocker" | "high" | "medium" | "low";
|
|
14
|
+
path: string;
|
|
15
|
+
line?: number;
|
|
16
|
+
title: string;
|
|
17
|
+
explanation: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CodexAdversarialReport {
|
|
21
|
+
requestDigest: Digest;
|
|
22
|
+
verdict: "pass" | "changes_requested" | "inconclusive";
|
|
23
|
+
summary: string;
|
|
24
|
+
findings: readonly CodexAdversarialFinding[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CodexReviewUsage {
|
|
28
|
+
inputTokens: number;
|
|
29
|
+
outputTokens: number;
|
|
30
|
+
cachedInputTokens?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CodexAdversarialReviewResult =
|
|
34
|
+
| { state: "reported"; report: CodexAdversarialReport; outputDigest: Digest; usage?: CodexReviewUsage }
|
|
35
|
+
| { state: "inconclusive"; error: string; report?: CodexAdversarialReport; outputDigest?: Digest; usage?: CodexReviewUsage };
|
|
36
|
+
|
|
37
|
+
export interface CodexAdversarialReviewOptions {
|
|
38
|
+
/** A prepared, exact review checkout. The caller must verify its Git identity and diff before and after this call. */
|
|
39
|
+
workspaceRoot: string;
|
|
40
|
+
/** Canonical digest of the review request and exact diff, supplied by the caller. */
|
|
41
|
+
requestDigest: Digest;
|
|
42
|
+
prompt: string;
|
|
43
|
+
timeoutMs: number;
|
|
44
|
+
maxOutputBytes: number;
|
|
45
|
+
executable?: string;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
/** Injection point for tests; production uses node:child_process.spawn. */
|
|
48
|
+
spawnProcess?: (command: string, args: string[], options: SpawnOptionsWithoutStdio) => ChildProcessWithoutNullStreams;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
52
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function exactKeys(value: Record<string, unknown>, required: readonly string[], optional: readonly string[] = []): boolean {
|
|
56
|
+
return required.every((key) => Object.hasOwn(value, key)) &&
|
|
57
|
+
Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nonEmptyText(value: unknown, maxLength: number): value is string {
|
|
61
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= maxLength && !value.includes("\u0000");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validFinding(value: unknown): value is CodexAdversarialFinding {
|
|
65
|
+
if (!isRecord(value) || !exactKeys(value, ["severity", "path", "title", "explanation"], ["line"])) return false;
|
|
66
|
+
if (!["blocker", "high", "medium", "low"].includes(String(value.severity))) return false;
|
|
67
|
+
if (!nonEmptyText(value.path, 512) || value.path.includes("\\") || value.path.startsWith("/") ||
|
|
68
|
+
/^[a-z]:/iu.test(value.path) || value.path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) return false;
|
|
69
|
+
if (!nonEmptyText(value.title, 500) || !nonEmptyText(value.explanation, 4_000)) return false;
|
|
70
|
+
return value.line === undefined || (Number.isSafeInteger(value.line) && Number(value.line) > 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseReport(text: string, requestDigest: Digest): CodexAdversarialReport | undefined {
|
|
74
|
+
let value: unknown;
|
|
75
|
+
try { value = JSON.parse(text); } catch { return undefined; }
|
|
76
|
+
if (!isRecord(value) || !exactKeys(value, ["requestDigest", "verdict", "summary", "findings"])) return undefined;
|
|
77
|
+
if (value.requestDigest !== requestDigest || !["pass", "changes_requested", "inconclusive"].includes(String(value.verdict)) ||
|
|
78
|
+
!nonEmptyText(value.summary, 4_000) || !Array.isArray(value.findings) ||
|
|
79
|
+
value.findings.length > MAX_FINDINGS || !value.findings.every(validFinding)) return undefined;
|
|
80
|
+
if ((value.verdict === "pass" && value.findings.length !== 0) ||
|
|
81
|
+
(value.verdict === "changes_requested" && value.findings.length === 0)) return undefined;
|
|
82
|
+
return value as unknown as CodexAdversarialReport;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseUsage(value: unknown): CodexReviewUsage | undefined {
|
|
86
|
+
if (!isRecord(value)) return undefined;
|
|
87
|
+
const input = value.input_tokens;
|
|
88
|
+
const output = value.output_tokens;
|
|
89
|
+
const cached = value.cached_input_tokens;
|
|
90
|
+
if (!Number.isSafeInteger(input) || Number(input) < 0 ||
|
|
91
|
+
!Number.isSafeInteger(output) || Number(output) < 0) return undefined;
|
|
92
|
+
return {
|
|
93
|
+
inputTokens: Number(input), outputTokens: Number(output),
|
|
94
|
+
...(Number.isSafeInteger(cached) && Number(cached) >= 0 ? { cachedInputTokens: Number(cached) } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseCodexEvents(stdout: string, requestDigest: Digest):
|
|
99
|
+
{ report?: CodexAdversarialReport; usage?: CodexReviewUsage; error?: string } {
|
|
100
|
+
let finalText: string | undefined;
|
|
101
|
+
let usage: CodexReviewUsage | undefined;
|
|
102
|
+
let completed = false;
|
|
103
|
+
let failure = false;
|
|
104
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
105
|
+
if (!line) continue;
|
|
106
|
+
let event: unknown;
|
|
107
|
+
try { event = JSON.parse(line); } catch { return { error: "invalid_jsonl" }; }
|
|
108
|
+
if (!isRecord(event) || typeof event.type !== "string") return { error: "invalid_event" };
|
|
109
|
+
if (event.type === "item.completed" && isRecord(event.item) && event.item.type === "agent_message") {
|
|
110
|
+
if (typeof event.item.text !== "string") return { error: "invalid_agent_message" };
|
|
111
|
+
finalText = event.item.text;
|
|
112
|
+
}
|
|
113
|
+
if (event.type === "turn.completed") {
|
|
114
|
+
completed = true;
|
|
115
|
+
usage = parseUsage(event.usage);
|
|
116
|
+
}
|
|
117
|
+
if (event.type === "turn.failed" || event.type === "error") failure = true;
|
|
118
|
+
}
|
|
119
|
+
if (failure) return { error: "codex_turn_failed", usage };
|
|
120
|
+
if (!completed) return { error: "missing_completed_turn", usage };
|
|
121
|
+
if (finalText === undefined) return { error: "missing_agent_report", usage };
|
|
122
|
+
const report = parseReport(finalText, requestDigest);
|
|
123
|
+
if (!report) return { error: "invalid_agent_report", usage };
|
|
124
|
+
return { report, usage };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function digestOutput(stdout: Buffer, stderr: Buffer): Digest {
|
|
128
|
+
const hash = createHash("sha256");
|
|
129
|
+
hash.update(stdout);
|
|
130
|
+
hash.update(stderr);
|
|
131
|
+
return `sha256:${hash.digest("hex")}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Codex's read-only sandbox is defense in depth; the caller owns checkout isolation and exact-diff verification. */
|
|
135
|
+
export async function runCodexAdversarialReview(options: CodexAdversarialReviewOptions): Promise<CodexAdversarialReviewResult> {
|
|
136
|
+
if (!DIGEST_PATTERN.test(options.requestDigest) || !nonEmptyText(options.prompt, MAX_PROMPT_BYTES) ||
|
|
137
|
+
Buffer.byteLength(options.prompt, "utf8") > MAX_PROMPT_BYTES ||
|
|
138
|
+
!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > MAX_TIMEOUT_MS ||
|
|
139
|
+
!Number.isSafeInteger(options.maxOutputBytes) || options.maxOutputBytes < 1 || options.maxOutputBytes > MAX_OUTPUT_BYTES) {
|
|
140
|
+
throw new TypeError("Invalid Codex review bounds or request");
|
|
141
|
+
}
|
|
142
|
+
const workspaceRoot = realpathSync(options.workspaceRoot);
|
|
143
|
+
if (!statSync(workspaceRoot).isDirectory()) throw new TypeError("Codex review workspace must be a directory");
|
|
144
|
+
if (options.signal?.aborted) return { state: "inconclusive", error: "cancelled" };
|
|
145
|
+
|
|
146
|
+
const args = ["--sandbox", "read-only", "--ask-for-approval", "never", "exec", "review",
|
|
147
|
+
"--uncommitted", "--json", "--ephemeral", "-"];
|
|
148
|
+
const prompt = `${options.prompt.trimEnd()}\n\nReturn ONLY a JSON object as the final answer with exactly these keys: ` +
|
|
149
|
+
`requestDigest, verdict, summary, findings. Set requestDigest to ${options.requestDigest}. ` +
|
|
150
|
+
`verdict is pass, changes_requested, or inconclusive. Each finding has severity ` +
|
|
151
|
+
`(blocker, high, medium, low), repository-relative path, optional positive line, title, and explanation. ` +
|
|
152
|
+
`Use an empty findings array only for pass or inconclusive. Do not use Markdown fences.\n`;
|
|
153
|
+
|
|
154
|
+
return await new Promise((resolve) => {
|
|
155
|
+
let child: ChildProcessWithoutNullStreams;
|
|
156
|
+
try {
|
|
157
|
+
child = (options.spawnProcess ?? spawn)(options.executable ?? "codex", args, {
|
|
158
|
+
cwd: workspaceRoot, shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"],
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
resolve({ state: "inconclusive", error: "spawn_failed" });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const stdout: Buffer[] = [];
|
|
166
|
+
const stderr: Buffer[] = [];
|
|
167
|
+
let captured = 0;
|
|
168
|
+
let reason: string | undefined;
|
|
169
|
+
let settled = false;
|
|
170
|
+
let hardTimer: ReturnType<typeof setTimeout> | undefined;
|
|
171
|
+
const finish = (code: number | null) => {
|
|
172
|
+
if (settled) return;
|
|
173
|
+
settled = true;
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
if (hardTimer) clearTimeout(hardTimer);
|
|
176
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
177
|
+
const out = Buffer.concat(stdout);
|
|
178
|
+
const err = Buffer.concat(stderr);
|
|
179
|
+
const outputDigest = digestOutput(out, err);
|
|
180
|
+
if (reason) {
|
|
181
|
+
resolve({ state: "inconclusive", error: reason, outputDigest });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (code !== 0) {
|
|
185
|
+
resolve({ state: "inconclusive", error: "codex_nonzero_exit", outputDigest });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const parsed = parseCodexEvents(out.toString("utf8"), options.requestDigest);
|
|
189
|
+
if (!parsed.report) {
|
|
190
|
+
resolve({ state: "inconclusive", error: parsed.error ?? "invalid_agent_report", outputDigest,
|
|
191
|
+
...(parsed.usage ? { usage: parsed.usage } : {}) });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (parsed.report.verdict === "inconclusive") {
|
|
195
|
+
resolve({ state: "inconclusive", error: "reviewer_inconclusive", report: parsed.report, outputDigest,
|
|
196
|
+
...(parsed.usage ? { usage: parsed.usage } : {}) });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
resolve({ state: "reported", report: parsed.report, outputDigest,
|
|
200
|
+
...(parsed.usage ? { usage: parsed.usage } : {}) });
|
|
201
|
+
};
|
|
202
|
+
const stop = (why: string) => {
|
|
203
|
+
if (reason || settled) return;
|
|
204
|
+
reason = why;
|
|
205
|
+
child.kill();
|
|
206
|
+
hardTimer = setTimeout(() => finish(null), 2_000);
|
|
207
|
+
};
|
|
208
|
+
const capture = (chunk: Buffer, target: Buffer[]) => {
|
|
209
|
+
if (reason || settled) return;
|
|
210
|
+
const remaining = options.maxOutputBytes - captured;
|
|
211
|
+
if (remaining > 0) {
|
|
212
|
+
const part = chunk.subarray(0, remaining);
|
|
213
|
+
target.push(part);
|
|
214
|
+
captured += part.length;
|
|
215
|
+
}
|
|
216
|
+
if (chunk.length > remaining) stop("output_limit");
|
|
217
|
+
};
|
|
218
|
+
const cancel = () => stop("cancelled");
|
|
219
|
+
child.stdout.on("data", (chunk: Buffer) => capture(chunk, stdout));
|
|
220
|
+
child.stderr.on("data", (chunk: Buffer) => capture(chunk, stderr));
|
|
221
|
+
child.on("error", () => stop("spawn_failed"));
|
|
222
|
+
child.on("close", finish);
|
|
223
|
+
const timer = setTimeout(() => stop("timeout"), options.timeoutMs);
|
|
224
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
225
|
+
if (options.signal?.aborted) cancel();
|
|
226
|
+
child.stdin.on("error", () => stop("stdin_failed"));
|
|
227
|
+
try { child.stdin.end(prompt); } catch { stop("stdin_failed"); }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Codex, type CodexOptions, type ThreadOptions, type TurnOptions, type ThreadEvent } from "@openai/codex-sdk";
|
|
2
|
+
import { realpath, stat, readFile } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
export interface CodexWorkerUsage { input_tokens: number; cached_input_tokens: number; output_tokens: number }
|
|
12
|
+
export interface CodexWorkerEvent { type: string; threadId?: string; summary?: string; usage?: CodexWorkerUsage }
|
|
13
|
+
export interface CodexWorkerInput {
|
|
14
|
+
cwd: string; prompt: string; role: "implementer" | "reviewer" | "investigator" | "decision";
|
|
15
|
+
model?: string; threadId?: string; signal: AbortSignal;
|
|
16
|
+
onEvent: (event: CodexWorkerEvent) => Promise<void> | void;
|
|
17
|
+
}
|
|
18
|
+
export interface CodexWorkerOutput {
|
|
19
|
+
threadId: string;
|
|
20
|
+
report: { summary: string; verdict?: "approved" | "changes_requested"; findings?: { description: string }[]; selectedNodeIds?: string[]; replanProposal?: string };
|
|
21
|
+
usage?: CodexWorkerUsage; eventsObserved: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CodexWorkerThread {
|
|
24
|
+
readonly id: string | null;
|
|
25
|
+
runStreamed(prompt: string, options: TurnOptions): Promise<{ events: AsyncIterable<ThreadEvent> }>;
|
|
26
|
+
}
|
|
27
|
+
export interface CodexWorkerClient {
|
|
28
|
+
startThread(options: ThreadOptions): CodexWorkerThread;
|
|
29
|
+
resumeThread(id: string, options: ThreadOptions): CodexWorkerThread;
|
|
30
|
+
}
|
|
31
|
+
export type CodexWorkerFactory = (options: CodexOptions) => CodexWorkerClient | Promise<CodexWorkerClient>;
|
|
32
|
+
export class CodexWorkerError extends Error {
|
|
33
|
+
constructor(readonly code: string, message: string) { super(message); this.name = "CodexWorkerError"; }
|
|
34
|
+
}
|
|
35
|
+
function fail(code: string, message: string): never { throw new CodexWorkerError(code, message); }
|
|
36
|
+
function text(value: unknown, maximum: number): value is string { return typeof value === "string" && value.trim().length > 0 && value.length <= maximum && !value.includes("\0"); }
|
|
37
|
+
function checkAbort(signal: AbortSignal): void { if (signal.aborted) fail("AF_CODEX_ABORTED", "Codex worker was cancelled; its outcome requires reconciliation"); }
|
|
38
|
+
|
|
39
|
+
/** Restrict inherited environment while preserving local ChatGPT authentication. */
|
|
40
|
+
export function codexWorkerEnvironment(source: NodeJS.ProcessEnv = process.env): Record<string, string> {
|
|
41
|
+
const allowed = new Set(["path", "systemroot", "windir", "comspec", "pathext", "temp", "tmp", "tmpdir", "home", "userprofile", "homedrive", "homepath", "appdata", "localappdata", "codex_home", "lang", "lc_all"]);
|
|
42
|
+
return Object.fromEntries(Object.entries(source).filter(([key, value]) => allowed.has(key.toLowerCase()) && typeof value === "string")) as Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Disable each effective inherited server and verify the resulting CLI configuration. */
|
|
46
|
+
export async function codexWorkerMcpIsolation(list: (overrides: string[]) => Promise<unknown>): Promise<string[]> {
|
|
47
|
+
const servers = await list([]);
|
|
48
|
+
if (!Array.isArray(servers) || servers.length > 256) fail("AF_CODEX_ISOLATION", "Cannot inspect effective Codex MCP configuration");
|
|
49
|
+
const overrides = servers.flatMap((server) => {
|
|
50
|
+
if (!server || typeof server !== "object" || !text(server.name, 128) || !/^[A-Za-z0-9_-]+$/u.test(server.name)) fail("AF_CODEX_ISOLATION", "Invalid effective Codex MCP server name");
|
|
51
|
+
// Plugin-provided servers may have no root-table transport to merge with.
|
|
52
|
+
// Supply an inert valid transport and explicitly disable it.
|
|
53
|
+
return [`mcp_servers.${server.name}.enabled=false`, `mcp_servers.${server.name}.command=${JSON.stringify(process.execPath)}`, `mcp_servers.${server.name}.args=[]`];
|
|
54
|
+
});
|
|
55
|
+
const observed = await list(overrides);
|
|
56
|
+
if (!Array.isArray(observed) || observed.length > 256 || observed.some((server) => !server || typeof server !== "object" || server.enabled !== false)) fail("AF_CODEX_ISOLATION", "Inherited Codex MCP servers remain enabled");
|
|
57
|
+
return overrides;
|
|
58
|
+
}
|
|
59
|
+
async function bundledCodexBinary(): Promise<string> {
|
|
60
|
+
const tripleByPlatform: Record<string, string> = { "win32:x64": "x86_64-pc-windows-msvc", "win32:arm64": "aarch64-pc-windows-msvc", "linux:x64": "x86_64-unknown-linux-musl", "linux:arm64": "aarch64-unknown-linux-musl", "darwin:x64": "x86_64-apple-darwin", "darwin:arm64": "aarch64-apple-darwin" };
|
|
61
|
+
const packageByPlatform: Record<string, string> = { "win32:x64": "@openai/codex-win32-x64", "win32:arm64": "@openai/codex-win32-arm64", "linux:x64": "@openai/codex-linux-x64", "linux:arm64": "@openai/codex-linux-arm64", "darwin:x64": "@openai/codex-darwin-x64", "darwin:arm64": "@openai/codex-darwin-arm64" };
|
|
62
|
+
const platform = `${process.platform}:${process.arch}`, triple = tripleByPlatform[platform], packageName = packageByPlatform[platform];
|
|
63
|
+
if (!triple || !packageName) fail("AF_CODEX_ISOLATION", "Unsupported Codex platform");
|
|
64
|
+
const require = createRequire(import.meta.resolve("@openai/codex-sdk")), codexPackage = require.resolve("@openai/codex/package.json");
|
|
65
|
+
const platformPackage = createRequire(codexPackage).resolve(`${packageName}/package.json`);
|
|
66
|
+
const root = join(dirname(platformPackage), "vendor", triple), binary = process.platform === "win32" ? "codex.exe" : "codex";
|
|
67
|
+
for (const path of [join(root, "bin", binary), join(root, "codex", binary)]) if (await stat(path).then((item) => item.isFile(), () => false)) return path;
|
|
68
|
+
fail("AF_CODEX_ISOLATION", "Bundled Codex executable is unavailable");
|
|
69
|
+
}
|
|
70
|
+
/** Hash observed local SDK/CLI/config inputs; persist hashes, never configuration/auth contents. */
|
|
71
|
+
export async function codexWorkerRuntimeIdentity(cwd: string): Promise<string> {
|
|
72
|
+
const digest = createHash("sha256"), binary = await bundledCodexBinary();
|
|
73
|
+
digest.update(binary); digest.update(await readFile(binary)); digest.update(await readFile(fileURLToPath(import.meta.resolve("@openai/codex-sdk"))));
|
|
74
|
+
const paths = [join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "config.toml"), join(cwd, ".codex/config.toml")];
|
|
75
|
+
for (const [index, path] of paths.entries()) { digest.update(index === 0 ? path : "project-config"); try { digest.update(await readFile(path)); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; digest.update("absent"); } }
|
|
76
|
+
return `sha256:${digest.digest("hex")}`;
|
|
77
|
+
}
|
|
78
|
+
const exec = promisify(execFile);
|
|
79
|
+
export async function codexWorkerIsolationOverrides(cwd: string, signal: AbortSignal): Promise<string[]> {
|
|
80
|
+
const executable = await bundledCodexBinary();
|
|
81
|
+
const overrides = await codexWorkerMcpIsolation(async (overrides) => {
|
|
82
|
+
const args = ["--cd", cwd, ...overrides.flatMap((override) => ["--config", override]), "mcp", "list", "--json"];
|
|
83
|
+
const output = await exec(executable, args, { cwd, env: codexWorkerEnvironment(), signal, timeout: 10000, maxBuffer: 1024 * 1024, windowsHide: true, encoding: "utf8" });
|
|
84
|
+
return JSON.parse(output.stdout);
|
|
85
|
+
});
|
|
86
|
+
return overrides;
|
|
87
|
+
}
|
|
88
|
+
async function isolatedCodexClient(options: CodexOptions, cwd: string, signal: AbortSignal): Promise<CodexWorkerClient> {
|
|
89
|
+
return new Codex({ ...options, configOverrides: await codexWorkerIsolationOverrides(cwd, signal) });
|
|
90
|
+
}
|
|
91
|
+
const reportSchema = {
|
|
92
|
+
type: "object", additionalProperties: false,
|
|
93
|
+
required: ["summary", "verdict", "findings", "selectedNodeIds", "replanProposal"],
|
|
94
|
+
properties: {
|
|
95
|
+
summary: { type: "string", minLength: 1, maxLength: 4096 },
|
|
96
|
+
verdict: { anyOf: [{ type: "string", enum: ["approved", "changes_requested"] }, { type: "null" }] },
|
|
97
|
+
findings: { type: "array", maxItems: 128, items: { type: "object", additionalProperties: false, required: ["description"], properties: { description: { type: "string", minLength: 1, maxLength: 4096 } } } },
|
|
98
|
+
selectedNodeIds: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
99
|
+
replanProposal: { anyOf: [{ type: "string", minLength: 1, maxLength: 4096 }, { type: "null" }] },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
function validateReport(raw: string, role: CodexWorkerInput["role"]): CodexWorkerOutput["report"] {
|
|
103
|
+
if (Buffer.byteLength(raw) > 64 * 1024) fail("AF_CODEX_REPORT", "Codex report exceeds its size limit");
|
|
104
|
+
let data: unknown;
|
|
105
|
+
try { data = JSON.parse(raw); } catch { fail("AF_CODEX_REPORT", "Codex did not return a JSON report"); }
|
|
106
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) fail("AF_CODEX_REPORT", "Invalid Codex report object");
|
|
107
|
+
const report = data as Record<string, unknown>;
|
|
108
|
+
const keys = Object.keys(report);
|
|
109
|
+
if (keys.length !== 5 || keys.some((key) => !Object.keys(reportSchema.properties).includes(key)) || !text(report.summary, 4096)) fail("AF_CODEX_REPORT", "Invalid Codex report fields");
|
|
110
|
+
if (report.verdict !== null && report.verdict !== "approved" && report.verdict !== "changes_requested") fail("AF_CODEX_REPORT", "Invalid Codex review verdict");
|
|
111
|
+
if (!Array.isArray(report.findings) || report.findings.length > 128 || report.findings.some((finding) => !finding || typeof finding !== "object" || Array.isArray(finding) || Object.keys(finding).length !== 1 || !text(finding.description, 4096))) fail("AF_CODEX_REPORT", "Invalid Codex findings");
|
|
112
|
+
if (!Array.isArray(report.selectedNodeIds) || report.selectedNodeIds.length > 128 || report.selectedNodeIds.some((id) => !text(id, 128) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/u.test(id)) || new Set(report.selectedNodeIds).size !== report.selectedNodeIds.length) fail("AF_CODEX_REPORT", "Invalid Codex branch selection");
|
|
113
|
+
if (report.replanProposal !== null && !text(report.replanProposal, 4096)) fail("AF_CODEX_REPORT", "Invalid Codex replan proposal");
|
|
114
|
+
if (role === "reviewer" && report.verdict === null) fail("AF_CODEX_REPORT", "Reviewer report requires an explicit verdict");
|
|
115
|
+
if (report.verdict === "approved" && report.findings.length > 0) fail("AF_CODEX_REPORT", "Approved review cannot contain unresolved findings");
|
|
116
|
+
return { summary: report.summary, ...(report.verdict !== null ? { verdict: report.verdict as "approved" | "changes_requested" } : {}), findings: report.findings as { description: string }[], selectedNodeIds: report.selectedNodeIds as string[], ...(report.replanProposal !== null ? { replanProposal: report.replanProposal as string } : {}) };
|
|
117
|
+
}
|
|
118
|
+
function usage(value: unknown): CodexWorkerUsage {
|
|
119
|
+
if (!value || typeof value !== "object") fail("AF_CODEX_EVENT", "Invalid Codex usage");
|
|
120
|
+
const report = value as Record<string, unknown>;
|
|
121
|
+
for (const key of ["input_tokens", "cached_input_tokens", "output_tokens"]) if (!Number.isSafeInteger(report[key]) || (report[key] as number) < 0) fail("AF_CODEX_EVENT", "Invalid Codex usage counters");
|
|
122
|
+
return { input_tokens: report.input_tokens as number, cached_input_tokens: report.cached_input_tokens as number, output_tokens: report.output_tokens as number };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Real SDK streaming with explicit completion; callbacks contain metadata only. */
|
|
126
|
+
async function runCodexWorkerInternal(input: CodexWorkerInput, factory: CodexWorkerFactory): Promise<CodexWorkerOutput> {
|
|
127
|
+
if (!input || !["implementer", "reviewer", "investigator", "decision"].includes(input.role) || !text(input.prompt, 128 * 1024) || !text(input.cwd, 4096) || typeof input.onEvent !== "function" || !input.signal || typeof input.signal.aborted !== "boolean") fail("AF_CODEX_INPUT", "Invalid Codex worker input");
|
|
128
|
+
if (input.model !== undefined && !text(input.model, 128)) fail("AF_CODEX_INPUT", "Invalid Codex model");
|
|
129
|
+
if (input.threadId !== undefined && (!text(input.threadId, 128) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/u.test(input.threadId))) fail("AF_CODEX_INPUT", "Invalid Codex thread ID");
|
|
130
|
+
checkAbort(input.signal);
|
|
131
|
+
const cwd = await realpath(input.cwd);
|
|
132
|
+
if (!(await stat(cwd)).isDirectory()) fail("AF_CODEX_INPUT", "Codex working directory must be a directory");
|
|
133
|
+
const client = await factory({ env: codexWorkerEnvironment(), config: { mcp_servers: {}, web_search: "disabled", approval_policy: "never", sandbox_workspace_write: { network_access: false }, shell_environment_policy: { inherit: "none", set: {} } } });
|
|
134
|
+
const options: ThreadOptions = { workingDirectory: cwd, sandboxMode: input.role === "implementer" ? "workspace-write" : "read-only", networkAccessEnabled: false, webSearchMode: "disabled", approvalPolicy: "never", ...(input.model ? { model: input.model } : {}) };
|
|
135
|
+
const thread = input.threadId ? client.resumeThread(input.threadId, options) : client.startThread(options);
|
|
136
|
+
let threadId = input.threadId ?? thread.id ?? "", finalMessage: string | undefined, completed = false, eventsObserved = 0, reportedUsage: CodexWorkerUsage | undefined;
|
|
137
|
+
const stream = await thread.runStreamed(`${input.prompt}\n\nReturn the structured report requested by the output schema. Role: ${input.role}. Never include credentials in reports. Use null for inapplicable verdict/replanProposal, and empty arrays for absent findings/branch selection.`, { outputSchema: reportSchema, signal: input.signal });
|
|
138
|
+
for await (const event of stream.events) {
|
|
139
|
+
checkAbort(input.signal);
|
|
140
|
+
if (++eventsObserved > 10000) fail("AF_CODEX_EVENT_LIMIT", "Codex worker event limit exceeded; reconcile its thread");
|
|
141
|
+
if (!["thread.started", "turn.started", "item.completed", "item.started", "item.updated", "turn.completed", "turn.failed", "error"].includes(event.type)) fail("AF_CODEX_EVENT", "Unknown Codex event type");
|
|
142
|
+
let notification: CodexWorkerEvent = { type: event.type };
|
|
143
|
+
if (event.type === "thread.started") {
|
|
144
|
+
if (!text(event.thread_id, 128) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/u.test(event.thread_id) || (threadId && threadId !== event.thread_id)) fail("AF_CODEX_EVENT", "Codex thread identity changed");
|
|
145
|
+
threadId = event.thread_id; notification = { type: event.type, threadId };
|
|
146
|
+
} else if (event.type === "item.completed" || event.type === "item.started" || event.type === "item.updated") {
|
|
147
|
+
if (!["command_execution", "file_change", "mcp_tool_call", "agent_message", "reasoning", "web_search", "error", "todo_list"].includes(event.item.type)) fail("AF_CODEX_EVENT", "Unknown Codex item type");
|
|
148
|
+
notification.summary = `Codex ${event.item.type}`;
|
|
149
|
+
if (event.type === "item.completed" && event.item.type === "agent_message") {
|
|
150
|
+
if (completed || !text(event.item.text, 64 * 1024)) fail("AF_CODEX_REPORT", "Invalid Codex response ordering or size");
|
|
151
|
+
finalMessage = event.item.text;
|
|
152
|
+
}
|
|
153
|
+
} else if (event.type === "turn.completed") {
|
|
154
|
+
if (completed) fail("AF_CODEX_EVENT", "Duplicate Codex completion");
|
|
155
|
+
completed = true; reportedUsage = usage(event.usage); notification.usage = reportedUsage;
|
|
156
|
+
} else if (event.type === "turn.failed" || event.type === "error") {
|
|
157
|
+
await input.onEvent({ type: event.type, ...(threadId ? { threadId } : {}), summary: "Codex execution failed; reconcile before retrying" });
|
|
158
|
+
fail("AF_CODEX_TURN_FAILED", "Codex execution failed; reconcile before retrying");
|
|
159
|
+
}
|
|
160
|
+
await input.onEvent(notification);
|
|
161
|
+
}
|
|
162
|
+
checkAbort(input.signal);
|
|
163
|
+
if (!completed || !threadId || finalMessage === undefined) fail("AF_CODEX_INCOMPLETE", "Codex stream ended without a completed report; reconcile its thread");
|
|
164
|
+
return { threadId, report: validateReport(finalMessage, input.role), ...(reportedUsage ? { usage: reportedUsage } : {}), eventsObserved };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function runCodexWorker(input: CodexWorkerInput, factory?: CodexWorkerFactory): Promise<CodexWorkerOutput> {
|
|
168
|
+
if (process.env.FORGE_FABRIC_TEST_MODE === "1" && !factory) fail("AF_CODEX_TEST_MODE", "Real LLM calls forbidden; provide a simulated SDK factory");
|
|
169
|
+
try { return await runCodexWorkerInternal(input, factory ?? ((options) => isolatedCodexClient(options, input.cwd, input.signal))); }
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error instanceof CodexWorkerError) throw error;
|
|
172
|
+
if (input?.signal?.aborted) fail("AF_CODEX_ABORTED", "Codex worker was cancelled; its outcome requires reconciliation");
|
|
173
|
+
fail("AF_CODEX_EXECUTION", "Codex SDK or event persistence failed; reconcile its thread before retrying");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** V2 keeps the operational envelope outside the activity's owner-registered schema. */
|
|
178
|
+
async function runTypedCodexWorkerInternal(input: CodexWorkerInput & { outputSchema: unknown; validateOutput: (data: unknown) => void }, factory?: CodexWorkerFactory): Promise<{ threadId: string; data: unknown; usage?: CodexWorkerUsage }> {
|
|
179
|
+
if (!input || !text(input.prompt, 128 * 1024) || !text(input.cwd, 4096) || !["implementer", "reviewer", "investigator", "decision"].includes(input.role) || typeof input.validateOutput !== "function") fail("AF_CODEX_INPUT", "Invalid typed worker input");
|
|
180
|
+
checkAbort(input.signal); const cwd = await realpath(input.cwd);
|
|
181
|
+
const options: CodexOptions = { env: codexWorkerEnvironment(), config: { mcp_servers: {}, web_search: "disabled", approval_policy: "never", sandbox_workspace_write: { network_access: false }, shell_environment_policy: { inherit: "none", set: {} } } };
|
|
182
|
+
const client = await (factory ?? ((configuration) => isolatedCodexClient(configuration, cwd, input.signal)))(options);
|
|
183
|
+
const threadOptions: ThreadOptions = { workingDirectory: cwd, sandboxMode: input.role === "implementer" ? "workspace-write" : "read-only", networkAccessEnabled: false, webSearchMode: "disabled", approvalPolicy: "never", ...(input.model ? { model: input.model } : {}) };
|
|
184
|
+
const thread = input.threadId ? client.resumeThread(input.threadId, threadOptions) : client.startThread(threadOptions);
|
|
185
|
+
let threadId = input.threadId ?? thread.id ?? "", finalMessage: string | undefined, completed = false, count = 0, observedUsage: CodexWorkerUsage | undefined;
|
|
186
|
+
const stream = await thread.runStreamed(`${input.prompt}\nReturn only data matching the requested output schema. Never include credentials.`, { outputSchema: input.outputSchema, signal: input.signal });
|
|
187
|
+
for await (const event of stream.events) {
|
|
188
|
+
checkAbort(input.signal); if (++count > 10000) fail("AF_CODEX_EVENT_LIMIT", "Typed worker event budget exceeded");
|
|
189
|
+
if (!["thread.started", "turn.started", "item.completed", "item.started", "item.updated", "turn.completed", "turn.failed", "error"].includes(event.type)) fail("AF_CODEX_EVENT", "Unknown typed event");
|
|
190
|
+
if (event.type === "thread.started") { if (!text(event.thread_id, 128) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/u.test(event.thread_id) || threadId && threadId !== event.thread_id) fail("AF_CODEX_EVENT", "Thread identity changed"); threadId = event.thread_id; await input.onEvent({ type: event.type, threadId }); }
|
|
191
|
+
else if (event.type === "item.completed" && event.item.type === "agent_message") { if (completed || !text(event.item.text, 4 * 1024 * 1024)) fail("AF_CODEX_REPORT", "Invalid typed message"); finalMessage = event.item.text; }
|
|
192
|
+
else if (event.type === "turn.completed") { if (completed) fail("AF_CODEX_EVENT", "Duplicate completion"); completed = true; observedUsage = usage(event.usage); await input.onEvent({ type: event.type, usage: observedUsage }); }
|
|
193
|
+
else if (event.type === "turn.failed" || event.type === "error") fail("AF_CODEX_TURN_FAILED", "Typed worker failed; reconcile before retry");
|
|
194
|
+
}
|
|
195
|
+
checkAbort(input.signal); if (!completed || !threadId || !finalMessage) fail("AF_CODEX_INCOMPLETE", "Typed worker completion not observed");
|
|
196
|
+
let data: unknown; try { data = JSON.parse(finalMessage); input.validateOutput(data); } catch { fail("AF_CODEX_REPORT", "Typed worker output schema invalid"); }
|
|
197
|
+
return { threadId, data, ...(observedUsage ? { usage: observedUsage } : {}) };
|
|
198
|
+
}
|
|
199
|
+
export async function runTypedCodexWorker(input: CodexWorkerInput & { outputSchema: unknown; validateOutput: (data: unknown) => void }, factory?: CodexWorkerFactory): Promise<{ threadId: string; data: unknown; usage?: CodexWorkerUsage }> {
|
|
200
|
+
if (process.env.FORGE_FABRIC_TEST_MODE === "1" && !factory) fail("AF_CODEX_TEST_MODE", "Real LLM calls forbidden; provide a simulated SDK factory");
|
|
201
|
+
try { return await runTypedCodexWorkerInternal(input, factory); }
|
|
202
|
+
catch (error) { if (error instanceof CodexWorkerError) throw error; fail("AF_CODEX_EXECUTION", "Typed SDK or persistence failed; reconcile before retry"); }
|
|
203
|
+
}
|