zob-harness 0.1.0
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/.pi/adapters/registry.json +103 -0
- package/.pi/agents/architecture-cartographer.md +53 -0
- package/.pi/agents/chief-vision.md +39 -0
- package/.pi/agents/clarifier.md +58 -0
- package/.pi/agents/context-steward.md +52 -0
- package/.pi/agents/doc-steward.md +34 -0
- package/.pi/agents/explore.md +49 -0
- package/.pi/agents/factory.md +41 -0
- package/.pi/agents/implementer.md +44 -0
- package/.pi/agents/librarian.md +32 -0
- package/.pi/agents/oracle-merge.md +50 -0
- package/.pi/agents/oracle.md +55 -0
- package/.pi/agents/pattern-miner.md +53 -0
- package/.pi/agents/planner.md +39 -0
- package/.pi/agents/project-dna-golden-evaluator.md +32 -0
- package/.pi/agents/project-dna-ontology-steward.md +30 -0
- package/.pi/agents/project-dna-oracle.md +56 -0
- package/.pi/agents/project-dna-orchestrator.md +60 -0
- package/.pi/agents/project-dna-query-steward.md +38 -0
- package/.pi/agents/project-dna-safety-preflight.md +54 -0
- package/.pi/agents/project-dna-test-linker.md +27 -0
- package/.pi/agents/qa.md +38 -0
- package/.pi/agents/refactor-cartographer.md +28 -0
- package/.pi/agents/refactor-mover.md +31 -0
- package/.pi/agents/refactor-oracle.md +49 -0
- package/.pi/agents/repo-scout.md +60 -0
- package/.pi/agents/sample-architect.md +48 -0
- package/.pi/agents/specifier.md +57 -0
- package/.pi/agents/symbol-range-curator.md +41 -0
- package/.pi/agents/synthesis.md +52 -0
- package/.pi/agents/temp-agent-creator.md +35 -0
- package/.pi/autonomy-policy.json +67 -0
- package/.pi/budget-policy.json +54 -0
- package/.pi/capabilities/zob-public-runtime-capabilities.json +1700 -0
- package/.pi/chains/explore-plan-oracle.json +78 -0
- package/.pi/chains/explore-spec-clarify-plan-oracle.json +64 -0
- package/.pi/chains/explore-spec-plan-oracle.json +53 -0
- package/.pi/chains/spec-clarify-plan-oracle.json +53 -0
- package/.pi/chains/spec-factory-oracle.json +42 -0
- package/.pi/chains/spec-plan-oracle.json +42 -0
- package/.pi/compute-profiles/defaults.json +19 -0
- package/.pi/compute-profiles/overrides.json +13 -0
- package/.pi/compute-profiles/risk-rules.json +16 -0
- package/.pi/daemon-policy.json +80 -0
- package/.pi/damage-control-rules.json +45 -0
- package/.pi/extensions/zob-child-safety/index.ts +212 -0
- package/.pi/extensions/zob-harness/AGENTS.md +28 -0
- package/.pi/extensions/zob-harness/index.ts +391 -0
- package/.pi/extensions/zob-harness/src/AGENTS.md +25 -0
- package/.pi/extensions/zob-harness/src/agents.ts +82 -0
- package/.pi/extensions/zob-harness/src/autonomous-runtime.ts +2912 -0
- package/.pi/extensions/zob-harness/src/autonomy-readiness.ts +778 -0
- package/.pi/extensions/zob-harness/src/budget-policy.ts +308 -0
- package/.pi/extensions/zob-harness/src/capabilities.ts +249 -0
- package/.pi/extensions/zob-harness/src/child-runner.ts +249 -0
- package/.pi/extensions/zob-harness/src/chronicle.ts +262 -0
- package/.pi/extensions/zob-harness/src/compute-profile.ts +602 -0
- package/.pi/extensions/zob-harness/src/compute-workflow-shape.ts +168 -0
- package/.pi/extensions/zob-harness/src/coms-v2/AGENTS.md +16 -0
- package/.pi/extensions/zob-harness/src/coms-v2/envelope.ts +121 -0
- package/.pi/extensions/zob-harness/src/coms-v2/identity.ts +53 -0
- package/.pi/extensions/zob-harness/src/coms-v2/ledger-bridge.ts +67 -0
- package/.pi/extensions/zob-harness/src/coms-v2/local-transport.ts +147 -0
- package/.pi/extensions/zob-harness/src/coms-v2/pending-replies.ts +80 -0
- package/.pi/extensions/zob-harness/src/coms-v2/policy.ts +125 -0
- package/.pi/extensions/zob-harness/src/coms-v2/presence.ts +55 -0
- package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +113 -0
- package/.pi/extensions/zob-harness/src/coms-v2/response-capture.ts +50 -0
- package/.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts +164 -0
- package/.pi/extensions/zob-harness/src/coms-v2/types.ts +149 -0
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +140 -0
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +452 -0
- package/.pi/extensions/zob-harness/src/constants.ts +108 -0
- package/.pi/extensions/zob-harness/src/context-gbrain.ts +465 -0
- package/.pi/extensions/zob-harness/src/daemon-policy.ts +223 -0
- package/.pi/extensions/zob-harness/src/daemon-readiness.ts +134 -0
- package/.pi/extensions/zob-harness/src/daemon-runtime.ts +393 -0
- package/.pi/extensions/zob-harness/src/factory/AGENTS.md +24 -0
- package/.pi/extensions/zob-harness/src/factory/agentic-plan.ts +65 -0
- package/.pi/extensions/zob-harness/src/factory/quarantine.ts +319 -0
- package/.pi/extensions/zob-harness/src/factory/run.ts +520 -0
- package/.pi/extensions/zob-harness/src/factory/validation.ts +454 -0
- package/.pi/extensions/zob-harness/src/factory-selector.ts +318 -0
- package/.pi/extensions/zob-harness/src/full-autonomy-test.ts +226 -0
- package/.pi/extensions/zob-harness/src/git-ops.ts +868 -0
- package/.pi/extensions/zob-harness/src/goal-room.ts +178 -0
- package/.pi/extensions/zob-harness/src/goal-runtime.ts +1569 -0
- package/.pi/extensions/zob-harness/src/goal-todo-imports.ts +111 -0
- package/.pi/extensions/zob-harness/src/goal-todo-types.ts +231 -0
- package/.pi/extensions/zob-harness/src/goal-todos.ts +1410 -0
- package/.pi/extensions/zob-harness/src/goal.ts +152 -0
- package/.pi/extensions/zob-harness/src/governed-requests.ts +436 -0
- package/.pi/extensions/zob-harness/src/interactive-autonomy.ts +595 -0
- package/.pi/extensions/zob-harness/src/launch-apply.ts +313 -0
- package/.pi/extensions/zob-harness/src/merge-queue.ts +290 -0
- package/.pi/extensions/zob-harness/src/mission-control.ts +573 -0
- package/.pi/extensions/zob-harness/src/model-availability.ts +52 -0
- package/.pi/extensions/zob-harness/src/model-routing.ts +429 -0
- package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +23 -0
- package/.pi/extensions/zob-harness/src/orchestration/adaptive-delegation.ts +547 -0
- package/.pi/extensions/zob-harness/src/orchestration/adaptive-workflow.ts +585 -0
- package/.pi/extensions/zob-harness/src/orchestration/lead-plan.ts +192 -0
- package/.pi/extensions/zob-harness/src/orchestration/plan.ts +168 -0
- package/.pi/extensions/zob-harness/src/orchestration/room.ts +346 -0
- package/.pi/extensions/zob-harness/src/orchestration/run.ts +134 -0
- package/.pi/extensions/zob-harness/src/orchestration/supervised-readonly.ts +1147 -0
- package/.pi/extensions/zob-harness/src/orchestration/widget-readers.ts +132 -0
- package/.pi/extensions/zob-harness/src/output-contracts.ts +656 -0
- package/.pi/extensions/zob-harness/src/project-dna.ts +533 -0
- package/.pi/extensions/zob-harness/src/promotion/AGENTS.md +24 -0
- package/.pi/extensions/zob-harness/src/promotion/candidate.ts +336 -0
- package/.pi/extensions/zob-harness/src/promotion/coms.ts +127 -0
- package/.pi/extensions/zob-harness/src/promotion/documentation.ts +142 -0
- package/.pi/extensions/zob-harness/src/promotion/factory.ts +107 -0
- package/.pi/extensions/zob-harness/src/promotion/ledger.ts +2 -0
- package/.pi/extensions/zob-harness/src/promotion/temp-agent.ts +151 -0
- package/.pi/extensions/zob-harness/src/promotion/types.ts +149 -0
- package/.pi/extensions/zob-harness/src/promotion/validate.ts +6 -0
- package/.pi/extensions/zob-harness/src/promotion/write-lane.ts +162 -0
- package/.pi/extensions/zob-harness/src/prompt-packs.ts +239 -0
- package/.pi/extensions/zob-harness/src/queue.ts +386 -0
- package/.pi/extensions/zob-harness/src/rules.ts +225 -0
- package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +26 -0
- package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +116 -0
- package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +715 -0
- package/.pi/extensions/zob-harness/src/runtime/commands.ts +1315 -0
- package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +516 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-click-markers.ts +141 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +415 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-markdown.ts +97 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +553 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-mouse.ts +205 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +434 -0
- package/.pi/extensions/zob-harness/src/runtime/events.ts +736 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +214 -0
- package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +144 -0
- package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +270 -0
- package/.pi/extensions/zob-harness/src/runtime/state.ts +403 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +117 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +136 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +365 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +70 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1854 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +810 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +46 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +38 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +61 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +77 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +106 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +123 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +93 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +62 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +147 -0
- package/.pi/extensions/zob-harness/src/runtime/widget.ts +353 -0
- package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +60 -0
- package/.pi/extensions/zob-harness/src/safety.ts +338 -0
- package/.pi/extensions/zob-harness/src/sandbox.ts +1508 -0
- package/.pi/extensions/zob-harness/src/schemas-project-dna.ts +47 -0
- package/.pi/extensions/zob-harness/src/schemas.ts +695 -0
- package/.pi/extensions/zob-harness/src/telemetry.ts +373 -0
- package/.pi/extensions/zob-harness/src/topology/AGENTS.md +22 -0
- package/.pi/extensions/zob-harness/src/topology/chains.ts +236 -0
- package/.pi/extensions/zob-harness/src/topology/coms.ts +211 -0
- package/.pi/extensions/zob-harness/src/topology/orchestration-profiles.ts +204 -0
- package/.pi/extensions/zob-harness/src/topology/teams.ts +113 -0
- package/.pi/extensions/zob-harness/src/types/core.ts +47 -0
- package/.pi/extensions/zob-harness/src/types.ts +939 -0
- package/.pi/extensions/zob-harness/src/utils/AGENTS.md +22 -0
- package/.pi/extensions/zob-harness/src/utils/formatting.ts +34 -0
- package/.pi/extensions/zob-harness/src/utils/hashing.ts +11 -0
- package/.pi/extensions/zob-harness/src/utils/json.ts +28 -0
- package/.pi/extensions/zob-harness/src/utils/paths.ts +54 -0
- package/.pi/extensions/zob-harness/src/utils/records.ts +25 -0
- package/.pi/extensions/zob-harness/src/utils/resources.ts +38 -0
- package/.pi/extensions/zob-harness/src/worker-pool.ts +672 -0
- package/.pi/extensions/zob-harness/src/workspace-claims.ts +297 -0
- package/.pi/extensions/zob-switch/index.ts +180 -0
- package/.pi/factories/budget-preflight-dry-run/batch-manifest.json +59 -0
- package/.pi/factories/budget-preflight-dry-run/factory.json +94 -0
- package/.pi/factories/budget-preflight-dry-run/pilot-manifest.json +50 -0
- package/.pi/factories/budget-preflight-dry-run/smoke-manifest.json +43 -0
- package/.pi/factories/code-review-matrix/batch-manifest.json +61 -0
- package/.pi/factories/code-review-matrix/factory.json +163 -0
- package/.pi/factories/code-review-matrix/pilot-manifest.json +41 -0
- package/.pi/factories/code-review-matrix/smoke-manifest.json +35 -0
- package/.pi/factories/factory-forge/batch-manifest.json +56 -0
- package/.pi/factories/factory-forge/factory.json +84 -0
- package/.pi/factories/factory-forge/pilot-manifest.json +32 -0
- package/.pi/factories/factory-forge/smoke-manifest.json +19 -0
- package/.pi/factories/opencode-pattern-canonizer/batch-manifest.json +54 -0
- package/.pi/factories/opencode-pattern-canonizer/factory.json +86 -0
- package/.pi/factories/opencode-pattern-canonizer/pilot-manifest.json +39 -0
- package/.pi/factories/opencode-pattern-canonizer/smoke-manifest.json +26 -0
- package/.pi/factories/project-dna/README.md +182 -0
- package/.pi/factories/project-dna/batch-manifest.json +37 -0
- package/.pi/factories/project-dna/example-project-dna-manifest-v2.json +80 -0
- package/.pi/factories/project-dna/example-project-dna-manifest.json +58 -0
- package/.pi/factories/project-dna/factory.json +131 -0
- package/.pi/factories/project-dna/golden-cases-smoke.json +62 -0
- package/.pi/factories/project-dna/pi-agentic-ontology.json +88 -0
- package/.pi/factories/project-dna/pilot-manifest.json +32 -0
- package/.pi/factories/project-dna/schemas/benchmark-suite.schema.json +27 -0
- package/.pi/factories/project-dna/schemas/code-knowledge-graph.schema.json +97 -0
- package/.pi/factories/project-dna/schemas/context-pack.schema.json +43 -0
- package/.pi/factories/project-dna/schemas/golden-case.schema.json +36 -0
- package/.pi/factories/project-dna/schemas/manifest-v2.schema.json +128 -0
- package/.pi/factories/project-dna/schemas/manifest.schema.json +77 -0
- package/.pi/factories/project-dna/schemas/ontology.schema.json +45 -0
- package/.pi/factories/project-dna/schemas/project-fingerprint.schema.json +28 -0
- package/.pi/factories/project-dna/schemas/query-steward-report.schema.json +52 -0
- package/.pi/factories/project-dna/smoke-manifest.json +27 -0
- package/.pi/factories/roadmap-smoke-lots/batch-manifest.json +49 -0
- package/.pi/factories/roadmap-smoke-lots/factory.json +89 -0
- package/.pi/factories/roadmap-smoke-lots/pilot-manifest.json +50 -0
- package/.pi/factories/roadmap-smoke-lots/smoke-manifest.json +35 -0
- package/.pi/git-policy.json +120 -0
- package/.pi/mission-control/zob_coms_transport.json +64 -0
- package/.pi/model-catalog.example.json +345 -0
- package/.pi/model-economy.example.json +196 -0
- package/.pi/model-routing.json +86 -0
- package/.pi/orchestrations/adaptive-chief-vision.json +193 -0
- package/.pi/orchestrations/ceo-feature-build.json +182 -0
- package/.pi/orchestrations/readonly-dynamic-smoke.json +75 -0
- package/.pi/output-contracts/agent-event.v1.json +19 -0
- package/.pi/output-contracts/base.v1.json +24 -0
- package/.pi/output-contracts/brain-lookup.v1.json +21 -0
- package/.pi/output-contracts/clarification.v1.json +21 -0
- package/.pi/output-contracts/context-pack.v1.json +20 -0
- package/.pi/output-contracts/context-request.v1.json +21 -0
- package/.pi/output-contracts/context-steward.v1.json +19 -0
- package/.pi/output-contracts/context-writeback-proposal.v1.json +18 -0
- package/.pi/output-contracts/delegation-request.v1.json +21 -0
- package/.pi/output-contracts/explore.v1.json +52 -0
- package/.pi/output-contracts/factory.v1.json +48 -0
- package/.pi/output-contracts/guidance-steward.v1.json +18 -0
- package/.pi/output-contracts/implement.v1.json +40 -0
- package/.pi/output-contracts/launch-authorization.v1.json +21 -0
- package/.pi/output-contracts/lead-plan.v1.json +22 -0
- package/.pi/output-contracts/mission-readiness.v1.json +20 -0
- package/.pi/output-contracts/oracle-merge.v1.json +44 -0
- package/.pi/output-contracts/oracle-request.v1.json +20 -0
- package/.pi/output-contracts/oracle.v1.json +44 -0
- package/.pi/output-contracts/orchestration-profile.v1.json +22 -0
- package/.pi/output-contracts/plan.v1.json +48 -0
- package/.pi/output-contracts/prompt-pack.v1.json +20 -0
- package/.pi/output-contracts/qa.v1.json +40 -0
- package/.pi/output-contracts/research.v1.json +36 -0
- package/.pi/output-contracts/spec.v1.json +22 -0
- package/.pi/output-contracts/synthesis.v1.json +44 -0
- package/.pi/output-contracts/temp-agent-card.v1.json +23 -0
- package/.pi/output-contracts/todo-child-result.v1.json +20 -0
- package/.pi/output-contracts/todo-child-result.v2.json +22 -0
- package/.pi/output-contracts/todo-claim-validation.v1.json +22 -0
- package/.pi/output-contracts/todo-split-request.v1.json +20 -0
- package/.pi/prompts/adaptive-workflow.md +63 -0
- package/.pi/prompts/autonomous-runtime.md +15 -0
- package/.pi/prompts/benchmark-contender.md +15 -0
- package/.pi/prompts/benchmark-judge.md +19 -0
- package/.pi/prompts/clarify-spec.md +20 -0
- package/.pi/prompts/compute-plan.md +36 -0
- package/.pi/prompts/compute-preview.md +42 -0
- package/.pi/prompts/contract.md +29 -0
- package/.pi/prompts/explore.md +13 -0
- package/.pi/prompts/factory-run.md +36 -0
- package/.pi/prompts/factory.md +20 -0
- package/.pi/prompts/implement.md +27 -0
- package/.pi/prompts/model-catalog.md +68 -0
- package/.pi/prompts/model-economy.md +64 -0
- package/.pi/prompts/oracle-merge.md +18 -0
- package/.pi/prompts/oracle.md +13 -0
- package/.pi/prompts/orchestrator.md +48 -0
- package/.pi/prompts/parallel-review.md +21 -0
- package/.pi/prompts/plan.md +21 -0
- package/.pi/prompts/project-dna.md +90 -0
- package/.pi/prompts/refactor-oracle.md +23 -0
- package/.pi/prompts/refactor-slice.md +24 -0
- package/.pi/prompts/research.md +20 -0
- package/.pi/prompts/spec.md +19 -0
- package/.pi/prompts/synthesis.md +18 -0
- package/.pi/rules/always.md +38 -0
- package/.pi/rules/docs.md +32 -0
- package/.pi/rules/factory.md +44 -0
- package/.pi/rules/oracle.md +34 -0
- package/.pi/rules/orchestration.md +44 -0
- package/.pi/rules/project.md +34 -0
- package/.pi/rules/prompts.md +43 -0
- package/.pi/rules/runtime.md +43 -0
- package/.pi/rules/sandbox.md +43 -0
- package/.pi/settings.json +28 -0
- package/.pi/skills/zob-agentic-access/SKILL.md +20 -0
- package/.pi/skills/zob-autonomous-runtime/SKILL.md +41 -0
- package/.pi/skills/zob-commit/SKILL.md +79 -0
- package/.pi/skills/zob-compaction-policy/SKILL.md +92 -0
- package/.pi/skills/zob-compute-profile/SKILL.md +108 -0
- package/.pi/skills/zob-coms-safety/SKILL.md +54 -0
- package/.pi/skills/zob-coms-v2-live/SKILL.md +47 -0
- package/.pi/skills/zob-delegation-routing/SKILL.md +82 -0
- package/.pi/skills/zob-factory/SKILL.md +28 -0
- package/.pi/skills/zob-goal-todo-tree/SKILL.md +279 -0
- package/.pi/skills/zob-harness/SKILL.md +68 -0
- package/.pi/skills/zob-mission-control-coms/SKILL.md +39 -0
- package/.pi/skills/zob-oracle/SKILL.md +21 -0
- package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +244 -0
- package/.pi/skills/zob-owner-pool-launcher/SKILL.md +261 -0
- package/.pi/skills/zob-project-dna/SKILL.md +275 -0
- package/.pi/skills/zob-sandbox/SKILL.md +29 -0
- package/.pi/skills/zob-spec/SKILL.md +25 -0
- package/.pi/skills/zob-split-refactor/SKILL.md +39 -0
- package/.pi/skills/zob-tool-router/SKILL.md +104 -0
- package/.pi/teams/zob-core.json +122 -0
- package/AGENTS.md +89 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/SECURITY.md +35 -0
- package/SOURCE_INDEX.md +46 -0
- package/package.json +135 -0
- package/scripts/README.md +57 -0
- package/scripts/autonomy/mission-readiness-secret-smoke.mjs +90 -0
- package/scripts/compute-profile/plan-workflow.mjs +85 -0
- package/scripts/compute-profile/preview.mjs +242 -0
- package/scripts/compute-profile/regression-smoke.mjs +38 -0
- package/scripts/compute-profile/summarize.mjs +72 -0
- package/scripts/compute-profile/validate-policy.mjs +50 -0
- package/scripts/compute-profile/validate-preview.mjs +95 -0
- package/scripts/compute-profile/validate-workflow.mjs +58 -0
- package/scripts/git-ops/commit-policy-smoke.mjs +221 -0
- package/scripts/goal-todo/child-goal-ref-smoke.mjs +252 -0
- package/scripts/harness-switch/static-smoke.mjs +43 -0
- package/scripts/model-catalog/validate-economy.mjs +223 -0
- package/scripts/model-catalog/validate.mjs +199 -0
- package/scripts/package-surface/validate-script-refs.mjs +190 -0
- package/scripts/path-policy/validate-smoke.mjs +103 -0
- package/scripts/project-dna/bench-smoke.mjs +217 -0
- package/scripts/project-dna/build-capsules.mjs +207 -0
- package/scripts/project-dna/build-sample-spec.mjs +140 -0
- package/scripts/project-dna/emit-golden-cases.mjs +75 -0
- package/scripts/project-dna/emit-ontology.mjs +75 -0
- package/scripts/project-dna/generate-sample.mjs +302 -0
- package/scripts/project-dna/oracle-review-smoke.mjs +157 -0
- package/scripts/project-dna/plan-workflow.mjs +289 -0
- package/scripts/project-dna/query-context.mjs +276 -0
- package/scripts/project-dna/query-steward.mjs +149 -0
- package/scripts/project-dna/scan.mjs +553 -0
- package/scripts/project-dna/validate-5of5.mjs +159 -0
- package/scripts/project-dna/validate-golden-cases.mjs +78 -0
- package/scripts/project-dna/validate-ontology.mjs +97 -0
- package/scripts/project-dna/validate-sample-project.mjs +105 -0
- package/scripts/project-dna/validate-scaffold.mjs +383 -0
- package/scripts/project-dna/validate-scan-artifacts.mjs +187 -0
- package/scripts/project-dna/validate-workflow.mjs +166 -0
- package/scripts/start-pi.sh +4 -0
- package/scripts/worker-pool/static-smoke.mjs +54 -0
- package/scripts/zpeer-local-e2e-smoke.mjs +395 -0
- package/scripts/zpeer-static-smoke.mjs +129 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { buildZobComsProjectId } from "./identity.js";
|
|
6
|
+
import type { ZobLivePeerCard, ZpeerRoomMembership } from "./types.js";
|
|
7
|
+
import { sha256 } from "../utils/hashing.js";
|
|
8
|
+
import { isRecord } from "../utils/records.js";
|
|
9
|
+
import { safeFileStem } from "../utils/paths.js";
|
|
10
|
+
|
|
11
|
+
const PROFILE_SCHEMA = "zob.zpeer-local-profile.v1";
|
|
12
|
+
const FORBIDDEN_PROFILE_KEYS = new Set(["body", "task", "prompt", "output", "content", "message", "response", "rationale", "text", "diff", "patch"]);
|
|
13
|
+
|
|
14
|
+
export interface ZpeerLocalProfile {
|
|
15
|
+
schema: typeof PROFILE_SCHEMA;
|
|
16
|
+
profileId: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
alias?: string;
|
|
19
|
+
roomId?: string;
|
|
20
|
+
activeRoomId?: string;
|
|
21
|
+
memberships?: ZpeerRoomMembership[];
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
localOnly: true;
|
|
25
|
+
networkEnabled: false;
|
|
26
|
+
bodyStored: false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function registryRoot(): string {
|
|
30
|
+
const override = process.env.ZOB_COMS_REGISTRY_ROOT;
|
|
31
|
+
if (override && override.trim().length > 0) return override;
|
|
32
|
+
return join(homedir(), ".pi", "zob-coms");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstEnvValue(names: string[]): { name: string; value: string } | undefined {
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
const value = process.env[name]?.trim();
|
|
38
|
+
if (value) return { name, value };
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function derivedSessionSeed(repoRoot: string): string | undefined {
|
|
44
|
+
const parts = [
|
|
45
|
+
process.env.TMUX_PANE,
|
|
46
|
+
process.env.STY,
|
|
47
|
+
process.env.TERM_SESSION_ID,
|
|
48
|
+
process.env.KITTY_WINDOW_ID,
|
|
49
|
+
process.env.WEZTERM_PANE,
|
|
50
|
+
process.env.WINDOWID,
|
|
51
|
+
process.env.VSCODE_PID,
|
|
52
|
+
process.env.TTY,
|
|
53
|
+
process.env.SSH_TTY,
|
|
54
|
+
].map((value) => value?.trim()).filter((value): value is string => Boolean(value));
|
|
55
|
+
if (parts.length === 0) return undefined;
|
|
56
|
+
return `${repoRoot}\n${parts.join("\n")}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveZpeerProfileId(repoRoot: string): string {
|
|
60
|
+
const explicit = firstEnvValue(["ZOB_ZPEER_PROFILE_ID", "ZPEER_PROFILE"]);
|
|
61
|
+
if (explicit) return safeFileStem(`explicit-${explicit.value}`).slice(0, 80) || `explicit-${sha256(explicit.value).slice(0, 16)}`;
|
|
62
|
+
const comsSession = firstEnvValue(["ZOB_COMS_SESSION_ID"]);
|
|
63
|
+
if (comsSession) return safeFileStem(`coms-${comsSession.value}`).slice(0, 80) || `coms-${sha256(comsSession.value).slice(0, 16)}`;
|
|
64
|
+
const terminalSeed = derivedSessionSeed(repoRoot);
|
|
65
|
+
if (terminalSeed) return `terminal-${sha256(terminalSeed).slice(0, 20)}`;
|
|
66
|
+
return `process-${sha256(`${repoRoot}:${process.pid}:${Date.now()}`).slice(0, 20)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function zpeerProfileDir(repoRoot: string): { dir: string; projectId: string } {
|
|
70
|
+
const projectId = buildZobComsProjectId(repoRoot);
|
|
71
|
+
return { dir: join(registryRoot(), "projects", projectId, "zpeer-profiles"), projectId };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function zpeerProfilePath(repoRoot: string, profileId = resolveZpeerProfileId(repoRoot)): string {
|
|
75
|
+
const { dir } = zpeerProfileDir(repoRoot);
|
|
76
|
+
return join(dir, `${safeFileStem(sha256(profileId).slice(0, 32))}.json`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function hasForbiddenProfileKey(value: unknown): boolean {
|
|
80
|
+
if (!value || typeof value !== "object") return false;
|
|
81
|
+
if (Array.isArray(value)) return value.some(hasForbiddenProfileKey);
|
|
82
|
+
return Object.entries(value).some(([key, child]) => FORBIDDEN_PROFILE_KEYS.has(key) || hasForbiddenProfileKey(child));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseZpeerLocalProfile(value: unknown, repoRoot: string): ZpeerLocalProfile | undefined {
|
|
86
|
+
if (!isRecord(value) || value.schema !== PROFILE_SCHEMA) return undefined;
|
|
87
|
+
if (hasForbiddenProfileKey(value) || value.bodyStored !== false || value.localOnly !== true || value.networkEnabled !== false) return undefined;
|
|
88
|
+
if (typeof value.profileId !== "string" || value.projectId !== buildZobComsProjectId(repoRoot)) return undefined;
|
|
89
|
+
if (value.alias !== undefined && typeof value.alias !== "string") return undefined;
|
|
90
|
+
if (value.roomId !== undefined && typeof value.roomId !== "string") return undefined;
|
|
91
|
+
if (value.activeRoomId !== undefined && typeof value.activeRoomId !== "string") return undefined;
|
|
92
|
+
if (value.memberships !== undefined) {
|
|
93
|
+
if (!Array.isArray(value.memberships)) return undefined;
|
|
94
|
+
for (const membership of value.memberships) {
|
|
95
|
+
if (!isRecord(membership)) return undefined;
|
|
96
|
+
if (typeof membership.roomId !== "string" || typeof membership.alias !== "string" || typeof membership.role !== "string") return undefined;
|
|
97
|
+
if (typeof membership.joinedAt !== "string" || membership.localOnly !== true || membership.networkEnabled !== false || membership.bodyStored !== false) return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") return undefined;
|
|
101
|
+
return value as unknown as ZpeerLocalProfile;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function readZpeerLocalProfile(repoRoot: string, profileId = resolveZpeerProfileId(repoRoot)): ZpeerLocalProfile | undefined {
|
|
105
|
+
const path = zpeerProfilePath(repoRoot, profileId);
|
|
106
|
+
if (!existsSync(path)) return undefined;
|
|
107
|
+
try {
|
|
108
|
+
return parseZpeerLocalProfile(JSON.parse(readFileSync(path, "utf8")) as unknown, repoRoot);
|
|
109
|
+
} catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function writeZpeerLocalProfile(repoRoot: string, input: { alias?: string; roomId?: string; activeRoomId?: string; memberships?: ZpeerRoomMembership[] }, profileId = resolveZpeerProfileId(repoRoot)): ZpeerLocalProfile {
|
|
115
|
+
const { dir, projectId } = zpeerProfileDir(repoRoot);
|
|
116
|
+
const existing = readZpeerLocalProfile(repoRoot, profileId);
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
const profile: ZpeerLocalProfile = {
|
|
119
|
+
schema: PROFILE_SCHEMA,
|
|
120
|
+
profileId,
|
|
121
|
+
projectId,
|
|
122
|
+
alias: input.alias ?? existing?.alias,
|
|
123
|
+
roomId: input.roomId ?? existing?.roomId,
|
|
124
|
+
activeRoomId: input.activeRoomId ?? input.roomId ?? existing?.activeRoomId,
|
|
125
|
+
memberships: input.memberships ?? existing?.memberships,
|
|
126
|
+
createdAt: existing?.createdAt ?? now,
|
|
127
|
+
updatedAt: now,
|
|
128
|
+
localOnly: true,
|
|
129
|
+
networkEnabled: false,
|
|
130
|
+
bodyStored: false,
|
|
131
|
+
};
|
|
132
|
+
if (hasForbiddenProfileKey(profile)) throw new Error("Refusing to persist ZPeer local profile with forbidden body-like keys");
|
|
133
|
+
mkdirSync(dir, { recursive: true });
|
|
134
|
+
writeFileSync(zpeerProfilePath(repoRoot, profileId), `${JSON.stringify(profile, null, 2)}\n`, "utf8");
|
|
135
|
+
return profile;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function writeZpeerLocalProfileFromPeer(repoRoot: string, peer: Pick<ZobLivePeerCard, "zpeerAlias" | "zpeerRoomId" | "zpeerActiveRoomId" | "zpeerMemberships">, profileId = resolveZpeerProfileId(repoRoot)): ZpeerLocalProfile {
|
|
139
|
+
return writeZpeerLocalProfile(repoRoot, { alias: peer.zpeerAlias, roomId: peer.zpeerRoomId, activeRoomId: peer.zpeerActiveRoomId, memberships: peer.zpeerMemberships }, profileId);
|
|
140
|
+
}
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { buildZobComsProjectId } from "./identity.js";
|
|
5
|
+
import { buildZobLiveEnvelope } from "./envelope.js";
|
|
6
|
+
import { sendZobLocalEnvelope } from "./local-transport.js";
|
|
7
|
+
import { readZobLiveRegistrySnapshot, writeZobLivePeerCard } from "./registry.js";
|
|
8
|
+
import type { ZobLivePeerCard, ZobLivePeerStatus, ZpeerRoomMembership, ZpeerRoomMembershipRole } from "./types.js";
|
|
9
|
+
import { validateZobComsEdge } from "../topology/coms.js";
|
|
10
|
+
import { loadTeamDefinition, validateTeamDefinition } from "../topology/teams.js";
|
|
11
|
+
import { sha256 } from "../utils/hashing.js";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ROOM_ID = "default";
|
|
14
|
+
const ALIAS_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{1,31}$/;
|
|
15
|
+
const ROOM_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
16
|
+
const MEMBERSHIP_ROLES = new Set<ZpeerRoomMembershipRole>(["member", "bridge", "observer"]);
|
|
17
|
+
|
|
18
|
+
export interface ZpeerRoomSummary {
|
|
19
|
+
schema: "zob.zpeer-room-summary.v1";
|
|
20
|
+
projectId: string;
|
|
21
|
+
roomId: string;
|
|
22
|
+
selfAlias?: string;
|
|
23
|
+
peerCount: number;
|
|
24
|
+
online: number;
|
|
25
|
+
stale: number;
|
|
26
|
+
offline: number;
|
|
27
|
+
aliases: string[];
|
|
28
|
+
duplicateAliases: string[];
|
|
29
|
+
membershipCount?: number;
|
|
30
|
+
localOnly: true;
|
|
31
|
+
networkEnabled: false;
|
|
32
|
+
bodyStored: false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ZpeerPeerRoomSummary extends ZpeerRoomSummary {
|
|
36
|
+
active: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ZpeerSendMode = "await" | "async" | "long";
|
|
40
|
+
export type ZpeerSendStatus = "delivered" | "waiting" | "reply" | "completed" | "blocked" | "error" | "timeout" | "expired";
|
|
41
|
+
|
|
42
|
+
export interface ZpeerSendResult {
|
|
43
|
+
status: ZpeerSendStatus;
|
|
44
|
+
reason?: string;
|
|
45
|
+
msgId?: string;
|
|
46
|
+
roomId?: string;
|
|
47
|
+
targetAlias?: string;
|
|
48
|
+
taskHash?: string;
|
|
49
|
+
outputHash?: string;
|
|
50
|
+
transientResponse?: string;
|
|
51
|
+
bodyStored: false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ZpeerSendFeedback {
|
|
55
|
+
kind: "delivered" | "waiting" | "reply" | "expired" | "timeout" | "blocked" | "error";
|
|
56
|
+
result: ZpeerSendResult;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ZpeerSendOptions {
|
|
60
|
+
mode?: ZpeerSendMode;
|
|
61
|
+
roomId?: string;
|
|
62
|
+
onFeedback?: (feedback: ZpeerSendFeedback) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ZpeerRoomPeer {
|
|
66
|
+
peer: ZobLivePeerCard;
|
|
67
|
+
membership: ZpeerRoomMembership;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function generatedZpeerAlias(peer: Pick<ZobLivePeerCard, "roleId" | "sessionHash">): string {
|
|
71
|
+
return safeZpeerAlias(`${peer.roleId}-${peer.sessionHash.slice(0, 6)}`) ?? `peer-${peer.sessionHash.slice(0, 8)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function safeZpeerAlias(value: string | undefined): string | undefined {
|
|
75
|
+
const trimmed = value?.trim().replace(/^@+/, "");
|
|
76
|
+
if (!trimmed || !ALIAS_PATTERN.test(trimmed)) return undefined;
|
|
77
|
+
return trimmed;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function safeZpeerRoomId(value: string | undefined): string | undefined {
|
|
81
|
+
const trimmed = value?.trim();
|
|
82
|
+
if (!trimmed || !ROOM_PATTERN.test(trimmed)) return undefined;
|
|
83
|
+
return trimmed;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function safeMembershipRole(value: string | undefined): ZpeerRoomMembershipRole {
|
|
87
|
+
return value && MEMBERSHIP_ROLES.has(value as ZpeerRoomMembershipRole) ? value as ZpeerRoomMembershipRole : "member";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildMembership(input: { roomId: string; alias: string; role?: string; joinedAt?: string }): ZpeerRoomMembership {
|
|
91
|
+
return {
|
|
92
|
+
roomId: input.roomId,
|
|
93
|
+
alias: input.alias,
|
|
94
|
+
role: safeMembershipRole(input.role),
|
|
95
|
+
joinedAt: input.joinedAt && Number.isFinite(Date.parse(input.joinedAt)) ? input.joinedAt : new Date().toISOString(),
|
|
96
|
+
localOnly: true,
|
|
97
|
+
networkEnabled: false,
|
|
98
|
+
bodyStored: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizeZpeerMemberships(memberships: readonly ZpeerRoomMembership[] | undefined): ZpeerRoomMembership[] {
|
|
103
|
+
const byRoom = new Map<string, ZpeerRoomMembership>();
|
|
104
|
+
for (const raw of memberships ?? []) {
|
|
105
|
+
const roomId = safeZpeerRoomId(raw.roomId);
|
|
106
|
+
const alias = safeZpeerAlias(raw.alias);
|
|
107
|
+
if (!roomId || !alias) continue;
|
|
108
|
+
byRoom.set(roomId, buildMembership({ roomId, alias, role: raw.role, joinedAt: raw.joinedAt }));
|
|
109
|
+
}
|
|
110
|
+
return [...byRoom.values()].sort((left, right) => left.roomId.localeCompare(right.roomId));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function zpeerMembershipsForPeer(peer: ZobLivePeerCard): ZpeerRoomMembership[] {
|
|
114
|
+
const memberships = normalizeZpeerMemberships(peer.zpeerMemberships);
|
|
115
|
+
const byRoom = new Map(memberships.map((membership) => [membership.roomId, membership]));
|
|
116
|
+
const legacyRoomId = safeZpeerRoomId(peer.zpeerRoomId) ?? DEFAULT_ROOM_ID;
|
|
117
|
+
const legacyAlias = safeZpeerAlias(peer.zpeerAlias) ?? generatedZpeerAlias(peer);
|
|
118
|
+
if (!byRoom.has(legacyRoomId)) {
|
|
119
|
+
byRoom.set(legacyRoomId, buildMembership({ roomId: legacyRoomId, alias: legacyAlias, role: "member", joinedAt: peer.startedAt }));
|
|
120
|
+
}
|
|
121
|
+
return [...byRoom.values()].sort((left, right) => left.roomId.localeCompare(right.roomId));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function activeZpeerRoomId(peer: ZobLivePeerCard): string {
|
|
125
|
+
const requested = safeZpeerRoomId(peer.zpeerActiveRoomId ?? peer.zpeerRoomId) ?? DEFAULT_ROOM_ID;
|
|
126
|
+
const memberships = zpeerMembershipsForPeer(peer);
|
|
127
|
+
return memberships.some((membership) => membership.roomId === requested) ? requested : memberships[0]?.roomId ?? DEFAULT_ROOM_ID;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function zpeerMembershipForRoom(peer: ZobLivePeerCard, roomId: string): ZpeerRoomMembership | undefined {
|
|
131
|
+
return zpeerMembershipsForPeer(peer).find((membership) => membership.roomId === roomId);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function activeZpeerMembership(peer: ZobLivePeerCard): ZpeerRoomMembership | undefined {
|
|
135
|
+
return zpeerMembershipForRoom(peer, activeZpeerRoomId(peer));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function peerAliasInRoom(peer: ZobLivePeerCard, roomId: string): string | undefined {
|
|
139
|
+
return zpeerMembershipForRoom(peer, roomId)?.alias;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isPeerInZpeerRoom(peer: ZobLivePeerCard, roomId: string): boolean {
|
|
143
|
+
return Boolean(zpeerMembershipForRoom(peer, roomId));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function upsertMembership(memberships: ZpeerRoomMembership[], next: ZpeerRoomMembership): ZpeerRoomMembership[] {
|
|
147
|
+
const replaced = memberships.filter((membership) => membership.roomId !== next.roomId);
|
|
148
|
+
return [...replaced, next].sort((left, right) => left.roomId.localeCompare(right.roomId));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function withZpeerMembershipState(repoRoot: string, peer: ZobLivePeerCard, memberships: ZpeerRoomMembership[], activeRoomIdInput?: string): ZobLivePeerCard {
|
|
152
|
+
const normalized = memberships.length > 0 ? memberships : zpeerMembershipsForPeer(peer);
|
|
153
|
+
const activeRoomId = safeZpeerRoomId(activeRoomIdInput) && normalized.some((membership) => membership.roomId === activeRoomIdInput)
|
|
154
|
+
? activeRoomIdInput
|
|
155
|
+
: normalized[0]?.roomId ?? DEFAULT_ROOM_ID;
|
|
156
|
+
const activeMembership = normalized.find((membership) => membership.roomId === activeRoomId) ?? normalized[0] ?? buildMembership({ roomId: DEFAULT_ROOM_ID, alias: generatedZpeerAlias(peer), role: "member", joinedAt: peer.startedAt });
|
|
157
|
+
return writeZobLivePeerCard(repoRoot, {
|
|
158
|
+
...peer,
|
|
159
|
+
projectId: buildZobComsProjectId(repoRoot),
|
|
160
|
+
zpeerRoomId: activeMembership.roomId,
|
|
161
|
+
zpeerAlias: activeMembership.alias,
|
|
162
|
+
zpeerActiveRoomId: activeMembership.roomId,
|
|
163
|
+
zpeerMemberships: normalized,
|
|
164
|
+
zpeerLocalOnly: true,
|
|
165
|
+
bodyStored: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hasLocalSocketEndpointEvidence(peer: ZobLivePeerCard): boolean {
|
|
170
|
+
return peer.transport === "local_socket"
|
|
171
|
+
&& Boolean(peer.endpoint)
|
|
172
|
+
&& !peer.endpoint.startsWith("pending-")
|
|
173
|
+
&& peer.endpoint !== "observe-only"
|
|
174
|
+
&& existsSync(peer.endpoint);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function zpeerReachableStatus(peer: ZobLivePeerCard): ZobLivePeerStatus {
|
|
178
|
+
if (peer.status === "online" && hasLocalSocketEndpointEvidence(peer)) return "online";
|
|
179
|
+
if (peer.status === "stale") return "stale";
|
|
180
|
+
return "offline";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function ensureZpeerFields(repoRoot: string, peer: ZobLivePeerCard, roomId?: string, alias?: string, restoredMemberships?: ZpeerRoomMembership[]): ZobLivePeerCard {
|
|
184
|
+
const hasRestoredMemberships = Boolean(restoredMemberships && restoredMemberships.length > 0);
|
|
185
|
+
const baseMemberships = hasRestoredMemberships
|
|
186
|
+
? normalizeZpeerMemberships(restoredMemberships)
|
|
187
|
+
: zpeerMembershipsForPeer(peer);
|
|
188
|
+
const restoredRoomId = (candidate: string | undefined): string | undefined => {
|
|
189
|
+
const safe = safeZpeerRoomId(candidate);
|
|
190
|
+
return safe && baseMemberships.some((membership) => membership.roomId === safe) ? safe : undefined;
|
|
191
|
+
};
|
|
192
|
+
const requestedRoomId = hasRestoredMemberships
|
|
193
|
+
? restoredRoomId(roomId) ?? restoredRoomId(peer.zpeerActiveRoomId) ?? baseMemberships[0]?.roomId ?? DEFAULT_ROOM_ID
|
|
194
|
+
: safeZpeerRoomId(roomId ?? peer.zpeerActiveRoomId ?? peer.zpeerRoomId) ?? baseMemberships[0]?.roomId ?? DEFAULT_ROOM_ID;
|
|
195
|
+
const existing = baseMemberships.find((membership) => membership.roomId === requestedRoomId);
|
|
196
|
+
const requestedAlias = safeZpeerAlias(alias ?? existing?.alias ?? peer.zpeerAlias) ?? generatedZpeerAlias(peer);
|
|
197
|
+
const requested = buildMembership({ roomId: requestedRoomId, alias: requestedAlias, role: existing?.role ?? "member", joinedAt: existing?.joinedAt ?? peer.startedAt });
|
|
198
|
+
return withZpeerMembershipState(repoRoot, peer, upsertMembership(baseMemberships, requested), requestedRoomId);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function refreshZpeerSelf(repoRoot: string, peer: ZobLivePeerCard, roomId?: string, alias?: string, restoredMemberships?: ZpeerRoomMembership[]): ZobLivePeerCard {
|
|
202
|
+
const ensured = ensureZpeerFields(repoRoot, peer, roomId, alias, restoredMemberships);
|
|
203
|
+
if (!hasLocalSocketEndpointEvidence(ensured)) return ensured;
|
|
204
|
+
return writeZobLivePeerCard(repoRoot, { ...ensured, heartbeatAt: new Date().toISOString(), status: "online" });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function peersInRoom(repoRoot: string, roomId: string): ZpeerRoomPeer[] {
|
|
208
|
+
const projectId = buildZobComsProjectId(repoRoot);
|
|
209
|
+
return readZobLiveRegistrySnapshot(repoRoot).peers
|
|
210
|
+
.filter((peer) => peer.projectId === projectId)
|
|
211
|
+
.flatMap((peer) => zpeerMembershipsForPeer(peer)
|
|
212
|
+
.filter((membership) => membership.roomId === roomId)
|
|
213
|
+
.map((membership) => ({ peer, membership })));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function buildZpeerRoomSummary(repoRoot: string, self?: ZobLivePeerCard, requestedRoomId?: string): ZpeerRoomSummary {
|
|
217
|
+
const roomId = safeZpeerRoomId(requestedRoomId) ?? (self ? activeZpeerRoomId(self) : DEFAULT_ROOM_ID);
|
|
218
|
+
const peers = peersInRoom(repoRoot, roomId);
|
|
219
|
+
const counts: Record<ZobLivePeerStatus, number> = { online: 0, stale: 0, offline: 0 };
|
|
220
|
+
const aliases = peers.map((entry) => entry.membership.alias).sort();
|
|
221
|
+
for (const entry of peers) counts[zpeerReachableStatus(entry.peer)] += 1;
|
|
222
|
+
const duplicateAliases = aliases.filter((alias, index) => aliases.indexOf(alias) !== index).filter((alias, index, all) => all.indexOf(alias) === index);
|
|
223
|
+
return {
|
|
224
|
+
schema: "zob.zpeer-room-summary.v1",
|
|
225
|
+
projectId: buildZobComsProjectId(repoRoot),
|
|
226
|
+
roomId,
|
|
227
|
+
selfAlias: self ? peerAliasInRoom(self, roomId) : undefined,
|
|
228
|
+
peerCount: peers.length,
|
|
229
|
+
online: counts.online,
|
|
230
|
+
stale: counts.stale,
|
|
231
|
+
offline: counts.offline,
|
|
232
|
+
aliases,
|
|
233
|
+
duplicateAliases,
|
|
234
|
+
membershipCount: self ? zpeerMembershipsForPeer(self).length : undefined,
|
|
235
|
+
localOnly: true,
|
|
236
|
+
networkEnabled: false,
|
|
237
|
+
bodyStored: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function buildZpeerPeerRoomSummaries(repoRoot: string, self: ZobLivePeerCard): ZpeerPeerRoomSummary[] {
|
|
242
|
+
const activeRoomId = activeZpeerRoomId(self);
|
|
243
|
+
return zpeerMembershipsForPeer(self).map((membership) => ({
|
|
244
|
+
...buildZpeerRoomSummary(repoRoot, self, membership.roomId),
|
|
245
|
+
active: membership.roomId === activeRoomId,
|
|
246
|
+
})).sort((left, right) => {
|
|
247
|
+
if (left.active !== right.active) return left.active ? -1 : 1;
|
|
248
|
+
return left.roomId.localeCompare(right.roomId);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function changeZpeerAlias(repoRoot: string, self: ZobLivePeerCard, requestedAlias: string, requestedRoomId?: string): { ok: true; peer: ZobLivePeerCard } | { ok: false; reason: string } {
|
|
253
|
+
const alias = safeZpeerAlias(requestedAlias);
|
|
254
|
+
if (!alias) return { ok: false, reason: "alias must match [a-zA-Z][a-zA-Z0-9_-]{1,31}" };
|
|
255
|
+
const roomId = safeZpeerRoomId(requestedRoomId) ?? activeZpeerRoomId(self);
|
|
256
|
+
const memberships = zpeerMembershipsForPeer(self);
|
|
257
|
+
const current = memberships.find((membership) => membership.roomId === roomId);
|
|
258
|
+
if (!current) return { ok: false, reason: `current peer is not a member of room '${roomId}'` };
|
|
259
|
+
const collision = peersInRoom(repoRoot, roomId).find((entry) => entry.peer.sessionHash !== self.sessionHash && entry.membership.alias === alias && entry.peer.status !== "offline");
|
|
260
|
+
if (collision) return { ok: false, reason: `alias '${alias}' is already used in room '${roomId}'` };
|
|
261
|
+
return { ok: true, peer: refreshZpeerSelf(repoRoot, withZpeerMembershipState(repoRoot, self, upsertMembership(memberships, { ...current, alias }), activeZpeerRoomId(self))) };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function joinZpeerRoom(repoRoot: string, self: ZobLivePeerCard, requestedRoom: string, requestedAlias?: string, role: ZpeerRoomMembershipRole = "member"): { ok: true; peer: ZobLivePeerCard } | { ok: false; reason: string } {
|
|
265
|
+
const roomId = safeZpeerRoomId(requestedRoom);
|
|
266
|
+
if (!roomId) return { ok: false, reason: "room must match [a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}" };
|
|
267
|
+
const memberships = zpeerMembershipsForPeer(self);
|
|
268
|
+
const existing = memberships.find((membership) => membership.roomId === roomId);
|
|
269
|
+
const alias = safeZpeerAlias(requestedAlias ?? existing?.alias ?? self.zpeerAlias) ?? generatedZpeerAlias(self);
|
|
270
|
+
const collision = peersInRoom(repoRoot, roomId).find((entry) => entry.peer.sessionHash !== self.sessionHash && entry.membership.alias === alias && entry.peer.status !== "offline");
|
|
271
|
+
if (collision) return { ok: false, reason: `alias '${alias}' already exists in target room '${roomId}'; rename first or use 'as <alias>'` };
|
|
272
|
+
const next = buildMembership({ roomId, alias, role, joinedAt: existing?.joinedAt ?? new Date().toISOString() });
|
|
273
|
+
return { ok: true, peer: refreshZpeerSelf(repoRoot, withZpeerMembershipState(repoRoot, self, upsertMembership(memberships, next), activeZpeerRoomId(self))) };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function leaveZpeerRoom(repoRoot: string, self: ZobLivePeerCard, requestedRoom: string): { ok: true; peer: ZobLivePeerCard } | { ok: false; reason: string } {
|
|
277
|
+
const roomId = safeZpeerRoomId(requestedRoom);
|
|
278
|
+
if (!roomId) return { ok: false, reason: "room must match [a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}" };
|
|
279
|
+
const memberships = zpeerMembershipsForPeer(self);
|
|
280
|
+
if (!memberships.some((membership) => membership.roomId === roomId)) return { ok: false, reason: `current peer is not a member of room '${roomId}'` };
|
|
281
|
+
if (memberships.length <= 1) return { ok: false, reason: "cannot leave the last zpeer room" };
|
|
282
|
+
const remaining = memberships.filter((membership) => membership.roomId !== roomId);
|
|
283
|
+
const nextActive = activeZpeerRoomId(self) === roomId ? remaining[0]?.roomId : activeZpeerRoomId(self);
|
|
284
|
+
return { ok: true, peer: refreshZpeerSelf(repoRoot, withZpeerMembershipState(repoRoot, self, remaining, nextActive)) };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function useZpeerRoom(repoRoot: string, self: ZobLivePeerCard, requestedRoom: string): { ok: true; peer: ZobLivePeerCard } | { ok: false; reason: string } {
|
|
288
|
+
const roomId = safeZpeerRoomId(requestedRoom);
|
|
289
|
+
if (!roomId) return { ok: false, reason: "room must match [a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}" };
|
|
290
|
+
const memberships = zpeerMembershipsForPeer(self);
|
|
291
|
+
if (!memberships.some((membership) => membership.roomId === roomId)) return { ok: false, reason: `current peer is not a member of room '${roomId}'; join it first` };
|
|
292
|
+
return { ok: true, peer: refreshZpeerSelf(repoRoot, withZpeerMembershipState(repoRoot, self, memberships, roomId)) };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function changeZpeerRoom(repoRoot: string, self: ZobLivePeerCard, requestedRoom: string): { ok: true; peer: ZobLivePeerCard } | { ok: false; reason: string } {
|
|
296
|
+
const roomId = safeZpeerRoomId(requestedRoom);
|
|
297
|
+
if (!roomId) return { ok: false, reason: "room must match [a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}" };
|
|
298
|
+
const memberships = zpeerMembershipsForPeer(self);
|
|
299
|
+
if (!memberships.some((membership) => membership.roomId === roomId)) {
|
|
300
|
+
const joined = joinZpeerRoom(repoRoot, self, roomId, self.zpeerAlias ?? generatedZpeerAlias(self));
|
|
301
|
+
if (!joined.ok) return joined;
|
|
302
|
+
return useZpeerRoom(repoRoot, joined.peer, roomId);
|
|
303
|
+
}
|
|
304
|
+
return useZpeerRoom(repoRoot, self, roomId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function zpeerComsDir(repoRoot: string): string {
|
|
308
|
+
return join(repoRoot, ".pi", "coms");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function appendHashOnlyZpeerJsonl(repoRoot: string, fileName: "peer-messages.jsonl" | "peer-status.jsonl", record: Record<string, unknown>): void {
|
|
312
|
+
mkdirSync(zpeerComsDir(repoRoot), { recursive: true });
|
|
313
|
+
appendFileSync(join(zpeerComsDir(repoRoot), fileName), `${JSON.stringify({ ...record, bodyStored: false, timestamp: new Date().toISOString() })}\n`, "utf8");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function appendZpeerPeerRecords(repoRoot: string, record: {
|
|
317
|
+
event: "attempt" | "ack" | "terminal";
|
|
318
|
+
status: ZpeerSendResult["status"] | "sent";
|
|
319
|
+
roomId: string;
|
|
320
|
+
msgId?: string;
|
|
321
|
+
senderAlias?: string;
|
|
322
|
+
targetAlias?: string;
|
|
323
|
+
taskHash?: string;
|
|
324
|
+
outputHash?: string;
|
|
325
|
+
reason?: string;
|
|
326
|
+
peerCount?: number;
|
|
327
|
+
}): void {
|
|
328
|
+
const base = {
|
|
329
|
+
schema: "zob.zpeer-peer-hash-ref.v1",
|
|
330
|
+
event: record.event,
|
|
331
|
+
status: record.status,
|
|
332
|
+
msgId: record.msgId,
|
|
333
|
+
roomIdHash: sha256(record.roomId),
|
|
334
|
+
senderAliasHash: record.senderAlias ? sha256(record.senderAlias) : undefined,
|
|
335
|
+
targetAliasHash: record.targetAlias ? sha256(record.targetAlias) : undefined,
|
|
336
|
+
taskHash: record.taskHash,
|
|
337
|
+
outputHash: record.outputHash,
|
|
338
|
+
reasonHash: record.reason ? sha256(record.reason) : undefined,
|
|
339
|
+
peerCount: record.peerCount,
|
|
340
|
+
localOnly: true,
|
|
341
|
+
networkEnabled: false,
|
|
342
|
+
bodyStored: false,
|
|
343
|
+
};
|
|
344
|
+
appendHashOnlyZpeerJsonl(repoRoot, "peer-messages.jsonl", base);
|
|
345
|
+
appendHashOnlyZpeerJsonl(repoRoot, "peer-status.jsonl", { ...base, schema: "zob.zpeer-peer-status.v1" });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function validateZpeerTopology(repoRoot: string, self: ZobLivePeerCard, target: ZobLivePeerCard): string | undefined {
|
|
349
|
+
if (self.roleId === target.roleId && self.roleType === "orchestrator" && target.roleType === "orchestrator") return undefined;
|
|
350
|
+
const teamName = self.team || target.team || "zob-core";
|
|
351
|
+
if (target.team !== teamName) return "zpeer topology blocked: peers are in different teams";
|
|
352
|
+
let topologyRoot = repoRoot;
|
|
353
|
+
let loaded = loadTeamDefinition(topologyRoot, teamName);
|
|
354
|
+
if (!loaded.definition && process.cwd() !== repoRoot) {
|
|
355
|
+
topologyRoot = process.cwd();
|
|
356
|
+
loaded = loadTeamDefinition(topologyRoot, teamName);
|
|
357
|
+
}
|
|
358
|
+
if (!loaded.definition) return `zpeer topology blocked: ${loaded.errors.join("; ")}`;
|
|
359
|
+
const definitionErrors = validateTeamDefinition(topologyRoot, loaded.definition);
|
|
360
|
+
if (definitionErrors.length > 0) return `zpeer topology blocked: ${definitionErrors.join("; ")}`;
|
|
361
|
+
const edgeErrors = validateZobComsEdge(loaded.definition, self.roleId, target.roleId);
|
|
362
|
+
if (edgeErrors.length > 0) return `zpeer topology blocked: ${edgeErrors.join("; ")}`;
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export async function sendZpeerPrompt(repoRoot: string, self: ZobLivePeerCard, targetAliasInput: string, transientPrompt: string, awaitReply: (msgId: string) => Promise<{ status: string; envelope?: { outputHash?: string; transientResponse?: string; errorHash?: string } }>, options: ZpeerSendOptions = {}): Promise<ZpeerSendResult> {
|
|
367
|
+
const roomId = safeZpeerRoomId(options.roomId) ?? activeZpeerRoomId(self);
|
|
368
|
+
const selfMembership = zpeerMembershipForRoom(self, roomId);
|
|
369
|
+
const senderAlias = selfMembership?.alias ?? activeZpeerMembership(self)?.alias ?? generatedZpeerAlias(self);
|
|
370
|
+
const targetAlias = safeZpeerAlias(targetAliasInput);
|
|
371
|
+
const taskHash = transientPrompt.trim() ? sha256(transientPrompt) : undefined;
|
|
372
|
+
const mode = options.mode ?? "await";
|
|
373
|
+
const emitFeedback = (kind: ZpeerSendFeedback["kind"], result: ZpeerSendResult): void => {
|
|
374
|
+
options.onFeedback?.({ kind, result });
|
|
375
|
+
};
|
|
376
|
+
const finish = (event: "attempt" | "ack" | "terminal", result: ZpeerSendResult, peerCount?: number): ZpeerSendResult => {
|
|
377
|
+
appendZpeerPeerRecords(repoRoot, {
|
|
378
|
+
event,
|
|
379
|
+
status: result.status,
|
|
380
|
+
roomId,
|
|
381
|
+
msgId: result.msgId,
|
|
382
|
+
senderAlias,
|
|
383
|
+
targetAlias: result.targetAlias,
|
|
384
|
+
taskHash: result.taskHash,
|
|
385
|
+
outputHash: result.outputHash,
|
|
386
|
+
reason: result.reason,
|
|
387
|
+
peerCount,
|
|
388
|
+
});
|
|
389
|
+
return { roomId, ...result };
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (!selfMembership) return finish("attempt", { status: "blocked", reason: `current peer is not a member of room '${roomId}'`, targetAlias: targetAlias ?? undefined, taskHash, bodyStored: false });
|
|
393
|
+
if (selfMembership.role === "observer") return finish("attempt", { status: "blocked", reason: `current peer is observer-only in room '${roomId}'`, targetAlias: targetAlias ?? undefined, taskHash, bodyStored: false });
|
|
394
|
+
if (!targetAlias) return finish("attempt", { status: "blocked", reason: "invalid target alias", bodyStored: false });
|
|
395
|
+
if (!transientPrompt.trim()) return finish("attempt", { status: "blocked", reason: "empty peer prompt", targetAlias, bodyStored: false });
|
|
396
|
+
const candidates = peersInRoom(repoRoot, roomId).filter((entry) => entry.membership.alias === targetAlias && entry.peer.sessionHash !== self.sessionHash);
|
|
397
|
+
if (targetAlias === senderAlias) return finish("attempt", { status: "blocked", reason: "cannot send to self", targetAlias, taskHash, bodyStored: false });
|
|
398
|
+
if (candidates.length === 0) return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} not found in room '${roomId}'`, targetAlias, taskHash, bodyStored: false }, 0);
|
|
399
|
+
if (candidates.length > 1) return finish("attempt", { status: "blocked", reason: `duplicate alias @${targetAlias} in room '${roomId}'`, targetAlias, taskHash, bodyStored: false }, candidates.length);
|
|
400
|
+
const target = candidates[0];
|
|
401
|
+
const targetReachableStatus = zpeerReachableStatus(target.peer);
|
|
402
|
+
if (targetReachableStatus !== "online") return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} is ${targetReachableStatus}`, targetAlias, taskHash, bodyStored: false }, 1);
|
|
403
|
+
const topologyBlocker = validateZpeerTopology(repoRoot, self, target.peer);
|
|
404
|
+
if (topologyBlocker) return finish("attempt", { status: "blocked", reason: topologyBlocker, targetAlias, taskHash, bodyStored: false }, 1);
|
|
405
|
+
if (target.peer.transport !== "local_socket" || target.peer.endpoint.startsWith("pending-") || target.peer.endpoint === "observe-only") return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} is not reachable by local_socket`, targetAlias, taskHash, bodyStored: false }, 1);
|
|
406
|
+
if (self.transport !== "local_socket" || !self.endpoint || self.endpoint.startsWith("pending-") || self.endpoint === "observe-only") return finish("attempt", { status: "blocked", reason: "current session has no local_socket reply endpoint", targetAlias, taskHash, bodyStored: false }, 1);
|
|
407
|
+
|
|
408
|
+
const msgId = `zpeer:${self.sessionHash.slice(0, 8)}:${target.peer.sessionHash.slice(0, 8)}:${Date.now()}`;
|
|
409
|
+
finish("attempt", { status: "delivered", msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
410
|
+
const liveEnvelope = buildZobLiveEnvelope({
|
|
411
|
+
type: "prompt",
|
|
412
|
+
msgId,
|
|
413
|
+
runId: `zpeer:${roomId}`,
|
|
414
|
+
sender: senderAlias,
|
|
415
|
+
receiver: target.membership.alias,
|
|
416
|
+
team: self.team,
|
|
417
|
+
taskHash,
|
|
418
|
+
replyEndpoint: self.endpoint,
|
|
419
|
+
replyEndpointHash: self.endpointHash,
|
|
420
|
+
transientPrompt,
|
|
421
|
+
});
|
|
422
|
+
try {
|
|
423
|
+
const ack = await sendZobLocalEnvelope(target.peer.endpoint, liveEnvelope, { timeoutMs: 5_000 });
|
|
424
|
+
if (ack.type !== "ack") return finish("terminal", { status: "error", reason: `expected ack, got ${ack.type}`, msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
425
|
+
appendZpeerPeerRecords(repoRoot, { event: "ack", status: "delivered", roomId, msgId, senderAlias, targetAlias, taskHash, peerCount: 1 });
|
|
426
|
+
if (mode === "async") {
|
|
427
|
+
const waiting = finish("terminal", { status: "waiting", reason: "delivered locally; awaiting async reply", msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
428
|
+
emitFeedback("waiting", waiting);
|
|
429
|
+
return waiting;
|
|
430
|
+
}
|
|
431
|
+
emitFeedback("delivered", { status: "delivered", roomId, msgId, targetAlias, taskHash, bodyStored: false });
|
|
432
|
+
emitFeedback("waiting", { status: "waiting", roomId, reason: mode === "long" ? "waiting for long peer reply" : "waiting for peer reply", msgId, targetAlias, taskHash, bodyStored: false });
|
|
433
|
+
const reply = await awaitReply(msgId);
|
|
434
|
+
const replyEnvelope = reply["envelope"];
|
|
435
|
+
if (reply.status === "completed") {
|
|
436
|
+
const transientResponse = replyEnvelope?.transientResponse;
|
|
437
|
+
const result = finish("terminal", { status: "reply", msgId, targetAlias, taskHash, outputHash: replyEnvelope?.outputHash ?? (transientResponse ? sha256(transientResponse) : undefined), transientResponse, bodyStored: false }, 1);
|
|
438
|
+
emitFeedback("reply", result);
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
if (reply.status === "timeout") {
|
|
442
|
+
const result = finish("terminal", { status: "timeout", reason: "await response timed out", msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
443
|
+
emitFeedback(mode === "long" ? "expired" : "timeout", result);
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
const result = finish("terminal", { status: "error", reason: replyEnvelope?.errorHash ?? "peer response error", msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
447
|
+
emitFeedback("error", result);
|
|
448
|
+
return result;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return finish("terminal", { status: "error", reason: error instanceof Error ? error.message : String(error), msgId, targetAlias, taskHash, bodyStored: false }, 1);
|
|
451
|
+
}
|
|
452
|
+
}
|