pi-daddy 0.22.0 → 0.24.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/CHANGELOG.md +40 -0
- package/README.md +12 -6
- package/contracts/daily-view/v1/README.md +114 -0
- package/contracts/daily-view/v1/fixtures/view.json +525 -0
- package/contracts/daily-view/v1/fixtures/view.txt +75 -0
- package/contracts/daily-view/v1/fixtures/work.jsonl +17 -0
- package/contracts/daily-view/v1/p03/README.md +26 -0
- package/contracts/daily-view/v1/p03/fixtures/retained-executions.json +241 -0
- package/contracts/daily-view/v1/p03/projection.schema.json +194 -0
- package/contracts/daily-view/v1/p03/provenance.json +21 -0
- package/contracts/dashboard-host/v1/README.md +176 -0
- package/contracts/debrief/v1/README.md +146 -0
- package/contracts/debrief/v1/intervention/README.md +32 -0
- package/contracts/debrief/v1/intervention/fixtures/artifact-3ee11136beb864f518252e5024dae9197421fbf97b6e981d3243c782b5c44bd0.txt +1 -0
- package/contracts/debrief/v1/intervention/fixtures/artifact-58905d872dd3c54a9705f67b05fa82edbad7af6476ae8993faf1a7b9fadfb905.txt +1 -0
- package/contracts/debrief/v1/intervention/fixtures/assessment.json +29 -0
- package/contracts/debrief/v1/intervention/fixtures/blind-view.json +20 -0
- package/contracts/debrief/v1/intervention/fixtures/choice.json +7 -0
- package/contracts/debrief/v1/intervention/fixtures/manifest.json +57 -0
- package/contracts/debrief/v1/intervention/fixtures/reveal.json +39 -0
- package/contracts/debrief/v1/provenance.json +89 -0
- package/contracts/debrief/v1/work-capture/README.md +38 -0
- package/contracts/debrief/v1/work-capture/fixtures/batch.json +17 -0
- package/contracts/debrief/v1/work-capture/fixtures/cases.json +184 -0
- package/contracts/debrief/v1/work-capture/fixtures/review.json +184 -0
- package/contracts/debrief/v1/work-capture/review-request.schema.json +41 -0
- package/contracts/debrief/v1/work-capture/work-case.schema.json +190 -0
- package/contracts/debrief/v2/README.md +71 -0
- package/contracts/debrief/v2/input-pin.json +66 -0
- package/contracts/debrief/v2/intervention-upstream.md +40 -0
- package/contracts/debrief/v2/work-signals-upstream.md +22 -0
- package/contracts/dispatch-control/v1/README.md +109 -0
- package/contracts/dispatch-control/v1/request.schema.json +19 -0
- package/contracts/effect-profile/v1/README.md +124 -0
- package/contracts/execution-retention/v1/README.md +109 -0
- package/contracts/execution-retention/v2/README.md +148 -0
- package/contracts/execution-retention/v2/fixtures/interrupted-native-bytes.json +91 -0
- package/contracts/execution-retention/v2/fixtures/missing-native-session.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-file-unknown-branch.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-id-without-bytes.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-live-branch.json +90 -0
- package/contracts/execution-retention/v2/fixtures/replaced-native-session.json +92 -0
- package/contracts/execution-retention/v2/fixtures/result-82c620cdfeb8ca6b858cf653d7f5e2eedca5315d6244216d60e8b36f03223d6a.bin +1 -0
- package/contracts/execution-retention/v2/fixtures/session-2ec1f134bc739984434f7fca689e1c3d1c39fce2dcadae709e9b6faedfcc4ac7.bin +3 -0
- package/contracts/execution-retention/v2/fixtures/session-9bdb4bd16a901da31580acf056bda525225259e15ec409e0410480109011772f.bin +3 -0
- package/contracts/execution-retention/v2/manifest.schema.json +858 -0
- package/contracts/experiment/v1/README.md +164 -0
- package/contracts/factory-order/v1/README.md +151 -0
- package/contracts/factory-order/v1/adoption-pin.json +8 -0
- package/contracts/intent-control/v1/README.md +135 -0
- package/contracts/intent-control/v1/request.schema.json +1096 -0
- package/contracts/intent-control/v2/README.md +34 -0
- package/contracts/intent-control/v2/request.schema.json +1070 -0
- package/contracts/ledger/v4/README.md +241 -0
- package/contracts/ledger/v4/fixtures/layout-options.json +428 -0
- package/contracts/ledger/v4/fixtures/work-acceptance.json +57 -0
- package/contracts/ledger/v4/fixtures/work-occurrence.json +44 -0
- package/contracts/ledger/v4/fixtures/work-revision.json +26 -0
- package/contracts/ledger/v4/fixtures/work-snapshot.json +73 -0
- package/contracts/ledger/v4/ledger-event.schema.json +162 -0
- package/contracts/ordinary-control/v1/README.md +59 -0
- package/contracts/producer-ipc/v1/README.md +137 -0
- package/dist/check-runner.d.ts +5 -0
- package/dist/check-runner.d.ts.map +1 -1
- package/dist/check-runner.js +16 -2
- package/dist/check-runner.js.map +1 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +49 -3
- package/dist/cli.js.map +1 -1
- package/dist/control-journal.d.ts +20 -0
- package/dist/control-journal.d.ts.map +1 -0
- package/dist/control-journal.js +73 -0
- package/dist/control-journal.js.map +1 -0
- package/dist/daily-dashboard-host.d.ts +99 -0
- package/dist/daily-dashboard-host.d.ts.map +1 -0
- package/dist/daily-dashboard-host.js +71 -0
- package/dist/daily-dashboard-host.js.map +1 -0
- package/dist/daily-view-input.d.ts +44 -0
- package/dist/daily-view-input.d.ts.map +1 -0
- package/dist/daily-view-input.js +68 -0
- package/dist/daily-view-input.js.map +1 -0
- package/dist/daily-view-render.d.ts +4 -0
- package/dist/daily-view-render.d.ts.map +1 -0
- package/dist/daily-view-render.js +41 -0
- package/dist/daily-view-render.js.map +1 -0
- package/dist/daily-view.d.ts +74 -0
- package/dist/daily-view.d.ts.map +1 -0
- package/dist/daily-view.js +170 -0
- package/dist/daily-view.js.map +1 -0
- package/dist/dashboard-cli.d.ts +26 -1
- package/dist/dashboard-cli.d.ts.map +1 -1
- package/dist/dashboard-cli.js +128 -3
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-harness.d.ts +22 -0
- package/dist/dashboard-harness.d.ts.map +1 -0
- package/dist/dashboard-harness.js +89 -0
- package/dist/dashboard-harness.js.map +1 -0
- package/dist/dashboard-herdr.d.ts.map +1 -1
- package/dist/dashboard-herdr.js +7 -2
- package/dist/dashboard-herdr.js.map +1 -1
- package/dist/dashboard-host-contract.d.ts +116 -0
- package/dist/dashboard-host-contract.d.ts.map +1 -0
- package/dist/dashboard-host-contract.js +14 -0
- package/dist/dashboard-host-contract.js.map +1 -0
- package/dist/dashboard-host-transport.d.ts +16 -0
- package/dist/dashboard-host-transport.d.ts.map +1 -0
- package/dist/dashboard-host-transport.js +124 -0
- package/dist/dashboard-host-transport.js.map +1 -0
- package/dist/dashboard-host.d.ts +203 -0
- package/dist/dashboard-host.d.ts.map +1 -0
- package/dist/dashboard-host.js +376 -0
- package/dist/dashboard-host.js.map +1 -0
- package/dist/dashboard-observation.d.ts +28 -0
- package/dist/dashboard-observation.d.ts.map +1 -0
- package/dist/dashboard-observation.js +86 -0
- package/dist/dashboard-observation.js.map +1 -0
- package/dist/debrief-contract.d.ts +65 -0
- package/dist/debrief-contract.d.ts.map +1 -0
- package/dist/debrief-contract.js +151 -0
- package/dist/debrief-contract.js.map +1 -0
- package/dist/debrief-fixture.d.ts +16 -0
- package/dist/debrief-fixture.d.ts.map +1 -0
- package/dist/debrief-fixture.js +41 -0
- package/dist/debrief-fixture.js.map +1 -0
- package/dist/debrief-host.d.ts +59 -0
- package/dist/debrief-host.d.ts.map +1 -0
- package/dist/debrief-host.js +49 -0
- package/dist/debrief-host.js.map +1 -0
- package/dist/debrief-render.d.ts +5 -0
- package/dist/debrief-render.d.ts.map +1 -0
- package/dist/debrief-render.js +65 -0
- package/dist/debrief-render.js.map +1 -0
- package/dist/debrief.d.ts +90 -0
- package/dist/debrief.d.ts.map +1 -0
- package/dist/debrief.js +263 -0
- package/dist/debrief.js.map +1 -0
- package/dist/delegate-types.d.ts +2 -0
- package/dist/delegate-types.d.ts.map +1 -1
- package/dist/delegate-types.js.map +1 -1
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +1 -0
- package/dist/delegate.js.map +1 -1
- package/dist/dispatch-control.d.ts +45 -0
- package/dist/dispatch-control.d.ts.map +1 -0
- package/dist/dispatch-control.js +76 -0
- package/dist/dispatch-control.js.map +1 -0
- package/dist/effect-profile-runtime.d.ts +40 -0
- package/dist/effect-profile-runtime.d.ts.map +1 -0
- package/dist/effect-profile-runtime.js +51 -0
- package/dist/effect-profile-runtime.js.map +1 -0
- package/dist/effect-profile.d.ts +40 -0
- package/dist/effect-profile.d.ts.map +1 -0
- package/dist/effect-profile.js +152 -0
- package/dist/effect-profile.js.map +1 -0
- package/dist/execution-retention.d.ts +90 -0
- package/dist/execution-retention.d.ts.map +1 -0
- package/dist/execution-retention.js +247 -0
- package/dist/execution-retention.js.map +1 -0
- package/dist/experiment-contract.d.ts +56 -0
- package/dist/experiment-contract.d.ts.map +1 -0
- package/dist/experiment-contract.js +80 -0
- package/dist/experiment-contract.js.map +1 -0
- package/dist/experiment-state.d.ts +22 -0
- package/dist/experiment-state.d.ts.map +1 -0
- package/dist/experiment-state.js +79 -0
- package/dist/experiment-state.js.map +1 -0
- package/dist/experiment-store.d.ts +30 -0
- package/dist/experiment-store.d.ts.map +1 -0
- package/dist/experiment-store.js +147 -0
- package/dist/experiment-store.js.map +1 -0
- package/dist/experiment.d.ts +65 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +357 -0
- package/dist/experiment.js.map +1 -0
- package/dist/factory-contract.d.ts +79 -0
- package/dist/factory-contract.d.ts.map +1 -0
- package/dist/factory-contract.js +89 -0
- package/dist/factory-contract.js.map +1 -0
- package/dist/factory-migration.d.ts +20 -0
- package/dist/factory-migration.d.ts.map +1 -0
- package/dist/factory-migration.js +55 -0
- package/dist/factory-migration.js.map +1 -0
- package/dist/factory-order.d.ts +848 -0
- package/dist/factory-order.d.ts.map +1 -0
- package/dist/factory-order.js +83 -0
- package/dist/factory-order.js.map +1 -0
- package/dist/factory-registry.d.ts +121 -0
- package/dist/factory-registry.d.ts.map +1 -0
- package/dist/factory-registry.js +133 -0
- package/dist/factory-registry.js.map +1 -0
- package/dist/file-lock.d.ts +8 -2
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +78 -19
- package/dist/file-lock.js.map +1 -1
- package/dist/herdr-poll.d.ts +8 -0
- package/dist/herdr-poll.d.ts.map +1 -1
- package/dist/herdr-poll.js +23 -0
- package/dist/herdr-poll.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/intent-application.d.ts +17 -0
- package/dist/intent-application.d.ts.map +1 -0
- package/dist/intent-application.js +113 -0
- package/dist/intent-application.js.map +1 -0
- package/dist/intent-control.d.ts +201 -0
- package/dist/intent-control.d.ts.map +1 -0
- package/dist/intent-control.js +110 -0
- package/dist/intent-control.js.map +1 -0
- package/dist/intent-scheduling.d.ts +9 -0
- package/dist/intent-scheduling.d.ts.map +1 -0
- package/dist/intent-scheduling.js +15 -0
- package/dist/intent-scheduling.js.map +1 -0
- package/dist/ledger-append.d.ts +15 -0
- package/dist/ledger-append.d.ts.map +1 -0
- package/dist/ledger-append.js +94 -0
- package/dist/ledger-append.js.map +1 -0
- package/dist/ledger.d.ts +11 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +3 -7
- package/dist/ledger.js.map +1 -1
- package/dist/native-session-target.d.ts +9 -0
- package/dist/native-session-target.d.ts.map +1 -0
- package/dist/native-session-target.js +29 -0
- package/dist/native-session-target.js.map +1 -0
- package/dist/native-session.d.ts +59 -0
- package/dist/native-session.d.ts.map +1 -0
- package/dist/native-session.js +154 -0
- package/dist/native-session.js.map +1 -0
- package/dist/order-schedule.d.ts +37 -0
- package/dist/order-schedule.d.ts.map +1 -0
- package/dist/order-schedule.js +81 -0
- package/dist/order-schedule.js.map +1 -0
- package/dist/ordinary-children.d.ts +52 -0
- package/dist/ordinary-children.d.ts.map +1 -0
- package/dist/ordinary-children.js +93 -0
- package/dist/ordinary-children.js.map +1 -0
- package/dist/producer-ipc-contract.d.ts +29 -0
- package/dist/producer-ipc-contract.d.ts.map +1 -0
- package/dist/producer-ipc-contract.js +29 -0
- package/dist/producer-ipc-contract.js.map +1 -0
- package/dist/producer-ipc.d.ts +55 -0
- package/dist/producer-ipc.d.ts.map +1 -0
- package/dist/producer-ipc.js +143 -0
- package/dist/producer-ipc.js.map +1 -0
- package/dist/resource-budget.d.ts +334 -0
- package/dist/resource-budget.d.ts.map +1 -0
- package/dist/resource-budget.js +415 -0
- package/dist/resource-budget.js.map +1 -0
- package/dist/retention-contract.d.ts +73 -0
- package/dist/retention-contract.d.ts.map +1 -0
- package/dist/retention-contract.js +104 -0
- package/dist/retention-contract.js.map +1 -0
- package/dist/retention-json.d.ts +3 -0
- package/dist/retention-json.d.ts.map +1 -0
- package/dist/retention-json.js +32 -0
- package/dist/retention-json.js.map +1 -0
- package/dist/run-child.d.ts +4 -0
- package/dist/run-child.d.ts.map +1 -1
- package/dist/run-child.js +16 -2
- package/dist/run-child.js.map +1 -1
- package/dist/run-herdr.d.ts +6 -0
- package/dist/run-herdr.d.ts.map +1 -1
- package/dist/run-herdr.js +19 -6
- package/dist/run-herdr.js.map +1 -1
- package/dist/vendor/adoption.d.ts +129 -0
- package/dist/vendor/adoption.d.ts.map +1 -0
- package/dist/vendor/adoption.js +102 -0
- package/dist/vendor/adoption.js.map +1 -0
- package/dist/work-command.d.ts +30 -0
- package/dist/work-command.d.ts.map +1 -0
- package/dist/work-command.js +148 -0
- package/dist/work-command.js.map +1 -0
- package/dist/work-ledger-destination.d.ts +20 -0
- package/dist/work-ledger-destination.d.ts.map +1 -0
- package/dist/work-ledger-destination.js +131 -0
- package/dist/work-ledger-destination.js.map +1 -0
- package/dist/work-ledger-json.d.ts +25 -0
- package/dist/work-ledger-json.d.ts.map +1 -0
- package/dist/work-ledger-json.js +267 -0
- package/dist/work-ledger-json.js.map +1 -0
- package/dist/work-ledger-occurrences.d.ts +227 -0
- package/dist/work-ledger-occurrences.d.ts.map +1 -0
- package/dist/work-ledger-occurrences.js +115 -0
- package/dist/work-ledger-occurrences.js.map +1 -0
- package/dist/work-ledger-projection.d.ts +295 -0
- package/dist/work-ledger-projection.d.ts.map +1 -0
- package/dist/work-ledger-projection.js +237 -0
- package/dist/work-ledger-projection.js.map +1 -0
- package/dist/work-ledger-snapshot.d.ts +29 -0
- package/dist/work-ledger-snapshot.d.ts.map +1 -0
- package/dist/work-ledger-snapshot.js +267 -0
- package/dist/work-ledger-snapshot.js.map +1 -0
- package/dist/work-ledger-types.d.ts +261 -0
- package/dist/work-ledger-types.d.ts.map +1 -0
- package/dist/work-ledger-types.js +13 -0
- package/dist/work-ledger-types.js.map +1 -0
- package/dist/work-ledger-validation.d.ts +14 -0
- package/dist/work-ledger-validation.d.ts.map +1 -0
- package/dist/work-ledger-validation.js +226 -0
- package/dist/work-ledger-validation.js.map +1 -0
- package/dist/work-ledger.d.ts +43 -0
- package/dist/work-ledger.d.ts.map +1 -0
- package/dist/work-ledger.js +225 -0
- package/dist/work-ledger.js.map +1 -0
- package/extensions/daily-dashboard-session.ts +30 -0
- package/extensions/daily-work-session.ts +20 -0
- package/extensions/delegate-chain.ts +1 -0
- package/extensions/delegation-native.ts +12 -0
- package/extensions/delegation.ts +25 -11
- package/extensions/execute-child.ts +44 -5
- package/extensions/executor-session.ts +9 -0
- package/extensions/grants-command.ts +14 -15
- package/extensions/grants-connected-command.ts +19 -0
- package/extensions/grants.ts +26 -2
- package/extensions/ordinary-runtime.ts +14 -0
- package/extensions/primary-shadow.ts +11 -0
- package/extensions/run-delegation.ts +9 -7
- package/extensions/session.ts +10 -20
- package/extensions/work-runtime.ts +44 -0
- package/package.json +78 -4
- package/src/check-runner.ts +20 -2
- package/src/cli.ts +41 -4
- package/src/control-journal.ts +26 -0
- package/src/daily-dashboard-host.ts +49 -0
- package/src/daily-view-input.ts +59 -0
- package/src/daily-view-render.ts +44 -0
- package/src/daily-view.ts +152 -0
- package/src/dashboard-cli.ts +108 -4
- package/src/dashboard-harness.ts +37 -0
- package/src/dashboard-herdr.ts +7 -2
- package/src/dashboard-host-contract.ts +45 -0
- package/src/dashboard-host-transport.ts +40 -0
- package/src/dashboard-host.ts +191 -0
- package/src/dashboard-observation.ts +45 -0
- package/src/debrief-contract.ts +127 -0
- package/src/debrief-fixture.ts +29 -0
- package/src/debrief-host.ts +53 -0
- package/src/debrief-render.ts +49 -0
- package/src/debrief.ts +198 -0
- package/src/delegate-types.ts +2 -0
- package/src/delegate.ts +1 -0
- package/src/dispatch-control.ts +81 -0
- package/src/effect-profile-runtime.ts +46 -0
- package/src/effect-profile.ts +129 -0
- package/src/execution-retention.ts +230 -0
- package/src/experiment-contract.ts +66 -0
- package/src/experiment-state.ts +44 -0
- package/src/experiment-store.ts +98 -0
- package/src/experiment.ts +231 -0
- package/src/factory-contract.ts +47 -0
- package/src/factory-migration.ts +29 -0
- package/src/factory-order.ts +56 -0
- package/src/factory-registry.ts +60 -0
- package/src/file-lock.ts +47 -11
- package/src/herdr-poll.ts +18 -0
- package/src/index.ts +32 -0
- package/src/intent-application.ts +90 -0
- package/src/intent-control.ts +107 -0
- package/src/intent-scheduling.ts +14 -0
- package/src/ledger-append.ts +72 -0
- package/src/ledger.ts +3 -9
- package/src/native-session-target.ts +23 -0
- package/src/native-session.ts +139 -0
- package/src/order-schedule.ts +42 -0
- package/src/ordinary-children.ts +59 -0
- package/src/producer-ipc-contract.ts +26 -0
- package/src/producer-ipc.ts +107 -0
- package/src/resource-budget.ts +349 -0
- package/src/retention-contract.ts +84 -0
- package/src/retention-json.ts +20 -0
- package/src/run-child.ts +13 -2
- package/src/run-herdr.ts +11 -7
- package/src/vendor/adoption.ts +109 -0
- package/src/work-command.ts +179 -0
- package/src/work-ledger-destination.ts +113 -0
- package/src/work-ledger-json.ts +228 -0
- package/src/work-ledger-occurrences.ts +99 -0
- package/src/work-ledger-projection.ts +262 -0
- package/src/work-ledger-snapshot.ts +246 -0
- package/src/work-ledger-types.ts +229 -0
- package/src/work-ledger-validation.ts +174 -0
- package/src/work-ledger.ts +186 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { projectWorkLedger, parseWorkLedgerText, type WorkProjectionContext, type WorkFrozen, type RevisionRef } from "./work-ledger.ts";
|
|
3
|
+
import { canonicalWorkJson, copyWorkJson } from "./work-ledger-json.ts";
|
|
4
|
+
import { readDailySnapshot, parseArchiveProjection, freezeDaily, type ArchiveExecution } from "./daily-view-input.ts";
|
|
5
|
+
|
|
6
|
+
export const DAILY_VIEW_VERSION = "pi-daddy-daily-view-v1" as const;
|
|
7
|
+
export interface DailyViewOptions {
|
|
8
|
+
archiveProjectionPath?: string; workLedgerPath?: string;
|
|
9
|
+
/** Explicit retained adapter bytes; never authority. Mutually exclusive with the corresponding path. */
|
|
10
|
+
archiveBytes?: Uint8Array; workBytes?: Uint8Array;
|
|
11
|
+
sourceManifestBytes?: Readonly<Record<string, Uint8Array>>;
|
|
12
|
+
/** Genuine independently selected host authority only. Never populated from P03 or a context file. */
|
|
13
|
+
workContext?: WorkFrozen<WorkProjectionContext>;
|
|
14
|
+
/** Policy-authorized exact paths keyed by expected raw-manifest SHA256. No producer paths followed. */
|
|
15
|
+
sourceManifestFiles?: Readonly<Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
export interface DailyAttempt {
|
|
18
|
+
executionId: string; logicalChildId: string | null; intentRuntime: string;
|
|
19
|
+
archive: ArchiveExecution | null; sourceAvailability: Array<{ sha256: string; state: "available" | "missing" | "mismatch" | "error" }>;
|
|
20
|
+
issues: string[]; activeBranch: null;
|
|
21
|
+
}
|
|
22
|
+
export interface DailyObligation {
|
|
23
|
+
key: string; intent: RevisionRef; obligation: RevisionRef; policy: RevisionRef;
|
|
24
|
+
acceptance: "accepted-under-supplied-authority" | "unaccepted" | "unresolved";
|
|
25
|
+
artifactCoverage: string; evidenceCoverage: string; receiptIds: string[]; claimReferences: string[];
|
|
26
|
+
attempts: string[]; activity: "unstarted" | "attempts-attached"; obstacles: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface DailyView {
|
|
29
|
+
version: typeof DAILY_VIEW_VERSION; readOnly: true; freshness: "snapshot-unknown";
|
|
30
|
+
model: "not-consulted-connectivity-unknown"; continuity: "initial-snapshot" | "resnapshot" | "reconnected-gap";
|
|
31
|
+
scopeChanged: boolean; scope: RevisionRef | null; selectedSnapshot: WorkProjectionContext["selectedSnapshot"];
|
|
32
|
+
scopeState: string; progress: { accepted: number; total: number } | null;
|
|
33
|
+
authority: "supplied-host-context" | "unavailable" | "stale-for-selection"; coverage: "partial";
|
|
34
|
+
sources: { archive: string; work: string; archiveSha256: string | null; workSha256: string | null };
|
|
35
|
+
obligations: DailyObligation[]; attempts: DailyAttempt[]; issues: string[]; narrative: string;
|
|
36
|
+
}
|
|
37
|
+
const refKey = (r: WorkFrozen<RevisionRef>) => `${r.kind}:${r.id}:${r.revision}:${r.digest}`;
|
|
38
|
+
const sorted = (values: readonly string[]) => [...new Set(values)].sort();
|
|
39
|
+
function contextCopy(value: DailyViewOptions["workContext"]): WorkProjectionContext {
|
|
40
|
+
const copied = JSON.parse(canonicalWorkJson(copyWorkJson(value ?? { selectedSnapshot: null, authority: null }))) as WorkProjectionContext;
|
|
41
|
+
if (projectWorkLedger("", copied).errors.some(e => e.code === "WORK_CONTEXT_INVALID")) throw new TypeError("invalid independent P01 context");
|
|
42
|
+
return copied;
|
|
43
|
+
}
|
|
44
|
+
const readers = new WeakSet<object>();
|
|
45
|
+
/** Only actual factory readers may carry continuity state through the dashboard; not arbitrary callbacks. */
|
|
46
|
+
export function isDailyViewReader(value: unknown): value is ReturnType<typeof createDailyViewReader> {
|
|
47
|
+
return typeof value === "function" && readers.has(value);
|
|
48
|
+
}
|
|
49
|
+
/** Each read starts from complete bounded snapshots. The tracker holds only gap/scope metadata, not authority. */
|
|
50
|
+
export function createDailyViewReader() {
|
|
51
|
+
let seen = false, gap = false, lastSelection = "";
|
|
52
|
+
const read = async (options: DailyViewOptions): Promise<DailyView> => {
|
|
53
|
+
try {
|
|
54
|
+
const archivePath = options.archiveProjectionPath, workPath = options.workLedgerPath;
|
|
55
|
+
const context = contextCopy(options.workContext);
|
|
56
|
+
const files = Object.fromEntries(Object.entries(options.sourceManifestFiles ?? {}));
|
|
57
|
+
if (Object.keys(files).length > 128 || Object.entries(files).some(([key, value]) => !/^[a-f0-9]{64}$/.test(key) || typeof value !== "string")) throw new TypeError("bounded explicit source file map required");
|
|
58
|
+
const snapshot = (bytes: Uint8Array | undefined, path: string | undefined, limit: number) => {
|
|
59
|
+
if (bytes === undefined) return readDailySnapshot(path, limit);
|
|
60
|
+
if (path !== undefined || !(bytes instanceof Uint8Array) || bytes.length > limit) throw new TypeError("bounded exclusive retained snapshot required");
|
|
61
|
+
const copy=Buffer.from(bytes); return { status: "read" as const, bytes: copy, sha256: createHash("sha256").update(copy).digest("hex") };
|
|
62
|
+
};
|
|
63
|
+
const entries=Object.entries(options.sourceManifestBytes??{});if(entries.length>128)throw Error("source byte map bound");
|
|
64
|
+
const manifestBytes=Object.fromEntries(entries.map(([key,bytes])=>{
|
|
65
|
+
if(!/^[a-f0-9]{64}$/.test(key)||!(bytes instanceof Uint8Array)||bytes.length>65536)throw Error("bounded source bytes required");return [key,Buffer.from(bytes)];
|
|
66
|
+
}));
|
|
67
|
+
const [archiveRead, workRead] = await Promise.all([snapshot(options.archiveBytes,archivePath, 2*1024*1024), snapshot(options.workBytes,workPath, 16 * 1024 * 1024)]);
|
|
68
|
+
let archiveStatus: string = archiveRead.status, workStatus: string = workRead.status;
|
|
69
|
+
let archive: ArchiveExecution[] = [], work = projectWorkLedger("", context), workText = "";
|
|
70
|
+
const issues: string[] = [];
|
|
71
|
+
if (archiveRead.status === "read") {
|
|
72
|
+
try { archive = parseArchiveProjection(new TextDecoder("utf-8", { fatal: true }).decode(archiveRead.bytes)).executions; }
|
|
73
|
+
catch { archiveStatus = "error"; issues.push("archive-invalid-or-unsupported-version"); }
|
|
74
|
+
}
|
|
75
|
+
if (workRead.status === "read") {
|
|
76
|
+
try { workText = new TextDecoder("utf-8", { fatal: true }).decode(workRead.bytes); work = projectWorkLedger(workText, context); }
|
|
77
|
+
catch { workStatus = "error"; issues.push("work-input-invalid"); }
|
|
78
|
+
if (work.errors.length) { workStatus = "error"; issues.push("work-projection-errors"); }
|
|
79
|
+
}
|
|
80
|
+
const selection = JSON.stringify(context.selectedSnapshot), scopeChanged = seen && selection !== lastSelection;
|
|
81
|
+
const unavailable = archiveStatus !== "read" || ((Boolean(workPath) || options.workBytes !== undefined) && workStatus !== "read");
|
|
82
|
+
if (unavailable) gap = true;
|
|
83
|
+
const continuity = seen ? gap && !unavailable ? "reconnected-gap" : "resnapshot" : "initial-snapshot";
|
|
84
|
+
seen = true; lastSelection = selection;
|
|
85
|
+
if (scopeChanged) issues.push("scope-selection-changed; prior acceptance not carried forward");
|
|
86
|
+
if (gap) issues.push("observation-gap; resnapshot is not continuity");
|
|
87
|
+
if (archiveStatus !== "read") issues.push(`archive-${archiveStatus}`);
|
|
88
|
+
if (workStatus !== "read") issues.push(`work-${workStatus}`);
|
|
89
|
+
// Authority snapshot and selected work snapshot inhabit different identity namespaces.
|
|
90
|
+
// Applicability comes from P01's exact claim/selection/receipt validation, never ID equality.
|
|
91
|
+
const stale = !work.claims.some(c => c.matchedReceiptIds.length) && work.claims.some(c =>
|
|
92
|
+
c.applicability === "superseded" || c.problems.some(p => p.code === "RECEIPT_MISMATCH"));
|
|
93
|
+
const authority: DailyView["authority"] = !context.authority ? "unavailable" : stale ? "stale-for-selection" : "supplied-host-context";
|
|
94
|
+
if (authority !== "supplied-host-context") issues.push(`host-authority-${authority}`);
|
|
95
|
+
const events = workText && work.scopeState === "valid" ? parseWorkLedgerText(workText).events : [];
|
|
96
|
+
const selected = events.find(e => e.event === "work_snapshot" && e.eventId === context.selectedSnapshot?.event.eventId && e.digest === context.selectedSnapshot.event.digest);
|
|
97
|
+
const scope = selected?.event === "work_snapshot" ? selected.payload.snapshot.scope : null;
|
|
98
|
+
const available = new Map<string, DailyAttempt["sourceAvailability"][number]["state"]>();
|
|
99
|
+
const requested = sorted(archive.flatMap(e => e.sourceReferences));
|
|
100
|
+
for (const sha256 of requested) {
|
|
101
|
+
if (manifestBytes[sha256]) { available.set(sha256,createHash("sha256").update(manifestBytes[sha256]).digest("hex")===sha256?"available":"mismatch"); continue; }
|
|
102
|
+
if (!files[sha256]) { available.set(sha256, "missing"); continue; }
|
|
103
|
+
const source = await readDailySnapshot(files[sha256], 64 * 1024);
|
|
104
|
+
available.set(sha256, source.status === "read" ? source.sha256 === sha256 ? "available" : "mismatch" : source.status === "error" ? "error" : "missing");
|
|
105
|
+
}
|
|
106
|
+
const attempts = new Map<string, DailyAttempt>();
|
|
107
|
+
for (const attempt of work.runtime?.attempts ?? []) attempts.set(attempt.executionId, {
|
|
108
|
+
executionId: attempt.executionId, logicalChildId: attempt.childId, intentRuntime: attempt.state, archive: null,
|
|
109
|
+
sourceAvailability: [], issues: attempt.problems.map(p => `P01:${p.code}`), activeBranch: null,
|
|
110
|
+
});
|
|
111
|
+
for (const entry of archive) {
|
|
112
|
+
const a = attempts.get(entry.executionId) ?? { executionId: entry.executionId, logicalChildId: null, intentRuntime: "unknown",
|
|
113
|
+
archive: null, sourceAvailability: [], issues: [], activeBranch: null } as DailyAttempt;
|
|
114
|
+
a.archive = entry; a.issues.push(...entry.issues.map(i => `P03:${i}`));
|
|
115
|
+
a.sourceAvailability = entry.sourceReferences.map(sha256 => ({ sha256, state: available.get(sha256) ?? "missing" }));
|
|
116
|
+
if (!entry.sourceReferences.length) a.issues.push("source-hashes-absent");
|
|
117
|
+
if (a.sourceAvailability.some(r => r.state !== "available")) a.issues.push("source-bytes-unavailable-or-mismatched");
|
|
118
|
+
const intent = work.runtime?.attempts.find(r => r.executionId === entry.executionId);
|
|
119
|
+
if (intent && (entry.parentExecutionIds.length !== 1 || entry.parentExecutionIds[0] !== intent.parentExecutionId)) a.issues.push("P01-P03-parent-disagreement");
|
|
120
|
+
if (intent && intent.state === "running" && entry.runtime === "terminal") a.issues.push("P01-P03-runtime-disagreement");
|
|
121
|
+
a.issues = sorted(a.issues); attempts.set(a.executionId, a);
|
|
122
|
+
}
|
|
123
|
+
const obligations: DailyObligation[] = work.obligations.map(o => {
|
|
124
|
+
const attached = (work.runtime?.attempts ?? []).filter(a => a.bindings.some(b => scope && refKey(b.scope) === refKey(scope) && refKey(b.obligation) === refKey(o.binding.obligation)));
|
|
125
|
+
return {
|
|
126
|
+
key: canonicalWorkJson(copyWorkJson(o.binding)), intent: o.binding.intent, obligation: o.binding.obligation, policy: o.binding.policy,
|
|
127
|
+
acceptance: authority === "supplied-host-context" ? o.acceptance : "unresolved", artifactCoverage: o.artifactCoverage.state,
|
|
128
|
+
evidenceCoverage: o.evidenceCoverage.state,
|
|
129
|
+
receiptIds: sorted(work.claims.filter(c => o.claims.some(r => r.eventId === c.claim.eventId && r.digest === c.claim.digest)).flatMap(c => [...c.matchedReceiptIds])),
|
|
130
|
+
claimReferences: o.claims.map(c => `${c.eventId}:${c.digest}`).sort(), attempts: attached.map(a => a.executionId).sort(),
|
|
131
|
+
activity: attached.length ? "attempts-attached" : "unstarted", obstacles: sorted(o.problems.map(p => `P01:${p.code}`)),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
obligations.sort((a, b) => {
|
|
135
|
+
const left = `${a.obligation.id}:${a.key}`, right = `${b.obligation.id}:${b.key}`;
|
|
136
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
137
|
+
});
|
|
138
|
+
const progress = authority === "supplied-host-context" && workStatus === "read" ? work.progress : null;
|
|
139
|
+
issues.push(...work.problems.map(p => `P01:${p.code}`));
|
|
140
|
+
const view: DailyView = { version: DAILY_VIEW_VERSION, readOnly: true, freshness: "snapshot-unknown", model: "not-consulted-connectivity-unknown",
|
|
141
|
+
continuity, scopeChanged, scope, selectedSnapshot: context.selectedSnapshot, scopeState: work.scopeState,
|
|
142
|
+
progress, authority, coverage: "partial",
|
|
143
|
+
sources: { archive: archiveStatus, work: workStatus, archiveSha256: archiveRead.status === "read" ? archiveRead.sha256 : null, workSha256: workRead.status === "read" ? workRead.sha256 : null },
|
|
144
|
+
obligations, attempts: [...attempts.values()].sort((a, b) => a.executionId < b.executionId ? -1 : a.executionId > b.executionId ? 1 : 0), issues: sorted(issues),
|
|
145
|
+
narrative: `${obligations.length} scoped obligation(s); ${progress ? `${progress.accepted}/${progress.total} accepted under supplied P01 authority` : "acceptance unresolved"}; ${attempts.size} distinct attempt(s). Runtime exits do not establish acceptance. Snapshot freshness unknown.`,
|
|
146
|
+
};
|
|
147
|
+
return freezeDaily(view);
|
|
148
|
+
} catch (error) { gap = true; seen = true; throw error; }
|
|
149
|
+
};
|
|
150
|
+
readers.add(read); return read;
|
|
151
|
+
}
|
|
152
|
+
export const readDailyView = (options: DailyViewOptions): Promise<DailyView> => createDailyViewReader()(options);
|
package/src/dashboard-cli.ts
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { connectDashboardHost, isDashboardConnection, ENV_DASHBOARD_HOST_SOCKET, type DashboardConnection } from "./dashboard-host-transport.ts";
|
|
3
|
+
import { dashboardHostRequest, type DashboardHostRequest } from "./dashboard-host.ts";
|
|
4
|
+
export { ENV_DASHBOARD_HOST_SOCKET } from "./dashboard-host-transport.ts";
|
|
2
5
|
import { realpathSync } from "node:fs";
|
|
3
6
|
import { readFile } from "node:fs/promises";
|
|
4
7
|
import { resolve } from "node:path";
|
|
5
8
|
import { pathToFileURL } from "node:url";
|
|
6
9
|
import { parseDashboardLedger } from "./dashboard-projection.ts";
|
|
7
10
|
import { renderDashboard } from "./dashboard-render.ts";
|
|
11
|
+
import { createDailyViewReader, isDailyViewReader, readDailyView, type DailyViewOptions } from "./daily-view.ts";
|
|
12
|
+
import { renderDailyView } from "./daily-view-render.ts";
|
|
13
|
+
import { projectWorkLedger, type WorkProjectionContext } from "./work-ledger.ts";
|
|
14
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
15
|
+
import { createInterface } from "node:readline";
|
|
16
|
+
import { isDebriefPresenter, type DebriefPresenter } from "./debrief.ts";
|
|
17
|
+
import { renderDebrief, debriefAction } from "./debrief-render.ts";
|
|
18
|
+
import { createFixtureDebrief } from "./debrief-fixture.ts";
|
|
19
|
+
export const ENV_DEBRIEF_FIXTURE = "PI_DADDY_DEBRIEF_FIXTURE";
|
|
20
|
+
export { debriefAction as dashboardDebriefAction } from "./debrief-render.ts";
|
|
21
|
+
|
|
22
|
+
export const ENV_DAILY_ARCHIVE = "PI_DADDY_ARCHIVE_PROJECTION";
|
|
23
|
+
export const ENV_DAILY_WORK = "PI_DADDY_WORK_LEDGER";
|
|
24
|
+
export const ENV_DAILY_SELECTION = "PI_DADDY_WORK_SNAPSHOT";
|
|
25
|
+
function dailySelection(text: string | undefined): WorkProjectionContext["selectedSnapshot"] {
|
|
26
|
+
if (!text) return null;
|
|
27
|
+
const selection = parseRetentionJson(text) as WorkProjectionContext["selectedSnapshot"];
|
|
28
|
+
if (projectWorkLedger("", { selectedSnapshot: selection, authority: null }).errors.some(e => e.code === "WORK_CONTEXT_INVALID")) throw new TypeError("invalid P01 snapshot selection");
|
|
29
|
+
return selection;
|
|
30
|
+
}
|
|
8
31
|
|
|
9
32
|
export const DASHBOARD_PROTOCOL_VERSION = 1 as const;
|
|
10
33
|
export const ENV_DASHBOARD_LEDGER = "PI_DADDY_LEDGER";
|
|
@@ -20,6 +43,19 @@ export interface DashboardFrameOptions {
|
|
|
20
43
|
width?: number;
|
|
21
44
|
details?: boolean;
|
|
22
45
|
now?: Date;
|
|
46
|
+
dailyView?: DailyViewOptions;
|
|
47
|
+
dailyReader?: ReturnType<typeof createDailyViewReader>;
|
|
48
|
+
dailyJson?: boolean;
|
|
49
|
+
debrief?: DebriefPresenter;
|
|
50
|
+
debriefJson?: boolean;
|
|
51
|
+
connected?: DashboardConnection;
|
|
52
|
+
}
|
|
53
|
+
export async function dashboardHostAction(host:DashboardConnection,line:string){
|
|
54
|
+
if(!isDashboardConnection(host)||Buffer.byteLength(line)>60000)throw Error("genuine bounded dashboard connection required");
|
|
55
|
+
const command=line.trim();
|
|
56
|
+
if(command.startsWith("{"))return host.action(dashboardHostRequest(parseRetentionJson(command,60000) as DashboardHostRequest));
|
|
57
|
+
if(!/^[a-zA-Z0-9:_-]{1,64}$/.test(command))throw Error("dashboard action must be a listed command key");
|
|
58
|
+
return host.humanAction(command);
|
|
23
59
|
}
|
|
24
60
|
|
|
25
61
|
function shellQuote(value: string): string {
|
|
@@ -57,6 +93,31 @@ export async function dashboardFrame(options: DashboardFrameOptions): Promise<st
|
|
|
57
93
|
if (options.protocol !== undefined && options.protocol !== DASHBOARD_PROTOCOL_VERSION) {
|
|
58
94
|
return incompatibleFrame(options.protocol);
|
|
59
95
|
}
|
|
96
|
+
if(options.connected){
|
|
97
|
+
if(!isDashboardConnection(options.connected))throw Error("original dashboard connection required");
|
|
98
|
+
let view:Awaited<ReturnType<DashboardConnection["frame"]>>;
|
|
99
|
+
try{view=await options.connected.frame();}catch{return "PI-DADDY — CONNECTED HOST UNAVAILABLE\nNo prior acceptance, presentation or continuity substituted. No action retried.";}
|
|
100
|
+
if(options.dailyJson||options.debriefJson)return JSON.stringify(view);
|
|
101
|
+
const source=view.source as {daily?:Parameters<typeof renderDailyView>[0]}|null;
|
|
102
|
+
const actions=(view.actions as {key:string;label:string}[]|undefined)??[];
|
|
103
|
+
return ["PI-DADDY — CONNECTED HOST (snapshot; identity/acceptance not authenticated)",source?.daily?renderDailyView(source.daily,options.width):"Source unavailable; no previous acceptance substituted.",
|
|
104
|
+
view.debrief?renderDebrief(view.debrief as Parameters<typeof renderDebrief>[0],options.width):"Debrief deferred/closed. No implicit presentation or steering.",
|
|
105
|
+
`Attention reserved ${view.attention.attentionUsed}/5 (not proof of delivery). Tip ${view.tip}.`,
|
|
106
|
+
actions.length?`ACTIONS — type one command and Enter\n${actions.map(action=>` ${action.key} — ${action.label}`).join("\n")}`:"ACTIONS — none currently authorized; refresh never acts."].join("\n\n");
|
|
107
|
+
}
|
|
108
|
+
if (options.debrief || options.debriefJson) {
|
|
109
|
+
if (!isDebriefPresenter(options.debrief)) return "PI-DADDY — DEBRIEF UNAVAILABLE: genuine presenter/host missing";
|
|
110
|
+
const view = options.debrief.view(); return options.debriefJson ? JSON.stringify(view) : renderDebrief(view, options.width);
|
|
111
|
+
}
|
|
112
|
+
if (options.dailyView) {
|
|
113
|
+
try {
|
|
114
|
+
if (options.dailyReader !== undefined && !isDailyViewReader(options.dailyReader)) throw new TypeError("untrusted daily reader callback");
|
|
115
|
+
const view = await (options.dailyReader ?? readDailyView)(options.dailyView);
|
|
116
|
+
return options.dailyJson ? JSON.stringify(view) : renderDailyView(view, options.width);
|
|
117
|
+
} catch {
|
|
118
|
+
return "PI-DADDY — DAILY VIEW UNAVAILABLE\nInvalid or unavailable read-only input. No previous acceptance or continuity was substituted.";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
60
121
|
if (!options.ledgerPath) return setupFrame(options.cwd);
|
|
61
122
|
|
|
62
123
|
const ledgerPath = resolve(options.cwd, options.ledgerPath);
|
|
@@ -90,25 +151,43 @@ interface CliOptions {
|
|
|
90
151
|
details: boolean;
|
|
91
152
|
color: boolean;
|
|
92
153
|
ledgerPath?: string;
|
|
154
|
+
archivePath?: string;
|
|
155
|
+
workPath?: string;
|
|
156
|
+
selection?: string;
|
|
157
|
+
dailyJson: boolean;
|
|
158
|
+
debriefFixture: boolean;
|
|
159
|
+
debriefJson: boolean;
|
|
160
|
+
hostSocket?: string;
|
|
93
161
|
}
|
|
94
162
|
|
|
95
163
|
function parseArgs(argv: string[]): CliOptions {
|
|
96
164
|
let once = false;
|
|
97
165
|
let details = false;
|
|
98
166
|
let color = process.stdout.isTTY;
|
|
99
|
-
let ledgerPath: string | undefined;
|
|
167
|
+
let ledgerPath: string | undefined, archivePath: string | undefined, workPath: string | undefined, selection: string | undefined;
|
|
168
|
+
let dailyJson = false, debriefFixture = false, debriefJson = false;let hostSocket:string|undefined;
|
|
100
169
|
for (let index = 0; index < argv.length; index += 1) {
|
|
101
170
|
const arg = argv[index];
|
|
102
171
|
if (arg === "--once") once = true;
|
|
103
172
|
else if (arg === "--details") details = true;
|
|
104
173
|
else if (arg === "--no-color") color = false;
|
|
105
174
|
else if (arg === "--ledger") ledgerPath = argv[++index];
|
|
175
|
+
else if (arg === "--daily-json") dailyJson = true;
|
|
176
|
+
else if (arg === "--debrief-fixture") debriefFixture = true;
|
|
177
|
+
else if (arg === "--debrief-json") debriefJson = true;
|
|
178
|
+
else if(arg === "--host-socket"){hostSocket=argv[++index];if(!hostSocket||hostSocket.startsWith("--"))throw Error("explicit host socket required");}
|
|
179
|
+
else if (["--archive-projection", "--work-ledger", "--work-snapshot"].includes(arg)) {
|
|
180
|
+
const value = argv[++index]; if (!value || value.startsWith("--")) throw new Error("missing daily view argument");
|
|
181
|
+
if (arg === "--archive-projection") archivePath = value;
|
|
182
|
+
else if (arg === "--work-ledger") workPath = value;
|
|
183
|
+
else selection = value;
|
|
184
|
+
}
|
|
106
185
|
else throw new Error(`unknown dashboard argument ${JSON.stringify(arg)}`);
|
|
107
186
|
}
|
|
108
|
-
return { once, details, color, ledgerPath };
|
|
187
|
+
return { once, details, color, ledgerPath, archivePath, workPath, selection, dailyJson, debriefFixture, debriefJson, hostSocket };
|
|
109
188
|
}
|
|
110
189
|
|
|
111
|
-
export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.ProcessEnv = process.env): Promise<void> {
|
|
190
|
+
export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.ProcessEnv = process.env, host: { debrief?: DebriefPresenter; connected?:DashboardConnection } = {}): Promise<void> {
|
|
112
191
|
const cli = parseArgs(argv);
|
|
113
192
|
const cwd = process.cwd();
|
|
114
193
|
const ledgerPath = cli.ledgerPath ?? (env[ENV_DASHBOARD_LEDGER]?.trim() || undefined);
|
|
@@ -117,6 +196,22 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
117
196
|
const key = env[ENV_DASHBOARD_KEY]?.trim();
|
|
118
197
|
process.title = `pi-daddy-dashboard${key ? `:${key.slice(0, 12)}` : ""}`;
|
|
119
198
|
|
|
199
|
+
const archivePath = cli.archivePath ?? env[ENV_DAILY_ARCHIVE];
|
|
200
|
+
const workPath = cli.workPath ?? env[ENV_DAILY_WORK];
|
|
201
|
+
const selectedSnapshot = dailySelection(cli.selection ?? env[ENV_DAILY_SELECTION]);
|
|
202
|
+
const dailyView = archivePath || workPath || selectedSnapshot || cli.dailyJson ? {
|
|
203
|
+
archiveProjectionPath: archivePath ? resolve(cwd, archivePath) : undefined,
|
|
204
|
+
workLedgerPath: workPath ? resolve(cwd, workPath) : undefined,
|
|
205
|
+
workContext: { selectedSnapshot, authority: null },
|
|
206
|
+
} : undefined;
|
|
207
|
+
const socket=cli.hostSocket??env[ENV_DASHBOARD_HOST_SOCKET];const connected=host.connected??(socket?connectDashboardHost(socket):undefined);
|
|
208
|
+
if(connected&&(host.debrief||cli.debriefFixture||env[ENV_DEBRIEF_FIXTURE]==="1"))throw Error("connected host and separate debrief are exclusive");
|
|
209
|
+
const debrief = host.debrief ?? (cli.debriefFixture || env[ENV_DEBRIEF_FIXTURE] === "1" ? await createFixtureDebrief() : undefined);
|
|
210
|
+
if (debrief) {
|
|
211
|
+
if (!isDebriefPresenter(debrief)) throw new Error("invalid debrief presenter");
|
|
212
|
+
if (!host.debrief) await debrief.open({ mode: "manual", userPresent: true }); // Explicit CLI fixture only; never override a host deferral.
|
|
213
|
+
}
|
|
214
|
+
const dailyReader = createDailyViewReader();
|
|
120
215
|
let previous = "";
|
|
121
216
|
const draw = async (clear: boolean): Promise<void> => {
|
|
122
217
|
const frame = await dashboardFrame({
|
|
@@ -126,6 +221,7 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
126
221
|
color: cli.color,
|
|
127
222
|
width: process.stdout.columns || 80,
|
|
128
223
|
details: cli.details,
|
|
224
|
+
dailyView, dailyReader, dailyJson: cli.dailyJson, debrief, debriefJson: cli.debriefJson, connected,
|
|
129
225
|
});
|
|
130
226
|
if (frame === previous && !clear) return;
|
|
131
227
|
previous = frame;
|
|
@@ -143,11 +239,19 @@ export async function runDashboard(argv = process.argv.slice(2), env: NodeJS.Pro
|
|
|
143
239
|
drawing = true;
|
|
144
240
|
void draw(true).finally(() => { drawing = false; });
|
|
145
241
|
};
|
|
242
|
+
const input = debrief || connected ? createInterface({ input: process.stdin, terminal: false }) : null;
|
|
243
|
+
let acting = false;
|
|
244
|
+
input?.on("line", line => {
|
|
245
|
+
if (acting) return;
|
|
246
|
+
acting = true;
|
|
247
|
+
void (connected?dashboardHostAction(connected,line):debriefAction(debrief!, line)).catch(() => { process.stderr.write("Debrief action refused or acknowledgement unknown; inspect/reconcile explicitly.\n"); })
|
|
248
|
+
.finally(() => { acting = false; redraw(); });
|
|
249
|
+
});
|
|
146
250
|
const timer = setInterval(redraw, DASHBOARD_REFRESH_MS);
|
|
147
251
|
process.on("SIGWINCH", redraw);
|
|
148
252
|
await new Promise<void>((settle) => {
|
|
149
253
|
const stop = () => {
|
|
150
|
-
clearInterval(timer);
|
|
254
|
+
clearInterval(timer); input?.close();
|
|
151
255
|
process.off("SIGWINCH", redraw);
|
|
152
256
|
settle();
|
|
153
257
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat,realpath,open,mkdir,mkdtemp,writeFile,symlink } from "node:fs/promises";
|
|
3
|
+
import { dirname,join,isAbsolute } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { dataDigest,byteDigest,detached,sha } from "./debrief-contract.ts";
|
|
6
|
+
import { DASHBOARD_HARNESS_PIN,type DashboardHarness } from "./dashboard-host-contract.ts";
|
|
7
|
+
export interface DashboardHarnessArtifact {version:"dashboard-harness-artifact-v1";sourceCommit:typeof DASHBOARD_HARNESS_PIN;files:Record<string,string>;typeboxRoot:string;typeboxPackageSha256:string}
|
|
8
|
+
const loaded=new WeakMap<object,string>();
|
|
9
|
+
export const DASHBOARD_HARNESS_BRIDGE_SOURCE="127b349310dd8f28e5d6b12148a063fce66a77dd";
|
|
10
|
+
const bridgeFunctions=["learningJournal","createTrustLifecycle","openTrustLifecycle","trustPolicyDigest","archivePolicyBinding","ingestPolicySource","readArchiveCheckpoint","readArchiveSource","retainArchiveSource","captureArchivedWorkSignals","readRetainedExecution","projectRetainedExecutions","createWorkCaseReviewer","createWorkSignalReviewer","retainBlindIntervention","openBlindIntervention"] as const;
|
|
11
|
+
/** Adopt the API published by the already-loaded skill-harness extension. Same-process source identity is not human authentication. */
|
|
12
|
+
export function adoptDashboardHarnessBridge(input:unknown):DashboardHarness{
|
|
13
|
+
const value=input as {version?:unknown;sourceCommit?:unknown;api?:unknown};
|
|
14
|
+
if(!value||typeof value!=="object"||Object.keys(value).sort().join()!=="api,sourceCommit,version"||value.version!=="skill-harness-dashboard-bridge-v1"||value.sourceCommit!==DASHBOARD_HARNESS_BRIDGE_SOURCE)throw Error("exact supported harness bridge required");
|
|
15
|
+
const api=value.api as DashboardHarness;if(!api||typeof api!=="object"||!Object.isFrozen(api)||bridgeFunctions.some(name=>typeof (api as unknown as Record<string,unknown>)[name]!=="function"))throw Error("frozen harness API with required source jobs required");
|
|
16
|
+
const digest=dataDigest({version:value.version,sourceCommit:value.sourceCommit,functions:bridgeFunctions});loaded.set(api,digest);return api;
|
|
17
|
+
}
|
|
18
|
+
export const loadedDashboardHarnessDigest=(h:unknown):string|null=>typeof h==="object"&&h!==null?loaded.get(h)??null:null;
|
|
19
|
+
async function privateDirectory(path:string){const stat=await lstat(path);if(!isAbsolute(path)||await realpath(path)!==path||!stat.isDirectory()||stat.mode&0o077||stat.uid!==process.getuid?.())throw Error("canonical private owned harness directory required");}
|
|
20
|
+
async function bytes(path:string){if(await realpath(path)!==path)throw Error("harness artifact alias");const fd=await open(path,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);try{const stat=await fd.stat();if(!stat.isFile()||stat.nlink!==1||stat.mode&0o022||stat.size>1048576)throw Error("bounded immutable artifact required");const value=Buffer.alloc(stat.size+1),n=await fd.read(value,0,value.length,0),after=await fd.stat();if(n.bytesRead!==stat.size||after.size!==stat.size||after.mtimeMs!==stat.mtimeMs||after.ctimeMs!==stat.ctimeMs)throw Error("artifact changed during read");return value.subarray(0,n.bytesRead);}finally{await fd.close();}}
|
|
21
|
+
/** Load approved real compiled artifacts into an exclusive private tree, avoiding stale module-cache paths.
|
|
22
|
+
* Byte identity is NOT human/module authentication or hostile-same-UID confinement. No build/install here.
|
|
23
|
+
* Typebox is the explicit existing peer; its package metadata is bound, not a whole peer-source attestation. */
|
|
24
|
+
export async function loadDashboardHarness(artifactRoot:string,input:DashboardHarnessArtifact,parent:string){
|
|
25
|
+
const manifest=detached(input);if(manifest.version!=="dashboard-harness-artifact-v1"||manifest.sourceCommit!==DASHBOARD_HARNESS_PIN||!sha(manifest.typeboxPackageSha256))throw Error("exact supported harness artifact required");
|
|
26
|
+
const files=Object.entries(manifest.files);if(!files.length||files.length>128||files.some(([p,h])=>!sha(h)||!/^([a-zA-Z0-9_-]+\/)*[a-zA-Z0-9_.-]+\.(js|json)$/.test(p)))throw Error("bounded closed artifact inventory required");
|
|
27
|
+
await privateDirectory(artifactRoot);await privateDirectory(parent);
|
|
28
|
+
if(await realpath(manifest.typeboxRoot)!==manifest.typeboxRoot||byteDigest(await bytes(join(manifest.typeboxRoot,"package.json")))!==manifest.typeboxPackageSha256)throw Error("retained typebox peer changed");
|
|
29
|
+
const content=new Map<string,Buffer>();let total=0;for(const[p,digest]of files){const b=await bytes(join(artifactRoot,p));total+=b.length;if(total>8*1024*1024||byteDigest(b)!==digest)throw Error("harness artifact digest mismatch");content.set(p,b);}
|
|
30
|
+
if(content.get("package.json")?.toString()!=='{"type":"module"}')throw Error("explicit ESM artifact root required");
|
|
31
|
+
const directory=await mkdtemp(join(parent,"loaded-harness-"));for(const[p,b]of content){await mkdir(dirname(join(directory,p)),{recursive:true,mode:0o700});await writeFile(join(directory,p),b,{flag:"wx",mode:0o400});}
|
|
32
|
+
await mkdir(join(directory,"node_modules"));await symlink(manifest.typeboxRoot,join(directory,"node_modules/typebox"));
|
|
33
|
+
const names=["learning-journal","trust-lifecycle","archive-policy","archive-observer","archive-checkpoint","archived-work","execution-retention-archive","work-case-review","blind-intervention","evidence-archive","work-signal-observation","work-signal-cases","work-case-archive"];
|
|
34
|
+
const api=Object.assign({},...await Promise.all([...names.map(n=>"packages/adapters/src/"+n+".js"),"core.js"].map(p=>{if(!content.has(p))throw Error("missing genuine harness entry");return import(pathToFileURL(join(directory,p)).href);}))) as DashboardHarness;
|
|
35
|
+
for(const[p,digest]of files)if(byteDigest(await bytes(join(directory,p)))!==digest)throw Error("loaded harness bytes changed");
|
|
36
|
+
Object.freeze(api);const digest=dataDigest(manifest);loaded.set(api,digest);return Object.freeze({api,artifactDigest:digest,directory,manifest});
|
|
37
|
+
}
|
package/src/dashboard-herdr.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ENV_DASHBOARD_KEY,
|
|
7
7
|
ENV_DASHBOARD_LEDGER,
|
|
8
8
|
ENV_DASHBOARD_PROTOCOL,
|
|
9
|
-
DASHBOARD_PROTOCOL_VERSION,
|
|
9
|
+
DASHBOARD_PROTOCOL_VERSION, ENV_DAILY_ARCHIVE, ENV_DAILY_WORK, ENV_DAILY_SELECTION, ENV_DEBRIEF_FIXTURE, ENV_DASHBOARD_HOST_SOCKET,
|
|
10
10
|
} from "./dashboard-cli.ts";
|
|
11
11
|
import { parseReply, type HerdrExec } from "./herdr-cli.ts";
|
|
12
12
|
import { withFileLock } from "./file-lock.ts";
|
|
@@ -263,8 +263,12 @@ export async function openOrReuseDashboard(input: DashboardOpenInput): Promise<D
|
|
|
263
263
|
const exec = input.exec ?? dashboardHerdrExec;
|
|
264
264
|
const ledgerPath = input.ledgerPath.trim() ? resolve(input.cwd, input.ledgerPath) : "";
|
|
265
265
|
const cwd = resolve(input.cwd);
|
|
266
|
+
// Explicit operator inputs/owned host endpoint only, never an authority file or ambient snapshot.
|
|
267
|
+
// The endpoint keeps original controller handles in its owning process; it is not TUI attachment.
|
|
268
|
+
const dailyEnv = [ENV_DAILY_ARCHIVE, ENV_DAILY_WORK, ENV_DAILY_SELECTION, ENV_DEBRIEF_FIXTURE, ENV_DASHBOARD_HOST_SOCKET]
|
|
269
|
+
.flatMap(name => process.env[name] ? ["--env", `${name}=${process.env[name]}`] : []);
|
|
266
270
|
const key = createHash("sha256")
|
|
267
|
-
.update(`${input.host.workspaceId}\0${input.host.tabId}\0${ledgerPath}`, "utf8")
|
|
271
|
+
.update(`${input.host.workspaceId}\0${input.host.tabId}\0${ledgerPath}${dailyEnv.length ? JSON.stringify([cwd, ...dailyEnv]) : ""}`, "utf8")
|
|
268
272
|
.digest("hex");
|
|
269
273
|
await mkdir(dirname(input.statePath), { recursive: true });
|
|
270
274
|
|
|
@@ -307,6 +311,7 @@ export async function openOrReuseDashboard(input: DashboardOpenInput): Promise<D
|
|
|
307
311
|
...(ledgerPath ? ["--env", `${ENV_DASHBOARD_LEDGER}=${ledgerPath}`] : []),
|
|
308
312
|
"--env", `${ENV_DASHBOARD_PROTOCOL}=${DASHBOARD_PROTOCOL_VERSION}`,
|
|
309
313
|
"--env", `${ENV_DASHBOARD_KEY}=${key}`,
|
|
314
|
+
...dailyEnv,
|
|
310
315
|
"--no-focus",
|
|
311
316
|
];
|
|
312
317
|
const opened = parseReply(await exec(args));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { dataDigest, detached, freeze, sha } from "./debrief-contract.ts";
|
|
2
|
+
import { controlShape } from "./dispatch-control.ts";
|
|
3
|
+
import type { DebriefHarness, DurableBlindBinding, CaseSelection } from "./debrief-host.ts";
|
|
4
|
+
import type { WorkProjectionContext, WorkFrozen } from "./work-ledger.ts";
|
|
5
|
+
import type { DispatchAuthority } from "./dispatch-control.ts";
|
|
6
|
+
import type { OrdinaryAuthority } from "./ordinary-children.ts";
|
|
7
|
+
import type { ExperimentAuthority } from "./experiment-contract.ts";
|
|
8
|
+
export const DASHBOARD_HARNESS_PIN = "127b349310dd8f28e5d6b12148a063fce66a77dd";
|
|
9
|
+
export interface HostEvent { id: string; prior: string | null; value: Record<string, unknown> }
|
|
10
|
+
export interface HostJournal { read(): HostEvent[]; append(prior: string, value: Record<string, unknown>): HostEvent }
|
|
11
|
+
/** Loaded trusted ports; an interface or caller-supplied pin is NOT module/human authentication. */
|
|
12
|
+
export interface DashboardHarness extends DebriefHarness {
|
|
13
|
+
learningJournal(path: string, initial?: Record<string, unknown>): HostJournal;
|
|
14
|
+
openTrustLifecycle(path: string): { history(): HostEvent[]; inspect(now: number): { policyId: string; attentionUsed: number; tip: string; grantExpansion: false }; expose(id: string, now: number): { mode: string; reason: string; replayed: boolean } };
|
|
15
|
+
createTrustLifecycle(path:string,input:unknown,authorized:readonly string[]):unknown;
|
|
16
|
+
trustPolicyDigest(input:unknown):string;
|
|
17
|
+
retainArchiveSource(root:string,input:unknown):{manifestId:string};
|
|
18
|
+
archivePolicyBinding(path: string, source: string): { policySha256: string; archiveRoot: string };
|
|
19
|
+
ingestPolicySource(path: string, source: string, previous?: string, expectedPolicy?: string): { checkpointId: string; sourceStatus: string; issues?: string[]; [key: string]: unknown };
|
|
20
|
+
readArchiveCheckpoint(root: string, checkpoint: string): { checkpoint: { sourceManifestId: string; issues: string[]; [key: string]: unknown } };
|
|
21
|
+
readArchiveSource(root: string, id: string): { status: string; bytes?: Uint8Array; reference?: { retention: string; sha256: string; [key: string]: unknown } };
|
|
22
|
+
captureArchivedWorkSignals(root: string, id: string, context: WorkFrozen<WorkProjectionContext>, facts: unknown): { caseBatchId: string; observationId: string; linkageManifestId: string };
|
|
23
|
+
readRetainedExecution(root: string, id: string): { projection: unknown };
|
|
24
|
+
projectRetainedExecutions(values: readonly unknown[]): unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface DashboardHostConfig {
|
|
27
|
+
version: "producer-dashboard-host-v1"; trustDirectory: string; trustPolicyId: string; archiveRoot: string; scope: string; author: string;
|
|
28
|
+
policyPath: string; policySha256: string; sources: { id: string; kind: "work" | "retention" | "facts" }[];
|
|
29
|
+
selection: WorkProjectionContext["selectedSnapshot"]; cases: CaseSelection | null; blind: DurableBlindBinding | null;
|
|
30
|
+
budgetDigest: string; experimentDigest: string | null; harnessArtifactDigest: string;
|
|
31
|
+
ordinaryDigest?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface DashboardHostRequest { version: "1.0"; requestId: string; hostDigest: string; expectedTip: string; selectionDigest: string; operation: "observe" | "present" | "presented" | "defer" | "debrief" | "dispatch" | "intent" | "cancel" | "dispatch-reconcile" | "intent-reconcile" | "ordinary-cancel"; payload: unknown }
|
|
34
|
+
export interface DashboardHostAuthority {
|
|
35
|
+
hostDigests: readonly string[]; requestDigests: readonly string[];
|
|
36
|
+
workContext: WorkFrozen<WorkProjectionContext>; dispatch: DispatchAuthority | null; experiment: ExperimentAuthority | null;
|
|
37
|
+
ordinary?: OrdinaryAuthority | null;
|
|
38
|
+
}
|
|
39
|
+
export const dashboardHostDigest = (c: DashboardHostConfig): string => dataDigest(c);
|
|
40
|
+
export const dashboardSelectionDigest = (selection: DashboardHostConfig["selection"]): string => dataDigest(selection);
|
|
41
|
+
export function dashboardHostRequest(input: DashboardHostRequest) {
|
|
42
|
+
const r=detached(input);controlShape(r,["version","requestId","hostDigest","expectedTip","selectionDigest","operation","payload"]);
|
|
43
|
+
if(r.version!=="1.0"||!/^[a-zA-Z0-9:_-]{1,128}$/.test(r.requestId)||![r.hostDigest,r.expectedTip,r.selectionDigest].every(sha)||!["observe","present","presented","defer","debrief","dispatch","intent","cancel","dispatch-reconcile","intent-reconcile","ordinary-cancel"].includes(r.operation)||Buffer.byteLength(JSON.stringify(r))>60000)throw Error("bounded exact dashboard request required");return freeze(r);
|
|
44
|
+
}
|
|
45
|
+
export const dashboardHostRequestDigest = (r: DashboardHostRequest): string => dataDigest(dashboardHostRequest(r));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createServer, createConnection, type Socket } from "node:net";
|
|
2
|
+
import { lstat, realpath, chmod } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute } from "node:path";
|
|
4
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
5
|
+
import { isDashboardHost, dashboardHostRequest, type DashboardHost, type DashboardHostRequest } from "./dashboard-host.ts";
|
|
6
|
+
export const ENV_DASHBOARD_HOST_SOCKET="PI_DADDY_HOST_SOCKET";
|
|
7
|
+
export type DashboardConnection={frame:DashboardHost["frame"];action:DashboardHost["action"];humanAction:DashboardHost["humanAction"]};
|
|
8
|
+
const connections=new WeakSet<object>();
|
|
9
|
+
export const isDashboardConnection=(v:unknown):v is DashboardConnection=>isDashboardHost(v)||typeof v==="object"&&v!==null&&connections.has(v);
|
|
10
|
+
async function parent(path:string){if(!isAbsolute(path)||Buffer.byteLength(path)>100||await realpath(dirname(path))!==dirname(path))throw Error("canonical bounded private dashboard socket required");const s=await lstat(dirname(path));if(!s.isDirectory()||s.mode&0o077||s.uid!==process.getuid?.())throw Error("private owned dashboard socket parent required");}
|
|
11
|
+
/** Same original host process. No worker socket, native TUI attach, PID lookup or authority transport. */
|
|
12
|
+
export async function serveDashboardHost(path:string,host:DashboardHost){
|
|
13
|
+
if(!isDashboardHost(host))throw Error("original dashboard host required");await parent(path);const clients=new Set<Socket>(),tasks=new Set<Promise<void>>();
|
|
14
|
+
const server=createServer(socket=>{if(clients.size>=4){socket.destroy();return;}clients.add(socket);socket.on("close",()=>clients.delete(socket));socket.on("error",()=>{});socket.setTimeout(5000,()=>socket.destroy());let size=0,used=false;const chunks:Buffer[]=[];
|
|
15
|
+
socket.on("data",bytes=>{if(used)return;size+=bytes.length;if(size>65536){socket.destroy();return;}chunks.push(bytes);if(bytes.at(-1)!==10)return;used=true;
|
|
16
|
+
const task=(async()=>{try{const text=new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks));const r=parseRetentionJson(text,65536) as {operation:string;request?:DashboardHostRequest;key?:string};let result:unknown;
|
|
17
|
+
if(Object.keys(r).join()==="operation"&&r.operation==="frame")result=await host.frame();
|
|
18
|
+
else if(Object.keys(r).sort().join()==="operation,request"&&r.operation==="action")result=await host.action(dashboardHostRequest(r.request!));
|
|
19
|
+
else if(Object.keys(r).sort().join()==="key,operation"&&r.operation==="human-action"&&typeof r.key==="string")result=await host.humanAction(r.key);
|
|
20
|
+
else throw Error("closed dashboard protocol required");
|
|
21
|
+
const output=JSON.stringify({ok:true,result})+"\n";if(Buffer.byteLength(output)>512*1024)throw Error("dashboard frame bound exceeded");socket.end(output);
|
|
22
|
+
}catch{socket.end(JSON.stringify({ok:false,error:"refused or acknowledgement unknown; explicit readback only"})+"\n");}})();tasks.add(task);void task.finally(()=>tasks.delete(task));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
await new Promise<void>((resolve,reject)=>{server.once("error",reject);server.listen(path,()=>{server.off("error",reject);resolve();});});
|
|
26
|
+
try{await chmod(path,0o600);}catch(error){server.close();throw error;}
|
|
27
|
+
return Object.freeze({path,close:()=>new Promise<void>((resolve,reject)=>{for(const c of clients)c.destroy();server.close(e=>e?reject(e):void Promise.allSettled([...tasks]).then(()=>resolve()));})});
|
|
28
|
+
}
|
|
29
|
+
/** Explicit operator-selected endpoint. Socket ownership is access policy, not module/human authentication. */
|
|
30
|
+
export function connectDashboardHost(path:string):DashboardConnection{
|
|
31
|
+
const call=async(operation:string,payload:{request?:DashboardHostRequest;key?:string}={})=>{await parent(path);const s=await lstat(path);if(!s.isSocket()||s.mode&0o077||s.uid!==process.getuid?.())throw Error("private original dashboard endpoint unavailable");
|
|
32
|
+
return new Promise<any>((resolve,reject)=>{const socket=createConnection(path);let size=0,done=false;const chunks:Buffer[]=[];const fail=(e:unknown)=>{if(!done){done=true;socket.destroy();reject(e);}};
|
|
33
|
+
socket.setTimeout(5000,()=>fail(Error("dashboard acknowledgement timeout; do not repeat action")));socket.on("error",fail);socket.on("connect",()=>socket.write(JSON.stringify({operation,...payload})+"\n"));
|
|
34
|
+
socket.on("data",b=>{size+=b.length;if(size>512*1024){fail(Error("dashboard frame limit"));return;}chunks.push(b);});
|
|
35
|
+
socket.on("end",()=>{try{const text=new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks));const r=parseRetentionJson(text,512*1024) as {ok:boolean;result:unknown};if(r.ok!==true)throw Error("dashboard refused or acknowledgement unknown");if(operation==="frame"&&(r.result as {version?:string})?.version!=="producer-dashboard-frame-v1")throw Error("incompatible dashboard host");if(!done){done=true;resolve(r.result);}}catch(e){fail(e);}});
|
|
36
|
+
socket.on("close",()=>{if(!done)fail(Error("dashboard disconnected; acknowledgement unknown"));});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const api={frame:()=>call("frame"),action:(r:DashboardHostRequest)=>call("action",{request:dashboardHostRequest(r)}),humanAction:(key:string)=>call("human-action",{key})};connections.add(api);return Object.freeze(api);
|
|
40
|
+
}
|