@geraldmaron/construct 1.4.2 → 1.5.1
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/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +57 -45
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +273 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +104 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -27,6 +27,21 @@
|
|
|
27
27
|
* mistake a prepared task for executed specialist output. When the execution
|
|
28
28
|
* contract resolves to prompt-only or host-direct, Construct owns no specialist
|
|
29
29
|
* task sequence and the run records that explicitly.
|
|
30
|
+
*
|
|
31
|
+
* Tenant propagation (ADR-0057/A7, LMCP-H1): resolveTenantContext runs once
|
|
32
|
+
* per planRun/executeRun call and its tenantId rides the run record, every
|
|
33
|
+
* task, and every emitted trace/run event. Enterprise mode with no
|
|
34
|
+
* resolvable tenant throws TenantResolutionError — planning and resuming a
|
|
35
|
+
* run both fail closed rather than persisting an unlabeled run.
|
|
36
|
+
*
|
|
37
|
+
* Run-level executionState (LMCP-F4): executeRun aggregates every task's
|
|
38
|
+
* LMCP-F1 executionState (prepared|executed|degraded-executed|failed) into one
|
|
39
|
+
* `run.executionState` via aggregateExecutionState, so a caller (construct
|
|
40
|
+
* status, an MCP host) never has to walk run.tasks to learn whether a run only
|
|
41
|
+
* prepared work or actually executed it. Precedence: failed >
|
|
42
|
+
* degraded-executed > executed > prepared; a zero-task run (prompt-only,
|
|
43
|
+
* host-direct) aggregates to null rather than fabricating a state for work
|
|
44
|
+
* that never happened.
|
|
30
45
|
*/
|
|
31
46
|
|
|
32
47
|
import { randomBytes } from 'node:crypto';
|
|
@@ -37,12 +52,16 @@ import { routeRequest } from '../orchestration-policy.mjs';
|
|
|
37
52
|
import { resolveExecution } from '../embedded-contract/execution.mjs';
|
|
38
53
|
import { detectHostCapabilities } from '../host-capabilities.mjs';
|
|
39
54
|
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
40
|
-
import { CHAIN_OF_THOUGHT_MODES } from '../config/schema.mjs';
|
|
55
|
+
import { CHAIN_OF_THOUGHT_MODES, DEFAULT_PROJECT_CONFIG } from '../config/schema.mjs';
|
|
41
56
|
import { newTraceId, newSpanId, emitTraceEvent } from '../worker/trace.mjs';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
57
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
58
|
+
import { resolveRunStore, projectKey } from './store.mjs';
|
|
59
|
+
import { resolveTraceStore } from './trace-store.mjs';
|
|
60
|
+
import { runTaskViaProvider, materializeTaskPrompt, INLINE, PROVIDER, HOST, WORKER_BACKEND_SET } from './worker.mjs';
|
|
44
61
|
import { roleHoldsWebCapability } from './web-capability.mjs';
|
|
45
62
|
import { emitRunEvent, isCancelRequested, clearCancel } from './events.mjs';
|
|
63
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
64
|
+
import { resolveTenantContext } from '../tenant/context.mjs';
|
|
46
65
|
|
|
47
66
|
export const WORKER_BACKENDS = WORKER_BACKEND_SET;
|
|
48
67
|
export const DEFAULT_WORKER_BACKEND = INLINE;
|
|
@@ -88,7 +107,7 @@ function pickExecution(data) {
|
|
|
88
107
|
};
|
|
89
108
|
}
|
|
90
109
|
|
|
91
|
-
function buildTasks(route) {
|
|
110
|
+
function buildTasks(route, tenantId) {
|
|
92
111
|
const reasons = route.dispatchReasons || {};
|
|
93
112
|
const handoffByProducer = new Map();
|
|
94
113
|
for (const edge of route.contractChain || []) {
|
|
@@ -99,6 +118,7 @@ function buildTasks(route) {
|
|
|
99
118
|
id: `t${i + 1}`,
|
|
100
119
|
seq: i,
|
|
101
120
|
role,
|
|
121
|
+
tenantId,
|
|
102
122
|
reason: reasons[role] || null,
|
|
103
123
|
handoffContract: handoffByProducer.get(role.replace(/^cx-/, '')) || null,
|
|
104
124
|
status: 'queued',
|
|
@@ -111,11 +131,26 @@ function buildTasks(route) {
|
|
|
111
131
|
}));
|
|
112
132
|
}
|
|
113
133
|
|
|
134
|
+
// Best-effort mirror of a local trace event into the team-shared trace store
|
|
135
|
+
// (LMCP-G10): a no-op in solo mode or when Postgres is unreachable
|
|
136
|
+
// (resolveTraceStore already encodes that fallback), and never allowed to
|
|
137
|
+
// fail the run itself — the local `.cx/traces` JSONL emitTraceEvent already
|
|
138
|
+
// wrote is the durable record either way.
|
|
139
|
+
|
|
140
|
+
function persistTeamTrace({ cwd, env, config, event, tenantId }) {
|
|
141
|
+
if (!event) return;
|
|
142
|
+
try {
|
|
143
|
+
const teamStore = resolveTraceStore({ env, cwd, config });
|
|
144
|
+
if (teamStore.kind !== 'postgres') return;
|
|
145
|
+
teamStore.saveTraceEvent(event, { project: projectKey(config, cwd), tenantId }).catch(() => {});
|
|
146
|
+
} catch { /* observability must not break the caller — see header comment */ }
|
|
147
|
+
}
|
|
148
|
+
|
|
114
149
|
// The worker backend is a deployment concern: an explicit option wins, then the
|
|
115
150
|
// run's recorded backend, then config (orchestration.workerBackend), then the
|
|
116
151
|
// inline default. An unknown value falls back to inline rather than guessing.
|
|
117
152
|
|
|
118
|
-
function resolveWorkerBackend({ explicit, run, config }) {
|
|
153
|
+
export function resolveWorkerBackend({ explicit, run, config }) {
|
|
119
154
|
const candidate = explicit || run?.workerBackend || config?.orchestration?.workerBackend || DEFAULT_WORKER_BACKEND;
|
|
120
155
|
return WORKER_BACKENDS.includes(candidate) ? candidate : DEFAULT_WORKER_BACKEND;
|
|
121
156
|
}
|
|
@@ -130,14 +165,32 @@ function resolveChainOfThought({ explicit, run, config }) {
|
|
|
130
165
|
return CHAIN_OF_THOUGHT.includes(candidate) ? candidate : DEFAULT_CHAIN_OF_THOUGHT;
|
|
131
166
|
}
|
|
132
167
|
|
|
133
|
-
|
|
168
|
+
// loadProjectConfig reverts the whole file to defaults on any validation
|
|
169
|
+
// error (ADR-0021's fail-safe stance); loadConfig historically discarded the
|
|
170
|
+
// errors/source that reversion carries, so a typo (e.g. store: "sqllite")
|
|
171
|
+
// silently flipped workerBackend back to inline with no user-visible signal.
|
|
172
|
+
// configWarnings names the offending key/value and states defaults were
|
|
173
|
+
// applied, mirrored into planRun's warnings and construct doctor
|
|
174
|
+
// (construct-uccl.5).
|
|
175
|
+
|
|
176
|
+
function loadConfigWithWarnings(cwd, env) {
|
|
134
177
|
try {
|
|
135
|
-
|
|
178
|
+
const loaded = loadProjectConfig(cwd, env);
|
|
179
|
+
const configWarnings = loaded.source === 'invalid'
|
|
180
|
+
? loaded.errors.map(
|
|
181
|
+
(e) => `construct.config.json invalid (${e}) — reverted to defaults (workerBackend=${DEFAULT_PROJECT_CONFIG.orchestration.workerBackend}). Fix construct.config.json to keep your settings.`,
|
|
182
|
+
)
|
|
183
|
+
: [];
|
|
184
|
+
return { config: loaded.config || {}, configWarnings };
|
|
136
185
|
} catch {
|
|
137
|
-
return {};
|
|
186
|
+
return { config: {}, configWarnings: [] };
|
|
138
187
|
}
|
|
139
188
|
}
|
|
140
189
|
|
|
190
|
+
function loadConfig(cwd, env) {
|
|
191
|
+
return loadConfigWithWarnings(cwd, env).config;
|
|
192
|
+
}
|
|
193
|
+
|
|
141
194
|
/**
|
|
142
195
|
* Plan a run: resolve the execution contract, decompose into a specialist chain,
|
|
143
196
|
* and persist a durable run record (status `planned`). Pure of model calls.
|
|
@@ -153,8 +206,17 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
153
206
|
workerBackend: explicitWorkerBackend = null,
|
|
154
207
|
} = request;
|
|
155
208
|
|
|
156
|
-
const config =
|
|
157
|
-
const { store, warnings: storeWarnings } = resolveRunStore({ config, env, cwd });
|
|
209
|
+
const { config, configWarnings } = loadConfigWithWarnings(cwd, env);
|
|
210
|
+
const { store, warnings: storeWarnings, degraded: storeDegraded, degradedReason: storeDegradedReason } = resolveRunStore({ config, env, cwd });
|
|
211
|
+
if (storeDegraded) {
|
|
212
|
+
process.stderr.write(`[construct] orchestration store degraded (${storeDegradedReason ?? 'unknown'}): ${storeWarnings.join('; ')}\n`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ADR-0057 (A7): tenant context is resolved once per run, here, and rides
|
|
216
|
+
// every run/task record below. Enterprise mode with no resolvable tenant
|
|
217
|
+
// throws TenantResolutionError — planRun fails closed rather than planning
|
|
218
|
+
// a run with a null tenant label.
|
|
219
|
+
const { tenantId } = resolveTenantContext({ env, config, mode: getDeploymentMode(env, { cwd }) });
|
|
158
220
|
|
|
159
221
|
const execData = resolveExecution(
|
|
160
222
|
{ workflowType, requestedStrategy, useConstruct, host, hostModel, hostProvider },
|
|
@@ -172,11 +234,12 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
172
234
|
// orchestrated execution; prompt-only and host-direct own none, and the run
|
|
173
235
|
// records that rather than implying orchestration that did not happen.
|
|
174
236
|
const orchestrates = execData.effectiveStrategy === 'orchestrated';
|
|
175
|
-
const tasks = orchestrates ? buildTasks(route) : [];
|
|
237
|
+
const tasks = orchestrates ? buildTasks(route, tenantId) : [];
|
|
176
238
|
|
|
177
239
|
const run = {
|
|
178
240
|
runId,
|
|
179
241
|
traceId,
|
|
242
|
+
tenantId,
|
|
180
243
|
createdAt: now,
|
|
181
244
|
updatedAt: now,
|
|
182
245
|
request: { summary: truncate(text, 200), workflowType, requestedStrategy },
|
|
@@ -195,7 +258,7 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
195
258
|
},
|
|
196
259
|
tasks,
|
|
197
260
|
status: 'planned',
|
|
198
|
-
warnings: [...(execData.warnings || []), ...storeWarnings],
|
|
261
|
+
warnings: [...(execData.warnings || []), ...storeWarnings, ...configWarnings],
|
|
199
262
|
semantics: RUNTIME_SEMANTICS,
|
|
200
263
|
executionSemantics: execData.semantics,
|
|
201
264
|
};
|
|
@@ -203,9 +266,9 @@ export async function planRun(request = {}, { env = process.env, cwd = process.c
|
|
|
203
266
|
await store.saveRun(run);
|
|
204
267
|
emitTraceEvent({
|
|
205
268
|
rootDir: cwd, env, traceId, spanId: newSpanId(), eventType: 'task_graph.created',
|
|
206
|
-
metadata: { runId, executionMode: run.execution.executionMode, specialists: run.plan.specialists, workerBackend: run.workerBackend },
|
|
269
|
+
metadata: { runId, tenantId, executionMode: run.execution.executionMode, specialists: run.plan.specialists, workerBackend: run.workerBackend },
|
|
207
270
|
});
|
|
208
|
-
emitRunEvent(runId, { type: 'planned', status: 'planned', executionMode: run.execution.executionMode, taskCount: tasks.length });
|
|
271
|
+
emitRunEvent(runId, { type: 'planned', status: 'planned', tenantId, executionMode: run.execution.executionMode, taskCount: tasks.length });
|
|
209
272
|
return run;
|
|
210
273
|
}
|
|
211
274
|
|
|
@@ -216,6 +279,11 @@ function prepareTaskInline(task) {
|
|
|
216
279
|
task.executor = 'inline:prepared';
|
|
217
280
|
task.status = 'prepared';
|
|
218
281
|
task.finishedAt = new Date().toISOString();
|
|
282
|
+
// The inline backend never resolves a persona or calls a model (ADR-0020), so
|
|
283
|
+
// packId/promptVersion/model/provider/toolGrants stay unset — there is nothing
|
|
284
|
+
// real to report yet. executionState still carries the honest signal: this task
|
|
285
|
+
// was prepared, not executed (LMCP-F1/F4).
|
|
286
|
+
task.executionState = 'prepared';
|
|
219
287
|
// A prepared web-capable task reached no web: mark it so a host never infers live
|
|
220
288
|
// web access from a task that only planned (ADR-0050).
|
|
221
289
|
if (roleHoldsWebCapability(task.role)) task.webCapability = 'prepare-only';
|
|
@@ -225,13 +293,13 @@ function prepareTaskInline(task) {
|
|
|
225
293
|
// task is recorded (status `failed`, task.error) and does not abort the run, so
|
|
226
294
|
// one specialist failure cannot lose the work of the others.
|
|
227
295
|
|
|
228
|
-
async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought) {
|
|
296
|
+
async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd) {
|
|
229
297
|
try {
|
|
230
298
|
const result = await runTaskViaProvider({
|
|
231
299
|
task, run,
|
|
232
300
|
model: run.execution?.selectedModel,
|
|
233
301
|
provider: run.execution?.selectedProvider,
|
|
234
|
-
env, fetchImpl, chainOfThought,
|
|
302
|
+
env, fetchImpl, chainOfThought, cwd,
|
|
235
303
|
});
|
|
236
304
|
task.output = result.output;
|
|
237
305
|
task.executor = `provider:${result.provider}:${result.model}`;
|
|
@@ -247,19 +315,196 @@ async function executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought)
|
|
|
247
315
|
task.webSearchRequests = result.webSearchRequests || 0;
|
|
248
316
|
}
|
|
249
317
|
|
|
318
|
+
// Evidence grounding (construct-5wkl AC#5): a citation the specialist wrote
|
|
319
|
+
// but that never appeared in its own governed webEvidence is unverified —
|
|
320
|
+
// the task still completes (real output, already paid for) but carries the
|
|
321
|
+
// warning rather than looking identical to a fully-grounded answer.
|
|
322
|
+
if (result.evidenceStatus) {
|
|
323
|
+
task.evidenceStatus = result.evidenceStatus;
|
|
324
|
+
task.unverifiedCitations = result.unverifiedCitations || [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Provider telemetry (construct-5wkl AC#3): redacted, machine-readable
|
|
328
|
+
// execution metadata — provider, model, finish_reason, usage, elapsedMs,
|
|
329
|
+
// retryCount, and reasoning request/return — so a debugging pass never has
|
|
330
|
+
// to re-run the task to learn why it behaved the way it did.
|
|
331
|
+
if (result.providerMeta) task.providerMeta = result.providerMeta;
|
|
332
|
+
|
|
333
|
+
// Persona resolution (LMCP-E2): personaAvailable rides on every task so a
|
|
334
|
+
// host never has to infer whether the specialist ran under its real prompt
|
|
335
|
+
// or the solo-mode fallback; degraded is set only on the fallback path.
|
|
336
|
+
task.personaAvailable = result.personaAvailable;
|
|
337
|
+
if (result.degraded) task.degraded = result.degraded;
|
|
338
|
+
|
|
339
|
+
// Execution provenance (LMCP-F1): which specialist/pack/prompt/model/provider
|
|
340
|
+
// actually ran and under which tool grants — the basis for audit and
|
|
341
|
+
// evaluation. Rides on every provider-executed task, not gated on
|
|
342
|
+
// chainOfThought, same as personaAvailable above.
|
|
343
|
+
task.specialistId = result.specialistId;
|
|
344
|
+
task.packId = result.packId;
|
|
345
|
+
task.promptVersion = result.promptVersion;
|
|
346
|
+
task.model = result.model;
|
|
347
|
+
task.provider = result.provider;
|
|
348
|
+
task.toolGrants = result.toolGrants || [];
|
|
349
|
+
task.executionState = result.executionState;
|
|
350
|
+
|
|
250
351
|
// `surface` attaches reasoning to the task so every display surface renders
|
|
251
352
|
// it; `telemetry_only` keeps it off the task (recorded only in the trace).
|
|
252
353
|
if (chainOfThought === 'surface' && result.reasoning) task.reasoning = result.reasoning;
|
|
253
|
-
return {
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
reasoning: result.reasoning || '',
|
|
357
|
+
webCapability: result.webCapability || null,
|
|
358
|
+
personaAvailable: result.personaAvailable,
|
|
359
|
+
personaDegraded: Boolean(result.degraded),
|
|
360
|
+
specialistId: result.specialistId,
|
|
361
|
+
packId: result.packId,
|
|
362
|
+
promptVersion: result.promptVersion,
|
|
363
|
+
model: result.model,
|
|
364
|
+
provider: result.provider,
|
|
365
|
+
toolGrants: result.toolGrants || [],
|
|
366
|
+
executionState: result.executionState,
|
|
367
|
+
};
|
|
254
368
|
} catch (err) {
|
|
255
369
|
task.executor = `provider:error`;
|
|
256
|
-
task.error = {
|
|
370
|
+
task.error = {
|
|
371
|
+
code: err.code || 'PROVIDER_EXECUTION_FAILED',
|
|
372
|
+
message: err.message,
|
|
373
|
+
...(err.remediation ? { remediation: err.remediation } : {}),
|
|
374
|
+
...(Number.isFinite(err.retryCount) ? { retryCount: err.retryCount } : {}),
|
|
375
|
+
};
|
|
257
376
|
task.status = 'failed';
|
|
258
377
|
task.finishedAt = new Date().toISOString();
|
|
259
|
-
|
|
378
|
+
// A refused/failed provider call never resolved a real specialist/pack/prompt to
|
|
379
|
+
// report — executionState is the one honest signal available here (LMCP-F1/F4).
|
|
380
|
+
task.executionState = 'failed';
|
|
381
|
+
// A content-shaped failure (empty/reasoning-only/content-filtered/malformed)
|
|
382
|
+
// still reached a real HTTP response, so its redacted provider metadata
|
|
383
|
+
// (construct-5wkl AC#3) is worth keeping even though the task failed.
|
|
384
|
+
if (err.providerMeta) task.providerMeta = err.providerMeta;
|
|
385
|
+
return { ok: false, reasoning: '', executionState: 'failed' };
|
|
260
386
|
}
|
|
261
387
|
}
|
|
262
388
|
|
|
389
|
+
// The host backend materializes one task's prompt (same materializeTaskPrompt the
|
|
390
|
+
// provider executor uses) and stops — no model call runs here. The calling host
|
|
391
|
+
// (the MCP client that requested the run) executes the prompt in its own session
|
|
392
|
+
// and reports the result back through submitHostTaskResult. A materialization
|
|
393
|
+
// failure (e.g. a team/enterprise persona hard-fail) is recorded as a task
|
|
394
|
+
// failure, same posture as a provider call that throws before ever reaching a
|
|
395
|
+
// model — one specialist's prompt failing to resolve does not lose the others.
|
|
396
|
+
|
|
397
|
+
function prepareTaskForHost(task, run, cwd, env) {
|
|
398
|
+
try {
|
|
399
|
+
const prompt = materializeTaskPrompt({ task, run, cwd, env });
|
|
400
|
+
task.hostPrompt = { system: prompt.system, user: prompt.user };
|
|
401
|
+
task.specialistId = prompt.specialistId;
|
|
402
|
+
task.packId = prompt.packId;
|
|
403
|
+
task.promptVersion = prompt.promptVersion;
|
|
404
|
+
task.toolGrants = prompt.toolGrants;
|
|
405
|
+
task.personaAvailable = prompt.personaAvailable;
|
|
406
|
+
if (prompt.degraded) task.degraded = prompt.degraded;
|
|
407
|
+
task.executor = 'host:awaiting';
|
|
408
|
+
task.status = 'awaiting-host';
|
|
409
|
+
task.finishedAt = null;
|
|
410
|
+
task.executionState = 'awaiting-host';
|
|
411
|
+
return { ok: true };
|
|
412
|
+
} catch (err) {
|
|
413
|
+
task.executor = 'host:error';
|
|
414
|
+
task.error = {
|
|
415
|
+
code: err.code || 'HOST_MATERIALIZE_FAILED',
|
|
416
|
+
message: err.message,
|
|
417
|
+
...(err.remediation ? { remediation: err.remediation } : {}),
|
|
418
|
+
};
|
|
419
|
+
task.status = 'failed';
|
|
420
|
+
task.finishedAt = new Date().toISOString();
|
|
421
|
+
task.executionState = 'failed';
|
|
422
|
+
return { ok: false };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Run-level executionState aggregates every task's LMCP-F1 executionState
|
|
427
|
+
// (prepared|executed|degraded-executed|failed|awaiting-host) into one honest
|
|
428
|
+
// signal for the whole run (LMCP-F4). A run with no tasks (prompt-only,
|
|
429
|
+
// host-direct) owns no specialist sequence, so it aggregates to null rather
|
|
430
|
+
// than fabricating a state for work that never happened. `failed` wins over
|
|
431
|
+
// everything — a run that failed even one task must never report an
|
|
432
|
+
// aggregate that reads as clean. `degraded-executed` beats `executed` next,
|
|
433
|
+
// so a solo-mode persona fallback is never hidden behind a bare 'executed'
|
|
434
|
+
// when at least one real execution also happened. `awaiting-host` beats
|
|
435
|
+
// `prepared` — a host-backend task materialized its prompt but has not
|
|
436
|
+
// prepared-and-stopped the way the inline backend does, so it is a distinct,
|
|
437
|
+
// more specific "still pending" signal. All-`prepared` is the most specific
|
|
438
|
+
// "nothing ran" signal and only applies when every task shares it.
|
|
439
|
+
|
|
440
|
+
const EXECUTION_STATE_PRECEDENCE = ['failed', 'degraded-executed', 'executed', 'awaiting-host', 'prepared'];
|
|
441
|
+
|
|
442
|
+
function aggregateExecutionState(tasks) {
|
|
443
|
+
if (!Array.isArray(tasks) || tasks.length === 0) return null;
|
|
444
|
+
const present = new Set(tasks.map((t) => t.executionState).filter(Boolean));
|
|
445
|
+
for (const state of EXECUTION_STATE_PRECEDENCE) {
|
|
446
|
+
if (present.has(state)) return state;
|
|
447
|
+
}
|
|
448
|
+
// No task carries a recognized executionState (pre-F1 legacy run) — nothing
|
|
449
|
+
// honest to report, so null rather than guessing.
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// The one honest terminal-status computation, shared by executeRun (the
|
|
454
|
+
// synchronous provider/inline/host-materialization path) and
|
|
455
|
+
// submitHostTaskResult (the asynchronous host-pickup completion path) — a run
|
|
456
|
+
// must resolve to the same completed/completed-with-failures/degraded/
|
|
457
|
+
// completed-prepare-only taxonomy regardless of which of those two callers
|
|
458
|
+
// closes it out, so this is written once and reused, not reimplemented.
|
|
459
|
+
|
|
460
|
+
async function finalizeRun(run, { cwd, env, config, store, cancelled = false, anyFailed = false, webUnavailable = false, personaDegraded = false } = {}) {
|
|
461
|
+
// LMCP-F4: aggregate every task's executionState into one run-level signal
|
|
462
|
+
// before the terminal status is computed, so a reader never has to walk
|
|
463
|
+
// run.tasks to learn whether the run prepared, executed, degraded-executed,
|
|
464
|
+
// awaited a host, or failed.
|
|
465
|
+
run.executionState = aggregateExecutionState(run.tasks);
|
|
466
|
+
|
|
467
|
+
let terminalStatus;
|
|
468
|
+
// degraded can come from the execution contract (no model resolved), a web
|
|
469
|
+
// capability gap, or a solo-mode persona fallback; any source means the run
|
|
470
|
+
// must not surface as a bare success.
|
|
471
|
+
const isDegraded = run.degraded || run.execution?.degraded || webUnavailable || personaDegraded;
|
|
472
|
+
if (isDegraded) {
|
|
473
|
+
run.degraded = true;
|
|
474
|
+
run.degradationReason = run.degradationReason || run.execution?.degradationReason
|
|
475
|
+
|| (webUnavailable ? 'capability-unavailable' : personaDegraded ? 'persona-fallback' : 'no-model-resolved');
|
|
476
|
+
}
|
|
477
|
+
// Prepare-only is the more specific "no specialist executed" signal, so an
|
|
478
|
+
// all-prepared run reports that even if also degraded; otherwise any degraded run
|
|
479
|
+
// — zero-task or executed-with-a-gap — is 'degraded', never bare 'completed'.
|
|
480
|
+
if (cancelled) {
|
|
481
|
+
terminalStatus = 'cancelled';
|
|
482
|
+
} else if (anyFailed) {
|
|
483
|
+
terminalStatus = 'completed-with-failures';
|
|
484
|
+
} else if (run.tasks.length > 0 && run.tasks.every((t) => t.status === 'prepared')) {
|
|
485
|
+
terminalStatus = 'completed-prepare-only';
|
|
486
|
+
} else if (isDegraded) {
|
|
487
|
+
terminalStatus = 'degraded';
|
|
488
|
+
} else {
|
|
489
|
+
terminalStatus = 'completed';
|
|
490
|
+
}
|
|
491
|
+
run.status = terminalStatus;
|
|
492
|
+
run.updatedAt = new Date().toISOString();
|
|
493
|
+
await store.saveRun(run);
|
|
494
|
+
clearCancel(run.runId);
|
|
495
|
+
// terminalStatus already encodes prepared-vs-executed (completed-prepare-only)
|
|
496
|
+
// and degraded-vs-clean, but its precedence order (cancelled > failed >
|
|
497
|
+
// prepare-only > degraded) can collapse a run that is BOTH e.g.
|
|
498
|
+
// completed-with-failures AND degraded into one string. executionState
|
|
499
|
+
// (LMCP-F4) and the degraded boolean ride alongside it so a trace/SSE
|
|
500
|
+
// consumer never has to re-read the run store to recover the signal
|
|
501
|
+
// terminalStatus's precedence dropped.
|
|
502
|
+
const completedTrace = emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'lifecycle.completed', metadata: { runId: run.runId, tenantId: run.tenantId, status: run.status, executionState: run.executionState, degraded: Boolean(run.degraded), tasks: run.tasks.length } });
|
|
503
|
+
persistTeamTrace({ cwd, env, config, event: completedTrace, tenantId: run.tenantId });
|
|
504
|
+
emitRunEvent(run.runId, { type: 'completed', status: run.status, executionState: run.executionState, degraded: Boolean(run.degraded), tenantId: run.tenantId });
|
|
505
|
+
return run;
|
|
506
|
+
}
|
|
507
|
+
|
|
263
508
|
/**
|
|
264
509
|
* Execute a planned run through the chosen worker backend, persisting after each
|
|
265
510
|
* task transition so the run is resumable and observable.
|
|
@@ -279,7 +524,14 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
|
|
|
279
524
|
throw err;
|
|
280
525
|
}
|
|
281
526
|
|
|
282
|
-
const runFilePath = join(cwd, '
|
|
527
|
+
const runFilePath = join(resolveStateDir(cwd, 'runtime', 'orchestration', 'runs'), `${runId}.json`);
|
|
528
|
+
|
|
529
|
+
// A run persisted before this bead carries no tenantId; resolve it now
|
|
530
|
+
// rather than leave the field permanently absent on resumed runs. Enterprise
|
|
531
|
+
// mode fails closed here exactly as it would have at planRun time.
|
|
532
|
+
if (run.tenantId === undefined) {
|
|
533
|
+
run.tenantId = resolveTenantContext({ env, config, mode: getDeploymentMode(env, { cwd }) }).tenantId;
|
|
534
|
+
}
|
|
283
535
|
|
|
284
536
|
try {
|
|
285
537
|
const backend = resolveWorkerBackend({ explicit: workerBackend, run, config });
|
|
@@ -294,49 +546,80 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
|
|
|
294
546
|
let anyFailed = false;
|
|
295
547
|
let cancelled = false;
|
|
296
548
|
let webUnavailable = false;
|
|
549
|
+
let personaDegraded = false;
|
|
297
550
|
for (const task of run.tasks) {
|
|
298
551
|
if (isCancelRequested(runId)) { cancelled = true; break; }
|
|
299
552
|
task.status = 'running';
|
|
300
553
|
task.startedAt = new Date().toISOString();
|
|
301
|
-
emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.started', role: task.role, taskId: task.id, metadata: { runId, workerBackend: backend } });
|
|
302
|
-
emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: 'running' });
|
|
554
|
+
emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.started', role: task.role, taskId: task.id, metadata: { runId, tenantId: run.tenantId, workerBackend: backend } });
|
|
555
|
+
emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: 'running', tenantId: run.tenantId });
|
|
303
556
|
|
|
304
557
|
let taskReasoning = '';
|
|
305
558
|
if (backend === PROVIDER) {
|
|
306
|
-
const res = await executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought);
|
|
559
|
+
const res = await executeTaskViaProvider(task, run, env, fetchImpl, chainOfThought, cwd);
|
|
307
560
|
if (!res.ok) anyFailed = true;
|
|
308
561
|
if (res.webCapability === 'unavailable') webUnavailable = true;
|
|
562
|
+
if (res.personaDegraded) personaDegraded = true;
|
|
309
563
|
taskReasoning = res.reasoning;
|
|
564
|
+
} else if (backend === HOST) {
|
|
565
|
+
const res = prepareTaskForHost(task, run, cwd, env);
|
|
566
|
+
if (!res.ok) anyFailed = true;
|
|
310
567
|
} else {
|
|
311
568
|
prepareTaskInline(task);
|
|
312
569
|
}
|
|
313
570
|
|
|
314
571
|
// telemetry_only records reasoning to the trace without ever surfacing it to
|
|
315
572
|
// a display; surface keeps it off the trace (it rides on task.reasoning).
|
|
316
|
-
|
|
573
|
+
// personaAvailable/degraded ride the trace unconditionally (not gated on
|
|
574
|
+
// chainOfThought) so a persona-degraded run is always discoverable from
|
|
575
|
+
// trace history alone, independent of reasoning-disclosure mode.
|
|
576
|
+
const completedMeta = { runId, tenantId: run.tenantId, status: task.status };
|
|
317
577
|
if (chainOfThought === 'telemetry_only' && taskReasoning) {
|
|
318
578
|
completedMeta.reasoning = taskReasoning;
|
|
319
579
|
completedMeta.reasoningChars = taskReasoning.length;
|
|
320
580
|
}
|
|
581
|
+
if (task.personaAvailable === false) {
|
|
582
|
+
completedMeta.personaAvailable = false;
|
|
583
|
+
completedMeta.degraded = task.degraded || 'persona-fallback';
|
|
584
|
+
}
|
|
585
|
+
// Execution provenance (LMCP-F1): specialist/pack/prompt/model/provider/
|
|
586
|
+
// tool-grants/executionState ride the trace unconditionally whenever the
|
|
587
|
+
// task carries them (inline prepare-only tasks carry only executionState;
|
|
588
|
+
// provider tasks, success or failure, carry the full set) — same
|
|
589
|
+
// always-on pattern as personaAvailable above, so a reader never has to
|
|
590
|
+
// reconstruct provenance from a separate source.
|
|
591
|
+
if (task.executionState !== undefined) completedMeta.executionState = task.executionState;
|
|
592
|
+
if (task.specialistId !== undefined) completedMeta.specialistId = task.specialistId;
|
|
593
|
+
if (task.packId !== undefined) completedMeta.packId = task.packId;
|
|
594
|
+
if (task.promptVersion !== undefined) completedMeta.promptVersion = task.promptVersion;
|
|
595
|
+
if (task.model !== undefined) completedMeta.model = task.model;
|
|
596
|
+
if (task.provider !== undefined) completedMeta.provider = task.provider;
|
|
597
|
+
if (task.toolGrants !== undefined) completedMeta.toolGrants = task.toolGrants;
|
|
321
598
|
emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.completed', role: task.role, taskId: task.id, metadata: completedMeta });
|
|
322
599
|
emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: task.status, executor: task.executor, ...(task.reasoning ? { reasoning: task.reasoning } : {}), ...(task.error ? { error: task.error } : {}) });
|
|
323
600
|
run.updatedAt = new Date().toISOString();
|
|
324
601
|
await store.saveRun(run);
|
|
325
602
|
}
|
|
326
603
|
|
|
327
|
-
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
604
|
+
// The host backend never executes synchronously — it materializes prompts
|
|
605
|
+
// and stands the run at 'awaiting-host' until the calling host submits every
|
|
606
|
+
// task's result via submitHostTaskResult, which finalizes through the same
|
|
607
|
+
// finalizeRun below. The only case that does NOT stand awaiting is when no
|
|
608
|
+
// task actually reached 'awaiting-host' (materialization failed for every
|
|
609
|
+
// task, or the route planned zero tasks) — there is nothing pending pickup,
|
|
610
|
+
// so the run must resolve to a real terminal state rather than stand
|
|
611
|
+
// forever with nothing to submit.
|
|
612
|
+
if (backend === HOST && run.tasks.some((t) => t.status === 'awaiting-host')) {
|
|
613
|
+
run.executionState = aggregateExecutionState(run.tasks);
|
|
614
|
+
run.status = 'awaiting-host';
|
|
615
|
+
run.updatedAt = new Date().toISOString();
|
|
616
|
+
await store.saveRun(run);
|
|
617
|
+
clearCancel(runId);
|
|
618
|
+
emitRunEvent(runId, { type: 'awaiting-host', status: 'awaiting-host', executionState: run.executionState, tenantId: run.tenantId, taskCount: run.tasks.length });
|
|
619
|
+
return run;
|
|
333
620
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
clearCancel(runId);
|
|
337
|
-
emitTraceEvent({ rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'lifecycle.completed', metadata: { runId, status: run.status, tasks: run.tasks.length } });
|
|
338
|
-
emitRunEvent(runId, { type: 'completed', status: run.status });
|
|
339
|
-
return run;
|
|
621
|
+
|
|
622
|
+
return finalizeRun(run, { cwd, env, config, store, cancelled, anyFailed, webUnavailable, personaDegraded });
|
|
340
623
|
} catch (err) {
|
|
341
624
|
run.status = 'error';
|
|
342
625
|
run.error = { code: err.code || 'RUN_EXECUTE_FAILED', message: err.message };
|
|
@@ -352,6 +635,99 @@ export async function executeRun(cwd, runId, { env = process.env, workerBackend
|
|
|
352
635
|
}
|
|
353
636
|
}
|
|
354
637
|
|
|
638
|
+
// Terminal run statuses a host result can never be submitted against — a run
|
|
639
|
+
// that already finalized has nothing left awaiting pickup, so a late submit is
|
|
640
|
+
// rejected rather than silently mutating a closed record.
|
|
641
|
+
|
|
642
|
+
const TERMINAL_RUN_STATUSES = new Set(['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error']);
|
|
643
|
+
|
|
644
|
+
function hostResultError(code, message) {
|
|
645
|
+
const err = new Error(message);
|
|
646
|
+
err.code = code;
|
|
647
|
+
return err;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Record the calling host's execution of one `awaiting-host` task, submitted
|
|
652
|
+
* by the host after it ran the materialized prompt (task.hostPrompt) in its own
|
|
653
|
+
* model session. The result is host-reported, not independently verified —
|
|
654
|
+
* every field this writes is tagged `provenanceSource: 'host-reported'` so it
|
|
655
|
+
* can never be mistaken for a construct-verified provider execution
|
|
656
|
+
* (task.executor also differs: `host:<hostRole>` vs `provider:<provider>:<model>`).
|
|
657
|
+
* When every task on the run is terminal, the run is finalized through the
|
|
658
|
+
* SAME honest terminal-status logic executeRun uses (finalizeRun) — completed
|
|
659
|
+
* vs completed-with-failures is decided once, not reimplemented here.
|
|
660
|
+
*
|
|
661
|
+
* Persistence is read-modify-write via store.saveRun, the same single-writer
|
|
662
|
+
* posture executeRun already has; concurrent submissions racing the same run
|
|
663
|
+
* are not safety-guarded here (team-mode row-locking is a deferred follow-up,
|
|
664
|
+
* see the ADR).
|
|
665
|
+
*
|
|
666
|
+
* @param {string} cwd
|
|
667
|
+
* @param {string} runId
|
|
668
|
+
* @param {string} taskId
|
|
669
|
+
* @param {{output:string, model?:string, provider?:string, reasoning?:string}} result
|
|
670
|
+
* @param {object} [opts] { env }
|
|
671
|
+
* @returns {Promise<{run:object, nextTask:object|null}>}
|
|
672
|
+
*/
|
|
673
|
+
export async function submitHostTaskResult(cwd, runId, taskId, result = {}, { env = process.env } = {}) {
|
|
674
|
+
const config = loadConfig(cwd, env);
|
|
675
|
+
const { store } = resolveRunStore({ config, env, cwd });
|
|
676
|
+
const run = await store.loadRun(runId);
|
|
677
|
+
if (!run) throw hostResultError('RUN_NOT_FOUND', `Orchestration run not found: ${runId}`);
|
|
678
|
+
|
|
679
|
+
const task = (run.tasks || []).find((t) => t.id === taskId);
|
|
680
|
+
if (!task) throw hostResultError('TASK_NOT_FOUND', `Task ${taskId} not found on run ${runId}.`);
|
|
681
|
+
|
|
682
|
+
if (TERMINAL_RUN_STATUSES.has(run.status)) {
|
|
683
|
+
throw hostResultError('RUN_ALREADY_TERMINAL', `Run ${runId} is already terminal (${run.status}); no task result can be submitted.`);
|
|
684
|
+
}
|
|
685
|
+
if (task.status !== 'awaiting-host') {
|
|
686
|
+
throw hostResultError('TASK_NOT_AWAITING_HOST', `Task ${taskId} is not awaiting host execution (status: ${task.status}).`);
|
|
687
|
+
}
|
|
688
|
+
const { output, model = null, provider = null, reasoning = null } = result;
|
|
689
|
+
if (typeof output !== 'string' || !output.trim()) {
|
|
690
|
+
throw hostResultError('HOST_RESULT_EMPTY_OUTPUT', `Host task result for ${taskId} requires non-empty output.`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
task.output = output;
|
|
694
|
+
task.executor = `host:${run.hostRole || 'unknown'}`;
|
|
695
|
+
task.status = 'done';
|
|
696
|
+
task.finishedAt = new Date().toISOString();
|
|
697
|
+
task.executionState = 'executed';
|
|
698
|
+
// Host-reported, never independently verified — the distinguishing marker a
|
|
699
|
+
// reader needs so this never looks like a construct-verified provider
|
|
700
|
+
// execution (which carries no provenanceSource field at all).
|
|
701
|
+
task.provenanceSource = 'host-reported';
|
|
702
|
+
if (model) task.model = model;
|
|
703
|
+
if (provider) task.provider = provider;
|
|
704
|
+
if (reasoning) task.reasoning = reasoning;
|
|
705
|
+
delete task.hostPrompt;
|
|
706
|
+
|
|
707
|
+
emitTraceEvent({
|
|
708
|
+
rootDir: cwd, env, traceId: run.traceId, spanId: newSpanId(), eventType: 'worker.completed', role: task.role, taskId: task.id,
|
|
709
|
+
metadata: {
|
|
710
|
+
runId, tenantId: run.tenantId, status: task.status, executionState: task.executionState,
|
|
711
|
+
specialistId: task.specialistId, packId: task.packId, promptVersion: task.promptVersion,
|
|
712
|
+
model: task.model, provider: task.provider, toolGrants: task.toolGrants, provenanceSource: task.provenanceSource,
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
emitRunEvent(runId, { type: 'task', taskId: task.id, role: task.role, status: task.status, executor: task.executor, ...(task.reasoning ? { reasoning: task.reasoning } : {}) });
|
|
716
|
+
|
|
717
|
+
run.updatedAt = new Date().toISOString();
|
|
718
|
+
|
|
719
|
+
const stillAwaiting = run.tasks.some((t) => t.status === 'awaiting-host');
|
|
720
|
+
if (stillAwaiting) {
|
|
721
|
+
await store.saveRun(run);
|
|
722
|
+
const nextTask = run.tasks.find((t) => t.status === 'awaiting-host');
|
|
723
|
+
return { run, nextTask };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const anyFailed = run.tasks.some((t) => t.status === 'failed');
|
|
727
|
+
const finalized = await finalizeRun(run, { cwd, env, config, store, anyFailed });
|
|
728
|
+
return { run: finalized, nextTask: null };
|
|
729
|
+
}
|
|
730
|
+
|
|
355
731
|
/**
|
|
356
732
|
* Plan and execute in one call (the common `orchestrate run` path).
|
|
357
733
|
*/
|
|
@@ -372,6 +748,15 @@ export async function runOrchestration(request = {}, { env = process.env, cwd =
|
|
|
372
748
|
*/
|
|
373
749
|
export async function startRun(request = {}, { env = process.env, cwd = process.cwd(), workerBackend = null, fetchImpl } = {}) {
|
|
374
750
|
const planned = await planRun(request, { env, cwd });
|
|
751
|
+
|
|
752
|
+
// Fire-and-forget: no claim, heartbeat, or retry — process death silently kills the run.
|
|
753
|
+
// Solo mode only; durable team/enterprise worker execution is a separate bead. runMode
|
|
754
|
+
// labels the record as in-process so orchestration_status never conflates it with a durable worker.
|
|
755
|
+
|
|
756
|
+
planned.runMode = 'in-process';
|
|
757
|
+
const { store } = resolveRunStore({ config: loadConfig(cwd, env), env, cwd });
|
|
758
|
+
await store.saveRun(planned);
|
|
759
|
+
|
|
375
760
|
Promise.resolve()
|
|
376
761
|
.then(() => executeRun(cwd, planned.runId, { env, workerBackend, fetchImpl }))
|
|
377
762
|
.catch((err) => {
|
|
@@ -388,7 +773,11 @@ export function hostAdapterMetadata(run) {
|
|
|
388
773
|
return {
|
|
389
774
|
runId: run.runId,
|
|
390
775
|
traceId: run.traceId,
|
|
776
|
+
tenantId: run.tenantId ?? null,
|
|
391
777
|
status: run.status,
|
|
778
|
+
// LMCP-F4: the run-level aggregate of every task's executionState, or null
|
|
779
|
+
// for a zero-task run (prompt-only/host-direct) or a pre-F4 legacy record.
|
|
780
|
+
executionState: run.executionState ?? aggregateExecutionState(run.tasks) ?? null,
|
|
392
781
|
requestedStrategy: e.requestedStrategy,
|
|
393
782
|
effectiveStrategy: e.effectiveStrategy,
|
|
394
783
|
executionMode: e.executionMode,
|
|
@@ -396,11 +785,29 @@ export function hostAdapterMetadata(run) {
|
|
|
396
785
|
workerBackend: run.workerBackend,
|
|
397
786
|
chainOfThought: run.chainOfThought ?? null,
|
|
398
787
|
hostRole: run.hostRole,
|
|
399
|
-
degraded: e.degraded,
|
|
400
|
-
degradationReason: e.degradationReason,
|
|
788
|
+
degraded: run.degraded ?? e.degraded ?? false,
|
|
789
|
+
degradationReason: run.degradationReason ?? e.degradationReason ?? null,
|
|
401
790
|
selectedProvider: e.selectedProvider,
|
|
402
791
|
selectedModel: e.selectedModel,
|
|
403
|
-
|
|
792
|
+
// ?? null / ?? [] on every LMCP-F1 field: a pre-F1 run record carries none of
|
|
793
|
+
// these keys, so a typed absence is returned rather than `undefined`.
|
|
794
|
+
tasks: (run.tasks || []).map((t) => ({
|
|
795
|
+
id: t.id, role: t.role, status: t.status, executor: t.executor, tenantId: t.tenantId ?? null,
|
|
796
|
+
reasoning: t.reasoning ?? null, output: t.output ?? null, error: t.error ?? null,
|
|
797
|
+
specialistId: t.specialistId ?? null,
|
|
798
|
+
packId: t.packId ?? null,
|
|
799
|
+
promptVersion: t.promptVersion ?? null,
|
|
800
|
+
model: t.model ?? null,
|
|
801
|
+
provider: t.provider ?? null,
|
|
802
|
+
toolGrants: t.toolGrants ?? [],
|
|
803
|
+
executionState: t.executionState ?? null,
|
|
804
|
+
personaAvailable: t.personaAvailable ?? null,
|
|
805
|
+
// Present only on a host-executed task — absence (not a bare `false`)
|
|
806
|
+
// is what keeps a provider-verified task from ever reading the same as
|
|
807
|
+
// a host-reported one.
|
|
808
|
+
...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
|
|
809
|
+
...(t.hostPrompt ? { hostPrompt: t.hostPrompt } : {}),
|
|
810
|
+
})),
|
|
404
811
|
warnings: run.warnings || [],
|
|
405
812
|
semantics: run.semantics,
|
|
406
813
|
executionSemantics: run.executionSemantics,
|