@pellux/goodvibes-sdk 0.38.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/artifacts/operator-contract.json +4468 -916
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +4 -0
- package/dist/events/communication.d.ts +1 -1
- package/dist/events/surfaces.d.ts +25 -3
- package/dist/events/surfaces.d.ts.map +1 -1
- package/dist/events/surfaces.js +22 -1
- package/dist/events/workflows.d.ts +7 -6
- package/dist/events/workflows.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/platform/agents/archetypes.d.ts.map +1 -1
- package/dist/platform/agents/archetypes.js +7 -0
- package/dist/platform/agents/index.d.ts +1 -0
- package/dist/platform/agents/index.d.ts.map +1 -1
- package/dist/platform/agents/index.js +1 -0
- package/dist/platform/agents/orchestrator-runner.d.ts +16 -3
- package/dist/platform/agents/orchestrator-runner.d.ts.map +1 -1
- package/dist/platform/agents/orchestrator-runner.js +20 -6
- package/dist/platform/agents/orchestrator.d.ts +42 -10
- package/dist/platform/agents/orchestrator.d.ts.map +1 -1
- package/dist/platform/agents/orchestrator.js +68 -20
- package/dist/platform/agents/planner-decomposition-runner.d.ts +38 -0
- package/dist/platform/agents/planner-decomposition-runner.d.ts.map +1 -0
- package/dist/platform/agents/planner-decomposition-runner.js +113 -0
- package/dist/platform/agents/turn-knowledge-injection.d.ts +88 -3
- package/dist/platform/agents/turn-knowledge-injection.d.ts.map +1 -1
- package/dist/platform/agents/turn-knowledge-injection.js +147 -30
- package/dist/platform/agents/worktree.d.ts +126 -8
- package/dist/platform/agents/worktree.d.ts.map +1 -1
- package/dist/platform/agents/worktree.js +240 -19
- package/dist/platform/agents/wrfc-controller.d.ts +24 -1
- package/dist/platform/agents/wrfc-controller.d.ts.map +1 -1
- package/dist/platform/agents/wrfc-controller.js +161 -35
- package/dist/platform/agents/wrfc-gate-runtime.d.ts +9 -0
- package/dist/platform/agents/wrfc-gate-runtime.d.ts.map +1 -1
- package/dist/platform/agents/wrfc-gate-runtime.js +7 -3
- package/dist/platform/agents/wrfc-gates.d.ts +1 -1
- package/dist/platform/agents/wrfc-gates.d.ts.map +1 -1
- package/dist/platform/agents/wrfc-gates.js +7 -1
- package/dist/platform/agents/wrfc-types.d.ts +23 -3
- package/dist/platform/agents/wrfc-types.d.ts.map +1 -1
- package/dist/platform/automation/types.d.ts +9 -1
- package/dist/platform/automation/types.d.ts.map +1 -1
- package/dist/platform/calendar/calendar-api-shared.d.ts +25 -0
- package/dist/platform/calendar/calendar-api-shared.d.ts.map +1 -0
- package/dist/platform/calendar/calendar-api-shared.js +100 -0
- package/dist/platform/calendar/calendar-connector.d.ts +79 -0
- package/dist/platform/calendar/calendar-connector.d.ts.map +1 -0
- package/dist/platform/calendar/calendar-connector.js +155 -0
- package/dist/platform/calendar/google-calendar-api.d.ts +25 -0
- package/dist/platform/calendar/google-calendar-api.d.ts.map +1 -0
- package/dist/platform/calendar/google-calendar-api.js +89 -0
- package/dist/platform/calendar/http-fetch-adapter.d.ts +25 -0
- package/dist/platform/calendar/http-fetch-adapter.d.ts.map +1 -0
- package/dist/platform/calendar/http-fetch-adapter.js +25 -0
- package/dist/platform/calendar/ics-parser.d.ts +35 -0
- package/dist/platform/calendar/ics-parser.d.ts.map +1 -0
- package/dist/platform/calendar/ics-parser.js +236 -0
- package/dist/platform/calendar/index.d.ts +41 -0
- package/dist/platform/calendar/index.d.ts.map +1 -0
- package/dist/platform/calendar/index.js +49 -0
- package/dist/platform/calendar/merged-calendar-model.d.ts +67 -0
- package/dist/platform/calendar/merged-calendar-model.d.ts.map +1 -0
- package/dist/platform/calendar/merged-calendar-model.js +181 -0
- package/dist/platform/calendar/microsoft-graph-api.d.ts +25 -0
- package/dist/platform/calendar/microsoft-graph-api.d.ts.map +1 -0
- package/dist/platform/calendar/microsoft-graph-api.js +95 -0
- package/dist/platform/calendar/oauth-flow.d.ts +62 -0
- package/dist/platform/calendar/oauth-flow.d.ts.map +1 -0
- package/dist/platform/calendar/oauth-flow.js +305 -0
- package/dist/platform/calendar/oauth-providers.d.ts +43 -0
- package/dist/platform/calendar/oauth-providers.d.ts.map +1 -0
- package/dist/platform/calendar/oauth-providers.js +120 -0
- package/dist/platform/calendar/oauth-token-store.d.ts +72 -0
- package/dist/platform/calendar/oauth-token-store.d.ts.map +1 -0
- package/dist/platform/calendar/oauth-token-store.js +189 -0
- package/dist/platform/calendar/oauth-types.d.ts +274 -0
- package/dist/platform/calendar/oauth-types.d.ts.map +1 -0
- package/dist/platform/calendar/oauth-types.js +22 -0
- package/dist/platform/calendar/rrule.d.ts +39 -0
- package/dist/platform/calendar/rrule.d.ts.map +1 -0
- package/dist/platform/calendar/rrule.js +261 -0
- package/dist/platform/calendar/subscription-store.d.ts +118 -0
- package/dist/platform/calendar/subscription-store.d.ts.map +1 -0
- package/dist/platform/calendar/subscription-store.js +293 -0
- package/dist/platform/calendar/types.d.ts +170 -0
- package/dist/platform/calendar/types.d.ts.map +1 -0
- package/dist/platform/calendar/types.js +21 -0
- package/dist/platform/companion/companion-chat-attachments.d.ts +29 -0
- package/dist/platform/companion/companion-chat-attachments.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-attachments.js +121 -0
- package/dist/platform/companion/companion-chat-branching.d.ts +66 -0
- package/dist/platform/companion/companion-chat-branching.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-branching.js +142 -0
- package/dist/platform/companion/companion-chat-broker-bridge.d.ts +40 -0
- package/dist/platform/companion/companion-chat-broker-bridge.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-broker-bridge.js +14 -0
- package/dist/platform/companion/companion-chat-broker-sync.d.ts +38 -0
- package/dist/platform/companion/companion-chat-broker-sync.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-broker-sync.js +80 -0
- package/dist/platform/companion/companion-chat-gc.d.ts +63 -0
- package/dist/platform/companion/companion-chat-gc.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-gc.js +38 -0
- package/dist/platform/companion/companion-chat-manager.d.ts +91 -23
- package/dist/platform/companion/companion-chat-manager.d.ts.map +1 -1
- package/dist/platform/companion/companion-chat-manager.js +195 -223
- package/dist/platform/companion/companion-chat-persistence.d.ts +9 -1
- package/dist/platform/companion/companion-chat-persistence.d.ts.map +1 -1
- package/dist/platform/companion/companion-chat-persistence.js +10 -2
- package/dist/platform/companion/companion-chat-routes.d.ts +2 -1
- package/dist/platform/companion/companion-chat-routes.d.ts.map +1 -1
- package/dist/platform/companion/companion-chat-routes.js +113 -4
- package/dist/platform/companion/companion-chat-turn-execution.d.ts +61 -0
- package/dist/platform/companion/companion-chat-turn-execution.d.ts.map +1 -0
- package/dist/platform/companion/companion-chat-turn-execution.js +107 -0
- package/dist/platform/companion/companion-chat-types.d.ts +67 -0
- package/dist/platform/companion/companion-chat-types.d.ts.map +1 -1
- package/dist/platform/config/credential-status.d.ts +28 -0
- package/dist/platform/config/credential-status.d.ts.map +1 -0
- package/dist/platform/config/credential-status.js +75 -0
- package/dist/platform/config/index.d.ts +25 -0
- package/dist/platform/config/index.d.ts.map +1 -1
- package/dist/platform/config/index.js +18 -0
- package/dist/platform/config/manager.d.ts +9 -0
- package/dist/platform/config/manager.d.ts.map +1 -1
- package/dist/platform/config/manager.js +21 -2
- package/dist/platform/config/migrations.d.ts +48 -0
- package/dist/platform/config/migrations.d.ts.map +1 -0
- package/dist/platform/config/migrations.js +54 -0
- package/dist/platform/config/schema-domain-core.d.ts +10 -1
- package/dist/platform/config/schema-domain-core.d.ts.map +1 -1
- package/dist/platform/config/schema-domain-core.js +46 -3
- package/dist/platform/config/schema-domain-runtime.d.ts +8 -0
- package/dist/platform/config/schema-domain-runtime.d.ts.map +1 -1
- package/dist/platform/config/schema-domain-runtime.js +32 -0
- package/dist/platform/config/schema-types.d.ts +21 -7
- package/dist/platform/config/schema-types.d.ts.map +1 -1
- package/dist/platform/config/schema.d.ts.map +1 -1
- package/dist/platform/config/schema.js +2 -0
- package/dist/platform/control-plane/approval-broker.d.ts +11 -0
- package/dist/platform/control-plane/approval-broker.d.ts.map +1 -1
- package/dist/platform/control-plane/approval-broker.js +17 -1
- package/dist/platform/control-plane/approval-hunk-apply.d.ts +77 -0
- package/dist/platform/control-plane/approval-hunk-apply.d.ts.map +1 -0
- package/dist/platform/control-plane/approval-hunk-apply.js +80 -0
- package/dist/platform/control-plane/gateway-disabled-response.d.ts +20 -0
- package/dist/platform/control-plane/gateway-disabled-response.d.ts.map +1 -0
- package/dist/platform/control-plane/gateway-disabled-response.js +24 -0
- package/dist/platform/control-plane/gateway-scope-enforcement.d.ts +68 -0
- package/dist/platform/control-plane/gateway-scope-enforcement.d.ts.map +1 -0
- package/dist/platform/control-plane/gateway-scope-enforcement.js +91 -0
- package/dist/platform/control-plane/gateway-utils.d.ts +17 -0
- package/dist/platform/control-plane/gateway-utils.d.ts.map +1 -1
- package/dist/platform/control-plane/gateway-utils.js +30 -0
- package/dist/platform/control-plane/gateway.d.ts +12 -2
- package/dist/platform/control-plane/gateway.d.ts.map +1 -1
- package/dist/platform/control-plane/gateway.js +27 -31
- package/dist/platform/control-plane/index.d.ts +8 -1
- package/dist/platform/control-plane/index.d.ts.map +1 -1
- package/dist/platform/control-plane/index.js +8 -0
- package/dist/platform/control-plane/invoke-input-validation.d.ts +56 -0
- package/dist/platform/control-plane/invoke-input-validation.d.ts.map +1 -0
- package/dist/platform/control-plane/invoke-input-validation.js +163 -0
- package/dist/platform/control-plane/method-catalog-admin.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-admin.js +12 -1
- package/dist/platform/control-plane/method-catalog-calendar.d.ts +16 -0
- package/dist/platform/control-plane/method-catalog-calendar.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-calendar.js +21 -0
- package/dist/platform/control-plane/method-catalog-channels.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-channels.js +32 -3
- package/dist/platform/control-plane/method-catalog-control-automation.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-control-automation.js +19 -34
- package/dist/platform/control-plane/method-catalog-control-companion.d.ts +15 -0
- package/dist/platform/control-plane/method-catalog-control-companion.d.ts.map +1 -0
- package/dist/platform/control-plane/method-catalog-control-companion.js +176 -0
- package/dist/platform/control-plane/method-catalog-control-core.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-control-core.js +104 -122
- package/dist/platform/control-plane/method-catalog-control.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-control.js +4 -0
- package/dist/platform/control-plane/method-catalog-email.d.ts +17 -0
- package/dist/platform/control-plane/method-catalog-email.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-email.js +21 -0
- package/dist/platform/control-plane/method-catalog-events.d.ts +28 -0
- package/dist/platform/control-plane/method-catalog-events.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-events.js +74 -1
- package/dist/platform/control-plane/method-catalog-fleet.d.ts +24 -0
- package/dist/platform/control-plane/method-catalog-fleet.d.ts.map +1 -0
- package/dist/platform/control-plane/method-catalog-fleet.js +65 -0
- package/dist/platform/control-plane/method-catalog-push.d.ts +30 -0
- package/dist/platform/control-plane/method-catalog-push.d.ts.map +1 -0
- package/dist/platform/control-plane/method-catalog-push.js +80 -0
- package/dist/platform/control-plane/method-catalog-route-reconcile.d.ts +84 -0
- package/dist/platform/control-plane/method-catalog-route-reconcile.d.ts.map +1 -0
- package/dist/platform/control-plane/method-catalog-route-reconcile.js +103 -0
- package/dist/platform/control-plane/method-catalog-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-runtime.js +83 -1
- package/dist/platform/control-plane/method-catalog-shared.d.ts +29 -0
- package/dist/platform/control-plane/method-catalog-shared.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog.d.ts +9 -0
- package/dist/platform/control-plane/method-catalog.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog.js +34 -3
- package/dist/platform/control-plane/operator-contract-schemas-admin.d.ts +8 -0
- package/dist/platform/control-plane/operator-contract-schemas-admin.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-admin.js +22 -1
- package/dist/platform/control-plane/operator-contract-schemas-fleet.d.ts +18 -0
- package/dist/platform/control-plane/operator-contract-schemas-fleet.d.ts.map +1 -0
- package/dist/platform/control-plane/operator-contract-schemas-fleet.js +182 -0
- package/dist/platform/control-plane/operator-contract-schemas-permissions.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-permissions.js +5 -0
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts +37 -0
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-runtime.js +100 -3
- package/dist/platform/control-plane/operator-contract-schemas.d.ts +1 -0
- package/dist/platform/control-plane/operator-contract-schemas.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas.js +1 -0
- package/dist/platform/control-plane/operator-contract.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract.js +8 -0
- package/dist/platform/control-plane/routes/checkpoints.d.ts +39 -0
- package/dist/platform/control-plane/routes/checkpoints.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/checkpoints.js +166 -0
- package/dist/platform/control-plane/routes/fleet.d.ts +53 -0
- package/dist/platform/control-plane/routes/fleet.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/fleet.js +153 -0
- package/dist/platform/control-plane/routes/gateway-verb-error.d.ts +30 -0
- package/dist/platform/control-plane/routes/gateway-verb-error.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/gateway-verb-error.js +36 -0
- package/dist/platform/control-plane/routes/index.d.ts +0 -6
- package/dist/platform/control-plane/routes/index.d.ts.map +1 -1
- package/dist/platform/control-plane/routes/index.js +5 -5
- package/dist/platform/control-plane/routes/invocation-params.d.ts +23 -0
- package/dist/platform/control-plane/routes/invocation-params.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/invocation-params.js +9 -0
- package/dist/platform/control-plane/routes/push.d.ts +26 -0
- package/dist/platform/control-plane/routes/push.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/push.js +104 -0
- package/dist/platform/control-plane/routes/register-gateway-verb-groups.d.ts +31 -0
- package/dist/platform/control-plane/routes/register-gateway-verb-groups.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/register-gateway-verb-groups.js +16 -0
- package/dist/platform/control-plane/routes/register-w3-s2.d.ts +26 -0
- package/dist/platform/control-plane/routes/register-w3-s2.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/register-w3-s2.js +8 -0
- package/dist/platform/control-plane/routes/session-search.d.ts +37 -0
- package/dist/platform/control-plane/routes/session-search.d.ts.map +1 -0
- package/dist/platform/control-plane/routes/session-search.js +113 -0
- package/dist/platform/control-plane/session-broker-gc.d.ts +16 -0
- package/dist/platform/control-plane/session-broker-gc.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker-gc.js +23 -6
- package/dist/platform/control-plane/session-broker-helpers.d.ts +3 -3
- package/dist/platform/control-plane/session-broker-helpers.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker-inputs.d.ts +18 -0
- package/dist/platform/control-plane/session-broker-inputs.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker-inputs.js +34 -0
- package/dist/platform/control-plane/session-broker-sessions.d.ts +102 -3
- package/dist/platform/control-plane/session-broker-sessions.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker-sessions.js +179 -5
- package/dist/platform/control-plane/session-broker-state.d.ts +20 -1
- package/dist/platform/control-plane/session-broker-state.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker-state.js +67 -5
- package/dist/platform/control-plane/session-broker.d.ts +51 -38
- package/dist/platform/control-plane/session-broker.d.ts.map +1 -1
- package/dist/platform/control-plane/session-broker.js +183 -75
- package/dist/platform/control-plane/session-store-importer.d.ts +57 -0
- package/dist/platform/control-plane/session-store-importer.d.ts.map +1 -0
- package/dist/platform/control-plane/session-store-importer.js +227 -0
- package/dist/platform/control-plane/session-types.d.ts +142 -7
- package/dist/platform/control-plane/session-types.d.ts.map +1 -1
- package/dist/platform/core/execution-plan.d.ts +43 -1
- package/dist/platform/core/execution-plan.d.ts.map +1 -1
- package/dist/platform/core/execution-plan.js +46 -10
- package/dist/platform/core/index.d.ts +1 -0
- package/dist/platform/core/index.d.ts.map +1 -1
- package/dist/platform/core/index.js +1 -0
- package/dist/platform/core/orchestrator-runtime.d.ts +23 -3
- package/dist/platform/core/orchestrator-runtime.d.ts.map +1 -1
- package/dist/platform/core/orchestrator-tool-runtime.d.ts +7 -0
- package/dist/platform/core/orchestrator-tool-runtime.d.ts.map +1 -1
- package/dist/platform/core/orchestrator-tool-runtime.js +10 -0
- package/dist/platform/core/orchestrator-turn-loop.d.ts +13 -6
- package/dist/platform/core/orchestrator-turn-loop.d.ts.map +1 -1
- package/dist/platform/core/orchestrator-turn-loop.js +29 -22
- package/dist/platform/core/orchestrator.d.ts +16 -10
- package/dist/platform/core/orchestrator.d.ts.map +1 -1
- package/dist/platform/core/orchestrator.js +23 -8
- package/dist/platform/core/plan-decomposition.d.ts +196 -0
- package/dist/platform/core/plan-decomposition.d.ts.map +1 -0
- package/dist/platform/core/plan-decomposition.js +417 -0
- package/dist/platform/core/plan-proposal.d.ts +39 -0
- package/dist/platform/core/plan-proposal.d.ts.map +1 -1
- package/dist/platform/core/wrfc-routing.d.ts +1 -0
- package/dist/platform/core/wrfc-routing.d.ts.map +1 -1
- package/dist/platform/core/wrfc-routing.js +51 -1
- package/dist/platform/daemon/boot.d.ts +69 -0
- package/dist/platform/daemon/boot.d.ts.map +1 -0
- package/dist/platform/daemon/boot.js +61 -0
- package/dist/platform/daemon/cli.js +2 -1
- package/dist/platform/daemon/control-plane.d.ts.map +1 -1
- package/dist/platform/daemon/control-plane.js +50 -1
- package/dist/platform/daemon/facade-composition.d.ts.map +1 -1
- package/dist/platform/daemon/facade-composition.js +6 -0
- package/dist/platform/daemon/facade.d.ts +15 -6
- package/dist/platform/daemon/facade.d.ts.map +1 -1
- package/dist/platform/daemon/facade.js +48 -43
- package/dist/platform/daemon/http/router-route-contexts.d.ts +1 -0
- package/dist/platform/daemon/http/router-route-contexts.d.ts.map +1 -1
- package/dist/platform/daemon/http/router-route-contexts.js +1 -0
- package/dist/platform/daemon/http/router-session-broker-adapter.d.ts +13 -0
- package/dist/platform/daemon/http/router-session-broker-adapter.d.ts.map +1 -0
- package/dist/platform/daemon/http/router-session-broker-adapter.js +27 -0
- package/dist/platform/daemon/http/router.d.ts +2 -1
- package/dist/platform/daemon/http/router.d.ts.map +1 -1
- package/dist/platform/daemon/http/router.js +42 -40
- package/dist/platform/daemon/http/runtime-route-types.d.ts +42 -4
- package/dist/platform/daemon/http/runtime-route-types.d.ts.map +1 -1
- package/dist/platform/daemon/http/webui-serving.d.ts +68 -0
- package/dist/platform/daemon/http/webui-serving.d.ts.map +1 -0
- package/dist/platform/daemon/http/webui-serving.js +230 -0
- package/dist/platform/daemon/index.d.ts +2 -0
- package/dist/platform/daemon/index.d.ts.map +1 -1
- package/dist/platform/daemon/index.js +1 -0
- package/dist/platform/daemon/service-manager.d.ts +16 -0
- package/dist/platform/daemon/service-manager.d.ts.map +1 -1
- package/dist/platform/daemon/service-manager.js +79 -15
- package/dist/platform/intelligence/tree-sitter/service.d.ts.map +1 -1
- package/dist/platform/intelligence/tree-sitter/service.js +16 -2
- package/dist/platform/knowledge/browser-history/readers.d.ts.map +1 -1
- package/dist/platform/knowledge/browser-history/readers.js +15 -5
- package/dist/platform/knowledge/knowledge-api.d.ts +1 -1
- package/dist/platform/knowledge/project-planning/service.d.ts +14 -1
- package/dist/platform/knowledge/project-planning/service.d.ts.map +1 -1
- package/dist/platform/knowledge/project-planning/service.js +77 -0
- package/dist/platform/knowledge/project-planning/types.d.ts +32 -0
- package/dist/platform/knowledge/project-planning/types.d.ts.map +1 -1
- package/dist/platform/orchestration/bookkeeping.d.ts +12 -0
- package/dist/platform/orchestration/bookkeeping.d.ts.map +1 -0
- package/dist/platform/orchestration/bookkeeping.js +91 -0
- package/dist/platform/orchestration/budget.d.ts +2 -2
- package/dist/platform/orchestration/cancellation.d.ts +2 -2
- package/dist/platform/orchestration/controller-compat.d.ts +16 -3
- package/dist/platform/orchestration/controller-compat.d.ts.map +1 -1
- package/dist/platform/orchestration/controller-compat.js +18 -4
- package/dist/platform/orchestration/dirty-guard.js +1 -1
- package/dist/platform/orchestration/engine.d.ts +26 -1
- package/dist/platform/orchestration/engine.d.ts.map +1 -1
- package/dist/platform/orchestration/engine.js +229 -25
- package/dist/platform/orchestration/index.d.ts +6 -4
- package/dist/platform/orchestration/index.d.ts.map +1 -1
- package/dist/platform/orchestration/index.js +3 -2
- package/dist/platform/orchestration/persistence.d.ts.map +1 -1
- package/dist/platform/orchestration/persistence.js +6 -3
- package/dist/platform/orchestration/phase-runner.d.ts +33 -6
- package/dist/platform/orchestration/phase-runner.d.ts.map +1 -1
- package/dist/platform/orchestration/phase-runner.js +98 -25
- package/dist/platform/orchestration/proposal-workstream.d.ts +56 -0
- package/dist/platform/orchestration/proposal-workstream.d.ts.map +1 -0
- package/dist/platform/orchestration/proposal-workstream.js +76 -0
- package/dist/platform/orchestration/scheduler.d.ts +20 -1
- package/dist/platform/orchestration/scheduler.d.ts.map +1 -1
- package/dist/platform/orchestration/scheduler.js +16 -0
- package/dist/platform/orchestration/types.d.ts +289 -4
- package/dist/platform/orchestration/types.d.ts.map +1 -1
- package/dist/platform/orchestration/types.js +32 -0
- package/dist/platform/orchestration/worktree-isolation.d.ts +33 -0
- package/dist/platform/orchestration/worktree-isolation.d.ts.map +1 -0
- package/dist/platform/orchestration/worktree-isolation.js +248 -0
- package/dist/platform/presentation/glyphs.d.ts +91 -0
- package/dist/platform/presentation/glyphs.d.ts.map +1 -0
- package/dist/platform/presentation/glyphs.js +93 -0
- package/dist/platform/presentation/index.d.ts +28 -0
- package/dist/platform/presentation/index.d.ts.map +1 -0
- package/dist/platform/presentation/index.js +27 -0
- package/dist/platform/presentation/thinking-phrases.d.ts +12 -0
- package/dist/platform/presentation/thinking-phrases.d.ts.map +1 -0
- package/dist/platform/presentation/thinking-phrases.js +32 -0
- package/dist/platform/presentation/tones.d.ts +121 -0
- package/dist/platform/presentation/tones.d.ts.map +1 -0
- package/dist/platform/presentation/tones.js +145 -0
- package/dist/platform/presentation/waiting-wording.d.ts +54 -0
- package/dist/platform/presentation/waiting-wording.d.ts.map +1 -0
- package/dist/platform/presentation/waiting-wording.js +51 -0
- package/dist/platform/providers/runtime-snapshot.d.ts.map +1 -1
- package/dist/platform/providers/runtime-snapshot.js +12 -2
- package/dist/platform/push/delivery.d.ts +48 -0
- package/dist/platform/push/delivery.d.ts.map +1 -0
- package/dist/platform/push/delivery.js +117 -0
- package/dist/platform/push/encryption.d.ts +41 -0
- package/dist/platform/push/encryption.d.ts.map +1 -0
- package/dist/platform/push/encryption.js +82 -0
- package/dist/platform/push/index.d.ts +17 -0
- package/dist/platform/push/index.d.ts.map +1 -0
- package/dist/platform/push/index.js +10 -0
- package/dist/platform/push/service.d.ts +75 -0
- package/dist/platform/push/service.d.ts.map +1 -0
- package/dist/platform/push/service.js +95 -0
- package/dist/platform/push/subscription-store.d.ts +50 -0
- package/dist/platform/push/subscription-store.d.ts.map +1 -0
- package/dist/platform/push/subscription-store.js +123 -0
- package/dist/platform/push/types.d.ts +68 -0
- package/dist/platform/push/types.d.ts.map +1 -0
- package/dist/platform/push/types.js +13 -0
- package/dist/platform/push/vapid.d.ts +54 -0
- package/dist/platform/push/vapid.d.ts.map +1 -0
- package/dist/platform/push/vapid.js +113 -0
- package/dist/platform/runtime/bootstrap-services.d.ts +62 -2
- package/dist/platform/runtime/bootstrap-services.d.ts.map +1 -1
- package/dist/platform/runtime/bootstrap-services.js +217 -50
- package/dist/platform/runtime/daemon-version-compat.d.ts +39 -0
- package/dist/platform/runtime/daemon-version-compat.d.ts.map +1 -0
- package/dist/platform/runtime/daemon-version-compat.js +75 -0
- package/dist/platform/runtime/detached-daemon-runtime.d.ts +26 -0
- package/dist/platform/runtime/detached-daemon-runtime.d.ts.map +1 -0
- package/dist/platform/runtime/detached-daemon-runtime.js +49 -0
- package/dist/platform/runtime/emitters/workflows.d.ts +1 -1
- package/dist/platform/runtime/emitters/workflows.d.ts.map +1 -1
- package/dist/platform/runtime/events/index.d.ts +13 -0
- package/dist/platform/runtime/events/index.d.ts.map +1 -1
- package/dist/platform/runtime/events/index.js +13 -0
- package/dist/platform/runtime/feature-flags/flags.d.ts.map +1 -1
- package/dist/platform/runtime/feature-flags/flags.js +39 -1
- package/dist/platform/runtime/fleet/adapters/agent.d.ts +2 -2
- package/dist/platform/runtime/fleet/adapters/agent.d.ts.map +1 -1
- package/dist/platform/runtime/fleet/adapters/agent.js +4 -4
- package/dist/platform/runtime/fleet/adapters/automation.d.ts +1 -1
- package/dist/platform/runtime/fleet/adapters/automation.js +1 -1
- package/dist/platform/runtime/fleet/adapters/code-index.d.ts +1 -1
- package/dist/platform/runtime/fleet/adapters/code-index.js +1 -1
- package/dist/platform/runtime/fleet/adapters/orchestration.d.ts +40 -18
- package/dist/platform/runtime/fleet/adapters/orchestration.d.ts.map +1 -1
- package/dist/platform/runtime/fleet/adapters/orchestration.js +138 -34
- package/dist/platform/runtime/fleet/adapters/schedule.d.ts +4 -4
- package/dist/platform/runtime/fleet/adapters/schedule.js +4 -4
- package/dist/platform/runtime/fleet/adapters/trigger.d.ts +3 -4
- package/dist/platform/runtime/fleet/adapters/trigger.d.ts.map +1 -1
- package/dist/platform/runtime/fleet/adapters/trigger.js +3 -4
- package/dist/platform/runtime/fleet/adapters/wrfc.d.ts +13 -0
- package/dist/platform/runtime/fleet/adapters/wrfc.d.ts.map +1 -1
- package/dist/platform/runtime/fleet/adapters/wrfc.js +49 -2
- package/dist/platform/runtime/fleet/registry.d.ts +7 -7
- package/dist/platform/runtime/fleet/registry.d.ts.map +1 -1
- package/dist/platform/runtime/fleet/registry.js +41 -7
- package/dist/platform/runtime/fleet/types.d.ts +9 -9
- package/dist/platform/runtime/fleet/types.d.ts.map +1 -1
- package/dist/platform/runtime/memory-spine/client.d.ts +127 -0
- package/dist/platform/runtime/memory-spine/client.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/client.js +113 -0
- package/dist/platform/runtime/memory-spine/index.d.ts +11 -0
- package/dist/platform/runtime/memory-spine/index.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/index.js +10 -0
- package/dist/platform/runtime/operator-client.d.ts +20 -2
- package/dist/platform/runtime/operator-client.d.ts.map +1 -1
- package/dist/platform/runtime/operator-client.js +11 -1
- package/dist/platform/runtime/provider-accounts/registry.d.ts.map +1 -1
- package/dist/platform/runtime/provider-accounts/registry.js +11 -1
- package/dist/platform/runtime/services.d.ts +11 -9
- package/dist/platform/runtime/services.d.ts.map +1 -1
- package/dist/platform/runtime/services.js +24 -9
- package/dist/platform/runtime/session-spine/client.d.ts +234 -0
- package/dist/platform/runtime/session-spine/client.d.ts.map +1 -0
- package/dist/platform/runtime/session-spine/client.js +449 -0
- package/dist/platform/runtime/session-spine/index.d.ts +12 -0
- package/dist/platform/runtime/session-spine/index.d.ts.map +1 -0
- package/dist/platform/runtime/session-spine/index.js +11 -0
- package/dist/platform/runtime/session-spine/union-cache.d.ts +225 -0
- package/dist/platform/runtime/session-spine/union-cache.d.ts.map +1 -0
- package/dist/platform/runtime/session-spine/union-cache.js +333 -0
- package/dist/platform/runtime/shell-command-ops.d.ts +3 -1
- package/dist/platform/runtime/shell-command-ops.d.ts.map +1 -1
- package/dist/platform/runtime/transports/daemon-http-client-validators.d.ts.map +1 -1
- package/dist/platform/runtime/transports/daemon-http-client-validators.js +22 -0
- package/dist/platform/runtime/transports/daemon-http-client.d.ts.map +1 -1
- package/dist/platform/runtime/transports/daemon-http-client.js +25 -2
- package/dist/platform/runtime/transports/http-types.d.ts +9 -2
- package/dist/platform/runtime/transports/http-types.d.ts.map +1 -1
- package/dist/platform/state/canonical-memory.d.ts +95 -0
- package/dist/platform/state/canonical-memory.d.ts.map +1 -0
- package/dist/platform/state/canonical-memory.js +155 -0
- package/dist/platform/state/code-index-chunking.js +1 -1
- package/dist/platform/state/code-index-db.d.ts +1 -1
- package/dist/platform/state/code-index-reindex.d.ts +73 -0
- package/dist/platform/state/code-index-reindex.d.ts.map +1 -0
- package/dist/platform/state/code-index-reindex.js +156 -0
- package/dist/platform/state/code-index-store.d.ts.map +1 -1
- package/dist/platform/state/code-index-store.js +11 -3
- package/dist/platform/state/index.d.ts +8 -0
- package/dist/platform/state/index.d.ts.map +1 -1
- package/dist/platform/state/index.js +4 -0
- package/dist/platform/state/memory-recall-contract.d.ts +118 -0
- package/dist/platform/state/memory-recall-contract.d.ts.map +1 -0
- package/dist/platform/state/memory-recall-contract.js +170 -0
- package/dist/platform/state/memory-registry.d.ts +8 -0
- package/dist/platform/state/memory-registry.d.ts.map +1 -1
- package/dist/platform/state/memory-registry.js +10 -0
- package/dist/platform/state/memory-store.d.ts +2 -0
- package/dist/platform/state/memory-store.d.ts.map +1 -1
- package/dist/platform/state/memory-store.js +4 -0
- package/dist/platform/state/memory-vector-store.d.ts.map +1 -1
- package/dist/platform/state/memory-vector-store.js +14 -3
- package/dist/platform/state/sqlite-store.d.ts +2 -0
- package/dist/platform/state/sqlite-store.d.ts.map +1 -1
- package/dist/platform/state/sqlite-store.js +4 -0
- package/dist/platform/state/sqlite-vec-loader.d.ts +1 -1
- package/dist/platform/state/sqlite-vec-loader.js +1 -1
- package/dist/platform/state/vibe-projection.d.ts +67 -0
- package/dist/platform/state/vibe-projection.d.ts.map +1 -0
- package/dist/platform/state/vibe-projection.js +115 -0
- package/dist/platform/tools/agent/index.d.ts.map +1 -1
- package/dist/platform/tools/agent/index.js +8 -0
- package/dist/platform/tools/agent/manager.d.ts +22 -10
- package/dist/platform/tools/agent/manager.d.ts.map +1 -1
- package/dist/platform/tools/agent/manager.js +13 -7
- package/dist/platform/tools/agent/schema.d.ts +26 -0
- package/dist/platform/tools/agent/schema.d.ts.map +1 -1
- package/dist/platform/tools/agent/schema.js +2 -2
- package/dist/platform/tools/agent/wrfc-batch-policy.d.ts +13 -0
- package/dist/platform/tools/agent/wrfc-batch-policy.d.ts.map +1 -1
- package/dist/platform/tools/agent/wrfc-batch-policy.js +60 -0
- package/dist/platform/tools/exec/runtime.d.ts +10 -0
- package/dist/platform/tools/exec/runtime.d.ts.map +1 -1
- package/dist/platform/tools/exec/runtime.js +27 -10
- package/dist/platform/tools/exec/schema.d.ts +2 -2
- package/dist/platform/tools/exec/schema.d.ts.map +1 -1
- package/dist/platform/tools/exec/schema.js +2 -1
- package/dist/platform/tools/fetch/runtime.d.ts +1 -1
- package/dist/platform/tools/index.d.ts +2 -0
- package/dist/platform/tools/index.d.ts.map +1 -1
- package/dist/platform/tools/index.js +2 -0
- package/dist/platform/tools/registry.d.ts +1 -1
- package/dist/platform/tools/registry.js +1 -1
- package/dist/platform/tools/state/index.d.ts +9 -0
- package/dist/platform/tools/state/index.d.ts.map +1 -1
- package/dist/platform/tools/state/index.js +60 -3
- package/dist/platform/types/foundation-contract.d.ts +1 -1
- package/dist/platform/types/foundation-contract.d.ts.map +1 -1
- package/dist/platform/types/index.d.ts +1 -1
- package/dist/platform/types/index.d.ts.map +1 -1
- package/dist/platform/types/tools.d.ts +1 -1
- package/dist/platform/utils/request-body.d.ts.map +1 -1
- package/dist/platform/utils/request-body.js +50 -5
- package/dist/platform/version.js +1 -1
- package/package.json +25 -9
- package/dist/platform/control-plane/routes/automation.d.ts +0 -3
- package/dist/platform/control-plane/routes/automation.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/automation.js +0 -63
- package/dist/platform/control-plane/routes/context.d.ts +0 -2
- package/dist/platform/control-plane/routes/context.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/context.js +0 -1
- package/dist/platform/control-plane/routes/operator.d.ts +0 -8
- package/dist/platform/control-plane/routes/operator.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/operator.js +0 -415
- package/dist/platform/control-plane/routes/remote.d.ts +0 -3
- package/dist/platform/control-plane/routes/remote.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/remote.js +0 -35
- package/dist/platform/control-plane/routes/sessions.d.ts +0 -3
- package/dist/platform/control-plane/routes/sessions.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/sessions.js +0 -43
- package/dist/platform/control-plane/routes/tasks.d.ts +0 -3
- package/dist/platform/control-plane/routes/tasks.d.ts.map +0 -1
- package/dist/platform/control-plane/routes/tasks.js +0 -22
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rrule.ts — a pure RRULE reader for an honest, deliberately-bounded subset.
|
|
3
|
+
*
|
|
4
|
+
* The honesty rule (see the decision record): an event's recurrence is either
|
|
5
|
+
* FULLY and correctly expanded, or NOT expanded at all with an explicit reason.
|
|
6
|
+
* We never emit occurrences from a rule we do not completely honor — a half-honored
|
|
7
|
+
* BYMONTHDAY would put events on wrong dates, which is worse than not expanding.
|
|
8
|
+
*
|
|
9
|
+
* Fully supported (expanded to real occurrences):
|
|
10
|
+
* - FREQ = DAILY | WEEKLY | MONTHLY | YEARLY
|
|
11
|
+
* - INTERVAL (default 1)
|
|
12
|
+
* - COUNT (occurrence cap)
|
|
13
|
+
* - UNTIL (inclusive end bound; DATE or date-time, 'Z' or floating)
|
|
14
|
+
* - BYDAY — ONLY for FREQ=WEEKLY with INTERVAL=1 and weekday-only tokens
|
|
15
|
+
* (MO,TU,WE,TH,FR,SA,SU with no leading ordinal). Under those
|
|
16
|
+
* conditions WKST does not change the matched set, so it is ignored
|
|
17
|
+
* safely. Any BYDAY outside that condition is treated as unsupported.
|
|
18
|
+
*
|
|
19
|
+
* Everything else present in the RRULE (BYMONTHDAY, BYMONTH, BYSETPOS, BYHOUR,
|
|
20
|
+
* BYWEEKNO, BYYEARDAY, ordinal BYDAY, BYDAY on non-weekly, INTERVAL>1 with BYDAY,
|
|
21
|
+
* or an unknown FREQ) marks the rule `unsupported` and names the offending part.
|
|
22
|
+
*
|
|
23
|
+
* PURE: no fs, no network, no process globals.
|
|
24
|
+
*/
|
|
25
|
+
const WEEKDAY_TOKENS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
|
|
26
|
+
const KNOWN_FREQ = new Set(['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']);
|
|
27
|
+
/** Parts we fully honor; anything else present makes the rule unsupported. */
|
|
28
|
+
const SUPPORTED_PARTS = new Set(['FREQ', 'INTERVAL', 'COUNT', 'UNTIL', 'WKST', 'BYDAY']);
|
|
29
|
+
function parseRRule(rule) {
|
|
30
|
+
const map = new Map();
|
|
31
|
+
for (const seg of rule.split(';')) {
|
|
32
|
+
const eq = seg.indexOf('=');
|
|
33
|
+
if (eq < 0)
|
|
34
|
+
continue;
|
|
35
|
+
const k = seg.slice(0, eq).trim().toUpperCase();
|
|
36
|
+
const v = seg.slice(eq + 1).trim();
|
|
37
|
+
if (k !== '')
|
|
38
|
+
map.set(k, v);
|
|
39
|
+
}
|
|
40
|
+
return { map };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Classify a raw RRULE into an honest RecurrenceInfo. Called by the parser at read
|
|
44
|
+
* time so the event carries its expansion verdict before any window is known.
|
|
45
|
+
*/
|
|
46
|
+
export function describeRecurrence(rule) {
|
|
47
|
+
const { map } = parseRRule(rule);
|
|
48
|
+
const freq = (map.get('FREQ') ?? '').toUpperCase();
|
|
49
|
+
if (!KNOWN_FREQ.has(freq)) {
|
|
50
|
+
return { rule, expansion: 'unsupported', unsupportedReason: `RRULE FREQ '${freq || '(missing)'}' is not expanded by this build` };
|
|
51
|
+
}
|
|
52
|
+
for (const part of map.keys()) {
|
|
53
|
+
if (!SUPPORTED_PARTS.has(part)) {
|
|
54
|
+
return { rule, expansion: 'unsupported', unsupportedReason: `RRULE part ${part} is not expanded by this build` };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (map.has('BYDAY')) {
|
|
58
|
+
const byDayVerdict = classifyByDay(freq, map);
|
|
59
|
+
if (byDayVerdict)
|
|
60
|
+
return { rule, expansion: 'unsupported', unsupportedReason: byDayVerdict };
|
|
61
|
+
}
|
|
62
|
+
return { rule, expansion: 'full' };
|
|
63
|
+
}
|
|
64
|
+
/** Return an unsupported-reason string if this BYDAY is outside the honored subset, else undefined. */
|
|
65
|
+
function classifyByDay(freq, map) {
|
|
66
|
+
if (freq !== 'WEEKLY') {
|
|
67
|
+
return `RRULE BYDAY on FREQ=${freq} is not expanded by this build (only weekly BYDAY is supported)`;
|
|
68
|
+
}
|
|
69
|
+
const interval = parseInterval(map);
|
|
70
|
+
if (interval !== 1) {
|
|
71
|
+
return 'RRULE BYDAY with INTERVAL>1 is not expanded by this build (week-boundary/WKST handling required)';
|
|
72
|
+
}
|
|
73
|
+
const tokens = (map.get('BYDAY') ?? '').split(',').map((t) => t.trim().toUpperCase());
|
|
74
|
+
for (const tok of tokens) {
|
|
75
|
+
if (!WEEKDAY_TOKENS.includes(tok)) {
|
|
76
|
+
return `RRULE BYDAY token '${tok}' with an ordinal is not expanded by this build`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
function parseInterval(map) {
|
|
82
|
+
const raw = map.get('INTERVAL');
|
|
83
|
+
if (!raw)
|
|
84
|
+
return 1;
|
|
85
|
+
const n = Number.parseInt(raw, 10);
|
|
86
|
+
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
87
|
+
}
|
|
88
|
+
/** Parse an UNTIL value ('YYYYMMDD' or 'YYYYMMDDThhmmss[Z]') to a 'YYYY-MM-DD' date string. */
|
|
89
|
+
function parseUntilDate(raw) {
|
|
90
|
+
const m = /^(\d{4})(\d{2})(\d{2})/.exec(raw.trim());
|
|
91
|
+
if (!m)
|
|
92
|
+
return undefined;
|
|
93
|
+
return `${m[1]}-${m[2]}-${m[3]}`;
|
|
94
|
+
}
|
|
95
|
+
/** Extract 'YYYY-MM-DD' from an EventDateTime value. */
|
|
96
|
+
function datePart(value) {
|
|
97
|
+
return value.slice(0, 10);
|
|
98
|
+
}
|
|
99
|
+
/** Hard ceiling so a COUNT-less UNTIL-less rule cannot run away. */
|
|
100
|
+
const MAX_OCCURRENCES = 1000;
|
|
101
|
+
function toUtcDate(ymd) {
|
|
102
|
+
const [y, m, d] = ymd.split('-').map((s) => Number.parseInt(s, 10));
|
|
103
|
+
return Date.UTC(y ?? 1970, (m ?? 1) - 1, d ?? 1);
|
|
104
|
+
}
|
|
105
|
+
function fromUtcDate(ms) {
|
|
106
|
+
const d = new Date(ms);
|
|
107
|
+
const y = d.getUTCFullYear().toString().padStart(4, '0');
|
|
108
|
+
const m = (d.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
109
|
+
const day = d.getUTCDate().toString().padStart(2, '0');
|
|
110
|
+
return `${y}-${m}-${day}`;
|
|
111
|
+
}
|
|
112
|
+
const DAY_MS = 86_400_000;
|
|
113
|
+
/** Build an occurrence start string that mirrors the seed's kind/zone. */
|
|
114
|
+
function occurrenceStart(event, ymd) {
|
|
115
|
+
const s = event.start;
|
|
116
|
+
if (s.kind === 'date')
|
|
117
|
+
return ymd;
|
|
118
|
+
const time = s.value.slice(10); // 'THH:mm:ss' or 'THH:mm:ssZ'
|
|
119
|
+
return `${ymd}${time}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Expand a (possibly recurring) event into concrete occurrences inside [window.from,
|
|
123
|
+
* window.to] inclusive. A non-recurring event yields at most its single seed. An
|
|
124
|
+
* event whose recurrence is `unsupported` yields ONLY its seed (if in-window),
|
|
125
|
+
* flagged `isSeed`, never a fabricated series — the caller reads
|
|
126
|
+
* `event.recurrence.expansion` to label it "recurrence not fully expanded".
|
|
127
|
+
*/
|
|
128
|
+
export function expandEvent(event, window) {
|
|
129
|
+
const seedYmd = datePart(event.start.value);
|
|
130
|
+
const fromMs = toUtcDate(window.from);
|
|
131
|
+
const toMs = toUtcDate(window.to);
|
|
132
|
+
const seedMs = toUtcDate(seedYmd);
|
|
133
|
+
const inWindow = (ms) => ms >= fromMs && ms <= toMs;
|
|
134
|
+
const seedOccurrence = () => inWindow(seedMs) ? [{ event, start: occurrenceStart(event, seedYmd), isSeed: true }] : [];
|
|
135
|
+
if (!event.recurrence || event.recurrence.expansion !== 'full') {
|
|
136
|
+
return seedOccurrence();
|
|
137
|
+
}
|
|
138
|
+
const { map } = parseRRule(event.recurrence.rule);
|
|
139
|
+
const freq = (map.get('FREQ') ?? '').toUpperCase();
|
|
140
|
+
const interval = parseInterval(map);
|
|
141
|
+
const count = map.has('COUNT') ? Number.parseInt(map.get('COUNT') ?? '', 10) : undefined;
|
|
142
|
+
const untilYmd = map.has('UNTIL') ? parseUntilDate(map.get('UNTIL') ?? '') : undefined;
|
|
143
|
+
const untilMs = untilYmd ? toUtcDate(untilYmd) : undefined;
|
|
144
|
+
const byDay = map.has('BYDAY')
|
|
145
|
+
? new Set((map.get('BYDAY') ?? '').split(',').map((t) => WEEKDAY_TOKENS.indexOf(t.trim().toUpperCase())))
|
|
146
|
+
: undefined;
|
|
147
|
+
const out = [];
|
|
148
|
+
let emitted = 0; // series position (counts every occurrence, in or out of window)
|
|
149
|
+
// `series` yields candidate UTC-midnight ms values in ascending series order.
|
|
150
|
+
// Weekly BYDAY walks day-by-day emitting matching weekdays; every other supported
|
|
151
|
+
// FREQ steps period-by-period from the seed. Both share one COUNT/UNTIL/window filter.
|
|
152
|
+
const emit = (ms) => {
|
|
153
|
+
// Return false to STOP the whole series (COUNT reached, past UNTIL, past window end).
|
|
154
|
+
if (count !== undefined && emitted >= count)
|
|
155
|
+
return false;
|
|
156
|
+
if (untilMs !== undefined && ms > untilMs)
|
|
157
|
+
return false;
|
|
158
|
+
if (ms > toMs)
|
|
159
|
+
return false;
|
|
160
|
+
emitted += 1;
|
|
161
|
+
if (ms >= fromMs) {
|
|
162
|
+
const ymd = fromUtcDate(ms);
|
|
163
|
+
out.push({ event, start: occurrenceStart(event, ymd), isSeed: ms === seedMs });
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
};
|
|
167
|
+
if (freq === 'WEEKLY' && byDay) {
|
|
168
|
+
let cursor = seedMs;
|
|
169
|
+
for (let i = 0; i < MAX_OCCURRENCES * 8; i++) {
|
|
170
|
+
if (byDay.has(new Date(cursor).getUTCDay())) {
|
|
171
|
+
if (!emit(cursor))
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
else if ((untilMs !== undefined && cursor > untilMs) || cursor > toMs) {
|
|
175
|
+
break; // no more matches possible past UNTIL / window end
|
|
176
|
+
}
|
|
177
|
+
cursor += DAY_MS;
|
|
178
|
+
if (cursor - seedMs > DAY_MS * 366 * 10)
|
|
179
|
+
break; // 10-year hard ceiling
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
if (freq === 'MONTHLY' || freq === 'YEARLY') {
|
|
184
|
+
stepMonthlyOrYearly(freq, interval, seedMs, toMs, untilMs, emit);
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
const step = freqStepper(freq, interval);
|
|
188
|
+
let cursor = seedMs;
|
|
189
|
+
for (let i = 0; i < MAX_OCCURRENCES; i++) {
|
|
190
|
+
if (!emit(cursor))
|
|
191
|
+
break;
|
|
192
|
+
cursor = step(cursor);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
/** A stepper that advances a UTC-midnight ms cursor by one FREQ*INTERVAL period. DAILY/WEEKLY
|
|
197
|
+
* only — MONTHLY/YEARLY need the anchor-day-preserving/skip-invalid logic in
|
|
198
|
+
* stepMonthlyOrYearly below, since naive Date.UTC(y, m+1, day) arithmetic normalizes an
|
|
199
|
+
* out-of-range day (e.g. day 31 in a 30-day month) into the NEXT month, permanently
|
|
200
|
+
* shifting the rest of the series (the bug this file used to have). */
|
|
201
|
+
function freqStepper(freq, interval) {
|
|
202
|
+
switch (freq) {
|
|
203
|
+
case 'DAILY':
|
|
204
|
+
return (ms) => ms + DAY_MS * interval;
|
|
205
|
+
case 'WEEKLY':
|
|
206
|
+
return (ms) => ms + DAY_MS * 7 * interval;
|
|
207
|
+
default:
|
|
208
|
+
return (ms) => ms + DAY_MS;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* MONTHLY/YEARLY stepping, RFC 5545-correct: every candidate is measured from the
|
|
213
|
+
* ORIGINAL anchor day-of-month (`seedDay`), never from a previously normalized/drifted
|
|
214
|
+
* cursor. A candidate (year, month, seedDay) that is not a real calendar date — Feb 31,
|
|
215
|
+
* Feb 29 in a non-leap year, Apr 31, etc. — does not exist and is SKIPPED entirely: it is
|
|
216
|
+
* never passed to `emit`, so it never consumes COUNT and never appears in the output.
|
|
217
|
+
*
|
|
218
|
+
* Citation (RFC 5545 §3.3.10, "Recurrence Rule"): "Recurrence rules may generate
|
|
219
|
+
* recurrence instances with an invalid date (e.g., February 30) or nonexistent local time
|
|
220
|
+
* ... Such recurrence instances MUST be ignored and MUST NOT count towards the recurrence
|
|
221
|
+
* count specified by the COUNT rule part." This function and its COUNT bookkeeping (via
|
|
222
|
+
* the caller's `emit`) implement that rule directly: e.g. FREQ=MONTHLY anchored on the
|
|
223
|
+
* 31st walks Jan 31 -> Mar 31 -> May 31 -> ... (Feb/Apr/Jun skipped, uncounted); FREQ=YEARLY
|
|
224
|
+
* anchored on Feb 29 walks leap year -> leap year only.
|
|
225
|
+
*/
|
|
226
|
+
function stepMonthlyOrYearly(freq, interval, seedMs, toMs, untilMs, emit) {
|
|
227
|
+
const seed = new Date(seedMs);
|
|
228
|
+
const seedYear = seed.getUTCFullYear();
|
|
229
|
+
const seedMonth = seed.getUTCMonth();
|
|
230
|
+
const seedDay = seed.getUTCDate();
|
|
231
|
+
// Generous candidate ceiling (not an occurrence ceiling — MAX_OCCURRENCES already
|
|
232
|
+
// bounds emitted occurrences via `emit`/COUNT): covers the worst case of a monthly
|
|
233
|
+
// anchor that only lands ~7 times a year (a 31st) needing many more candidate
|
|
234
|
+
// months than emitted occurrences, and a yearly Feb-29 anchor needing up to 4x the
|
|
235
|
+
// candidate years.
|
|
236
|
+
const maxCandidates = freq === 'MONTHLY' ? MAX_OCCURRENCES * 12 : MAX_OCCURRENCES * 4;
|
|
237
|
+
for (let k = 0; k < maxCandidates; k++) {
|
|
238
|
+
const year = freq === 'MONTHLY'
|
|
239
|
+
? Math.floor((seedYear * 12 + seedMonth + k * interval) / 12)
|
|
240
|
+
: seedYear + k * interval;
|
|
241
|
+
const month = freq === 'MONTHLY'
|
|
242
|
+
? (((seedYear * 12 + seedMonth + k * interval) % 12) + 12) % 12
|
|
243
|
+
: seedMonth;
|
|
244
|
+
// Bail out once even the FIRST of this candidate month is past every hard stop —
|
|
245
|
+
// further k only move further forward, so nothing later can help either. This
|
|
246
|
+
// check runs before the validity check so a run of skipped (invalid) candidates
|
|
247
|
+
// near the end of the window does not burn through maxCandidates.
|
|
248
|
+
const monthStartMs = Date.UTC(year, month, 1);
|
|
249
|
+
if (monthStartMs > toMs && (untilMs === undefined || monthStartMs > untilMs))
|
|
250
|
+
return;
|
|
251
|
+
const candidateMs = Date.UTC(year, month, seedDay);
|
|
252
|
+
const candidate = new Date(candidateMs);
|
|
253
|
+
const isRealDate = candidate.getUTCFullYear() === year
|
|
254
|
+
&& candidate.getUTCMonth() === month
|
|
255
|
+
&& candidate.getUTCDate() === seedDay;
|
|
256
|
+
if (!isRealDate)
|
|
257
|
+
continue; // invalid calendar date — skipped, does not consume COUNT
|
|
258
|
+
if (!emit(candidateMs))
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subscription-store.ts — named external-calendar feed subscriptions with honest,
|
|
3
|
+
* per-subscription status. The engine that agent-side `/calendar subscribe` and the
|
|
4
|
+
* connect wizard drive.
|
|
5
|
+
*
|
|
6
|
+
* Reaches the network ONLY through an injected `FeedFetcher`, and reads time ONLY
|
|
7
|
+
* through an injected `Clock`. That is the whole IO boundary: tests supply fake
|
|
8
|
+
* feeds and a fake clock, so no test ever touches a real URL, and refresh/staleness
|
|
9
|
+
* timing is deterministic. Persistence is the CALLER's job (the agent stores the
|
|
10
|
+
* feed URL via its secret manager and the rest of the metadata in its config);
|
|
11
|
+
* `snapshot()` / `restore()` move that metadata across restarts, events re-fetched.
|
|
12
|
+
*
|
|
13
|
+
* UX shape (per Mike's least-friction rule): `add({ url })` is paste-URL-and-done —
|
|
14
|
+
* it validates by fetching, auto-derives the subscription name from the feed's
|
|
15
|
+
* X-WR-CALNAME (falling back to the URL host) when the caller gives no name, and
|
|
16
|
+
* applies a sensible default refresh interval with no mandatory knobs. Every status
|
|
17
|
+
* it reports is honest: stale carries its age, unreachable/parse-error carry the
|
|
18
|
+
* stage and detail.
|
|
19
|
+
*
|
|
20
|
+
* PURE of ambient IO — no direct fs/network/process; all IO is injected.
|
|
21
|
+
*/
|
|
22
|
+
import type { CalendarEvent, CalendarSubscription, Clock, FeedFetcher, RefreshReport, SubscriptionSnapshot } from './types.js';
|
|
23
|
+
/** Sensible refresh cadence for a read-only feed that rarely changes minute-to-minute. */
|
|
24
|
+
export declare const DEFAULT_REFRESH_INTERVAL_MS: number;
|
|
25
|
+
/** Never hammer a feed faster than this, even if a caller asks. */
|
|
26
|
+
export declare const MIN_REFRESH_INTERVAL_MS: number;
|
|
27
|
+
/** A feed refreshed less often than this is capped up to here. */
|
|
28
|
+
export declare const MAX_REFRESH_INTERVAL_MS: number;
|
|
29
|
+
export interface SubscriptionStoreOptions {
|
|
30
|
+
readonly fetcher: FeedFetcher;
|
|
31
|
+
readonly clock?: Clock;
|
|
32
|
+
readonly defaultRefreshIntervalMs?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface AddSubscriptionInput {
|
|
35
|
+
readonly url: string;
|
|
36
|
+
/** Optional; when omitted, derived from the feed's X-WR-CALNAME or the URL host. */
|
|
37
|
+
readonly name?: string;
|
|
38
|
+
readonly refreshIntervalMs?: number;
|
|
39
|
+
}
|
|
40
|
+
/** The outcome of validate-by-fetch — what the wizard shows before saving. */
|
|
41
|
+
export type ValidationResult = {
|
|
42
|
+
readonly ok: true;
|
|
43
|
+
readonly calendarName?: string;
|
|
44
|
+
readonly eventCount: number;
|
|
45
|
+
readonly derivedName: string;
|
|
46
|
+
} | {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
/** Which stage failed, so failure wording can name it honestly. */
|
|
49
|
+
readonly stage: 'fetch' | 'parse';
|
|
50
|
+
readonly detail: string;
|
|
51
|
+
};
|
|
52
|
+
export type AddResult = {
|
|
53
|
+
readonly ok: true;
|
|
54
|
+
readonly subscription: CalendarSubscription;
|
|
55
|
+
readonly report: RefreshReport;
|
|
56
|
+
} | {
|
|
57
|
+
readonly ok: false;
|
|
58
|
+
readonly stage: 'fetch' | 'parse' | 'duplicate';
|
|
59
|
+
readonly detail: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Mask a feed URL for display. Google/Outlook "secret address" URLs grant read
|
|
63
|
+
* access, so a subscription's URL is secrets-adjacent — surfaces should show this,
|
|
64
|
+
* never the raw URL. Keeps the scheme+host and the last few chars, masks the middle.
|
|
65
|
+
*/
|
|
66
|
+
export declare function maskFeedUrl(url: string): string;
|
|
67
|
+
export declare class SubscriptionStore {
|
|
68
|
+
private readonly fetcher;
|
|
69
|
+
private readonly clock;
|
|
70
|
+
private readonly defaultInterval;
|
|
71
|
+
private readonly records;
|
|
72
|
+
constructor(options: SubscriptionStoreOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Fetch a feed WITHOUT saving it and report whether it is a usable calendar. Drives
|
|
75
|
+
* the wizard's validate-before-save step and the derived-name preview.
|
|
76
|
+
*/
|
|
77
|
+
validateByFetch(url: string, requestedName?: string): Promise<ValidationResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Paste-URL-and-done: validate by fetching, and on success register the subscription
|
|
80
|
+
* with an auto-derived name and default cadence, storing its events. Refuses (without
|
|
81
|
+
* saving) on a fetch/parse failure or a duplicate name, always with an honest reason.
|
|
82
|
+
*/
|
|
83
|
+
add(input: AddSubscriptionInput): Promise<AddResult>;
|
|
84
|
+
/** Remove a subscription and drop its cached events. Returns whether it existed. */
|
|
85
|
+
remove(name: string): boolean;
|
|
86
|
+
list(): CalendarSubscription[];
|
|
87
|
+
get(name: string): CalendarSubscription | undefined;
|
|
88
|
+
has(name: string): boolean;
|
|
89
|
+
/** Events from the most recent successful parse of the named subscription. */
|
|
90
|
+
events(name: string): readonly CalendarEvent[];
|
|
91
|
+
/** All subscriptions' events, each tagged with its source subscription name. */
|
|
92
|
+
allEvents(): {
|
|
93
|
+
readonly name: string;
|
|
94
|
+
readonly events: readonly CalendarEvent[];
|
|
95
|
+
}[];
|
|
96
|
+
/**
|
|
97
|
+
* Refresh one subscription. Skips the network when not due unless `force`. Sends
|
|
98
|
+
* conditional-fetch validators (etag/last-modified) so an unchanged feed comes back
|
|
99
|
+
* 304 and keeps its events. Updates honest status either way.
|
|
100
|
+
*/
|
|
101
|
+
refresh(name: string, opts?: {
|
|
102
|
+
force?: boolean;
|
|
103
|
+
}): Promise<RefreshReport>;
|
|
104
|
+
/** Apply a fetch result to a record, mutating status/events and returning the honest report. */
|
|
105
|
+
private applyFetch;
|
|
106
|
+
/** Refresh every subscription that is due (or never fetched). Used on boot + on demand. */
|
|
107
|
+
refreshDue(opts?: {
|
|
108
|
+
force?: boolean;
|
|
109
|
+
}): Promise<RefreshReport[]>;
|
|
110
|
+
/** Metadata snapshot for persistence; events are intentionally excluded (re-fetched on boot). */
|
|
111
|
+
snapshot(): SubscriptionSnapshot[];
|
|
112
|
+
/** Restore subscription metadata (from `snapshot()`); call `refreshDue()` afterward to load events. */
|
|
113
|
+
restore(snapshots: readonly SubscriptionSnapshot[]): void;
|
|
114
|
+
private deriveName;
|
|
115
|
+
private healthOf;
|
|
116
|
+
private toPublic;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=subscription-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-store.d.ts","sourceRoot":"","sources":["../../../src/platform/calendar/subscription-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,KAAK,EACL,WAAW,EAEX,aAAa,EAEb,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,0FAA0F;AAC1F,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAC1D,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,QAAiB,CAAC;AACtD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,QAAsB,CAAC;AAK3D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,GACxB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEN,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAClG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAmCrG;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;gBAErD,OAAO,EAAE,wBAAwB;IAM7C;;;OAGG;IACG,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBrF;;;;OAIG;IACG,GAAG,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAC;IA8B1D,oFAAoF;IACpF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI7B,IAAI,IAAI,oBAAoB,EAAE;IAI9B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAKnD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,8EAA8E;IAC9E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE;IAI9C,gFAAgF;IAChF,SAAS,IAAI;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAA;KAAE,EAAE;IAInF;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBnF,gGAAgG;IAChG,OAAO,CAAC,UAAU;IAkClB,2FAA2F;IACrF,UAAU,CAAC,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAQ1E,iGAAiG;IACjG,QAAQ,IAAI,oBAAoB,EAAE;IAYlC,uGAAuG;IACvG,OAAO,CAAC,SAAS,EAAE,SAAS,oBAAoB,EAAE,GAAG,IAAI;IAgBzD,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,QAAQ;CAqBjB"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subscription-store.ts — named external-calendar feed subscriptions with honest,
|
|
3
|
+
* per-subscription status. The engine that agent-side `/calendar subscribe` and the
|
|
4
|
+
* connect wizard drive.
|
|
5
|
+
*
|
|
6
|
+
* Reaches the network ONLY through an injected `FeedFetcher`, and reads time ONLY
|
|
7
|
+
* through an injected `Clock`. That is the whole IO boundary: tests supply fake
|
|
8
|
+
* feeds and a fake clock, so no test ever touches a real URL, and refresh/staleness
|
|
9
|
+
* timing is deterministic. Persistence is the CALLER's job (the agent stores the
|
|
10
|
+
* feed URL via its secret manager and the rest of the metadata in its config);
|
|
11
|
+
* `snapshot()` / `restore()` move that metadata across restarts, events re-fetched.
|
|
12
|
+
*
|
|
13
|
+
* UX shape (per Mike's least-friction rule): `add({ url })` is paste-URL-and-done —
|
|
14
|
+
* it validates by fetching, auto-derives the subscription name from the feed's
|
|
15
|
+
* X-WR-CALNAME (falling back to the URL host) when the caller gives no name, and
|
|
16
|
+
* applies a sensible default refresh interval with no mandatory knobs. Every status
|
|
17
|
+
* it reports is honest: stale carries its age, unreachable/parse-error carry the
|
|
18
|
+
* stage and detail.
|
|
19
|
+
*
|
|
20
|
+
* PURE of ambient IO — no direct fs/network/process; all IO is injected.
|
|
21
|
+
*/
|
|
22
|
+
import { parseIcs } from './ics-parser.js';
|
|
23
|
+
/** Sensible refresh cadence for a read-only feed that rarely changes minute-to-minute. */
|
|
24
|
+
export const DEFAULT_REFRESH_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
25
|
+
/** Never hammer a feed faster than this, even if a caller asks. */
|
|
26
|
+
export const MIN_REFRESH_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
|
27
|
+
/** A feed refreshed less often than this is capped up to here. */
|
|
28
|
+
export const MAX_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
29
|
+
/** Data is considered stale once it is older than this multiple of the interval. */
|
|
30
|
+
const STALE_MULTIPLE = 2;
|
|
31
|
+
/** Clamp a requested refresh interval into the safe [MIN, MAX] band. */
|
|
32
|
+
function clampInterval(requested, fallback) {
|
|
33
|
+
const v = requested ?? fallback;
|
|
34
|
+
if (!Number.isFinite(v) || v <= 0)
|
|
35
|
+
return fallback;
|
|
36
|
+
return Math.max(MIN_REFRESH_INTERVAL_MS, Math.min(MAX_REFRESH_INTERVAL_MS, v));
|
|
37
|
+
}
|
|
38
|
+
/** Best-effort host for name/derivation fallback; never throws. */
|
|
39
|
+
function urlHost(url) {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(url).host || url;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return url;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Mask a feed URL for display. Google/Outlook "secret address" URLs grant read
|
|
49
|
+
* access, so a subscription's URL is secrets-adjacent — surfaces should show this,
|
|
50
|
+
* never the raw URL. Keeps the scheme+host and the last few chars, masks the middle.
|
|
51
|
+
*/
|
|
52
|
+
export function maskFeedUrl(url) {
|
|
53
|
+
try {
|
|
54
|
+
const u = new URL(url);
|
|
55
|
+
const tail = u.pathname.length > 6 ? u.pathname.slice(-6) : u.pathname;
|
|
56
|
+
return `${u.protocol}//${u.host}/…${tail}`;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
if (url.length <= 10)
|
|
60
|
+
return '…';
|
|
61
|
+
return `${url.slice(0, 4)}…${url.slice(-4)}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class SubscriptionStore {
|
|
65
|
+
fetcher;
|
|
66
|
+
clock;
|
|
67
|
+
defaultInterval;
|
|
68
|
+
records = new Map();
|
|
69
|
+
constructor(options) {
|
|
70
|
+
this.fetcher = options.fetcher;
|
|
71
|
+
this.clock = options.clock ?? (() => Date.now());
|
|
72
|
+
this.defaultInterval = clampInterval(options.defaultRefreshIntervalMs, DEFAULT_REFRESH_INTERVAL_MS);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fetch a feed WITHOUT saving it and report whether it is a usable calendar. Drives
|
|
76
|
+
* the wizard's validate-before-save step and the derived-name preview.
|
|
77
|
+
*/
|
|
78
|
+
async validateByFetch(url, requestedName) {
|
|
79
|
+
const res = await this.fetcher({ url });
|
|
80
|
+
if (res.kind === 'error') {
|
|
81
|
+
return { ok: false, stage: 'fetch', detail: res.status ? `HTTP ${res.status}: ${res.message}` : res.message };
|
|
82
|
+
}
|
|
83
|
+
if (res.kind === 'not-modified') {
|
|
84
|
+
// A cold validate should never see 304 (no validators sent); treat as fetch failure.
|
|
85
|
+
return { ok: false, stage: 'fetch', detail: 'Server returned 304 Not Modified to an unconditional request — no calendar body to validate.' };
|
|
86
|
+
}
|
|
87
|
+
const parsed = parseIcs(res.body);
|
|
88
|
+
if (parsed.events.length === 0 && parsed.skipped.length === 0 && !parsed.calendarName) {
|
|
89
|
+
return { ok: false, stage: 'parse', detail: 'Fetched, but no VEVENTs or calendar name were found — this does not look like an iCalendar feed.' };
|
|
90
|
+
}
|
|
91
|
+
const derivedName = this.deriveName(requestedName, parsed.calendarName, url);
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
...(parsed.calendarName !== undefined ? { calendarName: parsed.calendarName } : {}),
|
|
95
|
+
eventCount: parsed.events.length,
|
|
96
|
+
derivedName,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Paste-URL-and-done: validate by fetching, and on success register the subscription
|
|
101
|
+
* with an auto-derived name and default cadence, storing its events. Refuses (without
|
|
102
|
+
* saving) on a fetch/parse failure or a duplicate name, always with an honest reason.
|
|
103
|
+
*/
|
|
104
|
+
async add(input) {
|
|
105
|
+
// Single unconditional fetch — no separate validate round trip. We fetch once,
|
|
106
|
+
// derive the name from the body, then apply that SAME result into the record.
|
|
107
|
+
const now = this.clock();
|
|
108
|
+
const res = await this.fetcher({ url: input.url });
|
|
109
|
+
if (res.kind === 'error') {
|
|
110
|
+
return { ok: false, stage: 'fetch', detail: res.status ? `HTTP ${res.status}: ${res.message}` : res.message };
|
|
111
|
+
}
|
|
112
|
+
if (res.kind === 'not-modified') {
|
|
113
|
+
return { ok: false, stage: 'fetch', detail: 'Server returned 304 Not Modified to an unconditional request — no calendar body to add.' };
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseIcs(res.body);
|
|
116
|
+
if (parsed.events.length === 0 && parsed.skipped.length === 0 && !parsed.calendarName) {
|
|
117
|
+
return { ok: false, stage: 'parse', detail: 'Fetched, but no VEVENTs or calendar name were found — this does not look like an iCalendar feed.' };
|
|
118
|
+
}
|
|
119
|
+
const name = this.deriveName(input.name, parsed.calendarName, input.url);
|
|
120
|
+
const record = {
|
|
121
|
+
name,
|
|
122
|
+
url: input.url,
|
|
123
|
+
refreshIntervalMs: clampInterval(input.refreshIntervalMs, this.defaultInterval),
|
|
124
|
+
lastResult: undefined,
|
|
125
|
+
events: [],
|
|
126
|
+
};
|
|
127
|
+
this.records.set(name, record);
|
|
128
|
+
record.lastFetchedAt = now;
|
|
129
|
+
const report = this.applyFetch(record, res, now);
|
|
130
|
+
return { ok: true, subscription: this.toPublic(record), report };
|
|
131
|
+
}
|
|
132
|
+
/** Remove a subscription and drop its cached events. Returns whether it existed. */
|
|
133
|
+
remove(name) {
|
|
134
|
+
return this.records.delete(name);
|
|
135
|
+
}
|
|
136
|
+
list() {
|
|
137
|
+
return [...this.records.values()].map((r) => this.toPublic(r));
|
|
138
|
+
}
|
|
139
|
+
get(name) {
|
|
140
|
+
const r = this.records.get(name);
|
|
141
|
+
return r ? this.toPublic(r) : undefined;
|
|
142
|
+
}
|
|
143
|
+
has(name) {
|
|
144
|
+
return this.records.has(name);
|
|
145
|
+
}
|
|
146
|
+
/** Events from the most recent successful parse of the named subscription. */
|
|
147
|
+
events(name) {
|
|
148
|
+
return this.records.get(name)?.events ?? [];
|
|
149
|
+
}
|
|
150
|
+
/** All subscriptions' events, each tagged with its source subscription name. */
|
|
151
|
+
allEvents() {
|
|
152
|
+
return [...this.records.values()].map((r) => ({ name: r.name, events: r.events }));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Refresh one subscription. Skips the network when not due unless `force`. Sends
|
|
156
|
+
* conditional-fetch validators (etag/last-modified) so an unchanged feed comes back
|
|
157
|
+
* 304 and keeps its events. Updates honest status either way.
|
|
158
|
+
*/
|
|
159
|
+
async refresh(name, opts = {}) {
|
|
160
|
+
const r = this.records.get(name);
|
|
161
|
+
if (!r)
|
|
162
|
+
return { name, outcome: 'skipped', health: 'never-fetched', detail: `No subscription named '${name}'.` };
|
|
163
|
+
const now = this.clock();
|
|
164
|
+
if (!opts.force && r.lastFetchedAt !== undefined && now - r.lastFetchedAt < r.refreshIntervalMs) {
|
|
165
|
+
return { name, outcome: 'skipped', health: this.healthOf(r, now), ...(r.eventCount !== undefined ? { eventCount: r.eventCount } : {}), detail: 'Not due for refresh yet.' };
|
|
166
|
+
}
|
|
167
|
+
r.lastFetchedAt = now;
|
|
168
|
+
const res = await this.fetcher({
|
|
169
|
+
url: r.url,
|
|
170
|
+
...(r.etag !== undefined ? { etag: r.etag } : {}),
|
|
171
|
+
...(r.lastModified !== undefined ? { lastModified: r.lastModified } : {}),
|
|
172
|
+
});
|
|
173
|
+
return this.applyFetch(r, res, now);
|
|
174
|
+
}
|
|
175
|
+
/** Apply a fetch result to a record, mutating status/events and returning the honest report. */
|
|
176
|
+
applyFetch(r, res, now) {
|
|
177
|
+
const name = r.name;
|
|
178
|
+
if (res.kind === 'error') {
|
|
179
|
+
r.lastResult = 'unreachable';
|
|
180
|
+
r.lastDetail = res.status ? `HTTP ${res.status}: ${res.message}` : res.message;
|
|
181
|
+
return { name, outcome: 'unreachable', health: this.healthOf(r, now), ...(r.eventCount !== undefined ? { eventCount: r.eventCount } : {}), detail: r.lastDetail };
|
|
182
|
+
}
|
|
183
|
+
if (res.kind === 'not-modified') {
|
|
184
|
+
r.lastResult = 'not-modified';
|
|
185
|
+
r.lastSucceededAt = now;
|
|
186
|
+
if (res.etag !== undefined)
|
|
187
|
+
r.etag = res.etag;
|
|
188
|
+
if (res.lastModified !== undefined)
|
|
189
|
+
r.lastModified = res.lastModified;
|
|
190
|
+
r.lastDetail = undefined;
|
|
191
|
+
return { name, outcome: 'not-modified', health: this.healthOf(r, now), ...(r.eventCount !== undefined ? { eventCount: r.eventCount } : {}) };
|
|
192
|
+
}
|
|
193
|
+
const parsed = parseIcs(res.body);
|
|
194
|
+
if (parsed.events.length === 0 && parsed.skipped.length > 0) {
|
|
195
|
+
r.lastResult = 'parse-error';
|
|
196
|
+
r.lastDetail = `Fetched, but every VEVENT was unusable: ${parsed.skipped[0]?.message ?? 'no usable events'}`;
|
|
197
|
+
return { name, outcome: 'parse-error', health: this.healthOf(r, now), detail: r.lastDetail };
|
|
198
|
+
}
|
|
199
|
+
r.events = [...parsed.events];
|
|
200
|
+
r.eventCount = parsed.events.length;
|
|
201
|
+
r.lastResult = 'ok';
|
|
202
|
+
r.lastSucceededAt = now;
|
|
203
|
+
r.lastDetail = parsed.diagnostics.length > 0 ? `${parsed.diagnostics.length} note(s): ${parsed.diagnostics[0]?.message ?? ''}` : undefined;
|
|
204
|
+
if (res.etag !== undefined)
|
|
205
|
+
r.etag = res.etag;
|
|
206
|
+
if (res.lastModified !== undefined)
|
|
207
|
+
r.lastModified = res.lastModified;
|
|
208
|
+
return { name, outcome: 'updated', health: 'ok', eventCount: r.eventCount, ...(r.lastDetail !== undefined ? { detail: r.lastDetail } : {}) };
|
|
209
|
+
}
|
|
210
|
+
/** Refresh every subscription that is due (or never fetched). Used on boot + on demand. */
|
|
211
|
+
async refreshDue(opts = {}) {
|
|
212
|
+
const reports = [];
|
|
213
|
+
for (const name of this.records.keys()) {
|
|
214
|
+
reports.push(await this.refresh(name, opts));
|
|
215
|
+
}
|
|
216
|
+
return reports;
|
|
217
|
+
}
|
|
218
|
+
/** Metadata snapshot for persistence; events are intentionally excluded (re-fetched on boot). */
|
|
219
|
+
snapshot() {
|
|
220
|
+
return [...this.records.values()].map((r) => ({
|
|
221
|
+
name: r.name,
|
|
222
|
+
url: r.url,
|
|
223
|
+
refreshIntervalMs: r.refreshIntervalMs,
|
|
224
|
+
...(r.lastFetchedAt !== undefined ? { lastFetchedAt: r.lastFetchedAt } : {}),
|
|
225
|
+
...(r.lastSucceededAt !== undefined ? { lastSucceededAt: r.lastSucceededAt } : {}),
|
|
226
|
+
...(r.etag !== undefined ? { etag: r.etag } : {}),
|
|
227
|
+
...(r.lastModified !== undefined ? { lastModified: r.lastModified } : {}),
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
/** Restore subscription metadata (from `snapshot()`); call `refreshDue()` afterward to load events. */
|
|
231
|
+
restore(snapshots) {
|
|
232
|
+
for (const s of snapshots) {
|
|
233
|
+
this.records.set(s.name, {
|
|
234
|
+
name: s.name,
|
|
235
|
+
url: s.url,
|
|
236
|
+
refreshIntervalMs: clampInterval(s.refreshIntervalMs, this.defaultInterval),
|
|
237
|
+
...(s.lastFetchedAt !== undefined ? { lastFetchedAt: s.lastFetchedAt } : {}),
|
|
238
|
+
...(s.lastSucceededAt !== undefined ? { lastSucceededAt: s.lastSucceededAt } : {}),
|
|
239
|
+
...(s.etag !== undefined ? { etag: s.etag } : {}),
|
|
240
|
+
...(s.lastModified !== undefined ? { lastModified: s.lastModified } : {}),
|
|
241
|
+
lastResult: undefined,
|
|
242
|
+
events: [],
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
deriveName(requested, calendarName, url) {
|
|
247
|
+
const candidate = (requested?.trim() || calendarName?.trim() || urlHost(url)).replace(/\s+/g, ' ').trim();
|
|
248
|
+
let name = candidate || urlHost(url);
|
|
249
|
+
// De-duplicate against existing names so an unnamed second Google feed still lands.
|
|
250
|
+
if (this.records.has(name)) {
|
|
251
|
+
let n = 2;
|
|
252
|
+
while (this.records.has(`${name} (${n})`))
|
|
253
|
+
n++;
|
|
254
|
+
name = `${name} (${n})`;
|
|
255
|
+
}
|
|
256
|
+
return name;
|
|
257
|
+
}
|
|
258
|
+
healthOf(r, now) {
|
|
259
|
+
if (r.lastResult === undefined)
|
|
260
|
+
return 'never-fetched';
|
|
261
|
+
if (r.lastResult === 'unreachable')
|
|
262
|
+
return 'unreachable';
|
|
263
|
+
if (r.lastResult === 'parse-error')
|
|
264
|
+
return 'parse-error';
|
|
265
|
+
if (r.lastSucceededAt === undefined)
|
|
266
|
+
return 'never-fetched';
|
|
267
|
+
const age = now - r.lastSucceededAt;
|
|
268
|
+
if (age > r.refreshIntervalMs * STALE_MULTIPLE)
|
|
269
|
+
return 'stale';
|
|
270
|
+
return 'ok';
|
|
271
|
+
}
|
|
272
|
+
toPublic(r) {
|
|
273
|
+
const now = this.clock();
|
|
274
|
+
const health = this.healthOf(r, now);
|
|
275
|
+
let detail = r.lastDetail;
|
|
276
|
+
if (health === 'stale' && r.lastSucceededAt !== undefined) {
|
|
277
|
+
const ageMin = Math.round((now - r.lastSucceededAt) / 60000);
|
|
278
|
+
detail = `Last updated ${ageMin} min ago (past the ${Math.round(r.refreshIntervalMs / 60000)} min refresh window).`;
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
name: r.name,
|
|
282
|
+
url: r.url,
|
|
283
|
+
refreshIntervalMs: r.refreshIntervalMs,
|
|
284
|
+
...(r.lastFetchedAt !== undefined ? { lastFetchedAt: r.lastFetchedAt } : {}),
|
|
285
|
+
...(r.lastSucceededAt !== undefined ? { lastSucceededAt: r.lastSucceededAt } : {}),
|
|
286
|
+
...(r.etag !== undefined ? { etag: r.etag } : {}),
|
|
287
|
+
...(r.lastModified !== undefined ? { lastModified: r.lastModified } : {}),
|
|
288
|
+
health,
|
|
289
|
+
...(detail !== undefined ? { detail } : {}),
|
|
290
|
+
...(r.eventCount !== undefined ? { eventCount: r.eventCount } : {}),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|