forgeos 0.1.0-alpha.4 → 0.1.0-alpha.40
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/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +418 -0
- package/LICENSE +21 -0
- package/README.md +67 -8
- 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 +34 -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 +36 -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/cair-protocol.md +103 -0
- package/docs/changelog.md +534 -0
- package/docs/forge-protocol.md +189 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +52 -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 +31 -3
- package/schemas/forge-manifest.schema.json +57 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1584 -125
- package/src/forge/agent-adapters/types.ts +224 -1
- package/src/forge/agent-memory/bridge.ts +1333 -0
- package/src/forge/agent-memory/context-pack.ts +277 -0
- package/src/forge/agent-memory/hook-runner.ts +312 -0
- package/src/forge/agent-memory/mcp.ts +224 -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 +273 -0
- package/src/forge/agent-memory/sources/codex.ts +119 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +204 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +801 -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 +106 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +248 -0
- package/src/forge/cli/ai.ts +339 -21
- package/src/forge/cli/auth.ts +348 -10
- 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 +437 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1620 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +887 -0
- package/src/forge/cli/deps.ts +82 -13
- package/src/forge/cli/dev.ts +961 -46
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/field-test.ts +329 -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 +107 -3
- package/src/forge/cli/new.ts +152 -13
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +389 -6
- package/src/forge/cli/parse.ts +1050 -34
- 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/rls.ts +43 -4
- package/src/forge/cli/run.ts +41 -0
- package/src/forge/cli/security.ts +112 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2218 -0
- package/src/forge/cli/verify.ts +1455 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +1020 -0
- package/src/forge/compiler/agent-contract/build.ts +670 -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/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/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 +30 -0
- package/src/forge/compiler/diagnostics/create.ts +21 -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 +205 -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 +618 -24
- 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 +1219 -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 +247 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +97 -31
- 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-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-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/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 +75 -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/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -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 +1202 -0
- package/src/forge/delta/store.ts +3284 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +808 -17
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +280 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +204 -9
- 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 +40 -6
- package/src/forge/make/templates.ts +155 -2
- 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/review/index.ts +158 -12
- package/src/forge/review/types.ts +15 -0
- 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 +263 -2
- package/src/forge/runtime/db/postgres-adapter.ts +6 -3
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +649 -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 +17 -3
- package/src/forge/ui/index.ts +209 -0
- package/src/forge/ui/types.ts +1 -0
- 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 +250 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +391 -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 +33 -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 +12 -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 +13 -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/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +1 -1
- package/templates/minimal-web/web/package.json +2 -2
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +33 -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 +18 -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/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 -14919
- 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 -3430
- 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
|
@@ -4,6 +4,7 @@ import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
|
4
4
|
import { GENERATED_DIR, GENERATOR_VERSION } from "../compiler/emitter/constants.ts";
|
|
5
5
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
6
6
|
import { hashStable } from "../compiler/primitives/hash.ts";
|
|
7
|
+
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
7
8
|
import { serializeCanonical } from "../compiler/primitives/serialize.ts";
|
|
8
9
|
import { secretLeakScan } from "../compiler/sandbox/secret-scan.ts";
|
|
9
10
|
import type { Diagnostic } from "../compiler/types/diagnostic.ts";
|
|
@@ -27,19 +28,41 @@ import type {
|
|
|
27
28
|
AgentDoctorResult,
|
|
28
29
|
AgentExportFile,
|
|
29
30
|
AgentExportResult,
|
|
31
|
+
AgentHooksSmokeResult,
|
|
32
|
+
AgentHooksStatusResult,
|
|
33
|
+
AgentOnboardResult,
|
|
30
34
|
AgentPrintContextResult,
|
|
35
|
+
AgentPrepareResult,
|
|
36
|
+
AgentTimelineItem,
|
|
37
|
+
AgentTimelineResult,
|
|
31
38
|
AgentTargetsResult,
|
|
32
39
|
CustomAdapterConfig,
|
|
33
40
|
AgentCheckResult,
|
|
34
41
|
} from "./types.ts";
|
|
42
|
+
import type { AgentMemoryEventRecord } from "../agent-memory/types.ts";
|
|
43
|
+
import {
|
|
44
|
+
formatAgentMemoryHuman,
|
|
45
|
+
formatAgentMemoryJson,
|
|
46
|
+
inspectAgentMemoryQueueFile,
|
|
47
|
+
runAgentMemoryCommand,
|
|
48
|
+
type AgentMemoryCommandResult,
|
|
49
|
+
} from "../agent-memory/bridge.ts";
|
|
50
|
+
import {
|
|
51
|
+
compareHookForgeVersions,
|
|
52
|
+
inspectCodexHookCommands,
|
|
53
|
+
probeCodexHookRunner,
|
|
54
|
+
readCodexHookMeta,
|
|
55
|
+
resolveForgeOnPath,
|
|
56
|
+
} from "../agent-memory/hook-runner.ts";
|
|
57
|
+
import { releaseManifest } from "../_generated/releaseManifest.ts";
|
|
58
|
+
import { runDevConsoleCycle } from "../dev-console/cycle.ts";
|
|
59
|
+
import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
|
|
35
60
|
|
|
36
61
|
export const AGENT_ADAPTER_VERSION = "agent-adapter-0.1.0";
|
|
37
62
|
export const AGENT_FORMAT_VERSION = "2026-06";
|
|
38
63
|
|
|
39
64
|
const USER_START = "<!-- user-notes:start -->";
|
|
40
65
|
const USER_END = "<!-- user-notes:end -->";
|
|
41
|
-
const GENERATED_START = "<!-- forge-generated:start -->";
|
|
42
|
-
const GENERATED_END = "<!-- forge-generated:end -->";
|
|
43
66
|
const CUSTOM_ADAPTERS_DIR = ".forge/agent-adapters";
|
|
44
67
|
|
|
45
68
|
function sorted(values: string[]): string[] {
|
|
@@ -55,6 +78,25 @@ function diagnostic(
|
|
|
55
78
|
return createDiagnostic({ severity, code, message, ...(file ? { file } : {}) });
|
|
56
79
|
}
|
|
57
80
|
|
|
81
|
+
function normalizeAgentCliCommandHints<T>(workspaceRoot: string, result: T): T {
|
|
82
|
+
if (!result || typeof result !== "object") {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
const value = result as Record<string, unknown>;
|
|
86
|
+
const normalized: Record<string, unknown> = { ...value };
|
|
87
|
+
for (const key of ["nextActions", "diagnostics", "commands", "checks", "steps"]) {
|
|
88
|
+
if (key in normalized) {
|
|
89
|
+
normalized[key] = normalizeForgeCliCommandsInValue(workspaceRoot, normalized[key], key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const key of ["installResult", "ingestResult"]) {
|
|
93
|
+
if (key in normalized) {
|
|
94
|
+
normalized[key] = normalizeAgentCliCommandHints(workspaceRoot, normalized[key]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return normalized as T;
|
|
98
|
+
}
|
|
99
|
+
|
|
58
100
|
function readText(workspaceRoot: string, relative: string): string | null {
|
|
59
101
|
const path = join(workspaceRoot, relative);
|
|
60
102
|
if (!nodeFileSystem.exists(path)) {
|
|
@@ -172,7 +214,7 @@ export function buildAgentContext(contract: AgentContract): AgentContext {
|
|
|
172
214
|
},
|
|
173
215
|
knownPitfalls: [
|
|
174
216
|
"Do not edit src/forge/_generated/** directly.",
|
|
175
|
-
"Do not
|
|
217
|
+
"Do not read secrets or server runtime config through process.env in Forge runtime code; public frontend bridge env is allowed.",
|
|
176
218
|
"Do not import network packages in command/query/liveQuery.",
|
|
177
219
|
"Preserve tenant isolation and policy declarations.",
|
|
178
220
|
"Use forge make, forge feature, forge refactor, forge impact, and forge repair before hand-editing architecture.",
|
|
@@ -222,99 +264,6 @@ export function buildAgentDoneCriteria(): AgentDoneCriteria {
|
|
|
222
264
|
};
|
|
223
265
|
}
|
|
224
266
|
|
|
225
|
-
function replaceGeneratedBlock(existing: string | null, generated: string, fallbackUserNotes: string): string {
|
|
226
|
-
const userBlock = extractUserBlock(existing) ?? `${USER_START}\n\n${fallbackUserNotes}\n\n${USER_END}`;
|
|
227
|
-
return `${GENERATED_START}\n${generated.trim()}\n${GENERATED_END}\n\n${userBlock.trim()}\n`;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function extractUserBlock(existing: string | null): string | null {
|
|
231
|
-
if (!existing) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
const start = existing.indexOf(USER_START);
|
|
235
|
-
const end = existing.indexOf(USER_END);
|
|
236
|
-
if (start === -1 || end === -1 || end < start) {
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
return existing.slice(start, end + USER_END.length);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function agentsMarkdown(contract: AgentContract, existing: string | null): string {
|
|
243
|
-
const context = buildAgentContext(contract);
|
|
244
|
-
const generated = `# AGENTS.md
|
|
245
|
-
|
|
246
|
-
## Project Type
|
|
247
|
-
|
|
248
|
-
This is a ForgeOS application.
|
|
249
|
-
|
|
250
|
-
## Required Workflow
|
|
251
|
-
|
|
252
|
-
Before editing:
|
|
253
|
-
|
|
254
|
-
\`\`\`bash
|
|
255
|
-
forge inspect all --json
|
|
256
|
-
forge doctor --json
|
|
257
|
-
\`\`\`
|
|
258
|
-
|
|
259
|
-
During editing:
|
|
260
|
-
|
|
261
|
-
\`\`\`bash
|
|
262
|
-
forge impact --changed --json
|
|
263
|
-
forge test plan --changed --json
|
|
264
|
-
\`\`\`
|
|
265
|
-
|
|
266
|
-
After editing:
|
|
267
|
-
|
|
268
|
-
\`\`\`bash
|
|
269
|
-
forge generate
|
|
270
|
-
forge check
|
|
271
|
-
forge verify --strict
|
|
272
|
-
\`\`\`
|
|
273
|
-
|
|
274
|
-
## Do Not
|
|
275
|
-
|
|
276
|
-
- Do not edit \`src/forge/_generated/**\`.
|
|
277
|
-
- Do not import network packages in \`command\`, \`query\`, or \`liveQuery\`.
|
|
278
|
-
- Do not use \`process.env\` directly in app code.
|
|
279
|
-
- Use \`ctx.secrets\`.
|
|
280
|
-
- Do not bypass tenant isolation.
|
|
281
|
-
- Do not call \`ctx.ai\` in \`command\`, \`query\`, or \`liveQuery\`.
|
|
282
|
-
- Do not manually modify \`forge.lock\` unless instructed.
|
|
283
|
-
|
|
284
|
-
## Runtime Model
|
|
285
|
-
|
|
286
|
-
- \`command\`: ${context.runtimeModel.command}.
|
|
287
|
-
- \`query\`: ${context.runtimeModel.query}.
|
|
288
|
-
- \`liveQuery\`: ${context.runtimeModel.liveQuery}.
|
|
289
|
-
- \`action\`: ${context.runtimeModel.action}.
|
|
290
|
-
- \`workflow\`: ${context.runtimeModel.workflow}.
|
|
291
|
-
|
|
292
|
-
## Common Commands
|
|
293
|
-
|
|
294
|
-
\`\`\`bash
|
|
295
|
-
forge make resource <name>
|
|
296
|
-
forge feature plan <blueprint>
|
|
297
|
-
forge refactor rename field <from> <to>
|
|
298
|
-
forge impact --changed --json
|
|
299
|
-
forge repair diagnose --from-last-test-run --json
|
|
300
|
-
forge agent print-context --json
|
|
301
|
-
\`\`\`
|
|
302
|
-
|
|
303
|
-
## Agent Adapter Exports
|
|
304
|
-
|
|
305
|
-
- Generic agents read \`.forge/agent/context.json\` and \`.forge/agent/playbooks/*.md\`.
|
|
306
|
-
- Codex skills are generated under \`.codex/skills/**\`.
|
|
307
|
-
- Cursor rules are generated under \`.cursor/rules/**\`.
|
|
308
|
-
- Claude instructions are generated in \`CLAUDE.md\` and \`.claude/**\`.
|
|
309
|
-
|
|
310
|
-
These files are derived from ForgeOS generated contracts. Regenerate them with:
|
|
311
|
-
|
|
312
|
-
\`\`\`bash
|
|
313
|
-
forge agent export --target all
|
|
314
|
-
\`\`\``;
|
|
315
|
-
return `# AGENTS.md\n\n${replaceGeneratedBlock(existing, generated, "Project-specific human notes go here.")}`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
267
|
function playbook(title: string, steps: string[]): string {
|
|
319
268
|
return `# Playbook: ${title}\n\n${steps.map((step, index) => `${index + 1}. ${step}`).join("\n")}\n`;
|
|
320
269
|
}
|
|
@@ -322,7 +271,7 @@ function playbook(title: string, steps: string[]): string {
|
|
|
322
271
|
function playbookFiles(): AgentExportFile[] {
|
|
323
272
|
const books: Array<[string, string, string[]]> = [
|
|
324
273
|
["add-command.md", "Add Command", [
|
|
325
|
-
"Run `forge
|
|
274
|
+
"Run `forge status --json`, `forge handoff --json`, and `forge agent print-context --json`.",
|
|
326
275
|
"Prefer `forge make command <resource.action> --table <table> --policy <policy>`.",
|
|
327
276
|
"Commands may write through `ctx.db` and emit with `ctx.emit`.",
|
|
328
277
|
"Commands must not import network packages, use `ctx.secrets`, or call `ctx.ai`.",
|
|
@@ -378,7 +327,7 @@ function playbookFiles(): AgentExportFile[] {
|
|
|
378
327
|
"Prefer targeted repairs and impacted tests before full verify.",
|
|
379
328
|
]],
|
|
380
329
|
["frontend-change.md", "Frontend Change", [
|
|
381
|
-
"Use generated client APIs and React hooks.",
|
|
330
|
+
"Use generated client APIs and framework bindings: React hooks or Vue composables.",
|
|
382
331
|
"Do not import server adapters or server-only packages into client code.",
|
|
383
332
|
"Preserve `ForgeError.traceId` in visible error states.",
|
|
384
333
|
"Run affected frontend tests and `forge verify --changed`.",
|
|
@@ -395,9 +344,8 @@ function playbookFiles(): AgentExportFile[] {
|
|
|
395
344
|
}));
|
|
396
345
|
}
|
|
397
346
|
|
|
398
|
-
function buildGenericFiles(contract: AgentContract
|
|
347
|
+
function buildGenericFiles(contract: AgentContract): AgentExportFile[] {
|
|
399
348
|
return [
|
|
400
|
-
{ path: "AGENTS.md", content: existingAgentsMd ?? agentsMarkdown(contract, null) },
|
|
401
349
|
{ path: ".forge/agent/context.json", content: renderJson(buildAgentContext(contract)) },
|
|
402
350
|
{ path: ".forge/agent/commands.json", content: renderJson(buildAgentCommandsMap()) },
|
|
403
351
|
{ path: ".forge/agent/done-criteria.json", content: renderJson(buildAgentDoneCriteria()) },
|
|
@@ -405,10 +353,25 @@ function buildGenericFiles(contract: AgentContract, existingAgentsMd?: string |
|
|
|
405
353
|
];
|
|
406
354
|
}
|
|
407
355
|
|
|
356
|
+
function buildGenericSupportFiles(contract: AgentContract): AgentExportFile[] {
|
|
357
|
+
return buildGenericFiles(contract);
|
|
358
|
+
}
|
|
359
|
+
|
|
408
360
|
function skill(name: string, description: string, body: string): string {
|
|
409
361
|
return `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${description}\n\n${body.trim()}\n`;
|
|
410
362
|
}
|
|
411
363
|
|
|
364
|
+
function codexAgentRole(name: string, description: string, developerInstructions: string): string {
|
|
365
|
+
return [
|
|
366
|
+
`name = "forge-${name}"`,
|
|
367
|
+
`description = "${description}"`,
|
|
368
|
+
"developer_instructions = '''",
|
|
369
|
+
developerInstructions.trim(),
|
|
370
|
+
"'''",
|
|
371
|
+
"",
|
|
372
|
+
].join("\n");
|
|
373
|
+
}
|
|
374
|
+
|
|
412
375
|
function buildCodexFiles(_contract: AgentContract, options: { skills: boolean }): AgentExportFile[] {
|
|
413
376
|
if (!options.skills) {
|
|
414
377
|
return [];
|
|
@@ -423,7 +386,7 @@ Rules:
|
|
|
423
386
|
- Commands must not use \`ctx.secrets\` or \`ctx.ai\`.
|
|
424
387
|
|
|
425
388
|
Steps:
|
|
426
|
-
1. Run \`forge
|
|
389
|
+
1. Run \`forge status --json\`, \`forge handoff --json\`, and \`forge agent print-context --json\`.
|
|
427
390
|
2. Prefer \`forge make command <resource.action> --table <table> --policy <policy>\`.
|
|
428
391
|
3. Run \`forge generate\`, \`forge check\`, and \`forge verify --changed\`.
|
|
429
392
|
4. Finish with \`forge verify --strict\`.
|
|
@@ -457,11 +420,45 @@ Prefer targeted checks before full verification.
|
|
|
457
420
|
path: `.codex/skills/${name}/SKILL.md`,
|
|
458
421
|
content: skill(name, description, body),
|
|
459
422
|
}));
|
|
460
|
-
const agents
|
|
423
|
+
const agents: Array<[string, string, string]> = [
|
|
424
|
+
["explorer", "Explore a ForgeOS app contract, routes, runtime entries, policies, and generated context before editing.", `
|
|
425
|
+
You are the ForgeOS explorer role.
|
|
426
|
+
|
|
427
|
+
Use ForgeOS inspection commands before reading broad file trees:
|
|
428
|
+
- forge status --json
|
|
429
|
+
- forge changed --json
|
|
430
|
+
- forge agent print-context --json
|
|
431
|
+
- forge inspect all --brief --json
|
|
432
|
+
|
|
433
|
+
Explain the app shape, key runtime entries, frontend bindings, policies, generated files, and likely files to inspect next. Prefer concise findings with exact file paths. Do not modify files.
|
|
434
|
+
`],
|
|
435
|
+
["worker", "Implement ForgeOS app changes using generated contracts, playbooks, and verification commands.", `
|
|
436
|
+
You are the ForgeOS worker role.
|
|
437
|
+
|
|
438
|
+
Before editing, read the generated agent context and changed-file summary. Prefer ForgeOS primitives such as forge do, forge make, forge add, forge refactor, and forge repair when they fit. Keep authored changes separate from generated artifacts, never edit src/forge/_generated directly, and run forge generate plus focused checks after source changes.
|
|
439
|
+
|
|
440
|
+
Return the implemented change, files touched, verification commands, and any remaining risks.
|
|
441
|
+
`],
|
|
442
|
+
["reviewer", "Review ForgeOS changes with focus on behavior, generated drift, policies, tests, and handoff readiness.", `
|
|
443
|
+
You are the ForgeOS reviewer role.
|
|
444
|
+
|
|
445
|
+
Start from forge changed --json and forge review run --changed --json. Review authored source, tests, docs, and config before generated artifacts. Look for runtime boundary violations, stale generated files, missing tests, tenant/auth mistakes, frontend binding drift, and unclear handoff state.
|
|
446
|
+
|
|
447
|
+
Lead with findings ordered by severity and include file paths and commands that prove the issue.
|
|
448
|
+
`],
|
|
449
|
+
["security", "Audit ForgeOS security boundaries, secrets, tenant scope, policies, hooks, and agent memory surfaces.", `
|
|
450
|
+
You are the ForgeOS security role.
|
|
451
|
+
|
|
452
|
+
Focus on command/query/liveQuery/action/workflow boundaries, process.env usage, ctx.secrets placement, tenant isolation, policy coverage, generated RLS artifacts, hook trust, Agent Memory privacy, and AI tool approval risks. Use forge inspect policies --json, forge auth check --json, forge ai redteam --json, and forge changed --json when relevant.
|
|
453
|
+
|
|
454
|
+
Report concrete risks, impacted files, exploit path if applicable, and the smallest safe fix.
|
|
455
|
+
`],
|
|
456
|
+
];
|
|
457
|
+
const agentFiles = agents.map(([name, description, instructions]) => ({
|
|
461
458
|
path: `.codex/agents/forge-${name}.toml`,
|
|
462
|
-
content:
|
|
459
|
+
content: codexAgentRole(name, description, instructions),
|
|
463
460
|
}));
|
|
464
|
-
return [...files, ...
|
|
461
|
+
return [...files, ...agentFiles];
|
|
465
462
|
}
|
|
466
463
|
|
|
467
464
|
function mdc(description: string, globs: string[], body: string): string {
|
|
@@ -511,7 +508,7 @@ function buildCursorFiles(_contract: AgentContract, options: { rules: boolean })
|
|
|
511
508
|
`# ForgeOS Security Rules
|
|
512
509
|
|
|
513
510
|
- Never include secret values in generated files, logs, or adapter exports.
|
|
514
|
-
- Use \`ctx.secrets
|
|
511
|
+
- Use \`ctx.secrets\` or generated config context for runtime secrets/config; public frontend bridge env is allowed.
|
|
515
512
|
- Preserve tenant-scoped reads and writes.
|
|
516
513
|
- Run \`forge policy check --strict-policies\` after access changes.`,
|
|
517
514
|
),
|
|
@@ -541,8 +538,11 @@ This is a ForgeOS app.
|
|
|
541
538
|
Start with:
|
|
542
539
|
|
|
543
540
|
\`\`\`bash
|
|
544
|
-
forge
|
|
545
|
-
forge
|
|
541
|
+
forge status --json
|
|
542
|
+
forge handoff --json
|
|
543
|
+
forge dev --once --json
|
|
544
|
+
forge agent print-context --json
|
|
545
|
+
forge check --json
|
|
546
546
|
\`\`\`
|
|
547
547
|
|
|
548
548
|
After changes:
|
|
@@ -558,7 +558,7 @@ Critical rules:
|
|
|
558
558
|
- Commands cannot use network packages, secrets, or AI.
|
|
559
559
|
- Queries/liveQueries are read-only.
|
|
560
560
|
- Use \`ctx.emit\` for side effects.
|
|
561
|
-
- Use \`ctx.secrets
|
|
561
|
+
- Use \`ctx.secrets\` or generated config context for runtime secrets/config; public frontend bridge env is allowed.
|
|
562
562
|
`;
|
|
563
563
|
return [
|
|
564
564
|
{ path: "CLAUDE.md", content: claude },
|
|
@@ -668,23 +668,22 @@ function builtFilesForTarget(
|
|
|
668
668
|
target: AgentAdapterTarget,
|
|
669
669
|
options: Pick<AgentCommandOptions, "skills" | "rules">,
|
|
670
670
|
): { files: AgentExportFile[]; diagnostics: Diagnostic[] } {
|
|
671
|
-
const existingAgentsMd = readText(workspaceRoot, "AGENTS.md");
|
|
672
671
|
if (target === "generic") {
|
|
673
|
-
return { files: buildGenericFiles(contract
|
|
672
|
+
return { files: buildGenericFiles(contract), diagnostics: [] };
|
|
674
673
|
}
|
|
675
674
|
if (target === "codex") {
|
|
676
|
-
return { files: [...
|
|
675
|
+
return { files: [...buildGenericSupportFiles(contract), ...buildCodexFiles(contract, { skills: options.skills })], diagnostics: [] };
|
|
677
676
|
}
|
|
678
677
|
if (target === "cursor") {
|
|
679
|
-
return { files: [...
|
|
678
|
+
return { files: [...buildGenericSupportFiles(contract), ...buildCursorFiles(contract, { rules: options.rules })], diagnostics: [] };
|
|
680
679
|
}
|
|
681
680
|
if (target === "claude") {
|
|
682
|
-
return { files: [...
|
|
681
|
+
return { files: [...buildGenericSupportFiles(contract), ...buildClaudeFiles(contract)], diagnostics: [] };
|
|
683
682
|
}
|
|
684
683
|
if (target === "all") {
|
|
685
684
|
const byPath = new Map<string, AgentExportFile>();
|
|
686
685
|
for (const file of [
|
|
687
|
-
...buildGenericFiles(contract
|
|
686
|
+
...buildGenericFiles(contract),
|
|
688
687
|
...buildCodexFiles(contract, { skills: options.skills }),
|
|
689
688
|
...buildCursorFiles(contract, { rules: options.rules }),
|
|
690
689
|
...buildClaudeFiles(contract),
|
|
@@ -831,10 +830,26 @@ export function runAgentCheck(options: AgentCommandOptions): AgentCheckResult {
|
|
|
831
830
|
}
|
|
832
831
|
const diag = [...built.diagnostics, ...validation];
|
|
833
832
|
for (const file of stale) {
|
|
834
|
-
diag.push(
|
|
833
|
+
diag.push(createDiagnostic({
|
|
834
|
+
severity: "error",
|
|
835
|
+
code: FORGE_AGENT_STALE_EXPORT,
|
|
836
|
+
message: `stale agent adapter export: ${file}`,
|
|
837
|
+
file,
|
|
838
|
+
fixHint: `Regenerate the ${options.target} adapter export.`,
|
|
839
|
+
suggestedCommands: [`forge agent export --target ${options.target}`, "forge verify --strict"],
|
|
840
|
+
docs: ["src/forge/_generated/agentAdapterManifest.json", "AGENTS.md"],
|
|
841
|
+
}));
|
|
835
842
|
}
|
|
836
843
|
for (const file of missing) {
|
|
837
|
-
diag.push(
|
|
844
|
+
diag.push(createDiagnostic({
|
|
845
|
+
severity: "error",
|
|
846
|
+
code: FORGE_AGENT_STALE_EXPORT,
|
|
847
|
+
message: `missing agent adapter export: ${file}`,
|
|
848
|
+
file,
|
|
849
|
+
fixHint: `Generate the ${options.target} adapter export.`,
|
|
850
|
+
suggestedCommands: [`forge agent export --target ${options.target}`, "forge verify --strict"],
|
|
851
|
+
docs: ["src/forge/_generated/agentAdapterManifest.json", "AGENTS.md"],
|
|
852
|
+
}));
|
|
838
853
|
}
|
|
839
854
|
const ok = stale.length === 0 && missing.length === 0 && diag.every((item) => item.severity !== "error");
|
|
840
855
|
return {
|
|
@@ -930,23 +945,1308 @@ export function runAgentClean(options: AgentCommandOptions): AgentExportResult {
|
|
|
930
945
|
};
|
|
931
946
|
}
|
|
932
947
|
|
|
933
|
-
|
|
948
|
+
function eventBindings(event: AgentMemoryEventRecord): Record<string, unknown> {
|
|
949
|
+
const data = event.data;
|
|
950
|
+
const bindings = data && typeof data === "object" && "bindings" in data
|
|
951
|
+
? (data as { bindings?: unknown }).bindings
|
|
952
|
+
: undefined;
|
|
953
|
+
return bindings && typeof bindings === "object" && !Array.isArray(bindings)
|
|
954
|
+
? bindings as Record<string, unknown>
|
|
955
|
+
: {};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function eventPayload(event: AgentMemoryEventRecord): Record<string, unknown> {
|
|
959
|
+
const data = event.data;
|
|
960
|
+
const envelope = data && typeof data === "object" && "envelope" in data
|
|
961
|
+
? (data as { envelope?: unknown }).envelope
|
|
962
|
+
: undefined;
|
|
963
|
+
const payload = envelope && typeof envelope === "object" && "payload" in envelope
|
|
964
|
+
? (envelope as { payload?: unknown }).payload
|
|
965
|
+
: undefined;
|
|
966
|
+
return payload && typeof payload === "object" && !Array.isArray(payload)
|
|
967
|
+
? payload as Record<string, unknown>
|
|
968
|
+
: {};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function eventIsForgeHookCanary(event: AgentMemoryEventRecord): boolean {
|
|
972
|
+
return eventPayload(event).forgeHookCanary === "FORGE_HOOK_SMOKE_CANARY";
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function eventIsForgeHookProbe(event: AgentMemoryEventRecord): boolean {
|
|
976
|
+
const payload = eventPayload(event);
|
|
977
|
+
return payload.forgeHookProbe === true ||
|
|
978
|
+
payload._parseError === true ||
|
|
979
|
+
payload._invalidPayload === true;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function eventHasUsefulSignal(event: AgentMemoryEventRecord): boolean {
|
|
983
|
+
const bindings = eventBindings(event);
|
|
984
|
+
const files = bindings.files;
|
|
985
|
+
const entries = bindings.entries;
|
|
986
|
+
const proofs = bindings.proofs;
|
|
987
|
+
return (
|
|
988
|
+
typeof bindings.toolName === "string" ||
|
|
989
|
+
typeof bindings.command === "string" ||
|
|
990
|
+
typeof bindings.status === "string" ||
|
|
991
|
+
(Array.isArray(files) && files.length > 0) ||
|
|
992
|
+
(Array.isArray(entries) && entries.length > 0) ||
|
|
993
|
+
(Array.isArray(proofs) && proofs.length > 0)
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function eventIsNativeHookEvent(event: AgentMemoryEventRecord): boolean {
|
|
998
|
+
return (
|
|
999
|
+
!eventIsForgeHookCanary(event) &&
|
|
1000
|
+
!eventIsForgeHookProbe(event) &&
|
|
1001
|
+
event.integrationKind === "native-hook" &&
|
|
1002
|
+
event.trustLevel === "direct-hook"
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function hookApprovalStatusFor(
|
|
1007
|
+
target: AgentAdapterTarget,
|
|
1008
|
+
installed: boolean,
|
|
1009
|
+
nativeSignals: number,
|
|
1010
|
+
observableSignals = 0,
|
|
1011
|
+
memoryReadable = true,
|
|
1012
|
+
): "not-required" | "waiting-for-user-trust" | "accepted" | "trusted" | "memory-unavailable" {
|
|
1013
|
+
if (target !== "codex" || !installed) {
|
|
1014
|
+
return "not-required";
|
|
1015
|
+
}
|
|
1016
|
+
if (!memoryReadable) {
|
|
1017
|
+
return "memory-unavailable";
|
|
1018
|
+
}
|
|
1019
|
+
if (nativeSignals > 0) {
|
|
1020
|
+
return "trusted";
|
|
1021
|
+
}
|
|
1022
|
+
return observableSignals > 0 ? "accepted" : "waiting-for-user-trust";
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function hookNativeTrustStatusFor(
|
|
1026
|
+
target: AgentAdapterTarget,
|
|
1027
|
+
installed: boolean,
|
|
1028
|
+
nativeSignals: number,
|
|
1029
|
+
memoryReadable = true,
|
|
1030
|
+
): "not-required" | "waiting-for-native-signal" | "trusted" | "memory-unavailable" {
|
|
1031
|
+
if (target !== "codex" || !installed) {
|
|
1032
|
+
return "not-required";
|
|
1033
|
+
}
|
|
1034
|
+
if (!memoryReadable) {
|
|
1035
|
+
return "memory-unavailable";
|
|
1036
|
+
}
|
|
1037
|
+
return nativeSignals > 0 ? "trusted" : "waiting-for-native-signal";
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function codexHookApprovalMessage(
|
|
1041
|
+
approvalStatus: ReturnType<typeof hookApprovalStatusFor>,
|
|
1042
|
+
nativeTrustStatus: ReturnType<typeof hookNativeTrustStatusFor>,
|
|
1043
|
+
canarySignals = 0,
|
|
1044
|
+
): string {
|
|
1045
|
+
if (approvalStatus === "waiting-for-user-trust") {
|
|
1046
|
+
if (canarySignals > 0) {
|
|
1047
|
+
return "ForgeOS can see the Codex smoke canary; hook approval is accepted, but trusted native Codex signal proof is still pending";
|
|
1048
|
+
}
|
|
1049
|
+
return "ForgeOS has not seen a trusted native Codex hook signal yet; approve the Codex Desktop hook prompt if shown, then continue a Codex session in this workspace";
|
|
1050
|
+
}
|
|
1051
|
+
if (approvalStatus === "accepted" && nativeTrustStatus === "waiting-for-native-signal") {
|
|
1052
|
+
return "Codex hook approval is accepted for local editing; ForgeOS is still waiting for a trusted native Codex signal for stronger provenance";
|
|
1053
|
+
}
|
|
1054
|
+
if (approvalStatus === "trusted") {
|
|
1055
|
+
return "Codex Desktop hook trust is confirmed by a native hook signal";
|
|
1056
|
+
}
|
|
1057
|
+
if (approvalStatus === "memory-unavailable") {
|
|
1058
|
+
return "Codex Desktop hook trust cannot be verified until Agent Memory is readable";
|
|
1059
|
+
}
|
|
1060
|
+
return "Codex Desktop hook trust is not required";
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function hookApprovalNextActions(target: AgentAdapterTarget, canarySignals = 0): string[] {
|
|
1064
|
+
if (target !== "codex") {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
return canarySignals > 0
|
|
1068
|
+
? [
|
|
1069
|
+
"Continue or send one Codex message in this workspace so a normal native hook event is emitted",
|
|
1070
|
+
"If Codex Desktop shows a hook approval prompt, approve it",
|
|
1071
|
+
`forge agent hooks status --target ${target} --json`,
|
|
1072
|
+
]
|
|
1073
|
+
: [
|
|
1074
|
+
"Approve the installed hooks in Codex Desktop (Confiar em tudo or Revisar hooks)",
|
|
1075
|
+
"Start or continue a Codex session in this workspace so a native hook event is emitted",
|
|
1076
|
+
`forge agent hooks status --target ${target} --json`,
|
|
1077
|
+
];
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function inspectQueuedHookSignals(workspaceRoot: string, installTarget: string | null) {
|
|
1081
|
+
if (installTarget !== "codex") {
|
|
1082
|
+
return undefined;
|
|
1083
|
+
}
|
|
1084
|
+
return inspectAgentMemoryQueueFile({
|
|
1085
|
+
workspaceRoot,
|
|
1086
|
+
watchFile: join(workspaceRoot, ".forge", "agent", "events.ndjson"),
|
|
1087
|
+
source: "codex",
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function stringArray(value: unknown): string[] {
|
|
1092
|
+
return Array.isArray(value)
|
|
1093
|
+
? value.filter((item): item is string => typeof item === "string" && item.length > 0)
|
|
1094
|
+
: [];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function stringValue(value: unknown): string | undefined {
|
|
1098
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function agentTimelineItem(event: AgentMemoryEventRecord): AgentTimelineItem {
|
|
1102
|
+
const bindings = eventBindings(event);
|
|
1103
|
+
return {
|
|
1104
|
+
id: event.id,
|
|
1105
|
+
source: event.sourceName,
|
|
1106
|
+
integration: event.integrationKind,
|
|
1107
|
+
trustLevel: event.trustLevel,
|
|
1108
|
+
kind: event.normalizedKind,
|
|
1109
|
+
capturedAt: event.capturedAt,
|
|
1110
|
+
...(event.externalSessionId ? { sessionId: event.externalSessionId } : {}),
|
|
1111
|
+
...(event.externalTurnId ? { turnId: event.externalTurnId } : {}),
|
|
1112
|
+
...(event.summary ? { summary: event.summary } : {}),
|
|
1113
|
+
...(stringValue(bindings.toolName) ? { toolName: stringValue(bindings.toolName) } : {}),
|
|
1114
|
+
...(stringValue(bindings.command) ? { command: stringValue(bindings.command) } : {}),
|
|
1115
|
+
...(stringValue(bindings.status) ? { status: stringValue(bindings.status) } : {}),
|
|
1116
|
+
files: stringArray(bindings.files),
|
|
1117
|
+
entries: stringArray(bindings.entries),
|
|
1118
|
+
proofs: stringArray(bindings.proofs),
|
|
1119
|
+
confidence: event.confidence,
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function agentTimelineSourceFilter(target: AgentAdapterTarget): string | undefined {
|
|
1124
|
+
if (!target || target === "all" || target === "generic") {
|
|
1125
|
+
return undefined;
|
|
1126
|
+
}
|
|
1127
|
+
return agentMemorySourceForTarget(target);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
export async function runAgentTimeline(options: AgentCommandOptions): Promise<AgentTimelineResult> {
|
|
1131
|
+
const target = options.target || "all";
|
|
1132
|
+
const sourceFilter = agentTimelineSourceFilter(target);
|
|
1133
|
+
const requestedLimit = options.limit ?? 50;
|
|
1134
|
+
const memoryResult = await runAgentMemoryCommand({
|
|
1135
|
+
subcommand: "memory",
|
|
1136
|
+
workspaceRoot: options.workspaceRoot,
|
|
1137
|
+
json: true,
|
|
1138
|
+
target: "generic",
|
|
1139
|
+
source: "generic",
|
|
1140
|
+
limit: sourceFilter ? 200 : requestedLimit,
|
|
1141
|
+
});
|
|
1142
|
+
if (!("events" in memoryResult) || memoryResult.ok === false) {
|
|
1143
|
+
const diagnostics = "diagnostics" in memoryResult ? memoryResult.diagnostics ?? [] : [];
|
|
1144
|
+
const nextActions = "nextActions" in memoryResult
|
|
1145
|
+
? memoryResult.nextActions ?? ["forge delta status --json"]
|
|
1146
|
+
: ["forge delta status --json"];
|
|
1147
|
+
return {
|
|
1148
|
+
schemaVersion: "0.1.0",
|
|
1149
|
+
ok: false,
|
|
1150
|
+
timeline: "agent",
|
|
1151
|
+
target,
|
|
1152
|
+
...(sourceFilter ? { sourceFilter } : {}),
|
|
1153
|
+
summary: { events: 0, sessions: 0, files: 0, entries: 0, proofs: 0, tools: 0 },
|
|
1154
|
+
events: [],
|
|
1155
|
+
files: [],
|
|
1156
|
+
entries: [],
|
|
1157
|
+
proofs: [],
|
|
1158
|
+
sessions: [],
|
|
1159
|
+
nextActions,
|
|
1160
|
+
diagnostics,
|
|
1161
|
+
exitCode: 1,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const filtered = sourceFilter
|
|
1166
|
+
? memoryResult.events.filter((event) => event.sourceName === sourceFilter)
|
|
1167
|
+
: memoryResult.events;
|
|
1168
|
+
const events = filtered.slice(-requestedLimit).map(agentTimelineItem);
|
|
1169
|
+
const files = sorted(events.flatMap((event) => event.files));
|
|
1170
|
+
const entries = sorted(events.flatMap((event) => event.entries));
|
|
1171
|
+
const proofs = sorted(events.flatMap((event) => event.proofs));
|
|
1172
|
+
const sessions = sorted(events.flatMap((event) => event.sessionId ? [event.sessionId] : []));
|
|
1173
|
+
const tools = sorted(events.flatMap((event) => event.toolName ? [event.toolName] : []));
|
|
1174
|
+
return {
|
|
1175
|
+
schemaVersion: "0.1.0",
|
|
1176
|
+
ok: true,
|
|
1177
|
+
timeline: "agent",
|
|
1178
|
+
target,
|
|
1179
|
+
...(sourceFilter ? { sourceFilter } : {}),
|
|
1180
|
+
summary: {
|
|
1181
|
+
events: events.length,
|
|
1182
|
+
sessions: sessions.length,
|
|
1183
|
+
files: files.length,
|
|
1184
|
+
entries: entries.length,
|
|
1185
|
+
proofs: proofs.length,
|
|
1186
|
+
tools: tools.length,
|
|
1187
|
+
...(events.at(-1)?.capturedAt ? { latestEventAt: events.at(-1)?.capturedAt } : {}),
|
|
1188
|
+
},
|
|
1189
|
+
events,
|
|
1190
|
+
files,
|
|
1191
|
+
entries,
|
|
1192
|
+
proofs,
|
|
1193
|
+
sessions,
|
|
1194
|
+
nextActions: [
|
|
1195
|
+
"forge agent context --current --json",
|
|
1196
|
+
"forge changed --json",
|
|
1197
|
+
"forge timeline --json --for-agent",
|
|
1198
|
+
],
|
|
1199
|
+
diagnostics: [],
|
|
1200
|
+
exitCode: 0,
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function agentMemorySourceForTarget(target: AgentAdapterTarget): string {
|
|
1205
|
+
if (target === "claude") return "claude-code";
|
|
1206
|
+
if (target === "codex" || target === "cursor" || target === "claude-code") return target;
|
|
1207
|
+
return String(target || "generic");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function hookInstallFilesPresent(workspaceRoot: string, installResult: unknown): {
|
|
1211
|
+
planned: string[];
|
|
1212
|
+
missing: string[];
|
|
1213
|
+
} {
|
|
1214
|
+
const planned =
|
|
1215
|
+
installResult &&
|
|
1216
|
+
typeof installResult === "object" &&
|
|
1217
|
+
"filesPlanned" in installResult &&
|
|
1218
|
+
Array.isArray((installResult as { filesPlanned?: unknown }).filesPlanned)
|
|
1219
|
+
? ((installResult as { filesPlanned: unknown[] }).filesPlanned.filter((file): file is string => typeof file === "string"))
|
|
1220
|
+
: [];
|
|
1221
|
+
return {
|
|
1222
|
+
planned,
|
|
1223
|
+
missing: planned.filter((file) => !nodeFileSystem.exists(join(workspaceRoot, file))),
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function lastAgentSignal(events: AgentMemoryEventRecord[]): AgentHooksStatusResult["lastSignal"] {
|
|
1228
|
+
const event = events.at(-1);
|
|
1229
|
+
return event
|
|
1230
|
+
? {
|
|
1231
|
+
kind: event.normalizedKind,
|
|
1232
|
+
...(event.summary ? { summary: event.summary } : {}),
|
|
1233
|
+
capturedAt: event.capturedAt,
|
|
1234
|
+
...(eventWorkspaceRoot(event) ? { workspaceRoot: eventWorkspaceRoot(event) } : {}),
|
|
1235
|
+
}
|
|
1236
|
+
: undefined;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function normalizeAgentWorkspaceRoot(value: unknown): string {
|
|
1240
|
+
return normalizePath(String(value ?? "")).replace(/\/+$/, "").toLowerCase();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function eventWorkspaceRoot(event: AgentMemoryEventRecord): string | undefined {
|
|
1244
|
+
const envelope = event.data?.envelope;
|
|
1245
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
1246
|
+
return undefined;
|
|
1247
|
+
}
|
|
1248
|
+
const workspace = (envelope as { workspace?: unknown }).workspace;
|
|
1249
|
+
if (!workspace || typeof workspace !== "object" || Array.isArray(workspace)) {
|
|
1250
|
+
return undefined;
|
|
1251
|
+
}
|
|
1252
|
+
const root = (workspace as { root?: unknown }).root;
|
|
1253
|
+
return typeof root === "string" && root.trim() ? root : undefined;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function eventBelongsToWorkspace(event: AgentMemoryEventRecord, workspaceRoot: string): boolean {
|
|
1257
|
+
const root = eventWorkspaceRoot(event);
|
|
1258
|
+
if (!root) {
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
return normalizeAgentWorkspaceRoot(root) === normalizeAgentWorkspaceRoot(workspaceRoot);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
async function readHookMemoryStatus(
|
|
1265
|
+
workspaceRoot: string,
|
|
1266
|
+
source: string,
|
|
1267
|
+
limit: number,
|
|
1268
|
+
): Promise<{
|
|
1269
|
+
events: AgentMemoryEventRecord[];
|
|
1270
|
+
diagnostics: Diagnostic[];
|
|
1271
|
+
ignoredOutOfWorkspaceEvents: number;
|
|
1272
|
+
workspaceRoot: string;
|
|
1273
|
+
}> {
|
|
1274
|
+
const diagnostics: Diagnostic[] = [];
|
|
1275
|
+
const memoryResult = await runAgentMemoryCommand({
|
|
1276
|
+
subcommand: "memory",
|
|
1277
|
+
workspaceRoot,
|
|
1278
|
+
json: true,
|
|
1279
|
+
target: source,
|
|
1280
|
+
source,
|
|
1281
|
+
entry: source,
|
|
1282
|
+
limit,
|
|
1283
|
+
}).catch((error: unknown) => {
|
|
1284
|
+
diagnostics.push(diagnostic(
|
|
1285
|
+
"error",
|
|
1286
|
+
"FORGE_AGENT_MEMORY_UNAVAILABLE",
|
|
1287
|
+
error instanceof Error ? error.message : "agent memory store is unavailable",
|
|
1288
|
+
));
|
|
1289
|
+
return { ok: false as const, events: [], exitCode: 1 as const };
|
|
1290
|
+
});
|
|
1291
|
+
if (
|
|
1292
|
+
memoryResult &&
|
|
1293
|
+
typeof memoryResult === "object" &&
|
|
1294
|
+
"diagnostics" in memoryResult &&
|
|
1295
|
+
Array.isArray((memoryResult as { diagnostics?: unknown }).diagnostics)
|
|
1296
|
+
) {
|
|
1297
|
+
diagnostics.push(...(memoryResult as { diagnostics: Diagnostic[] }).diagnostics);
|
|
1298
|
+
}
|
|
1299
|
+
const allEvents = "events" in memoryResult ? memoryResult.events ?? [] : [];
|
|
1300
|
+
const events = allEvents.filter((event) => eventBelongsToWorkspace(event, workspaceRoot));
|
|
1301
|
+
return {
|
|
1302
|
+
events,
|
|
1303
|
+
diagnostics,
|
|
1304
|
+
ignoredOutOfWorkspaceEvents: allEvents.length - events.length,
|
|
1305
|
+
workspaceRoot: normalizeAgentWorkspaceRoot(workspaceRoot),
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentHooksStatusResult> {
|
|
1310
|
+
const target = options.target || "codex";
|
|
1311
|
+
const source = agentMemorySourceForTarget(target);
|
|
1312
|
+
const installTarget = hookInstallTarget(target);
|
|
1313
|
+
if (!installTarget) {
|
|
1314
|
+
const diag = diagnostic(
|
|
1315
|
+
"error",
|
|
1316
|
+
"FORGE_AGENT_HOOK_TARGET_UNSUPPORTED",
|
|
1317
|
+
`agent hooks supports codex, claude, and cursor targets; got ${target}`,
|
|
1318
|
+
);
|
|
1319
|
+
return {
|
|
1320
|
+
ok: false,
|
|
1321
|
+
target,
|
|
1322
|
+
installed: false,
|
|
1323
|
+
bridgeWritable: false,
|
|
1324
|
+
deltaWritable: false,
|
|
1325
|
+
visibleInMemory: false,
|
|
1326
|
+
recentEvents: 0,
|
|
1327
|
+
usefulSignals: 0,
|
|
1328
|
+
nativeSignals: 0,
|
|
1329
|
+
canarySignals: 0,
|
|
1330
|
+
approvalRequired: false,
|
|
1331
|
+
approvalStatus: "not-required",
|
|
1332
|
+
nativeTrustStatus: "not-required",
|
|
1333
|
+
checks: [{ name: "target", ok: false, message: diag.message }],
|
|
1334
|
+
nextActions: ["forge agent list-targets --json"],
|
|
1335
|
+
diagnostics: [diag],
|
|
1336
|
+
exitCode: 1,
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const installResult = await runAgentMemoryCommand({
|
|
1341
|
+
subcommand: "install",
|
|
1342
|
+
workspaceRoot: options.workspaceRoot,
|
|
1343
|
+
json: options.json,
|
|
1344
|
+
target: installTarget,
|
|
1345
|
+
source: installTarget,
|
|
1346
|
+
dryRun: true,
|
|
1347
|
+
force: false,
|
|
1348
|
+
});
|
|
1349
|
+
const installOk =
|
|
1350
|
+
typeof installResult === "object" && installResult !== null && "exitCode" in installResult
|
|
1351
|
+
? (installResult as { exitCode?: number }).exitCode === 0
|
|
1352
|
+
: true;
|
|
1353
|
+
const hookFiles = hookInstallFilesPresent(options.workspaceRoot, installResult);
|
|
1354
|
+
const memory = await readHookMemoryStatus(options.workspaceRoot, source, options.limit ?? 25);
|
|
1355
|
+
const queuedHooks = inspectQueuedHookSignals(options.workspaceRoot, installTarget);
|
|
1356
|
+
const usefulEvents = memory.events.filter(eventHasUsefulSignal);
|
|
1357
|
+
const nativeEvents = memory.events.filter(eventIsNativeHookEvent);
|
|
1358
|
+
const canaryEvents = memory.events.filter(eventIsForgeHookCanary);
|
|
1359
|
+
const installed = hookFiles.missing.length === 0;
|
|
1360
|
+
const bridgeWritable = installOk;
|
|
1361
|
+
const deltaWritable = memory.diagnostics.length === 0;
|
|
1362
|
+
const visibleInMemory = memory.events.length > 0;
|
|
1363
|
+
const queuedEvents = queuedHooks?.events ?? 0;
|
|
1364
|
+
const usefulSignals = usefulEvents.length + (queuedHooks?.usefulSignals ?? 0);
|
|
1365
|
+
const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
|
|
1366
|
+
const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
|
|
1367
|
+
const visibleHookSignals = visibleInMemory || queuedEvents > 0;
|
|
1368
|
+
const observableSignals = Math.max(usefulSignals, canarySignals);
|
|
1369
|
+
const approvalStatus = hookApprovalStatusFor(target, installed, nativeSignals, observableSignals, deltaWritable);
|
|
1370
|
+
const nativeTrustStatus = hookNativeTrustStatusFor(target, installed, nativeSignals, deltaWritable);
|
|
1371
|
+
const approvalRequired = approvalStatus === "waiting-for-user-trust";
|
|
1372
|
+
const codexHookInspection = installTarget === "codex"
|
|
1373
|
+
? inspectCodexHookCommands(options.workspaceRoot)
|
|
1374
|
+
: undefined;
|
|
1375
|
+
const codexHookMeta = installTarget === "codex"
|
|
1376
|
+
? readCodexHookMeta(options.workspaceRoot)
|
|
1377
|
+
: undefined;
|
|
1378
|
+
const codexVersionMatch = installTarget === "codex"
|
|
1379
|
+
? compareHookForgeVersions(codexHookMeta ?? null)
|
|
1380
|
+
: undefined;
|
|
1381
|
+
const forgeOnPath = installTarget === "codex"
|
|
1382
|
+
? resolveForgeOnPath(options.workspaceRoot)
|
|
1383
|
+
: undefined;
|
|
1384
|
+
const usesLegacyForgeCli = codexHookInspection?.usesLegacyForgeCli === true;
|
|
1385
|
+
const usesLightweightRunner = codexHookInspection?.usesLightweightRunner === true;
|
|
1386
|
+
const hookCommandHealthy = installTarget !== "codex" || (usesLightweightRunner && !usesLegacyForgeCli);
|
|
1387
|
+
const hookVersionHealthy = installTarget !== "codex" || !usesLightweightRunner || codexVersionMatch?.matches === true;
|
|
1388
|
+
const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 && !approvalRequired && hookCommandHealthy && hookVersionHealthy;
|
|
1389
|
+
const nextActions = ok
|
|
1390
|
+
? uniqueCommands([
|
|
1391
|
+
...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
|
|
1392
|
+
`forge agent memory --entry ${source} --json`,
|
|
1393
|
+
`forge agent context --current --json`,
|
|
1394
|
+
])
|
|
1395
|
+
: uniqueCommands([
|
|
1396
|
+
...(!installed ? [`forge agent install ${installTarget} --json`] : []),
|
|
1397
|
+
...(usesLegacyForgeCli ? [`forge agent install ${installTarget} --force --json`] : []),
|
|
1398
|
+
...(codexVersionMatch && !codexVersionMatch.matches ? [`forge agent install ${installTarget} --force --json`] : []),
|
|
1399
|
+
...(approvalRequired ? hookApprovalNextActions(target, canarySignals) : []),
|
|
1400
|
+
...(installed && deltaWritable && !visibleHookSignals ? [`forge agent hooks smoke --target ${target} --json`] : []),
|
|
1401
|
+
...(visibleHookSignals && usefulSignals === 0 ? [`forge agent ingest ${source} --event PostToolUse --json`] : []),
|
|
1402
|
+
...(!deltaWritable ? ["forge delta status --json", "forge delta repair --dry-run --json"] : []),
|
|
1403
|
+
...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
|
|
1404
|
+
...(usesLightweightRunner ? [`forge agent ingest ${source} --watch --file .forge/agent/events.ndjson --json`] : []),
|
|
1405
|
+
]);
|
|
1406
|
+
|
|
1407
|
+
return {
|
|
1408
|
+
ok,
|
|
1409
|
+
target,
|
|
1410
|
+
installTarget,
|
|
1411
|
+
installed,
|
|
1412
|
+
bridgeWritable,
|
|
1413
|
+
deltaWritable,
|
|
1414
|
+
visibleInMemory,
|
|
1415
|
+
recentEvents: memory.events.length,
|
|
1416
|
+
queuedEvents,
|
|
1417
|
+
usefulSignals,
|
|
1418
|
+
nativeSignals,
|
|
1419
|
+
canarySignals,
|
|
1420
|
+
approvalRequired,
|
|
1421
|
+
approvalStatus,
|
|
1422
|
+
nativeTrustStatus,
|
|
1423
|
+
workspaceRoot: memory.workspaceRoot,
|
|
1424
|
+
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1425
|
+
...(lastAgentSignal(memory.events) ? { lastSignal: lastAgentSignal(memory.events) } : {}),
|
|
1426
|
+
checks: [
|
|
1427
|
+
{
|
|
1428
|
+
name: "hook-bridge-installed",
|
|
1429
|
+
ok: installed,
|
|
1430
|
+
message: installed
|
|
1431
|
+
? `${installTarget} hook bridge files are present`
|
|
1432
|
+
: `missing hook bridge files: ${hookFiles.missing.join(", ")}`,
|
|
1433
|
+
evidence: { planned: hookFiles.planned, missing: hookFiles.missing },
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
name: "hook-bridge-installable",
|
|
1437
|
+
ok: bridgeWritable,
|
|
1438
|
+
message: bridgeWritable ? "hook bridge install plan is valid" : "hook bridge install failed",
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: "agent-memory-readable",
|
|
1442
|
+
ok: deltaWritable,
|
|
1443
|
+
message: deltaWritable ? "agent memory store is readable" : "agent memory store is unavailable",
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: "visible-in-memory",
|
|
1447
|
+
ok: visibleHookSignals,
|
|
1448
|
+
message: visibleInMemory
|
|
1449
|
+
? `${memory.events.length} recent ${source} events visible for this workspace`
|
|
1450
|
+
: queuedEvents > 0
|
|
1451
|
+
? `${queuedEvents} queued ${source} hook event(s) visible for this workspace and waiting for ingest`
|
|
1452
|
+
: memory.ignoredOutOfWorkspaceEvents > 0
|
|
1453
|
+
? `ignored ${memory.ignoredOutOfWorkspaceEvents} ${source} event(s) from other workspaces`
|
|
1454
|
+
: "no hook events visible in memory yet",
|
|
1455
|
+
evidence: {
|
|
1456
|
+
workspaceRoot: memory.workspaceRoot,
|
|
1457
|
+
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1458
|
+
queuedEvents,
|
|
1459
|
+
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1460
|
+
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1461
|
+
queuedLatestEventAt: queuedHooks?.latestEventAt,
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
name: "workspace-scope",
|
|
1466
|
+
ok: true,
|
|
1467
|
+
message: memory.ignoredOutOfWorkspaceEvents > 0
|
|
1468
|
+
? `ignored ${memory.ignoredOutOfWorkspaceEvents} out-of-workspace agent event(s)`
|
|
1469
|
+
: "agent memory events are scoped to this workspace",
|
|
1470
|
+
evidence: {
|
|
1471
|
+
workspaceRoot: memory.workspaceRoot,
|
|
1472
|
+
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
name: "useful-signals",
|
|
1477
|
+
ok: usefulSignals > 0,
|
|
1478
|
+
message: usefulSignals > 0
|
|
1479
|
+
? `${usefulSignals} events include useful tool, file, command, status, entry, or proof signals`
|
|
1480
|
+
: "no useful tool, file, command, status, entry, or proof signals found",
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
name: "native-hook-signal",
|
|
1484
|
+
ok: target !== "codex" || !deltaWritable || nativeSignals > 0 || approvalStatus === "accepted",
|
|
1485
|
+
message: target !== "codex"
|
|
1486
|
+
? "native Codex hook approval is not required for this target"
|
|
1487
|
+
: !deltaWritable
|
|
1488
|
+
? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
|
|
1489
|
+
: nativeSignals > 0
|
|
1490
|
+
? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
|
|
1491
|
+
: "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
|
|
1492
|
+
evidence: {
|
|
1493
|
+
nativeSignals,
|
|
1494
|
+
canarySignals,
|
|
1495
|
+
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1496
|
+
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1497
|
+
memoryReadable: deltaWritable,
|
|
1498
|
+
nativeTrustStatus,
|
|
1499
|
+
trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
name: "codex-hook-approval",
|
|
1504
|
+
ok: !approvalRequired && approvalStatus !== "memory-unavailable",
|
|
1505
|
+
message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
|
|
1506
|
+
evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
|
|
1507
|
+
},
|
|
1508
|
+
...(installTarget === "codex"
|
|
1509
|
+
? [
|
|
1510
|
+
{
|
|
1511
|
+
name: "hook-runner-mode",
|
|
1512
|
+
ok: hookCommandHealthy,
|
|
1513
|
+
message: usesLegacyForgeCli
|
|
1514
|
+
? "Codex hooks still call full forge agent ingest; reinstall with forge agent install codex --force"
|
|
1515
|
+
: usesLightweightRunner
|
|
1516
|
+
? "Codex hooks use the lightweight workspace runner (.forge/agent/codex-hook.mjs)"
|
|
1517
|
+
: "Codex hook command mode is unknown; reinstall hooks",
|
|
1518
|
+
evidence: codexHookInspection,
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
name: "hook-forge-version",
|
|
1522
|
+
ok: hookVersionHealthy,
|
|
1523
|
+
message: codexVersionMatch?.matches
|
|
1524
|
+
? `hook manifest matches runtime Forge ${releaseManifest.packageVersion}`
|
|
1525
|
+
: codexVersionMatch?.installedVersion
|
|
1526
|
+
? `hook manifest is ${codexVersionMatch.installedVersion}, runtime is ${codexVersionMatch.runtimeVersion}`
|
|
1527
|
+
: "hook manifest is missing; reinstall hooks to pin workspace runner version",
|
|
1528
|
+
evidence: {
|
|
1529
|
+
manifest: codexHookMeta,
|
|
1530
|
+
runtimeVersion: releaseManifest.packageVersion,
|
|
1531
|
+
forgeOnPath,
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
name: "hook-global-forge",
|
|
1536
|
+
ok: !usesLegacyForgeCli,
|
|
1537
|
+
message: usesLegacyForgeCli
|
|
1538
|
+
? `hooks call global/legacy forge CLI${forgeOnPath?.path ? ` (${forgeOnPath.path}${forgeOnPath.version ? ` ${forgeOnPath.version}` : ""})` : ""}`
|
|
1539
|
+
: forgeOnPath
|
|
1540
|
+
? `PATH forge resolves to ${forgeOnPath.path}${forgeOnPath.version ? ` (${forgeOnPath.version})` : ""}; hooks use workspace runner instead`
|
|
1541
|
+
: "hooks use workspace runner; no global forge resolution needed",
|
|
1542
|
+
evidence: { forgeOnPath, usesLegacyForgeCli },
|
|
1543
|
+
},
|
|
1544
|
+
]
|
|
1545
|
+
: []),
|
|
1546
|
+
],
|
|
1547
|
+
nextActions,
|
|
1548
|
+
installResult,
|
|
1549
|
+
diagnostics: [
|
|
1550
|
+
...memory.diagnostics,
|
|
1551
|
+
...(usesLegacyForgeCli
|
|
1552
|
+
? [createDiagnostic({
|
|
1553
|
+
severity: "warning",
|
|
1554
|
+
code: "FORGE_AGENT_HOOK_LEGACY_CLI",
|
|
1555
|
+
message: "Codex hooks still spawn the full Forge CLI per event. Reinstall with forge agent install codex --force to use the lightweight queue runner.",
|
|
1556
|
+
suggestedCommands: [`forge agent install ${installTarget} --force --json`, `forge agent hooks smoke --target ${target} --json`],
|
|
1557
|
+
})]
|
|
1558
|
+
: []),
|
|
1559
|
+
...(codexVersionMatch && !codexVersionMatch.matches
|
|
1560
|
+
? [createDiagnostic({
|
|
1561
|
+
severity: "warning",
|
|
1562
|
+
code: "FORGE_AGENT_HOOK_VERSION_MISMATCH",
|
|
1563
|
+
message: `Installed Codex hook manifest is ${codexVersionMatch.installedVersion}, but this Forge runtime is ${codexVersionMatch.runtimeVersion}.`,
|
|
1564
|
+
suggestedCommands: [`forge agent install ${installTarget} --force --json`],
|
|
1565
|
+
})]
|
|
1566
|
+
: []),
|
|
1567
|
+
],
|
|
1568
|
+
exitCode: ok ? 0 : 1,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
export async function runAgentDoctor(options: AgentCommandOptions): Promise<AgentDoctorResult> {
|
|
1573
|
+
const target = options.target || "generic";
|
|
934
1574
|
const check = runAgentCheck(options);
|
|
1575
|
+
const source = agentMemorySourceForTarget(target);
|
|
1576
|
+
const installTarget = hookInstallTarget(target);
|
|
1577
|
+
const installResult = installTarget
|
|
1578
|
+
? await runAgentMemoryCommand({
|
|
1579
|
+
subcommand: "install",
|
|
1580
|
+
workspaceRoot: options.workspaceRoot,
|
|
1581
|
+
json: options.json,
|
|
1582
|
+
target: installTarget,
|
|
1583
|
+
source: installTarget,
|
|
1584
|
+
dryRun: true,
|
|
1585
|
+
force: false,
|
|
1586
|
+
})
|
|
1587
|
+
: undefined;
|
|
1588
|
+
const hookFiles = installTarget
|
|
1589
|
+
? hookInstallFilesPresent(options.workspaceRoot, installResult)
|
|
1590
|
+
: { planned: [], missing: [] };
|
|
1591
|
+
const memory = await readHookMemoryStatus(options.workspaceRoot, source, options.limit ?? 25);
|
|
1592
|
+
const queuedHooks = inspectQueuedHookSignals(options.workspaceRoot, installTarget);
|
|
1593
|
+
const memoryDiagnostics = memory.diagnostics;
|
|
1594
|
+
const recentEvents = memory.events;
|
|
1595
|
+
const usefulEvents = recentEvents.filter(eventHasUsefulSignal);
|
|
1596
|
+
const nativeEvents = recentEvents.filter(eventIsNativeHookEvent);
|
|
1597
|
+
const canaryEvents = recentEvents.filter(eventIsForgeHookCanary);
|
|
1598
|
+
const queuedEvents = queuedHooks?.events ?? 0;
|
|
1599
|
+
const visibleHookSignals = recentEvents.length > 0 || queuedEvents > 0;
|
|
1600
|
+
const usefulSignals = usefulEvents.length + (queuedHooks?.usefulSignals ?? 0);
|
|
1601
|
+
const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
|
|
1602
|
+
const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
|
|
1603
|
+
const adapterState = check.missing.length > 0 ? "missing" : check.stale.length > 0 ? "stale" : "ready";
|
|
1604
|
+
const installed = !installTarget || hookFiles.missing.length === 0;
|
|
1605
|
+
const memoryReadable = memoryDiagnostics.length === 0;
|
|
1606
|
+
const observableSignals = Math.max(usefulSignals, canarySignals);
|
|
1607
|
+
const approvalStatus = hookApprovalStatusFor(
|
|
1608
|
+
target,
|
|
1609
|
+
Boolean(installTarget && installed),
|
|
1610
|
+
nativeSignals,
|
|
1611
|
+
observableSignals,
|
|
1612
|
+
memoryReadable,
|
|
1613
|
+
);
|
|
1614
|
+
const nativeTrustStatus = hookNativeTrustStatusFor(target, Boolean(installTarget && installed), nativeSignals, memoryReadable);
|
|
1615
|
+
const approvalRequired = approvalStatus === "waiting-for-user-trust";
|
|
1616
|
+
const hookBridgeState = !installTarget
|
|
1617
|
+
? "not-supported"
|
|
1618
|
+
: !memoryReadable
|
|
1619
|
+
? "memory-unavailable"
|
|
1620
|
+
: approvalRequired
|
|
1621
|
+
? "waiting-for-user-trust"
|
|
1622
|
+
: hookFiles.missing.length === 0
|
|
1623
|
+
? "ready"
|
|
1624
|
+
: "missing";
|
|
935
1625
|
const checks = [
|
|
936
|
-
{
|
|
1626
|
+
{
|
|
1627
|
+
name: "adapter-export",
|
|
1628
|
+
ok: check.missing.length === 0 && check.stale.length === 0,
|
|
1629
|
+
message: adapterState === "ready" ? "agent adapter exports are current" : `adapter exports are ${adapterState}`,
|
|
1630
|
+
evidence: { missing: check.missing, stale: check.stale },
|
|
1631
|
+
},
|
|
1632
|
+
{ name: "AGENTS.md", ok: readText(options.workspaceRoot, "AGENTS.md") !== null },
|
|
937
1633
|
{ name: "agent-context", ok: !check.missing.includes(".forge/agent/context.json") },
|
|
938
1634
|
{ name: "commands", ok: !check.missing.includes(".forge/agent/commands.json") },
|
|
939
1635
|
{ name: "done-criteria", ok: !check.missing.includes(".forge/agent/done-criteria.json") },
|
|
940
|
-
{
|
|
1636
|
+
{
|
|
1637
|
+
name: "hook-bridge",
|
|
1638
|
+
ok: !installTarget || hookFiles.missing.length === 0,
|
|
1639
|
+
message: !installTarget
|
|
1640
|
+
? "this target has no native hook bridge"
|
|
1641
|
+
: hookFiles.missing.length === 0
|
|
1642
|
+
? `${installTarget} hook bridge files are present`
|
|
1643
|
+
: `missing hook bridge files: ${hookFiles.missing.join(", ")}`,
|
|
1644
|
+
evidence: { target: installTarget, planned: hookFiles.planned, missing: hookFiles.missing },
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
name: "recent-memory",
|
|
1648
|
+
ok: (!installTarget || visibleHookSignals) && memoryDiagnostics.length === 0,
|
|
1649
|
+
message: memoryDiagnostics.length > 0
|
|
1650
|
+
? "agent memory store is unavailable"
|
|
1651
|
+
: recentEvents.length > 0
|
|
1652
|
+
? `${recentEvents.length} recent ${source} memory events for this workspace`
|
|
1653
|
+
: queuedEvents > 0
|
|
1654
|
+
? `${queuedEvents} queued ${source} hook event(s) for this workspace are waiting for ingest`
|
|
1655
|
+
: memory.ignoredOutOfWorkspaceEvents > 0
|
|
1656
|
+
? `ignored ${memory.ignoredOutOfWorkspaceEvents} ${source} event(s) from other workspaces`
|
|
1657
|
+
: "no recent agent memory events found",
|
|
1658
|
+
evidence: {
|
|
1659
|
+
workspaceRoot: memory.workspaceRoot,
|
|
1660
|
+
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1661
|
+
recent: recentEvents.slice(-5).map((event) => ({
|
|
1662
|
+
kind: event.normalizedKind,
|
|
1663
|
+
summary: event.summary,
|
|
1664
|
+
capturedAt: event.capturedAt,
|
|
1665
|
+
workspaceRoot: eventWorkspaceRoot(event),
|
|
1666
|
+
})),
|
|
1667
|
+
queuedEvents,
|
|
1668
|
+
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1669
|
+
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1670
|
+
queuedLatestEventAt: queuedHooks?.latestEventAt,
|
|
1671
|
+
},
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
name: "workspace-scope",
|
|
1675
|
+
ok: true,
|
|
1676
|
+
message: memory.ignoredOutOfWorkspaceEvents > 0
|
|
1677
|
+
? `ignored ${memory.ignoredOutOfWorkspaceEvents} out-of-workspace agent event(s)`
|
|
1678
|
+
: "agent memory events are scoped to this workspace",
|
|
1679
|
+
evidence: {
|
|
1680
|
+
workspaceRoot: memory.workspaceRoot,
|
|
1681
|
+
ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "useful-signals",
|
|
1686
|
+
ok: !installTarget || usefulSignals > 0,
|
|
1687
|
+
message: usefulSignals > 0
|
|
1688
|
+
? `${usefulSignals} events include files, entries, commands, tools, status, or proofs`
|
|
1689
|
+
: "events do not yet include useful files, entries, commands, tools, status, or proofs",
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
name: "native-hook-signal",
|
|
1693
|
+
ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0 || approvalStatus === "accepted",
|
|
1694
|
+
message: !installTarget || target !== "codex"
|
|
1695
|
+
? "native Codex hook approval is not required for this target"
|
|
1696
|
+
: !memoryReadable
|
|
1697
|
+
? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
|
|
1698
|
+
: nativeSignals > 0
|
|
1699
|
+
? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
|
|
1700
|
+
: "hook approval is accepted, but Codex has not emitted a trusted native hook signal yet",
|
|
1701
|
+
evidence: {
|
|
1702
|
+
nativeSignals,
|
|
1703
|
+
canarySignals,
|
|
1704
|
+
queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
|
|
1705
|
+
queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
|
|
1706
|
+
memoryReadable,
|
|
1707
|
+
nativeTrustStatus,
|
|
1708
|
+
trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
|
|
1709
|
+
},
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
name: "codex-hook-approval",
|
|
1713
|
+
ok: !approvalRequired && approvalStatus !== "memory-unavailable",
|
|
1714
|
+
message: codexHookApprovalMessage(approvalStatus, nativeTrustStatus, canarySignals),
|
|
1715
|
+
evidence: { approvalStatus, approvalRequired, nativeTrustStatus },
|
|
1716
|
+
},
|
|
941
1717
|
{ name: "secret-scan", ok: !check.diagnostics.some((diag) => diag.code === FORGE_AGENT_SECRET_LEAK) },
|
|
942
1718
|
];
|
|
943
1719
|
const ok = checks.every((item) => item.ok) && check.exitCode === 0;
|
|
944
|
-
|
|
1720
|
+
const nextActions = ok
|
|
1721
|
+
? [
|
|
1722
|
+
`forge agent context --current --json`,
|
|
1723
|
+
`forge agent memory --entry ${source} --json`,
|
|
1724
|
+
]
|
|
1725
|
+
: [
|
|
1726
|
+
...(check.missing.length > 0 || check.stale.length > 0 ? [`forge agent export --target ${target}`] : []),
|
|
1727
|
+
...(installTarget && hookFiles.missing.length > 0 ? [`forge agent install ${installTarget} --json`] : []),
|
|
1728
|
+
...(approvalRequired ? hookApprovalNextActions(target, canarySignals) : []),
|
|
1729
|
+
...(memoryDiagnostics.length > 0 ? ["forge delta status --json", "forge delta repair --dry-run --json"] : []),
|
|
1730
|
+
...(installTarget && !visibleHookSignals && memoryDiagnostics.length === 0 ? [`forge agent hooks smoke --target ${target} --json`] : []),
|
|
1731
|
+
...(installTarget && visibleHookSignals && usefulSignals === 0 ? [`forge agent ingest ${source} --event PostToolUse --json`] : []),
|
|
1732
|
+
...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
|
|
1733
|
+
];
|
|
1734
|
+
return {
|
|
1735
|
+
ok,
|
|
1736
|
+
target,
|
|
1737
|
+
summary: {
|
|
1738
|
+
adapter: adapterState,
|
|
1739
|
+
hookBridge: hookBridgeState,
|
|
1740
|
+
approvalRequired,
|
|
1741
|
+
approvalStatus,
|
|
1742
|
+
nativeTrustStatus,
|
|
1743
|
+
recentEvents: recentEvents.length,
|
|
1744
|
+
queuedEvents,
|
|
1745
|
+
usefulSignals,
|
|
1746
|
+
nativeSignals,
|
|
1747
|
+
canarySignals,
|
|
1748
|
+
...(recentEvents.at(-1)?.capturedAt || queuedHooks?.latestEventAt
|
|
1749
|
+
? { lastEventAt: queuedHooks?.latestEventAt ?? recentEvents.at(-1)?.capturedAt }
|
|
1750
|
+
: {}),
|
|
1751
|
+
},
|
|
1752
|
+
checks,
|
|
1753
|
+
nextActions,
|
|
1754
|
+
diagnostics: [...check.diagnostics, ...memoryDiagnostics],
|
|
1755
|
+
exitCode: ok ? 0 : 1,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function hookInstallTarget(target: AgentAdapterTarget): string | null {
|
|
1760
|
+
if (target === "codex") return "codex";
|
|
1761
|
+
if (target === "claude") return "claude-code";
|
|
1762
|
+
if (target === "cursor") return "cursor";
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function openCommandForTarget(target: AgentAdapterTarget): string | undefined {
|
|
1767
|
+
if (target === "codex") return "codex";
|
|
1768
|
+
if (target === "claude") return "claude";
|
|
1769
|
+
if (target === "cursor") return "cursor .";
|
|
1770
|
+
return undefined;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function agentCommandHints(target: AgentAdapterTarget): AgentPrepareResult["commands"] {
|
|
1774
|
+
const installTarget = hookInstallTarget(target);
|
|
1775
|
+
return {
|
|
1776
|
+
context: "forge agent context --current --json",
|
|
1777
|
+
export: `forge agent export --target ${target}`,
|
|
1778
|
+
check: `forge agent check --target ${target} --json`,
|
|
1779
|
+
...(installTarget ? { install: `forge agent install ${installTarget} --json` } : {}),
|
|
1780
|
+
...(installTarget ? { hooksStatus: `forge agent hooks status --target ${target} --json` } : {}),
|
|
1781
|
+
...(installTarget ? { hooksSmoke: `forge agent hooks smoke --target ${target} --json` } : {}),
|
|
1782
|
+
...(openCommandForTarget(target) ? { open: openCommandForTarget(target) } : {}),
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
export async function runAgentPrepare(options: AgentCommandOptions): Promise<AgentPrepareResult> {
|
|
1787
|
+
const target = options.target || "generic";
|
|
1788
|
+
const exportResult = runAgentExport({ ...options, target });
|
|
1789
|
+
const installTarget = hookInstallTarget(target);
|
|
1790
|
+
const installResult = installTarget
|
|
1791
|
+
? await runAgentMemoryCommand({
|
|
1792
|
+
subcommand: "install",
|
|
1793
|
+
workspaceRoot: options.workspaceRoot,
|
|
1794
|
+
json: options.json,
|
|
1795
|
+
target: installTarget,
|
|
1796
|
+
source: installTarget,
|
|
1797
|
+
dryRun: options.dryRun,
|
|
1798
|
+
force: options.force,
|
|
1799
|
+
})
|
|
1800
|
+
: undefined;
|
|
1801
|
+
const checkResult = runAgentCheck({ ...options, target });
|
|
1802
|
+
const diagnostics = [
|
|
1803
|
+
...exportResult.diagnostics,
|
|
1804
|
+
...checkResult.diagnostics,
|
|
1805
|
+
];
|
|
1806
|
+
const installOk =
|
|
1807
|
+
!installResult ||
|
|
1808
|
+
(typeof installResult === "object" && installResult !== null && "exitCode" in installResult
|
|
1809
|
+
? (installResult as { exitCode?: number }).exitCode === 0
|
|
1810
|
+
: true);
|
|
1811
|
+
const ok = exportResult.ok && checkResult.ok && installOk;
|
|
1812
|
+
return {
|
|
1813
|
+
ok,
|
|
1814
|
+
target,
|
|
1815
|
+
exportResult,
|
|
1816
|
+
checkResult,
|
|
1817
|
+
...(installResult ? { installResult } : {}),
|
|
1818
|
+
commands: agentCommandHints(target),
|
|
1819
|
+
diagnostics,
|
|
1820
|
+
exitCode: ok ? 0 : 1,
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function uniqueCommands(commands: Array<string | undefined>): string[] {
|
|
1825
|
+
return [...new Set(commands.filter((command): command is string => Boolean(command)))];
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
export async function runAgentOnboard(options: AgentCommandOptions): Promise<AgentOnboardResult> {
|
|
1829
|
+
const target = options.target || "codex";
|
|
1830
|
+
const installTarget = hookInstallTarget(target);
|
|
1831
|
+
const initialContext = runAgentPrintContext(options.workspaceRoot);
|
|
1832
|
+
const preflightDev = initialContext.context === null
|
|
1833
|
+
? await runDevConsoleCycle({
|
|
1834
|
+
workspaceRoot: options.workspaceRoot,
|
|
1835
|
+
mode: "once",
|
|
1836
|
+
strictSecrets: false,
|
|
1837
|
+
includeImpact: false,
|
|
1838
|
+
})
|
|
1839
|
+
: undefined;
|
|
1840
|
+
const prepare = await runAgentPrepare({ ...options, target });
|
|
1841
|
+
const hookSmoke = installTarget && !options.dryRun
|
|
1842
|
+
? await runAgentHooksSmoke({ ...options, target, subcommand: "hooks", hookAction: "smoke" })
|
|
1843
|
+
: undefined;
|
|
1844
|
+
const doctor = await runAgentDoctor({ ...options, target, subcommand: "doctor" });
|
|
1845
|
+
const context = runAgentPrintContext(options.workspaceRoot);
|
|
1846
|
+
const dev = await runDevConsoleCycle({
|
|
1847
|
+
workspaceRoot: options.workspaceRoot,
|
|
1848
|
+
mode: "once",
|
|
1849
|
+
strictSecrets: true,
|
|
1850
|
+
includeImpact: true,
|
|
1851
|
+
});
|
|
1852
|
+
const agentContext = dev.summary.agentContext;
|
|
1853
|
+
const diagnostics = [
|
|
1854
|
+
...(preflightDev?.ok ? [] : initialContext.diagnostics),
|
|
1855
|
+
...(preflightDev?.diagnostics ?? []),
|
|
1856
|
+
...prepare.diagnostics,
|
|
1857
|
+
...(hookSmoke?.diagnostics ?? []),
|
|
1858
|
+
...doctor.diagnostics,
|
|
1859
|
+
...context.diagnostics,
|
|
1860
|
+
...dev.diagnostics,
|
|
1861
|
+
];
|
|
1862
|
+
const readyToEdit =
|
|
1863
|
+
prepare.ok &&
|
|
1864
|
+
context.context !== null &&
|
|
1865
|
+
dev.ok &&
|
|
1866
|
+
agentContext.safeToEdit &&
|
|
1867
|
+
(!hookSmoke || hookSmoke.ok) &&
|
|
1868
|
+
doctor.summary.adapter === "ready" &&
|
|
1869
|
+
!doctor.summary.approvalRequired;
|
|
1870
|
+
const steps = [
|
|
1871
|
+
...(preflightDev
|
|
1872
|
+
? [{
|
|
1873
|
+
name: "generated-preflight",
|
|
1874
|
+
ok: preflightDev.ok,
|
|
1875
|
+
message: preflightDev.ok
|
|
1876
|
+
? "generated context was created before adapter preparation"
|
|
1877
|
+
: "generated context preflight failed",
|
|
1878
|
+
}]
|
|
1879
|
+
: []),
|
|
1880
|
+
{
|
|
1881
|
+
name: "adapter-prepare",
|
|
1882
|
+
ok: prepare.ok,
|
|
1883
|
+
message: prepare.ok
|
|
1884
|
+
? `${target} adapter files are present and current`
|
|
1885
|
+
: `${target} adapter files need attention`,
|
|
1886
|
+
},
|
|
1887
|
+
...(hookSmoke
|
|
1888
|
+
? [{
|
|
1889
|
+
name: "hook-smoke",
|
|
1890
|
+
ok: hookSmoke.ok,
|
|
1891
|
+
message: hookSmoke.approvalRequired
|
|
1892
|
+
? `${target} hook files are installed, but no accepted hook signal is visible yet`
|
|
1893
|
+
: hookSmoke.ok
|
|
1894
|
+
? `${target} hooks recorded a useful canary signal`
|
|
1895
|
+
: `${target} hooks did not prove memory visibility`,
|
|
1896
|
+
}]
|
|
1897
|
+
: [{
|
|
1898
|
+
name: "hook-smoke",
|
|
1899
|
+
ok: !installTarget,
|
|
1900
|
+
message: installTarget
|
|
1901
|
+
? "hook smoke skipped because this was a dry run"
|
|
1902
|
+
: "this target has no native hook bridge",
|
|
1903
|
+
}]),
|
|
1904
|
+
...(installTarget
|
|
1905
|
+
? [{
|
|
1906
|
+
name: "hook-approval",
|
|
1907
|
+
ok: !doctor.summary.approvalRequired,
|
|
1908
|
+
message: doctor.summary.approvalRequired
|
|
1909
|
+
? "approve the installed hooks in Codex Desktop if prompted, then continue or start a Codex session in this workspace"
|
|
1910
|
+
: doctor.summary.approvalStatus === "memory-unavailable"
|
|
1911
|
+
? "Codex hook trust cannot be verified until Agent Memory is readable"
|
|
1912
|
+
: doctor.summary.approvalStatus === "trusted"
|
|
1913
|
+
? "Codex hook trust is confirmed by a native hook signal"
|
|
1914
|
+
: doctor.summary.approvalStatus === "accepted"
|
|
1915
|
+
? "Codex hook approval is accepted; trusted native signal proof is still pending"
|
|
1916
|
+
: "hook approval is not required for this target",
|
|
1917
|
+
}]
|
|
1918
|
+
: []),
|
|
1919
|
+
{
|
|
1920
|
+
name: "agent-doctor",
|
|
1921
|
+
ok: doctor.ok,
|
|
1922
|
+
message: doctor.ok ? "adapter, hooks, and memory are ready" : "agent doctor found follow-up actions",
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
name: "dev-snapshot",
|
|
1926
|
+
ok: dev.ok && agentContext.safeToEdit,
|
|
1927
|
+
message: dev.ok
|
|
1928
|
+
? `safeToEdit=${agentContext.safeToEdit}; generatedFresh=${agentContext.generatedFresh}; generatedChangedFiles=${agentContext.generatedChangedFiles}; changedFiles=${agentContext.changedFiles}`
|
|
1929
|
+
: "dev snapshot found blocking diagnostics",
|
|
1930
|
+
},
|
|
1931
|
+
{
|
|
1932
|
+
name: "context",
|
|
1933
|
+
ok: context.context !== null,
|
|
1934
|
+
message: context.context ? "generated agent context is readable" : "generated agent context is missing",
|
|
1935
|
+
},
|
|
1936
|
+
];
|
|
1937
|
+
const commandHints = agentCommandHints(target);
|
|
1938
|
+
const nextActions = readyToEdit
|
|
1939
|
+
? uniqueCommands([
|
|
1940
|
+
commandHints.open,
|
|
1941
|
+
"forge changed --json",
|
|
1942
|
+
"forge agent context --current --json",
|
|
1943
|
+
"forge do verify --json",
|
|
1944
|
+
])
|
|
1945
|
+
: uniqueCommands([
|
|
1946
|
+
...doctor.nextActions,
|
|
1947
|
+
...dev.nextActions.map((action) => action.command),
|
|
1948
|
+
...(context.context ? [] : ["forge generate"]),
|
|
1949
|
+
"forge dev --once --json",
|
|
1950
|
+
]);
|
|
1951
|
+
return {
|
|
1952
|
+
schemaVersion: "0.1.0",
|
|
1953
|
+
ok: readyToEdit,
|
|
1954
|
+
target,
|
|
1955
|
+
readyToEdit,
|
|
1956
|
+
summary: {
|
|
1957
|
+
adapter: doctor.summary.adapter,
|
|
1958
|
+
hookBridge: doctor.summary.hookBridge,
|
|
1959
|
+
approvalRequired: doctor.summary.approvalRequired,
|
|
1960
|
+
approvalStatus: doctor.summary.approvalStatus,
|
|
1961
|
+
nativeTrustStatus: doctor.summary.nativeTrustStatus,
|
|
1962
|
+
memorySignals: doctor.summary.usefulSignals,
|
|
1963
|
+
nativeSignals: doctor.summary.nativeSignals,
|
|
1964
|
+
canarySignals: doctor.summary.canarySignals,
|
|
1965
|
+
generatedFresh: agentContext.generatedFresh,
|
|
1966
|
+
generatedChanged: agentContext.generatedChanged,
|
|
1967
|
+
generatedChangedFiles: agentContext.generatedChangedFiles,
|
|
1968
|
+
safeToEdit: agentContext.safeToEdit,
|
|
1969
|
+
changedFiles: agentContext.changedFiles,
|
|
1970
|
+
...(dev.summary.primaryAction?.command ? { primaryAction: dev.summary.primaryAction.command } : {}),
|
|
1971
|
+
},
|
|
1972
|
+
steps,
|
|
1973
|
+
recommendedReadFiles: agentContext.recommendedReadFiles,
|
|
1974
|
+
commands: {
|
|
1975
|
+
changed: "forge changed --json",
|
|
1976
|
+
dev: "forge dev --once --json",
|
|
1977
|
+
context: "forge agent context --current --json",
|
|
1978
|
+
verify: "forge do verify --json",
|
|
1979
|
+
...(commandHints.hooksStatus ? { hooksStatus: commandHints.hooksStatus } : {}),
|
|
1980
|
+
...(commandHints.hooksSmoke ? { hooksSmoke: commandHints.hooksSmoke } : {}),
|
|
1981
|
+
...(commandHints.open ? { open: commandHints.open } : {}),
|
|
1982
|
+
},
|
|
1983
|
+
nextActions,
|
|
1984
|
+
diagnostics,
|
|
1985
|
+
exitCode: readyToEdit ? 0 : 1,
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
export async function runAgentHooksStatus(options: AgentCommandOptions): Promise<AgentHooksStatusResult> {
|
|
1990
|
+
return readAgentHookStatus(options);
|
|
945
1991
|
}
|
|
946
1992
|
|
|
947
|
-
export async function
|
|
948
|
-
|
|
949
|
-
|
|
1993
|
+
export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<AgentHooksSmokeResult> {
|
|
1994
|
+
const target = options.target || "codex";
|
|
1995
|
+
const installTarget = hookInstallTarget(target);
|
|
1996
|
+
const source = agentMemorySourceForTarget(target);
|
|
1997
|
+
const canaryMarker = "FORGE_HOOK_SMOKE_CANARY";
|
|
1998
|
+
if (!installTarget) {
|
|
1999
|
+
const diag = diagnostic(
|
|
2000
|
+
"error",
|
|
2001
|
+
"FORGE_AGENT_HOOK_TARGET_UNSUPPORTED",
|
|
2002
|
+
`agent hook smoke supports codex, claude, and cursor targets; got ${target}`,
|
|
2003
|
+
);
|
|
2004
|
+
return {
|
|
2005
|
+
ok: false,
|
|
2006
|
+
target,
|
|
2007
|
+
smokeReady: false,
|
|
2008
|
+
trustedNativeReady: false,
|
|
2009
|
+
readinessLevel: "none",
|
|
2010
|
+
installed: false,
|
|
2011
|
+
bridgeWritable: false,
|
|
2012
|
+
deltaWritable: false,
|
|
2013
|
+
visibleInMemory: false,
|
|
2014
|
+
usefulSignals: 0,
|
|
2015
|
+
nativeSignals: 0,
|
|
2016
|
+
canarySignals: 0,
|
|
2017
|
+
approvalRequired: false,
|
|
2018
|
+
approvalStatus: "not-required",
|
|
2019
|
+
nativeTrustStatus: "not-required",
|
|
2020
|
+
checks: [{ name: "target", ok: false, message: diag.message }],
|
|
2021
|
+
nextActions: ["forge agent list-targets --json"],
|
|
2022
|
+
diagnostics: [diag],
|
|
2023
|
+
exitCode: 1,
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
const installResult = await runAgentMemoryCommand({
|
|
2028
|
+
subcommand: "install",
|
|
2029
|
+
workspaceRoot: options.workspaceRoot,
|
|
2030
|
+
json: options.json,
|
|
2031
|
+
target: installTarget,
|
|
2032
|
+
source: installTarget,
|
|
2033
|
+
dryRun: options.dryRun,
|
|
2034
|
+
force: options.force,
|
|
2035
|
+
});
|
|
2036
|
+
const installOk =
|
|
2037
|
+
typeof installResult === "object" && installResult !== null && "exitCode" in installResult
|
|
2038
|
+
? (installResult as { exitCode?: number }).exitCode === 0
|
|
2039
|
+
: true;
|
|
2040
|
+
|
|
2041
|
+
const ingestResult = options.dryRun
|
|
2042
|
+
? undefined
|
|
2043
|
+
: await runAgentMemoryCommand({
|
|
2044
|
+
subcommand: "ingest",
|
|
2045
|
+
workspaceRoot: options.workspaceRoot,
|
|
2046
|
+
json: options.json,
|
|
2047
|
+
target: installTarget,
|
|
2048
|
+
source: installTarget,
|
|
2049
|
+
eventName: installTarget === "cursor" ? "FileChange" : "SessionStart",
|
|
2050
|
+
input: {
|
|
2051
|
+
forgeHookCanary: canaryMarker,
|
|
2052
|
+
cwd: options.workspaceRoot,
|
|
2053
|
+
provider: installTarget,
|
|
2054
|
+
status: "completed",
|
|
2055
|
+
summary: "Forge hook smoke event recorded",
|
|
2056
|
+
filesChanged: ["AGENTS.md"],
|
|
2057
|
+
command: "forge agent hooks smoke",
|
|
2058
|
+
},
|
|
2059
|
+
});
|
|
2060
|
+
const ingestOk =
|
|
2061
|
+
options.dryRun ||
|
|
2062
|
+
(typeof ingestResult === "object" && ingestResult !== null && "exitCode" in ingestResult
|
|
2063
|
+
? (ingestResult as { exitCode?: number }).exitCode === 0
|
|
2064
|
+
: false);
|
|
2065
|
+
const ingestDiagnostics =
|
|
2066
|
+
ingestResult &&
|
|
2067
|
+
typeof ingestResult === "object" &&
|
|
2068
|
+
"diagnostics" in ingestResult &&
|
|
2069
|
+
Array.isArray((ingestResult as { diagnostics?: unknown }).diagnostics)
|
|
2070
|
+
? (ingestResult as { diagnostics: Diagnostic[] }).diagnostics
|
|
2071
|
+
: [];
|
|
2072
|
+
const ingestNextActions =
|
|
2073
|
+
ingestResult &&
|
|
2074
|
+
typeof ingestResult === "object" &&
|
|
2075
|
+
"nextActions" in ingestResult &&
|
|
2076
|
+
Array.isArray((ingestResult as { nextActions?: unknown }).nextActions)
|
|
2077
|
+
? (ingestResult as { nextActions: unknown[] }).nextActions.filter((action): action is string => typeof action === "string")
|
|
2078
|
+
: [];
|
|
2079
|
+
const ingestStoreBusy = ingestDiagnostics.some((diag) => diag.code === "FORGE_DELTA_BUSY");
|
|
2080
|
+
const hookRunnerProbe = installTarget === "codex" && !options.dryRun
|
|
2081
|
+
? await probeCodexHookRunner(options.workspaceRoot, { maxDurationMs: 5000, stdinHangBudgetMs: 3000 })
|
|
2082
|
+
: undefined;
|
|
2083
|
+
const ingestedEventId =
|
|
2084
|
+
ingestResult &&
|
|
2085
|
+
typeof ingestResult === "object" &&
|
|
2086
|
+
"event" in ingestResult &&
|
|
2087
|
+
(ingestResult as { event?: { id?: string } }).event?.id;
|
|
2088
|
+
const memoryAfterSmoke = options.dryRun
|
|
2089
|
+
? { events: [], diagnostics: [] as Diagnostic[] }
|
|
2090
|
+
: await readHookMemoryStatus(options.workspaceRoot, source, Math.max(options.limit ?? 25, 50));
|
|
2091
|
+
const status = await readAgentHookStatus({ ...options, target });
|
|
2092
|
+
const canaryEvent = ingestedEventId
|
|
2093
|
+
? memoryAfterSmoke.events.find((event) => event.id === ingestedEventId)
|
|
2094
|
+
: undefined;
|
|
2095
|
+
const visibleInMemory = options.dryRun
|
|
2096
|
+
? false
|
|
2097
|
+
: Boolean(canaryEvent);
|
|
2098
|
+
const checks = [
|
|
2099
|
+
{ name: "hook-install", ok: installOk, message: installOk ? "hook bridge files are available" : "hook bridge install failed" },
|
|
2100
|
+
{ name: "canary-ingest", ok: ingestOk, message: options.dryRun ? "dry-run skipped ingest" : ingestOk ? "canary event was normalized and stored" : "canary ingest failed" },
|
|
2101
|
+
{
|
|
2102
|
+
name: "canary-memory-readable",
|
|
2103
|
+
ok: options.dryRun || memoryAfterSmoke.diagnostics.length === 0,
|
|
2104
|
+
message: options.dryRun
|
|
2105
|
+
? "dry-run skipped memory read"
|
|
2106
|
+
: memoryAfterSmoke.diagnostics.length === 0
|
|
2107
|
+
? `${memoryAfterSmoke.events.length} memory event(s) inspected after canary ingest`
|
|
2108
|
+
: "agent memory was not readable after canary ingest",
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
name: "canary-visible",
|
|
2112
|
+
ok: options.dryRun || !ingestOk || visibleInMemory,
|
|
2113
|
+
message: options.dryRun
|
|
2114
|
+
? "dry-run skipped memory visibility check"
|
|
2115
|
+
: !ingestOk
|
|
2116
|
+
? "not checked because canary ingest failed"
|
|
2117
|
+
: visibleInMemory
|
|
2118
|
+
? "canary event is visible in agent memory"
|
|
2119
|
+
: "canary event was not visible in agent memory",
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
name: "codex-hook-approval",
|
|
2123
|
+
ok: status.approvalStatus !== "memory-unavailable",
|
|
2124
|
+
message: codexHookApprovalMessage(status.approvalStatus, status.nativeTrustStatus, status.canarySignals),
|
|
2125
|
+
},
|
|
2126
|
+
...(installTarget === "codex"
|
|
2127
|
+
? [
|
|
2128
|
+
{
|
|
2129
|
+
name: "hook-runner-latency",
|
|
2130
|
+
ok: options.dryRun || hookRunnerProbe?.ok === true,
|
|
2131
|
+
message: options.dryRun
|
|
2132
|
+
? "dry-run skipped hook runner latency probe"
|
|
2133
|
+
: hookRunnerProbe?.ok
|
|
2134
|
+
? `lightweight hook runner exited in ${hookRunnerProbe.durationMs}ms and queued NDJSON`
|
|
2135
|
+
: hookRunnerProbe?.error ?? "hook runner latency probe failed",
|
|
2136
|
+
},
|
|
2137
|
+
{
|
|
2138
|
+
name: "hook-stdin-hang-safe",
|
|
2139
|
+
ok: options.dryRun || hookRunnerProbe?.stdinHangSafe === true,
|
|
2140
|
+
message: options.dryRun
|
|
2141
|
+
? "dry-run skipped stdin hang probe"
|
|
2142
|
+
: hookRunnerProbe?.stdinHangSafe
|
|
2143
|
+
? `hook runner exited without waiting for stdin EOF (${hookRunnerProbe.stdinHangDurationMs ?? 0}ms)`
|
|
2144
|
+
: "hook runner may hang when stdin never closes",
|
|
2145
|
+
},
|
|
2146
|
+
]
|
|
2147
|
+
: []),
|
|
2148
|
+
];
|
|
2149
|
+
const diagnostics = [
|
|
2150
|
+
...ingestDiagnostics,
|
|
2151
|
+
...memoryAfterSmoke.diagnostics,
|
|
2152
|
+
...(!installOk
|
|
2153
|
+
? [diagnostic("error", "FORGE_AGENT_HOOK_INSTALL_FAILED", `hook bridge install failed for ${installTarget}`)]
|
|
2154
|
+
: []),
|
|
2155
|
+
...(!ingestOk && !ingestStoreBusy
|
|
2156
|
+
? [diagnostic(
|
|
2157
|
+
"error",
|
|
2158
|
+
"FORGE_AGENT_HOOK_CANARY_MISSING",
|
|
2159
|
+
`Forge hook smoke did not record a canary event for ${installTarget}; install hooks and restart the external agent, then run forge agent hooks smoke --target ${target} --json`,
|
|
2160
|
+
)]
|
|
2161
|
+
: []),
|
|
2162
|
+
...(!options.dryRun && ingestOk && !visibleInMemory
|
|
2163
|
+
? [createDiagnostic({
|
|
2164
|
+
severity: "error",
|
|
2165
|
+
code: "FORGE_AGENT_HOOK_CANARY_NOT_VISIBLE",
|
|
2166
|
+
message: `Forge hook smoke ingested canary ${ingestedEventId ?? canaryMarker} for ${installTarget}, but that event was not visible in agent memory; inspect hook status and DeltaDB before trusting hooks.`,
|
|
2167
|
+
suggestedCommands: [`forge agent hooks status --target ${target} --json`, `forge agent memory --entry ${source} --json`, "forge delta status --json"],
|
|
2168
|
+
})]
|
|
2169
|
+
: []),
|
|
2170
|
+
...(status.approvalRequired
|
|
2171
|
+
? [createDiagnostic({
|
|
2172
|
+
severity: "warning",
|
|
2173
|
+
code: "FORGE_AGENT_HOOK_APPROVAL_REQUIRED",
|
|
2174
|
+
message: "Codex Desktop hook approval is still pending because ForgeOS has not seen a canary, useful hook event, or trusted native hook signal yet. Approve the hook prompt if Codex shows one, then continue a Codex session in this workspace.",
|
|
2175
|
+
suggestedCommands: hookApprovalNextActions(target, status.canarySignals),
|
|
2176
|
+
})]
|
|
2177
|
+
: []),
|
|
2178
|
+
...(hookRunnerProbe && !hookRunnerProbe.ok
|
|
2179
|
+
? [createDiagnostic({
|
|
2180
|
+
severity: "error",
|
|
2181
|
+
code: "FORGE_AGENT_HOOK_RUNNER_SLOW",
|
|
2182
|
+
message: hookRunnerProbe.error ?? `Codex hook runner probe failed after ${hookRunnerProbe.durationMs}ms`,
|
|
2183
|
+
suggestedCommands: [`forge agent install ${installTarget} --force --json`, `forge agent hooks status --target ${target} --json`],
|
|
2184
|
+
})]
|
|
2185
|
+
: []),
|
|
2186
|
+
];
|
|
2187
|
+
const smokeReady = checks.every((check) => check.ok);
|
|
2188
|
+
const trustedNativeReady =
|
|
2189
|
+
status.approvalStatus === "trusted" || status.approvalStatus === "not-required";
|
|
2190
|
+
const readinessLevel = trustedNativeReady
|
|
2191
|
+
? "trusted-native"
|
|
2192
|
+
: smokeReady
|
|
2193
|
+
? "canary"
|
|
2194
|
+
: "none";
|
|
2195
|
+
return {
|
|
2196
|
+
ok: smokeReady,
|
|
2197
|
+
target,
|
|
2198
|
+
installTarget,
|
|
2199
|
+
smokeReady,
|
|
2200
|
+
trustedNativeReady,
|
|
2201
|
+
readinessLevel,
|
|
2202
|
+
installed: status.installed,
|
|
2203
|
+
bridgeWritable: installOk,
|
|
2204
|
+
deltaWritable: status.deltaWritable && ingestOk && memoryAfterSmoke.diagnostics.length === 0,
|
|
2205
|
+
visibleInMemory,
|
|
2206
|
+
usefulSignals: status.usefulSignals,
|
|
2207
|
+
nativeSignals: status.nativeSignals,
|
|
2208
|
+
canarySignals: status.canarySignals,
|
|
2209
|
+
approvalRequired: status.approvalRequired,
|
|
2210
|
+
approvalStatus: status.approvalStatus,
|
|
2211
|
+
nativeTrustStatus: status.nativeTrustStatus,
|
|
2212
|
+
...(canaryEvent ? { lastSignal: lastAgentSignal([canaryEvent]) } : status.lastSignal ? { lastSignal: status.lastSignal } : {}),
|
|
2213
|
+
...(hookRunnerProbe ? { hookRunnerProbe } : {}),
|
|
2214
|
+
canary: {
|
|
2215
|
+
marker: canaryMarker,
|
|
2216
|
+
source,
|
|
2217
|
+
eventName: installTarget === "cursor" ? "FileChange" : "SessionStart",
|
|
2218
|
+
...(ingestedEventId ? { ingestedEventId } : {}),
|
|
2219
|
+
memoryEventsChecked: memoryAfterSmoke.events.length,
|
|
2220
|
+
visible: visibleInMemory,
|
|
2221
|
+
},
|
|
2222
|
+
checks,
|
|
2223
|
+
nextActions: smokeReady
|
|
2224
|
+
? uniqueCommands([
|
|
2225
|
+
...(status.approvalRequired ? hookApprovalNextActions(target, status.canarySignals) : []),
|
|
2226
|
+
`forge agent hooks status --target ${target} --json`,
|
|
2227
|
+
`forge agent memory --entry ${source} --json`,
|
|
2228
|
+
])
|
|
2229
|
+
: uniqueCommands([
|
|
2230
|
+
...ingestNextActions,
|
|
2231
|
+
...(status.approvalRequired ? hookApprovalNextActions(target, status.canarySignals) : []),
|
|
2232
|
+
...status.nextActions,
|
|
2233
|
+
`forge agent hooks status --target ${target} --json`,
|
|
2234
|
+
`forge agent memory --entry ${source} --json`,
|
|
2235
|
+
`forge agent timeline --target ${target} --json`,
|
|
2236
|
+
"forge delta status --json",
|
|
2237
|
+
]),
|
|
2238
|
+
installResult,
|
|
2239
|
+
...(ingestResult ? { ingestResult } : {}),
|
|
2240
|
+
diagnostics,
|
|
2241
|
+
exitCode: smokeReady ? 0 : 1,
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
type AgentCommandResult =
|
|
2246
|
+
AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult | AgentPrepareResult | AgentOnboardResult | AgentHooksSmokeResult | AgentHooksStatusResult | AgentTimelineResult | AgentMemoryCommandResult
|
|
2247
|
+
;
|
|
2248
|
+
|
|
2249
|
+
async function runAgentCommandRaw(options: AgentCommandOptions): Promise<AgentCommandResult> {
|
|
950
2250
|
if (options.subcommand === "list-targets") {
|
|
951
2251
|
return runAgentListTargets(options.workspaceRoot);
|
|
952
2252
|
}
|
|
@@ -959,12 +2259,51 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
|
959
2259
|
if (options.subcommand === "doctor") {
|
|
960
2260
|
return runAgentDoctor({ ...options, target: options.target || "generic" });
|
|
961
2261
|
}
|
|
2262
|
+
if (options.subcommand === "onboard") {
|
|
2263
|
+
return runAgentOnboard({ ...options, target: options.target || "codex" });
|
|
2264
|
+
}
|
|
962
2265
|
if (options.subcommand === "print-context") {
|
|
963
2266
|
return runAgentPrintContext(options.workspaceRoot);
|
|
964
2267
|
}
|
|
965
2268
|
if (options.subcommand === "clean") {
|
|
966
2269
|
return runAgentClean(options);
|
|
967
2270
|
}
|
|
2271
|
+
if (options.subcommand === "prepare") {
|
|
2272
|
+
return runAgentPrepare(options);
|
|
2273
|
+
}
|
|
2274
|
+
if (options.subcommand === "hooks") {
|
|
2275
|
+
if (options.hookAction === "status") {
|
|
2276
|
+
return runAgentHooksStatus(options);
|
|
2277
|
+
}
|
|
2278
|
+
return runAgentHooksSmoke(options);
|
|
2279
|
+
}
|
|
2280
|
+
if (options.subcommand === "timeline") {
|
|
2281
|
+
return runAgentTimeline(options);
|
|
2282
|
+
}
|
|
2283
|
+
if (
|
|
2284
|
+
options.subcommand === "install" ||
|
|
2285
|
+
options.subcommand === "ingest" ||
|
|
2286
|
+
options.subcommand === "context" ||
|
|
2287
|
+
options.subcommand === "memory"
|
|
2288
|
+
) {
|
|
2289
|
+
return runAgentMemoryCommand({
|
|
2290
|
+
subcommand: options.subcommand,
|
|
2291
|
+
workspaceRoot: options.workspaceRoot,
|
|
2292
|
+
json: options.json,
|
|
2293
|
+
target: options.target,
|
|
2294
|
+
source: options.target,
|
|
2295
|
+
eventName: options.eventName,
|
|
2296
|
+
input: options.input,
|
|
2297
|
+
entry: options.entry,
|
|
2298
|
+
current: options.current,
|
|
2299
|
+
dryRun: options.dryRun,
|
|
2300
|
+
force: options.force,
|
|
2301
|
+
limit: options.limit,
|
|
2302
|
+
watch: options.watch,
|
|
2303
|
+
file: options.file,
|
|
2304
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
968
2307
|
return {
|
|
969
2308
|
ok: false,
|
|
970
2309
|
target: options.target,
|
|
@@ -978,19 +2317,138 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
|
978
2317
|
};
|
|
979
2318
|
}
|
|
980
2319
|
|
|
2320
|
+
export async function runAgentCommand(options: AgentCommandOptions): Promise<AgentCommandResult> {
|
|
2321
|
+
return normalizeAgentCliCommandHints(options.workspaceRoot, await runAgentCommandRaw(options));
|
|
2322
|
+
}
|
|
2323
|
+
|
|
981
2324
|
export function formatAgentJson(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
|
|
2325
|
+
if ("timeline" in result && result.timeline === "agent") {
|
|
2326
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
2327
|
+
}
|
|
2328
|
+
if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result || "watch" in result) {
|
|
2329
|
+
return formatAgentMemoryJson(result as AgentMemoryCommandResult);
|
|
2330
|
+
}
|
|
982
2331
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
983
2332
|
}
|
|
984
2333
|
|
|
985
2334
|
export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
|
|
2335
|
+
if ("timeline" in result && result.timeline === "agent") {
|
|
2336
|
+
return [
|
|
2337
|
+
`agent timeline for ${result.target}: ${result.summary.events} event(s)`,
|
|
2338
|
+
...(result.summary.latestEventAt ? [`latest: ${result.summary.latestEventAt}`] : []),
|
|
2339
|
+
...(result.files.length > 0 ? [`files: ${result.files.slice(0, 8).join(", ")}`] : []),
|
|
2340
|
+
...(result.entries.length > 0 ? [`entries: ${result.entries.slice(0, 8).join(", ")}`] : []),
|
|
2341
|
+
"",
|
|
2342
|
+
...result.events.slice(-12).map((event) => {
|
|
2343
|
+
const parts = [
|
|
2344
|
+
event.capturedAt,
|
|
2345
|
+
event.source,
|
|
2346
|
+
event.kind,
|
|
2347
|
+
event.toolName,
|
|
2348
|
+
event.status,
|
|
2349
|
+
event.summary,
|
|
2350
|
+
].filter(Boolean);
|
|
2351
|
+
return `- ${parts.join(" | ")}`;
|
|
2352
|
+
}),
|
|
2353
|
+
...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
|
|
2354
|
+
].join("\n") + "\n";
|
|
2355
|
+
}
|
|
2356
|
+
if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result || "watch" in result) {
|
|
2357
|
+
return formatAgentMemoryHuman(result as AgentMemoryCommandResult);
|
|
2358
|
+
}
|
|
986
2359
|
if ("targets" in result) {
|
|
987
2360
|
return `${result.targets.map((target) => `${target.name}${target.default ? " (default)" : ""}${target.optional ? " (optional)" : ""}${target.custom ? " (custom)" : ""}`).join("\n")}\n`;
|
|
988
2361
|
}
|
|
989
2362
|
if ("context" in result) {
|
|
990
2363
|
return `${JSON.stringify(result.context, null, 2)}\n`;
|
|
991
2364
|
}
|
|
2365
|
+
if ("exportResult" in result) {
|
|
2366
|
+
return [
|
|
2367
|
+
`agent prepare ${result.ok ? "ok" : "failed"} for ${result.target}`,
|
|
2368
|
+
"commands:",
|
|
2369
|
+
...Object.entries(result.commands).map(([name, command]) => `- ${name}: ${command}`),
|
|
2370
|
+
"files written:",
|
|
2371
|
+
...(result.exportResult.filesWritten.length > 0 ? result.exportResult.filesWritten.map((file) => `- ${file}`) : ["- none"]),
|
|
2372
|
+
].join("\n") + "\n";
|
|
2373
|
+
}
|
|
2374
|
+
if ("readyToEdit" in result) {
|
|
2375
|
+
return [
|
|
2376
|
+
`agent onboard ${result.ok ? "ready" : "needs attention"} for ${result.target}`,
|
|
2377
|
+
`ready to edit: ${result.readyToEdit ? "yes" : "no"}`,
|
|
2378
|
+
`hook bridge: ${result.summary.hookBridge}`,
|
|
2379
|
+
`hook approval: ${result.summary.approvalStatus}`,
|
|
2380
|
+
`native trust: ${result.summary.nativeTrustStatus}`,
|
|
2381
|
+
`generated fresh: ${result.summary.generatedFresh ? "yes" : "no"}`,
|
|
2382
|
+
`generated changed: ${result.summary.generatedChangedFiles}`,
|
|
2383
|
+
`changed files: ${result.summary.changedFiles}`,
|
|
2384
|
+
"",
|
|
2385
|
+
"steps:",
|
|
2386
|
+
...result.steps.map((step) => `${step.ok ? "OK" : "WARN"} ${step.name}: ${step.message}`),
|
|
2387
|
+
...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
|
|
2388
|
+
].join("\n") + "\n";
|
|
2389
|
+
}
|
|
2390
|
+
if ("ingestResult" in result || ("checks" in result && "installResult" in result)) {
|
|
2391
|
+
const smoke = result as AgentHooksSmokeResult;
|
|
2392
|
+
return [
|
|
2393
|
+
`agent hooks smoke ${smoke.ok ? "ok" : "failed"} for ${smoke.target}`,
|
|
2394
|
+
`smoke ready: ${smoke.smokeReady ? "yes" : "no"}`,
|
|
2395
|
+
`trusted native ready: ${smoke.trustedNativeReady ? "yes" : "no"}`,
|
|
2396
|
+
`readiness level: ${smoke.readinessLevel}`,
|
|
2397
|
+
`approval: ${smoke.approvalStatus}`,
|
|
2398
|
+
`native trust: ${smoke.nativeTrustStatus}`,
|
|
2399
|
+
`native signals: ${smoke.nativeSignals}`,
|
|
2400
|
+
`canary signals: ${smoke.canarySignals}`,
|
|
2401
|
+
...smoke.checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
|
|
2402
|
+
...(smoke.canary
|
|
2403
|
+
? [
|
|
2404
|
+
"",
|
|
2405
|
+
"Canary:",
|
|
2406
|
+
` marker: ${smoke.canary.marker}`,
|
|
2407
|
+
` source: ${smoke.canary.source}`,
|
|
2408
|
+
` event: ${smoke.canary.eventName}`,
|
|
2409
|
+
...(smoke.canary.ingestedEventId ? [` ingested id: ${smoke.canary.ingestedEventId}`] : []),
|
|
2410
|
+
` memory events checked: ${smoke.canary.memoryEventsChecked}`,
|
|
2411
|
+
` visible: ${smoke.canary.visible ? "yes" : "no"}`,
|
|
2412
|
+
]
|
|
2413
|
+
: []),
|
|
2414
|
+
...(smoke.lastSignal
|
|
2415
|
+
? [
|
|
2416
|
+
"",
|
|
2417
|
+
`last signal: ${smoke.lastSignal.kind}${smoke.lastSignal.summary ? ` - ${smoke.lastSignal.summary}` : ""}`,
|
|
2418
|
+
`captured at: ${smoke.lastSignal.capturedAt}`,
|
|
2419
|
+
]
|
|
2420
|
+
: []),
|
|
2421
|
+
...(smoke.nextActions.length > 0 ? ["", "Next:", ...smoke.nextActions.map((command) => ` ${command}`)] : []),
|
|
2422
|
+
].join("\n") + "\n";
|
|
2423
|
+
}
|
|
2424
|
+
if ("installed" in result && "visibleInMemory" in result) {
|
|
2425
|
+
return [
|
|
2426
|
+
`agent hooks status ${result.ok ? "ready" : "needs attention"} for ${result.target}`,
|
|
2427
|
+
`installed: ${result.installed ? "yes" : "no"}`,
|
|
2428
|
+
`bridge writable: ${result.bridgeWritable ? "yes" : "no"}`,
|
|
2429
|
+
`delta writable: ${result.deltaWritable ? "yes" : "no"}`,
|
|
2430
|
+
`visible in memory: ${result.visibleInMemory ? "yes" : "no"}`,
|
|
2431
|
+
`useful signals: ${result.usefulSignals}`,
|
|
2432
|
+
`native signals: ${result.nativeSignals}`,
|
|
2433
|
+
`canary signals: ${result.canarySignals}`,
|
|
2434
|
+
`approval: ${result.approvalStatus}`,
|
|
2435
|
+
`native trust: ${result.nativeTrustStatus}`,
|
|
2436
|
+
...("recentEvents" in result ? [`recent events: ${result.recentEvents}`] : []),
|
|
2437
|
+
...("queuedEvents" in result ? [`queued events: ${result.queuedEvents ?? 0}`] : []),
|
|
2438
|
+
...(result.lastSignal ? [`last signal: ${result.lastSignal.kind}${result.lastSignal.summary ? ` - ${result.lastSignal.summary}` : ""}`] : []),
|
|
2439
|
+
...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
|
|
2440
|
+
].join("\n") + "\n";
|
|
2441
|
+
}
|
|
992
2442
|
if ("checks" in result) {
|
|
993
|
-
|
|
2443
|
+
const nextActions = "nextActions" in result && Array.isArray(result.nextActions)
|
|
2444
|
+
? result.nextActions as string[]
|
|
2445
|
+
: [];
|
|
2446
|
+
return [
|
|
2447
|
+
`Forge Agent Doctor ${result.ok ? "ready" : "needs attention"}`,
|
|
2448
|
+
"",
|
|
2449
|
+
...result.checks.map((check) => `${check.ok ? "OK" : "WARN"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
|
|
2450
|
+
...(nextActions.length > 0 ? ["", "Next:", ...nextActions.map((command) => ` ${command}`)] : []),
|
|
2451
|
+
].join("\n") + "\n";
|
|
994
2452
|
}
|
|
995
2453
|
if ("stale" in result) {
|
|
996
2454
|
if (result.ok) {
|
|
@@ -998,5 +2456,6 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
|
|
|
998
2456
|
}
|
|
999
2457
|
return `agent adapter exports are stale\nmissing: ${result.missing.join(", ") || "none"}\nstale: ${result.stale.join(", ") || "none"}\n`;
|
|
1000
2458
|
}
|
|
1001
|
-
|
|
2459
|
+
const exportResult = result as AgentExportResult;
|
|
2460
|
+
return `agent export ${exportResult.ok ? "ok" : "failed"} for ${exportResult.target}\nfiles written:\n${exportResult.filesWritten.map((file: string) => `- ${file}`).join("\n") || "- none"}\n`;
|
|
1002
2461
|
}
|