create-principles-disciple 1.144.27 → 1.145.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/LICENSE +73 -21
- package/NOTICE +2 -0
- package/README.md +1 -1
- package/_release/product-identity.json +2 -2
- package/codex-adapter/dist/index.d.ts +2 -2
- package/codex-adapter/dist/index.js +1 -1
- package/codex-adapter/dist/ingestion/admission.js +16 -0
- package/codex-adapter/dist/ingestion/codex-version.d.ts +10 -4
- package/codex-adapter/dist/ingestion/codex-version.js +10 -4
- package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +48 -0
- package/codex-adapter/dist/ingestion/explicit-owner-statement.js +123 -0
- package/codex-adapter/dist/ingestion/ingestion.js +50 -1
- package/codex-adapter/dist/pd-hook.js +66 -3
- package/codex-adapter/dist/worker/workspace-worker.d.ts +14 -0
- package/codex-adapter/dist/worker/workspace-worker.js +194 -11
- package/codex-adapter/package.json +6 -4
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +2 -2
- package/console/dist/server/models/ApprovalsConsoleModel.js +1 -1
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +1 -1
- package/console/dist/server/models/PrinciplesConsoleModel.d.ts +26 -1
- package/console/dist/server/models/PrinciplesConsoleModel.js +18 -1
- package/console/dist/server/routes/activations.js +4 -4
- package/console/dist/server/routes/approvals.js +2 -2
- package/console/dist/server/routes/failed-tasks.d.ts +9 -0
- package/console/dist/server/routes/failed-tasks.js +116 -7
- package/console/dist/server/routes/principles.js +73 -1
- package/console/dist/shared/prompt-injection-contract.d.ts +1 -1
- package/console/dist/ui/api.d.ts +5 -5
- package/console/dist/ui/i18n/en.json +13 -2
- package/console/dist/ui/i18n/zh-CN.json +13 -2
- package/console/dist/ui/pages/activation/ActivationPage.js +2 -1
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +42 -4
- package/console/dist/ui/pages/focus/FocusPage.js +3 -3
- package/console/dist/ui/pages/principles/PrinciplesPage.js +7 -2
- package/console/dist/ui/utils/validators.d.ts +6 -2
- package/console/dist/ui/utils/validators.js +19 -4
- package/console/dist/web/assets/app.css +26 -0
- package/console/dist/web/assets/app.js +24 -24
- package/console/package.json +6 -5
- package/core/dist/host/host-installer.d.ts +3 -1
- package/core/dist/host/host-installer.d.ts.map +1 -1
- package/core/dist/host/host-installer.js.map +1 -1
- package/core/dist/runtime-v2/admission-gate.d.ts +8 -0
- package/core/dist/runtime-v2/admission-gate.d.ts.map +1 -1
- package/core/dist/runtime-v2/admission-gate.js +3 -2
- package/core/dist/runtime-v2/admission-gate.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +16 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/core/dist/runtime-v2/context-payload.d.ts +43 -3
- package/core/dist/runtime-v2/context-payload.d.ts.map +1 -1
- package/core/dist/runtime-v2/context-payload.js +15 -1
- package/core/dist/runtime-v2/context-payload.js.map +1 -1
- package/core/dist/runtime-v2/evidence-guards.d.ts +6 -0
- package/core/dist/runtime-v2/evidence-guards.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-guards.js +11 -0
- package/core/dist/runtime-v2/evidence-guards.js.map +1 -1
- package/core/dist/runtime-v2/evolution/evolution-types.d.ts +4 -4
- package/core/dist/runtime-v2/evolution/evolution-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/evolution/evolution-types.js +1 -1
- package/core/dist/runtime-v2/evolution/evolution-types.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +6 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.d.ts.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +8 -0
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +5 -2
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +3 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +11 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.js +10 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +3 -3
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -5
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +13 -16
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/owner-decision-review.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/owner-decision-review.js +5 -1
- package/core/dist/runtime-v2/internalization/owner-decision-review.js.map +1 -1
- package/core/dist/runtime-v2/internalization/owner-review.d.ts +2 -2
- package/core/dist/runtime-v2/internalization/owner-review.js +2 -2
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts +12 -0
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-output.js +20 -0
- package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +32 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js +38 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization-queue-read-model.d.ts +6 -0
- package/core/dist/runtime-v2/internalization-queue-read-model.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization-queue-read-model.js +8 -1
- package/core/dist/runtime-v2/internalization-queue-read-model.js.map +1 -1
- package/core/dist/runtime-v2/intervention-evidence-normalizer.d.ts.map +1 -1
- package/core/dist/runtime-v2/intervention-evidence-normalizer.js +10 -3
- package/core/dist/runtime-v2/intervention-evidence-normalizer.js.map +1 -1
- package/core/dist/runtime-v2/pain-ingress.d.ts +1 -1
- package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.d.ts +68 -2
- package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.js +541 -18
- package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +4 -0
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts +2 -2
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js +2 -2
- package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js.map +1 -1
- package/core/dist/runtime-v2/store/candidate/admission-document.d.ts +90 -0
- package/core/dist/runtime-v2/store/candidate/admission-document.d.ts.map +1 -0
- package/core/dist/runtime-v2/store/candidate/admission-document.js +142 -0
- package/core/dist/runtime-v2/store/candidate/admission-document.js.map +1 -0
- package/core/dist/runtime-v2/store/candidate/candidate-store.d.ts +53 -0
- package/core/dist/runtime-v2/store/candidate/candidate-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/candidate/memory-candidate-store.d.ts +35 -0
- package/core/dist/runtime-v2/store/candidate/memory-candidate-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/candidate/memory-candidate-store.js +48 -0
- package/core/dist/runtime-v2/store/candidate/memory-candidate-store.js.map +1 -1
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.d.ts +44 -0
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js +136 -0
- package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js.map +1 -1
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
- package/core/dist/runtime-v2/store/intervention-evidence-schema.js +1 -1
- package/core/dist/runtime-v2/store/runtime-state-manager.d.ts +65 -0
- package/core/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/runtime-state-manager.js +46 -0
- package/core/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-connection.js +42 -0
- package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.d.ts +1 -1
- package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.js.map +1 -1
- package/core/dist/runtime-v2/store/task/sqlite-family-reopen.d.ts +19 -0
- package/core/dist/runtime-v2/store/task/sqlite-family-reopen.d.ts.map +1 -0
- package/core/dist/runtime-v2/store/task/sqlite-family-reopen.js +36 -0
- package/core/dist/runtime-v2/store/task/sqlite-family-reopen.js.map +1 -0
- package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts +23 -0
- package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/task/sqlite-task-store.js +50 -0
- package/core/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
- package/core/dist/runtime-v2/task-status.d.ts +3 -1
- package/core/dist/runtime-v2/task-status.d.ts.map +1 -1
- package/core/dist/runtime-v2/task-status.js +1 -1
- package/core/dist/runtime-v2/task-status.js.map +1 -1
- package/core/dist/runtime-v2/trajectory-schema.d.ts.map +1 -1
- package/core/dist/runtime-v2/trajectory-schema.js +11 -0
- package/core/dist/runtime-v2/trajectory-schema.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +2 -2
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js +1 -1
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/dist/runtime-v2/types/evidence-chain-contract.d.ts +1 -1
- package/core/dist/runtime-v2/types/evidence-chain-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/evidence-chain-contract.js +2 -2
- package/core/dist/runtime-v2/types/evidence-chain-contract.js.map +1 -1
- package/core/dist/runtime-v2/types/intervention-evidence-contract.d.ts +3 -3
- package/core/dist/runtime-v2/types/intervention-evidence-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/intervention-evidence-contract.js +1 -0
- package/core/dist/runtime-v2/types/intervention-evidence-contract.js.map +1 -1
- package/core/dist/telemetry-event.d.ts +2 -2
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +4 -0
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/dist/trajectory-types.d.ts +2 -2
- package/core/dist/trajectory-types.d.ts.map +1 -1
- package/core/package.json +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +42 -11
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +140 -36
- package/dist/installer.js.map +1 -1
- package/dist/installers/index.d.ts +18 -7
- package/dist/installers/index.d.ts.map +1 -1
- package/dist/installers/index.js +13 -4
- package/dist/installers/index.js.map +1 -1
- package/dist/installers/workbuddy-host-installer.d.ts +246 -0
- package/dist/installers/workbuddy-host-installer.d.ts.map +1 -0
- package/dist/installers/workbuddy-host-installer.js +1419 -0
- package/dist/installers/workbuddy-host-installer.js.map +1 -0
- package/dist/mvp-config.d.ts +24 -0
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +36 -0
- package/dist/mvp-config.js.map +1 -1
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +45 -8
- package/dist/prompts.js.map +1 -1
- package/dist/uninstaller.d.ts +1 -1
- package/dist/uninstaller.d.ts.map +1 -1
- package/dist/uninstaller.js +29 -4
- package/dist/uninstaller.js.map +1 -1
- package/host-runtime/dist/governance-signal-admission.d.ts +63 -0
- package/host-runtime/dist/governance-signal-admission.js +322 -53
- package/host-runtime/dist/host-liveness-contract.js +7 -1
- package/host-runtime/dist/index.d.ts +1 -0
- package/host-runtime/dist/index.js +5 -1
- package/host-runtime/dist/internalization-consumer-cycle.d.ts +12 -0
- package/host-runtime/dist/internalization-consumer-cycle.js +43 -1
- package/host-runtime/dist/intervention-evidence-ingress.d.ts +1 -1
- package/host-runtime/dist/product-telemetry/service.js +1 -1
- package/host-runtime/dist/production-pain-evidence.d.ts +59 -1
- package/host-runtime/dist/production-pain-evidence.js +200 -17
- package/host-runtime/dist/prompt-activation-deliverability.js +5 -4
- package/host-runtime/dist/prompt-injection-event-evidence.d.ts +1 -1
- package/host-runtime/dist/prompt-injection-event-evidence.js +5 -1
- package/host-runtime/dist/prompt-injection-projection.d.ts +14 -5
- package/host-runtime/dist/prompt-injection-projection.js +30 -16
- package/host-runtime/dist/workbuddy-worker-status.d.ts +24 -0
- package/host-runtime/dist/workbuddy-worker-status.js +52 -0
- package/host-runtime/package.json +7 -5
- package/install-layout/dist/index.d.ts +18 -1
- package/install-layout/dist/index.js +15 -2
- package/install-layout/package.json +5 -3
- package/package.json +9 -7
- package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
- package/pd-cli/dist/commands/candidate.js +44 -4
- package/pd-cli/dist/commands/candidate.js.map +1 -1
- package/pd-cli/dist/commands/codex-worker.d.ts.map +1 -1
- package/pd-cli/dist/commands/codex-worker.js +30 -1
- package/pd-cli/dist/commands/codex-worker.js.map +1 -1
- package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/commands/config-doctor.js +19 -4
- package/pd-cli/dist/commands/config-doctor.js.map +1 -1
- package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
- package/pd-cli/dist/commands/diagnose.js +4 -1
- package/pd-cli/dist/commands/diagnose.js.map +1 -1
- package/pd-cli/dist/commands/health-codex-install.d.ts +85 -0
- package/pd-cli/dist/commands/health-codex-install.d.ts.map +1 -0
- package/pd-cli/dist/commands/health-codex-install.js +207 -0
- package/pd-cli/dist/commands/health-codex-install.js.map +1 -0
- package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
- package/pd-cli/dist/commands/health-codex.js +78 -0
- package/pd-cli/dist/commands/health-codex.js.map +1 -1
- package/pd-cli/dist/commands/pain-list.d.ts +3 -2
- package/pd-cli/dist/commands/pain-list.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-list.js +10 -10
- package/pd-cli/dist/commands/pain-list.js.map +1 -1
- package/pd-cli/dist/commands/pain-record.d.ts +2 -0
- package/pd-cli/dist/commands/pain-record.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-record.js +36 -0
- package/pd-cli/dist/commands/pain-record.js.map +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts +6 -0
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +152 -5
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +3 -3
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/dist/commands/workbuddy-worker.d.ts +19 -0
- package/pd-cli/dist/commands/workbuddy-worker.d.ts.map +1 -0
- package/pd-cli/dist/commands/workbuddy-worker.js +168 -0
- package/pd-cli/dist/commands/workbuddy-worker.js.map +1 -0
- package/pd-cli/dist/index.js +29 -1
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/resolve-workspace.d.ts +14 -2
- package/pd-cli/dist/resolve-workspace.d.ts.map +1 -1
- package/pd-cli/dist/resolve-workspace.js +88 -21
- package/pd-cli/dist/resolve-workspace.js.map +1 -1
- package/pd-cli/dist/services/admission-write-gate.d.ts +24 -0
- package/pd-cli/dist/services/admission-write-gate.d.ts.map +1 -0
- package/pd-cli/dist/services/admission-write-gate.js +41 -0
- package/pd-cli/dist/services/admission-write-gate.js.map +1 -0
- package/pd-cli/dist/services/pd-config-loader.d.ts +39 -7
- package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -1
- package/pd-cli/dist/services/pd-config-loader.js +111 -18
- package/pd-cli/dist/services/pd-config-loader.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +10 -0
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +59 -4
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/pd-cli/package.json +8 -5
- package/plugin/dist/bundle.js +564 -509
- package/plugin/dist/governance-audit.js +1 -1
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/dist/rulehost-evidence.js +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +7 -5
- package/release-manager/dist/index.d.ts.map +1 -1
- package/release-manager/dist/index.js +84 -8
- package/release-manager/dist/index.js.map +1 -1
- package/release-manager/dist/installer.d.ts +42 -11
- package/release-manager/dist/installer.d.ts.map +1 -1
- package/release-manager/dist/installer.js +140 -36
- package/release-manager/dist/installer.js.map +1 -1
- package/release-manager/dist/installers/index.d.ts +18 -7
- package/release-manager/dist/installers/index.d.ts.map +1 -1
- package/release-manager/dist/installers/index.js +13 -4
- package/release-manager/dist/installers/index.js.map +1 -1
- package/release-manager/dist/installers/workbuddy-host-installer.d.ts +246 -0
- package/release-manager/dist/installers/workbuddy-host-installer.d.ts.map +1 -0
- package/release-manager/dist/installers/workbuddy-host-installer.js +1419 -0
- package/release-manager/dist/installers/workbuddy-host-installer.js.map +1 -0
- package/release-manager/dist/mvp-config.d.ts +24 -0
- package/release-manager/dist/mvp-config.d.ts.map +1 -1
- package/release-manager/dist/mvp-config.js +36 -0
- package/release-manager/dist/mvp-config.js.map +1 -1
- package/release-manager/dist/prompts.d.ts +13 -0
- package/release-manager/dist/prompts.d.ts.map +1 -1
- package/release-manager/dist/prompts.js +45 -8
- package/release-manager/dist/prompts.js.map +1 -1
- package/release-manager/dist/uninstaller.d.ts +1 -1
- package/release-manager/dist/uninstaller.d.ts.map +1 -1
- package/release-manager/dist/uninstaller.js +29 -4
- package/release-manager/dist/uninstaller.js.map +1 -1
- package/release-manager/package.json +8 -6
- package/workbuddy-adapter/dist/codec/index.d.ts +2 -0
- package/workbuddy-adapter/dist/codec/index.js +2 -0
- package/workbuddy-adapter/dist/codec/input-decoder.d.ts +30 -0
- package/workbuddy-adapter/dist/codec/input-decoder.js +232 -0
- package/workbuddy-adapter/dist/codec/output-encoder.d.ts +62 -0
- package/workbuddy-adapter/dist/codec/output-encoder.js +75 -0
- package/workbuddy-adapter/dist/host-adapter.d.ts +33 -0
- package/workbuddy-adapter/dist/host-adapter.js +23 -0
- package/workbuddy-adapter/dist/index.d.ts +16 -0
- package/workbuddy-adapter/dist/index.js +16 -0
- package/workbuddy-adapter/dist/pd-hook.d.ts +17 -0
- package/workbuddy-adapter/dist/pd-hook.js +505 -0
- package/workbuddy-adapter/dist/tool-semantics.d.ts +22 -0
- package/workbuddy-adapter/dist/tool-semantics.js +33 -0
- package/workbuddy-adapter/dist/workbuddy-evidence-recorder.d.ts +50 -0
- package/workbuddy-adapter/dist/workbuddy-evidence-recorder.js +238 -0
- package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +41 -0
- package/workbuddy-adapter/dist/worker/workspace-worker.js +219 -0
- package/workbuddy-adapter/dist/workspace-resolution.d.ts +15 -0
- package/workbuddy-adapter/dist/workspace-resolution.js +53 -0
- package/workbuddy-adapter/package.json +64 -0
- package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +6 -0
- package/workbuddy-adapter/plugin/hooks/hooks.json +62 -0
- package/workbuddy-adapter/plugin/scripts/wrapper.cjs +79 -0
- package/console/dist/ui/utils/navigation.d.ts +0 -9
- package/console/dist/ui/utils/navigation.js +0 -7
- package/core/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts +0 -54
- package/core/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts.map +0 -1
- package/core/dist/runtime-v2/internalization/dimension-coverage-policy.js +0 -70
- package/core/dist/runtime-v2/internalization/dimension-coverage-policy.js.map +0 -1
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* 3. lease + run one Diagnostician task, then ONE bounded downstream
|
|
10
10
|
* consumer cycle through the SHARED host-neutral executor
|
|
11
11
|
* (internalization_auto_consumer = workspace execution authority)
|
|
12
|
+
* 4. SPEC-CODEX-PAIN-CLOSURE-001 CR-05: continue the post-processing of
|
|
13
|
+
* succeeded diagnoses (pending candidates, intake/seed orphans) WITHOUT
|
|
14
|
+
* re-running the diagnosis LLM; CR-08: persist cycle liveness facts
|
|
15
|
+
* (readiness is computed elsewhere and never implies liveness).
|
|
12
16
|
*
|
|
13
17
|
* Every step reuses existing authority: catch-up reuses the Slice A/B
|
|
14
18
|
* ingestion + admission seams, reconciliation reuses
|
|
@@ -29,6 +33,29 @@ import { catchUpCodexIngestion } from '../ingestion/catch-up.js';
|
|
|
29
33
|
import { CODEX_TOOL_SEMANTIC_MAPPINGS, CODEX_TOOL_SEMANTICS } from '../tool-semantics.js';
|
|
30
34
|
const WORKER_OWNER = 'companion-worker';
|
|
31
35
|
const DEFAULT_DIAG_CANDIDATE_LIMIT = 5;
|
|
36
|
+
/** CR-05 §9.1: bounded per-cycle post-processing continuation budget. */
|
|
37
|
+
const DEFAULT_POST_PROCESSING_LIMIT = 2;
|
|
38
|
+
/** CR-08 §9.3: liveness facts are worker-owned runtime state (not governance truth). */
|
|
39
|
+
function isPlainRecord(value) {
|
|
40
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
const LIVENESS_FILE = 'codex-worker-liveness.json';
|
|
43
|
+
const LIVENESS_STAGE_DEADLINE_MS = {
|
|
44
|
+
catch_up: 5 * 60_000,
|
|
45
|
+
reconcile: 5 * 60_000,
|
|
46
|
+
diagnosis_execution: 15 * 60_000,
|
|
47
|
+
post_processing: 5 * 60_000,
|
|
48
|
+
downstream_cycle: 10 * 60_000,
|
|
49
|
+
};
|
|
50
|
+
export function readCodexWorkerLiveness(workspaceDir) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(path.resolve(workspaceDir), '.state', LIVENESS_FILE), 'utf8'));
|
|
53
|
+
return isPlainRecord(parsed) ? parsed : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
32
59
|
function workerLogger(logger) {
|
|
33
60
|
return logger ?? {
|
|
34
61
|
info: () => undefined,
|
|
@@ -44,6 +71,46 @@ function directoryExists(dir) {
|
|
|
44
71
|
return false;
|
|
45
72
|
}
|
|
46
73
|
}
|
|
74
|
+
function writeLiveness(workspaceDir, patch) {
|
|
75
|
+
try {
|
|
76
|
+
const root = path.resolve(workspaceDir);
|
|
77
|
+
const stateDir = path.join(root, '.state');
|
|
78
|
+
const file = path.join(stateDir, LIVENESS_FILE);
|
|
79
|
+
// The subpath is a constant, so containment always holds — the explicit
|
|
80
|
+
// check keeps the write boundary auditable (defense in depth).
|
|
81
|
+
if (!file.startsWith(root + path.sep))
|
|
82
|
+
return;
|
|
83
|
+
if (!directoryExists(stateDir))
|
|
84
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
85
|
+
let current = {};
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
88
|
+
if (isPlainRecord(parsed))
|
|
89
|
+
current = parsed;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
current = {};
|
|
93
|
+
}
|
|
94
|
+
const merged = { ...current, ...patch, updatedAt: new Date().toISOString() };
|
|
95
|
+
// CodeQL (insecure temporary file): the fixed predictable `.tmp` name is a
|
|
96
|
+
// symlink-swap surface in a shared state dir. Write+rename through an
|
|
97
|
+
// EXCLUSIVE random temp file in the same directory (O_EXCL | 0600) — the
|
|
98
|
+
// rename stays atomic within the directory.
|
|
99
|
+
const tmp = path.join(stateDir, `${LIVENESS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}.tmp`);
|
|
100
|
+
const fd = fs.openSync(tmp, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
101
|
+
try {
|
|
102
|
+
fs.writeFileSync(fd, JSON.stringify(merged));
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.closeSync(fd);
|
|
106
|
+
}
|
|
107
|
+
fs.renameSync(tmp, file);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// liveness bookkeeping must never break the cycle (rc-9: the cycle report
|
|
111
|
+
// itself remains the observable degradation channel)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
47
114
|
/**
|
|
48
115
|
* Oldest pending diagnostician first; retry_wait candidates are filtered to
|
|
49
116
|
* those whose backoff window has ELAPSED, so an old-but-still-waiting task
|
|
@@ -51,18 +118,42 @@ function directoryExists(dir) {
|
|
|
51
118
|
* bounded (`limit`), and executePendingDiagnosis re-enforces the backoff
|
|
52
119
|
* window on the chosen task — no double bookkeeping.
|
|
53
120
|
*/
|
|
54
|
-
|
|
121
|
+
const DIAGNOSTICIAN_FAIR_SHARE_SCOPE = 'codex-worker-diagnostician-fair-share';
|
|
122
|
+
/**
|
|
123
|
+
* SPEC §9.1 fair-share pick (review P1): pending and due-retry are both
|
|
124
|
+
* continuously-eligible classes; neither may starve the other while new
|
|
125
|
+
* pending signals keep arriving. A persistent round-robin cursor (stored in
|
|
126
|
+
* the existing reconciliation_cursor authority, so a worker restart does NOT
|
|
127
|
+
* reset it to pending-priority) alternates service between the two classes —
|
|
128
|
+
* each eligible class is served at least every other cycle, well inside the
|
|
129
|
+
* 3-full-cycle bound. Single-eligible work is always served.
|
|
130
|
+
*/
|
|
131
|
+
async function pickDiagnosticianCandidate(stateManager, limit, nowIso) {
|
|
55
132
|
const pending = await stateManager.listTasks({ taskKind: 'diagnostician', status: 'pending', orderBy: 'updated_at_asc', limit });
|
|
56
|
-
const [
|
|
57
|
-
|
|
58
|
-
return first;
|
|
133
|
+
const [pendingTask] = pending;
|
|
134
|
+
let retryTask = null;
|
|
59
135
|
const retrying = await stateManager.listTasks({ taskKind: 'diagnostician', status: 'retry_wait', orderBy: 'updated_at_asc', limit });
|
|
60
136
|
for (const candidate of retrying) {
|
|
61
137
|
if (isRetryWaitBackoffElapsed(candidate.status, candidate.leaseExpiresAt)) {
|
|
62
|
-
|
|
138
|
+
retryTask = candidate;
|
|
139
|
+
break;
|
|
63
140
|
}
|
|
64
141
|
}
|
|
65
|
-
|
|
142
|
+
if (pendingTask === undefined && retryTask === null)
|
|
143
|
+
return null;
|
|
144
|
+
if (retryTask === null) {
|
|
145
|
+
await stateManager.setDiagnosticianServiceCursor(DIAGNOSTICIAN_FAIR_SHARE_SCOPE, { lastUpdatedAt: nowIso, lastTaskId: 'pending' });
|
|
146
|
+
return pendingTask ?? null;
|
|
147
|
+
}
|
|
148
|
+
if (pendingTask === undefined) {
|
|
149
|
+
await stateManager.setDiagnosticianServiceCursor(DIAGNOSTICIAN_FAIR_SHARE_SCOPE, { lastUpdatedAt: nowIso, lastTaskId: 'retry_wait' });
|
|
150
|
+
return retryTask;
|
|
151
|
+
}
|
|
152
|
+
const cursor = await stateManager.getDiagnosticianServiceCursor(DIAGNOSTICIAN_FAIR_SHARE_SCOPE);
|
|
153
|
+
const serveRetryFirst = cursor?.lastTaskId !== 'retry_wait';
|
|
154
|
+
const servedClass = serveRetryFirst ? 'retry_wait' : 'pending';
|
|
155
|
+
await stateManager.setDiagnosticianServiceCursor(DIAGNOSTICIAN_FAIR_SHARE_SCOPE, { lastUpdatedAt: nowIso, lastTaskId: servedClass });
|
|
156
|
+
return serveRetryFirst ? retryTask : pendingTask;
|
|
66
157
|
}
|
|
67
158
|
/**
|
|
68
159
|
* Run ONE bounded worker cycle for a canonical workspace. Never throws:
|
|
@@ -73,6 +164,7 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
73
164
|
const logger = workerLogger(options.logger);
|
|
74
165
|
const emitEvent = options.emitEvent ?? ((_event, _payload) => undefined);
|
|
75
166
|
const base = { workspaceDir };
|
|
167
|
+
const cycleStartedAt = new Date();
|
|
76
168
|
// Step 1 — Workspace eligibility.
|
|
77
169
|
if (!directoryExists(workspaceDir)) {
|
|
78
170
|
return { ...base, mode: 'degraded', reason: 'workspace_missing', nextAction: 'The workspace directory no longer exists; remove it from the install manifest or restore it. No PD state was mutated.' };
|
|
@@ -86,6 +178,13 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
86
178
|
if (flags['host.codex']?.enabled !== true) {
|
|
87
179
|
return { ...base, mode: 'paused', reason: 'host.codex_disabled', nextAction: 'Set features.host.codex.enabled=true in the Workspace .pd/config.yaml to enable Codex PD behavior.' };
|
|
88
180
|
}
|
|
181
|
+
// CR-08: liveness bookkeeping starts only after the workspace proved
|
|
182
|
+
// eligible — writing into a missing workspace directory would fabricate it.
|
|
183
|
+
writeLiveness(workspaceDir, {
|
|
184
|
+
lastCycleStartedAt: cycleStartedAt.toISOString(),
|
|
185
|
+
runningStage: 'catch_up',
|
|
186
|
+
stageDeadline: new Date(cycleStartedAt.getTime() + LIVENESS_STAGE_DEADLINE_MS.catch_up).toISOString(),
|
|
187
|
+
});
|
|
89
188
|
const consumerFlag = loadFeatureFlagFromConfig(workspaceDir, 'internalization_auto_consumer', { info: (m) => logger.info(m), warn: (m) => logger.warn(m) });
|
|
90
189
|
// PRI-638 P1-C: the Diagnostician capability gate is resolved from the same
|
|
91
190
|
// canonical authority the runtime factory uses. An Owner-disabled agent is
|
|
@@ -128,7 +227,7 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
128
227
|
mode: 'paused',
|
|
129
228
|
reason: 'internalization_auto_consumer_disabled',
|
|
130
229
|
nextAction: 'Automatic internalization execution is paused for this Workspace; manual commands remain available: pd diagnose, pd runtime internalization run-once.',
|
|
131
|
-
report: { catchUp, reconcile, diagnostician: null, downstream: null },
|
|
230
|
+
report: { catchUp, reconcile, postProcessing: null, diagnostician: null, downstream: null },
|
|
132
231
|
};
|
|
133
232
|
}
|
|
134
233
|
else {
|
|
@@ -144,7 +243,7 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
144
243
|
if (sweep.recovered > 0 || sweep.errors.length > 0) {
|
|
145
244
|
emitEvent('CODEX_WORKER_RECOVERY_SWEEP', JSON.stringify({ recovered: sweep.recovered, failed: sweep.errors.length }));
|
|
146
245
|
}
|
|
147
|
-
const candidate = await pickDiagnosticianCandidate(handle.stateManager, options.diagnosticianCandidateLimit ?? DEFAULT_DIAG_CANDIDATE_LIMIT);
|
|
246
|
+
const candidate = await pickDiagnosticianCandidate(handle.stateManager, options.diagnosticianCandidateLimit ?? DEFAULT_DIAG_CANDIDATE_LIMIT, new Date().toISOString());
|
|
148
247
|
if (candidate !== null) {
|
|
149
248
|
const stateDir = path.join(workspaceDir, '.state');
|
|
150
249
|
const bridge = await createPainSignalBridge({
|
|
@@ -196,7 +295,82 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
196
295
|
mode: 'degraded',
|
|
197
296
|
reason: `diagnostician_execution_failed:${detail}`,
|
|
198
297
|
nextAction: 'Inspect the Workspace runtime profile and provider configuration; the task keeps its pending/retry state and no evidence was mutated.',
|
|
199
|
-
report: { catchUp, reconcile, diagnostician: null, downstream: null },
|
|
298
|
+
report: { catchUp, reconcile, postProcessing: null, diagnostician: null, downstream: null },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Step 6b — SPEC-CODEX-PAIN-CLOSURE-001 CR-05 (SPEC §9.1): continue the
|
|
303
|
+
// post-processing of SUCCEEDED diagnoses (candidates stuck before
|
|
304
|
+
// admission/intake/seed — the original incident shape) without re-running
|
|
305
|
+
// the diagnosis LLM. Runs in the SAME consumer-flag authority as fresh
|
|
306
|
+
// diagnoses; every class (pending / due-retry / post-processing) is served
|
|
307
|
+
// every cycle it has eligible work, so no class can starve another
|
|
308
|
+
// (SPEC §9.1: each eligible class served within 3 full cycles).
|
|
309
|
+
let postProcessing = null;
|
|
310
|
+
writeLiveness(workspaceDir, { runningStage: 'post_processing', stageDeadline: new Date(Date.now() + LIVENESS_STAGE_DEADLINE_MS.post_processing).toISOString() });
|
|
311
|
+
if (consumerFlag.enabled && !diagnosticianDisabled) {
|
|
312
|
+
try {
|
|
313
|
+
const handle = await createRuntimeStateHandle({ workspaceDir, readonly: false });
|
|
314
|
+
try {
|
|
315
|
+
const { eligible: backlog, corrupt } = await handle.stateManager.listDiagnosisPostProcessingBacklog({ limit: DEFAULT_POST_PROCESSING_LIMIT });
|
|
316
|
+
const { eligible: backlogAll, corrupt: corruptAll } = await handle.stateManager.listDiagnosisPostProcessingBacklog({ limit: 200 });
|
|
317
|
+
const oldestCreatedAt = backlogAll[0]?.createdAt;
|
|
318
|
+
const blocked = [];
|
|
319
|
+
// SPEC review round-4 P1: corrupt admission_json rows are surfaced as a
|
|
320
|
+
// candidate-level queryable blocker — never deleted, never eligible for
|
|
321
|
+
// writes, never blocking healthy work (the scan filters via json_valid).
|
|
322
|
+
for (const item of corruptAll) {
|
|
323
|
+
blocked.push({ candidateId: item.candidateId, reason: 'admission_json_corrupt: repair or re-run the diagnosis (pd candidate show reports the unreadable disposition)' });
|
|
324
|
+
}
|
|
325
|
+
const resumedTaskIds = new Set();
|
|
326
|
+
if (backlog.length > 0) {
|
|
327
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
328
|
+
const bridge = await createPainSignalBridge({
|
|
329
|
+
workspaceDir,
|
|
330
|
+
stateDir,
|
|
331
|
+
ledgerAdapter: new PrincipleTreeLedgerAdapter({ stateDir }),
|
|
332
|
+
owner: WORKER_OWNER,
|
|
333
|
+
effectiveConfig: config.effective,
|
|
334
|
+
getEnvVar: (name) => process.env[name],
|
|
335
|
+
});
|
|
336
|
+
for (const item of backlog) {
|
|
337
|
+
const resumed = await bridge.resumeSucceededDiagnosisPostProcessing({ taskId: item.taskId });
|
|
338
|
+
if (resumed.status === 'resumed') {
|
|
339
|
+
resumedTaskIds.add(item.taskId);
|
|
340
|
+
}
|
|
341
|
+
else if (resumed.status === 'blocked') {
|
|
342
|
+
blocked.push({ candidateId: item.candidateId, reason: resumed.reason ?? 'blocked' });
|
|
343
|
+
}
|
|
344
|
+
for (const skip of resumed.skipped ?? [])
|
|
345
|
+
blocked.push(skip);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
postProcessing = {
|
|
349
|
+
resumedTaskIds: [...resumedTaskIds],
|
|
350
|
+
processedCount: backlog.length,
|
|
351
|
+
remainingCount: Math.max(0, backlogAll.length - backlog.length),
|
|
352
|
+
oldestAgeMs: oldestCreatedAt !== undefined ? Math.max(0, Date.now() - Date.parse(oldestCreatedAt)) : null,
|
|
353
|
+
blocked,
|
|
354
|
+
};
|
|
355
|
+
if (corrupt.length > 0) {
|
|
356
|
+
emitEvent('CODEX_WORKER_ADMISSION_CORRUPT', JSON.stringify({ candidateIds: corruptAll.map((c) => c.candidateId) }));
|
|
357
|
+
}
|
|
358
|
+
if (backlog.length > 0) {
|
|
359
|
+
emitEvent('CODEX_WORKER_POST_PROCESSING', JSON.stringify(postProcessing));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
await handle.close().catch(() => undefined);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
const detail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
|
|
368
|
+
return {
|
|
369
|
+
...base,
|
|
370
|
+
mode: 'degraded',
|
|
371
|
+
reason: `post_processing_continuation_failed:${detail}`,
|
|
372
|
+
nextAction: 'Inspect the workspace state.db principle_candidates/tasks; the worker retries automatically on the next cycle.',
|
|
373
|
+
report: { catchUp, reconcile, postProcessing: null, diagnostician, downstream: null },
|
|
200
374
|
};
|
|
201
375
|
}
|
|
202
376
|
}
|
|
@@ -223,9 +397,10 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
223
397
|
mode: 'degraded',
|
|
224
398
|
reason: `host_tool_declaration_persist_failed:${declared.reason}`,
|
|
225
399
|
nextAction: 'Repair Codex host declaration persistence (.pd/host-tool-semantics/codex.json) before running the downstream internalization cycle; the worker retries automatically on the next cycle.',
|
|
226
|
-
report: { catchUp, reconcile, diagnostician, downstream: null },
|
|
400
|
+
report: { catchUp, reconcile, postProcessing: null, diagnostician, downstream: null },
|
|
227
401
|
};
|
|
228
402
|
}
|
|
403
|
+
writeLiveness(workspaceDir, { runningStage: 'downstream_cycle', stageDeadline: new Date(Date.now() + LIVENESS_STAGE_DEADLINE_MS.downstream_cycle).toISOString() });
|
|
229
404
|
const downstream = await runInternalizationConsumerCycle(workspaceDir, {
|
|
230
405
|
owner: WORKER_OWNER,
|
|
231
406
|
logLabel: 'CodexWorker',
|
|
@@ -249,11 +424,19 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
249
424
|
: diagnostician?.status === 'degraded' ? `diagnostician_degraded:${diagnostician.message ?? 'unknown'}`
|
|
250
425
|
: downstream.skipReason === 'runtime_config_error' || downstream.skipReason === 'cycle_error' || downstream.skipReason === 'config_malformed' ? `downstream:${downstream.skipReason}`
|
|
251
426
|
: undefined;
|
|
427
|
+
writeLiveness(workspaceDir, {
|
|
428
|
+
runningStage: null,
|
|
429
|
+
stageDeadline: null,
|
|
430
|
+
lastCycleCompletedAt: new Date().toISOString(),
|
|
431
|
+
lastCycleDurationMs: Date.now() - cycleStartedAt.getTime(),
|
|
432
|
+
lastCycleMode: degradedReason !== undefined ? 'degraded' : 'ready',
|
|
433
|
+
...(degradedReason !== undefined ? { lastDegradedReason: degradedReason } : {}),
|
|
434
|
+
});
|
|
252
435
|
return {
|
|
253
436
|
...base,
|
|
254
437
|
mode: degradedReason !== undefined ? 'degraded' : 'ready',
|
|
255
438
|
...(degradedReason !== undefined ? { reason: degradedReason, nextAction: 'Inspect the Workspace .pd/config.yaml runtime profile and the per-step report above; the worker retries automatically on the next cycle.' } : {}),
|
|
256
|
-
report: { catchUp, reconcile, diagnostician, downstream },
|
|
439
|
+
report: { catchUp, reconcile, postProcessing, diagnostician, downstream },
|
|
257
440
|
};
|
|
258
441
|
}
|
|
259
442
|
// Slice D (PRI-625): the mode authority moved to host-runtime so the CLI and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"README.md"
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"NOTICE"
|
|
26
28
|
],
|
|
27
29
|
"scripts": {
|
|
28
30
|
"build": "tsc",
|
|
@@ -38,10 +40,10 @@
|
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@types/node": "^26.4.1",
|
|
40
42
|
"typescript": "^7.0.2",
|
|
41
|
-
"vite": "^8.3.
|
|
43
|
+
"vite": "^8.3.4",
|
|
42
44
|
"vitest": "^5.0.0"
|
|
43
45
|
},
|
|
44
|
-
"license": "
|
|
46
|
+
"license": "Apache-2.0",
|
|
45
47
|
"repository": {
|
|
46
48
|
"type": "git",
|
|
47
49
|
"url": "git+https://github.com/csuzngjh/principles.git",
|
|
@@ -78,12 +78,12 @@ export type ApproveWithActivationResult = {
|
|
|
78
78
|
capacity: {
|
|
79
79
|
category: 'single_item_exceeds_budget' | 'route_unconfirmed' | 'content_unconfirmed';
|
|
80
80
|
route?: 'legacy_trim' | 'shared_render';
|
|
81
|
-
hostKind?: 'openclaw' | 'codex';
|
|
81
|
+
hostKind?: 'openclaw' | 'codex' | 'workbuddy';
|
|
82
82
|
budget?: number;
|
|
83
83
|
costChars?: number;
|
|
84
84
|
overByChars?: number;
|
|
85
85
|
perHost?: {
|
|
86
|
-
hostKind: 'openclaw' | 'codex';
|
|
86
|
+
hostKind: 'openclaw' | 'codex' | 'workbuddy';
|
|
87
87
|
fits: boolean;
|
|
88
88
|
costChars: number;
|
|
89
89
|
}[];
|
|
@@ -306,7 +306,7 @@ export class ApprovalsConsoleModel {
|
|
|
306
306
|
return `injection_budget_check_failed: could not recompute the prompt injection projection (${message}); the activation is committed but its injection status is unverified. nextAction=check .pd/state.db readability and re-run pd runtime activation list`;
|
|
307
307
|
}
|
|
308
308
|
if (resolution.status === 'unconfirmed') {
|
|
309
|
-
return `injection_capacity_unconfirmed: the activation is committed but this workspace's effective injection route cannot be confirmed (${resolution.decision.unconfirmedReason ?? 'unknown reason'}); per-host forecasts: ${resolution.perHost.map((entry) => `${entry.hostKind}=${entry.projection.route} (${entry.projection.usedChars}c${entry.projection.truncated ? ', truncated' : ''})`).join('; ')}. nextAction=${resolution.decision.nextAction ?? 'pass an explicit target host (openclaw|codex)'}`;
|
|
309
|
+
return `injection_capacity_unconfirmed: the activation is committed but this workspace's effective injection route cannot be confirmed (${resolution.decision.unconfirmedReason ?? 'unknown reason'}); per-host forecasts: ${resolution.perHost.map((entry) => `${entry.hostKind}=${entry.projection.route} (${entry.projection.usedChars}c${entry.projection.truncated ? ', truncated' : ''})`).join('; ')}. nextAction=${resolution.decision.nextAction ?? 'pass an explicit target host (openclaw|codex|workbuddy)'}`;
|
|
310
310
|
}
|
|
311
311
|
const { projection } = resolution;
|
|
312
312
|
if (projection.injectedActivationIds.includes(activationId)) {
|
|
@@ -363,7 +363,7 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
363
363
|
truncated: reference?.truncated ?? false,
|
|
364
364
|
capacityStatus: 'unconfirmed',
|
|
365
365
|
unconfirmedReason: resolution.decision.unconfirmedReason ?? 'effective injection route cannot be confirmed',
|
|
366
|
-
nextAction: resolution.decision.nextAction ?? 'pass an explicit target host (openclaw|codex)',
|
|
366
|
+
nextAction: resolution.decision.nextAction ?? 'pass an explicit target host (openclaw|codex|workbuddy)',
|
|
367
367
|
perHost: resolution.perHost.map((entry) => ({
|
|
368
368
|
hostKind: entry.hostKind,
|
|
369
369
|
route: entry.projection.route,
|
|
@@ -32,6 +32,15 @@ export interface PrincipleListItem {
|
|
|
32
32
|
conflictsWithCount: number;
|
|
33
33
|
createdAt: string;
|
|
34
34
|
updatedAt: string;
|
|
35
|
+
/**
|
|
36
|
+
* PRI-956 (F15): the ledger `status` is an ASSET lifecycle ("ingested &
|
|
37
|
+
* not archived") and has no deactivated value — a deactivated principle
|
|
38
|
+
* stays `active` here forever. When the runtime authority (activations
|
|
39
|
+
* table) says every prompt activation for this principle is deactivated,
|
|
40
|
+
* the route annotates it so the UI can disclose "runtime revoked" instead
|
|
41
|
+
* of implying "in effect" from the asset status alone.
|
|
42
|
+
*/
|
|
43
|
+
runtimeDeactivated?: boolean;
|
|
35
44
|
/** Detected language of the principle text ('en' | 'zh' | 'unknown'). PRI-332 */
|
|
36
45
|
detectedLanguage: 'en' | 'zh' | 'unknown';
|
|
37
46
|
/** Structured readability warning code — front-end renders via i18n. PRI-332 P1-5 */
|
|
@@ -72,6 +81,13 @@ export interface PrinciplesListOutput {
|
|
|
72
81
|
categories?: Record<string, number>;
|
|
73
82
|
/** If the approval cross-check was unavailable, this explains why (ERR-002) */
|
|
74
83
|
approvalCrossCheckUnavailable?: string;
|
|
84
|
+
/**
|
|
85
|
+
* PRI-956 (F15, D3): if the activations-table query that determines
|
|
86
|
+
* CURRENT runtime revocation was unavailable, this explains why — the UI
|
|
87
|
+
* must express "runtime status cannot be confirmed" instead of defaulting
|
|
88
|
+
* to either active or revoked.
|
|
89
|
+
*/
|
|
90
|
+
revocationCheckUnavailable?: string;
|
|
75
91
|
}
|
|
76
92
|
export type PrincipleFilter = 'all' | 'actionable';
|
|
77
93
|
export interface PrincipleDetailOutput {
|
|
@@ -86,7 +102,16 @@ export declare class PrinciplesConsoleModel {
|
|
|
86
102
|
constructor(workspaceDir: string);
|
|
87
103
|
private getLedgerPath;
|
|
88
104
|
private loadLedger;
|
|
89
|
-
|
|
105
|
+
/**
|
|
106
|
+
* PRI-956 (F15): decision-id sets from the route's cross-checks. All
|
|
107
|
+
* optional; `deactivated` comes from the activations table (runtime
|
|
108
|
+
* authority) and only annotates disclosure — never re-buckets.
|
|
109
|
+
*/
|
|
110
|
+
listPrinciples(filter?: PrincipleFilter, decisionIds?: {
|
|
111
|
+
decided?: Set<string>;
|
|
112
|
+
pendingApproval?: Set<string>;
|
|
113
|
+
deactivated?: Set<string>;
|
|
114
|
+
}): Promise<PrinciplesListOutput>;
|
|
90
115
|
getPrincipleDetail(principleId: string): Promise<PrincipleDetailOutput | null>;
|
|
91
116
|
archivePrinciple(principleId: string): Promise<boolean>;
|
|
92
117
|
unarchivePrinciple(principleId: string): Promise<boolean>;
|
|
@@ -148,7 +148,15 @@ export class PrinciplesConsoleModel {
|
|
|
148
148
|
this.cacheTimestamp = now;
|
|
149
149
|
return ledger;
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
/**
|
|
152
|
+
* PRI-956 (F15): decision-id sets from the route's cross-checks. All
|
|
153
|
+
* optional; `deactivated` comes from the activations table (runtime
|
|
154
|
+
* authority) and only annotates disclosure — never re-buckets.
|
|
155
|
+
*/
|
|
156
|
+
async listPrinciples(filter, decisionIds) {
|
|
157
|
+
const decidedPrincipleIds = decisionIds?.decided;
|
|
158
|
+
const pendingApprovalPrincipleIds = decisionIds?.pendingApproval;
|
|
159
|
+
const deactivatedPrincipleIds = decisionIds?.deactivated;
|
|
152
160
|
const ledger = this.loadLedger();
|
|
153
161
|
const principles = Object.values(ledger.tree.principles);
|
|
154
162
|
const items = [];
|
|
@@ -185,6 +193,15 @@ export class PrinciplesConsoleModel {
|
|
|
185
193
|
const classified = classifyPrinciples(items, decidedPrincipleIds, pendingApprovalPrincipleIds);
|
|
186
194
|
const workspaceClassified = classified.filter((c) => c.category !== 'builtin');
|
|
187
195
|
const workspaceItems = workspaceClassified.map((c) => c.principle);
|
|
196
|
+
// PRI-956 (F15): disclose runtime revocation — the ledger status above is
|
|
197
|
+
// asset lifecycle and never reflects deactivation; the runtime authority
|
|
198
|
+
// (activations table) is the only source for "currently revoked".
|
|
199
|
+
if (deactivatedPrincipleIds) {
|
|
200
|
+
for (const p of workspaceItems) {
|
|
201
|
+
if (deactivatedPrincipleIds.has(p.id))
|
|
202
|
+
p.runtimeDeactivated = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
188
205
|
// PRI-641: summary and categories are computed over the same workspace
|
|
189
206
|
// corpus the caller sees, so `principles[]` and `summary` can never
|
|
190
207
|
// disagree (no "list 3, total 13" projection).
|
|
@@ -99,8 +99,8 @@ export async function handleActivationsRoute(req, res, workspaceDir, subPath, au
|
|
|
99
99
|
let targetHost;
|
|
100
100
|
const rawHost = url.searchParams.get('host');
|
|
101
101
|
if (rawHost !== null) {
|
|
102
|
-
if (rawHost !== 'openclaw' && rawHost !== 'codex') {
|
|
103
|
-
sendError(res, 400, 'validation_error', 'host must be one of: openclaw, codex');
|
|
102
|
+
if (rawHost !== 'openclaw' && rawHost !== 'codex' && rawHost !== 'workbuddy') {
|
|
103
|
+
sendError(res, 400, 'validation_error', 'host must be one of: openclaw, codex, workbuddy');
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
targetHost = rawHost;
|
|
@@ -242,8 +242,8 @@ export async function handleActivationsRoute(req, res, workspaceDir, subPath, au
|
|
|
242
242
|
}
|
|
243
243
|
let targetHost;
|
|
244
244
|
if (Object.hasOwn(body, 'host')) {
|
|
245
|
-
if (body.host !== 'openclaw' && body.host !== 'codex') {
|
|
246
|
-
sendError(res, 400, 'validation_error', 'host must be one of: openclaw, codex');
|
|
245
|
+
if (body.host !== 'openclaw' && body.host !== 'codex' && body.host !== 'workbuddy') {
|
|
246
|
+
sendError(res, 400, 'validation_error', 'host must be one of: openclaw, codex, workbuddy');
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
targetHost = body.host;
|
|
@@ -133,8 +133,8 @@ export async function handleApprovalsRoute(req, res, workspaceDir, subPath) {
|
|
|
133
133
|
// when the workspace's applicable hosts would take different routes.
|
|
134
134
|
let targetHost;
|
|
135
135
|
if (Object.hasOwn(parsed, 'host')) {
|
|
136
|
-
if (parsed.host !== 'openclaw' && parsed.host !== 'codex') {
|
|
137
|
-
sendBadRequest(res, 'host must be one of: openclaw, codex');
|
|
136
|
+
if (parsed.host !== 'openclaw' && parsed.host !== 'codex' && parsed.host !== 'workbuddy') {
|
|
137
|
+
sendBadRequest(res, 'host must be one of: openclaw, codex, workbuddy');
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
targetHost = parsed.host;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { SqliteTaskStore } from '@principles/core/runtime-v2';
|
|
3
3
|
export declare function disposeFailedTasksModels(): void;
|
|
4
|
+
export interface PipelineHealth {
|
|
5
|
+
status: 'healthy' | 'backlogged' | 'stalled' | 'disabled' | 'unknown';
|
|
6
|
+
pendingCount: number | null;
|
|
7
|
+
oldestPendingTaskKind: string | null;
|
|
8
|
+
oldestPendingAgeMinutes: number | null;
|
|
9
|
+
lastProgressAt: string | null;
|
|
10
|
+
note: string;
|
|
11
|
+
nextAction?: string;
|
|
12
|
+
}
|
|
4
13
|
export type FailedTasksContext = {
|
|
5
14
|
workspaceDir: string;
|
|
6
15
|
subPath: string;
|
|
@@ -155,6 +155,104 @@ function parseRecoverBody(body) {
|
|
|
155
155
|
const force = raw.force === true;
|
|
156
156
|
return { ok: true, reason, force };
|
|
157
157
|
}
|
|
158
|
+
// ── PRI-955 (F11): derived learning-queue health projection ─────────────────
|
|
159
|
+
//
|
|
160
|
+
// "No failed tasks" is NOT "the pipeline is healthy" — the dogfood run
|
|
161
|
+
// (2026-10-13) had a 13-deep pending backlog with downstream stages starving
|
|
162
|
+
// for 35+ minutes while this page reported the pipeline as healthy. The
|
|
163
|
+
// health projection below is DERIVED on read from the same state.db facts the
|
|
164
|
+
// failed-task list already uses (no second truth, no new persistence):
|
|
165
|
+
// - pendingCount: pending runner-kind tasks (the learning queue)
|
|
166
|
+
// - oldestPendingAgeMinutes: queue age of the oldest pending task
|
|
167
|
+
// - lastProgressAt: newest succeeded transition (updated_at of the newest
|
|
168
|
+
// succeeded task) — evidence that the pipeline actually advanced recently
|
|
169
|
+
// Status rules (documented, never over-claimed):
|
|
170
|
+
// stalled — work is waiting AND nothing progressed inside the window
|
|
171
|
+
// backlogged — work is waiting and the pipeline still progresses
|
|
172
|
+
// healthy — nothing waiting AND a succeeded transition inside the window
|
|
173
|
+
// unknown — no usable evidence (fresh workspace, unreadable counters)
|
|
174
|
+
const PIPELINE_STALL_WINDOW_MS = 30 * 60 * 1000;
|
|
175
|
+
const PIPELINE_FRESH_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
176
|
+
function ageMinutes(iso, nowMs) {
|
|
177
|
+
if (typeof iso !== 'string' || iso.length === 0)
|
|
178
|
+
return null;
|
|
179
|
+
const ms = Date.parse(iso);
|
|
180
|
+
if (!Number.isFinite(ms))
|
|
181
|
+
return null;
|
|
182
|
+
return Math.max(0, Math.floor((nowMs - ms) / 60000));
|
|
183
|
+
}
|
|
184
|
+
async function derivePipelineHealth(store, consumerEnabled) {
|
|
185
|
+
const nowMs = Date.now();
|
|
186
|
+
try {
|
|
187
|
+
// C2/C4 (PRI-955 review): exact, untruncated aggregates over the
|
|
188
|
+
// authoritative tasks table, scoped to learning-pipeline runner kinds —
|
|
189
|
+
// a global MAX(succeeded) would let unrelated task kinds fake freshness,
|
|
190
|
+
// and a LIMIT-200 list length would misreport the queue size.
|
|
191
|
+
const agg = await store.aggregatePipelineHealth();
|
|
192
|
+
const { pendingCount } = agg;
|
|
193
|
+
const oldestPendingAgeMinutes = ageMinutes(agg.oldestPendingCreatedAt, nowMs);
|
|
194
|
+
const lastProgressAgeMinutes = ageMinutes(agg.lastSucceededAt, nowMs);
|
|
195
|
+
if (!consumerEnabled) {
|
|
196
|
+
return {
|
|
197
|
+
status: 'disabled', pendingCount, oldestPendingTaskKind: agg.oldestPendingTaskKind, oldestPendingAgeMinutes, lastProgressAt: agg.lastSucceededAt,
|
|
198
|
+
note: `内化自动消费者已禁用(internalization_auto_consumer=false)——学习管道不会推进。当前待处理 ${pendingCount} 个。`,
|
|
199
|
+
nextAction: '在 .pd/config.yaml 设置 features.internalization_auto_consumer.enabled=true,或改用 pd runtime internalization run-once 手动推进。',
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (pendingCount > 0) {
|
|
203
|
+
const stalled = (oldestPendingAgeMinutes ?? 0) > PIPELINE_STALL_WINDOW_MS / 60000
|
|
204
|
+
&& (lastProgressAgeMinutes === null || lastProgressAgeMinutes > PIPELINE_STALL_WINDOW_MS / 60000);
|
|
205
|
+
if (stalled) {
|
|
206
|
+
return {
|
|
207
|
+
status: 'stalled', pendingCount, oldestPendingTaskKind: agg.oldestPendingTaskKind, oldestPendingAgeMinutes, lastProgressAt: agg.lastSucceededAt,
|
|
208
|
+
note: `学习队列有 ${pendingCount} 个待处理任务,最老等待 ${oldestPendingAgeMinutes ?? '?'} 分钟,且窗口内无任何推进。`,
|
|
209
|
+
nextAction: '检查内化消费者是否在运行(OpenClaw 自动消费 / pd codex worker),并查看任务队列明细。',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
status: 'backlogged', pendingCount, oldestPendingTaskKind: agg.oldestPendingTaskKind, oldestPendingAgeMinutes, lastProgressAt: agg.lastSucceededAt,
|
|
214
|
+
note: `学习队列有 ${pendingCount} 个待处理任务(最老等待 ${oldestPendingAgeMinutes ?? '?'} 分钟);最近推进 ${agg.lastSucceededAt ?? '未知'}。`,
|
|
215
|
+
nextAction: '积压会随消费者周期逐步消化;若长时间不降,检查消费者运行状态与模型通道。',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// PRI-955 review P1: healthy requires NO unresolved states either —
|
|
219
|
+
// pendingCount=0 + recent success is NOT healthy if failed, NHR, or
|
|
220
|
+
// retry_wait tasks are still present.
|
|
221
|
+
if (agg.failedCount > 0 || agg.needsHumanReviewCount > 0 || agg.retryWaitCount > 0) {
|
|
222
|
+
const parts = [];
|
|
223
|
+
if (agg.failedCount > 0)
|
|
224
|
+
parts.push(`${agg.failedCount} 个失败`);
|
|
225
|
+
if (agg.needsHumanReviewCount > 0)
|
|
226
|
+
parts.push(`${agg.needsHumanReviewCount} 个需人工审查`);
|
|
227
|
+
if (agg.retryWaitCount > 0)
|
|
228
|
+
parts.push(`${agg.retryWaitCount} 个等待重试`);
|
|
229
|
+
return {
|
|
230
|
+
status: 'backlogged', pendingCount: 0, oldestPendingTaskKind: null, oldestPendingAgeMinutes: null, lastProgressAt: agg.lastSucceededAt,
|
|
231
|
+
note: `学习队列无积压,但存在未解决任务:${parts.join('、')}。`,
|
|
232
|
+
nextAction: '在失败任务列表查看详情,或在治理焦点处理需人工审查的任务。',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (lastProgressAgeMinutes !== null && lastProgressAgeMinutes <= PIPELINE_FRESH_WINDOW_MS / 60000) {
|
|
236
|
+
return {
|
|
237
|
+
status: 'healthy', pendingCount: 0, oldestPendingTaskKind: null, oldestPendingAgeMinutes: null, lastProgressAt: agg.lastSucceededAt,
|
|
238
|
+
note: `学习队列无积压;最近推进 ${agg.lastSucceededAt}。`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
status: 'unknown', pendingCount: 0, oldestPendingTaskKind: null, oldestPendingAgeMinutes: null, lastProgressAt: agg.lastSucceededAt,
|
|
243
|
+
note: '当前没有待处理任务,但窗口内没有可确认的推进证据——无法判定管道健康。',
|
|
244
|
+
nextAction: '产生一次真实任务(或运行一次内化消费者)后回到本页复查。',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// rc-9: 降级必须可观察——无法读取队列事实时报告 unknown,而非默认健康。
|
|
249
|
+
return {
|
|
250
|
+
status: 'unknown', pendingCount: null, oldestPendingTaskKind: null, oldestPendingAgeMinutes: null, lastProgressAt: null,
|
|
251
|
+
note: '学习队列状态不可读(计数失败)——无法判定管道健康。',
|
|
252
|
+
nextAction: '检查 .pd/state.db 可读性,或用 pd health 查看工作区诊断。',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
158
256
|
/**
|
|
159
257
|
* Dispatch one recovery attempt through the shared core services.
|
|
160
258
|
*
|
|
@@ -341,7 +439,8 @@ export async function handleFailedTasksRoute(req, res, ctx) {
|
|
|
341
439
|
sendSuccess(res, {
|
|
342
440
|
tasks: [],
|
|
343
441
|
total: 0,
|
|
344
|
-
|
|
442
|
+
pipeline: { status: 'unknown', pendingCount: null, oldestPendingTaskKind: null, oldestPendingAgeMinutes: null, lastProgressAt: null, note: 'state.db 尚未初始化——工作区还没有任何任务,管道健康状态未知。', nextAction: '运行时开始创建任务后回到本页复查。' },
|
|
443
|
+
nextAction: 'No failed tasks. (state.db not yet initialized — pipeline health is UNKNOWN until the runtime creates tasks.)',
|
|
345
444
|
});
|
|
346
445
|
return;
|
|
347
446
|
}
|
|
@@ -363,19 +462,29 @@ export async function handleFailedTasksRoute(req, res, ctx) {
|
|
|
363
462
|
? { ownerDecisionRequired: decisionRequiredIds.has(task.taskId ?? '') }
|
|
364
463
|
: {}),
|
|
365
464
|
}));
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
465
|
+
// PRI-955 (F11): "no failed tasks" ≠ "pipeline healthy" — derive the
|
|
466
|
+
// learning-queue health projection from the same state.db facts and let
|
|
467
|
+
// the UI show it next to the empty claim.
|
|
468
|
+
const consumerFlag = ctx.featureFlags && Object.hasOwn(ctx.featureFlags, 'internalization_auto_consumer')
|
|
469
|
+
? ctx.featureFlags.internalization_auto_consumer
|
|
470
|
+
: undefined;
|
|
471
|
+
const consumerEnabled = consumerFlag ? consumerFlag.enabled === true : true; // 无该 flag 时无法断言禁用——不因缺失而宣称 disabled
|
|
472
|
+
const pipeline = await derivePipelineHealth(store, consumerEnabled);
|
|
473
|
+
// Only claim anything like "healthy" from the pipeline projection, never
|
|
474
|
+
// from the failed-task count alone. When tasks.length === 0 but
|
|
475
|
+
// total > 0, the caller has paginated past the last page.
|
|
370
476
|
if (total === 0) {
|
|
371
477
|
sendSuccess(res, {
|
|
372
478
|
tasks: enriched,
|
|
373
479
|
total,
|
|
374
|
-
|
|
480
|
+
pipeline,
|
|
481
|
+
nextAction: pipeline.status === 'healthy'
|
|
482
|
+
? `No failed tasks in the queried range. ${pipeline.note}`
|
|
483
|
+
: `No failed tasks in the queried range. Pipeline status: ${pipeline.status} — ${pipeline.note}`,
|
|
375
484
|
});
|
|
376
485
|
}
|
|
377
486
|
else {
|
|
378
|
-
sendSuccess(res, { tasks: enriched, total });
|
|
487
|
+
sendSuccess(res, { tasks: enriched, total, pipeline });
|
|
379
488
|
}
|
|
380
489
|
}
|
|
381
490
|
catch (err) {
|